推荐 最新
米斯达

如何约束entity中的属性只能是特定的某些值?

如题,比如gender,只能是男/女/未知,比如状态,只能是0/1,这个要如何约束?是在Java层面约束还是mysql层面约束?

8
1
0
浏览量407
武士先生

MybatisPlus(4)

一、id生成策略(insert)不同的表应用不同的id生成策略日志:自增(1,2,3,4,.....)购物订单:特殊规则(FQ23948AK3843)外卖单:关联地区日期等信息(10  04  20200314  34  91)关系表:可省略id...这个时候我们就可以使用@TableId去修改id设置。我们可以看到一共有八种IdType,也就是说八种id生成策略,在之前我们添加一共新用户的id特别长:1、IdType.AUTO 然后我们给user实体类id添加 @TableId注解,并且设置属性type:package com.example.domain; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @Data @TableName("tbl_user") public class User { @TableId(type= IdType.AUTO) private Long id; private String name; @TableField(value = "pwd",select = false) private String password; private Integer age; private String tel; @TableField(exist = false) private Integer online; } 我们去添加新用户试试,看id的变换:@Test void textSave(){ User user=new User(); user.setName("热爱编程"); user.setPassword("123456"); user.setAge(60); user.setTel("123456789"); userDao.insert(user); } 运行代码,控制台:2、IdType源码 我们点进IdType进入查看源码,这里它给出了所有的ID策略:// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.baomidou.mybatisplus.annotation; public enum IdType { AUTO(0), NONE(1), INPUT(2), ASSIGN_ID(3), ASSIGN_UUID(4), /** @deprecated */ @Deprecated ID_WORKER(3), /** @deprecated */ @Deprecated ID_WORKER_STR(3), /** @deprecated */ @Deprecated UUID(4); private final int key; private IdType(int key) { this.key = key; } public int getKey() { return this.key; } } 我们可以看到这些策略后面都有一个数字,数字相同的是同一个策略,只不过是换了一个名字。数字为3的是通过雪花算法生成一个id给你,ID_WORKER(3)  这个是用来生成整数的,而ID_WORKER_STR(3)是用来生成字符串的,它们两个统一合并为ASSIGN_ID(3)。3、IdType.INPUT接下来我们试试将@TableId(type= IdType.AUTO)改成@TableId(type= IdType.INPUT),而在数据库中我们需要将id的自增策略关闭:这个策略需要我们必须传id值:不然id为null。 给它设置id继续运行:@Test void textSave(){ User user=new User(); user.setId(123L); user.setName("热爱编程"); user.setPassword("123456"); user.setAge(60); user.setTel("123456789"); userDao.insert(user); } 数据库数据添加:4、 IdType.ASSIGN_IDASSIGN ID(3): 雪花算法生成id (可兼容数值型与字符串型 )雪花算法(Snowflake Algorithm)是一种用于生成唯一ID的分布式算法。它最早由Twitter公司提出,并在分布式系统中广泛应用。雪花算法的核心思想是通过组合不同的因素来生成全局唯一的ID。雪花算法的ID由以下几部分组成:时间戳(Timestamp):占据了ID的高位,精确到毫秒级别,能够保证生成的ID在一定时间内是递增有序的。机器ID(Machine ID):用于标识不同的机器,一般由数据中心ID和机器ID组成,可以根据实际需求进行分配。序列号(Sequence Number):用于标识同一毫秒内生成的不同ID,能够解决同一毫秒内并发生成ID的唯一性问题。通过将时间戳、机器ID和序列号进行组合,就可以生成一个全局唯一的ID。雪花算法的优点包括高性能、高可用性和可扩展性,适用于大规模分布式系统中生成唯一ID的需求。需要注意的是,雪花算法并不保证ID的全局唯一性,而是在实际应用中通过合理的配置和使用来达到足够的唯一性。另外,雪花算法在分布式系统中的应用还需要考虑时钟回拨等异常情况的处理。这个是不需要自己添加id的:@Test void textSave(){ User user=new User(); user.setName("热爱编程"); user.setPassword("123456"); user.setAge(60); user.setTel("123456789"); userDao.insert(user); } 5、全局配置如果我们一个一个id去添加注解,这是相当麻烦的,所以可以使用全局配置来进行统一的id生成策略。# 配置数据库的连接字符串 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/ku2022?characterEncoding=utf8 username: root password: "123456" driver-class-name: com.mysql.cj.jdbc.Driver main: banner-mode: off #不显示logo mybatis-plus: mapper-locations: classpath:mapper/*Mapper.xml configuration: # 配置打印 MyBatis-plus 执行的 SQL log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: banner: false #不显示logo db-config: id-type: assign_id 设置id-type就可以进行配置id生成策略,不仅仅id可以,上篇提到的表名不同步也可以:# 配置数据库的连接字符串 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/ku2022?characterEncoding=utf8 username: root password: "123456" driver-class-name: com.mysql.cj.jdbc.Driver main: banner-mode: off #不显示logo mybatis-plus: mapper-locations: classpath:mapper/*Mapper.xml configuration: # 配置打印 MyBatis-plus 执行的 SQL log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: banner: false #不显示logo db-config: id-type: assign_id table-prefix: tbl_ 我们将实体类表名的@TableName("tbl_user")注掉,运行添加用户的代码:二、多条数据操作(delete/select)1、删除多条数据在上面我们添加很多用户名为 热爱编程 的用户,下面我们将他们一起删除:@Test void testDelete(){ List<Long> list=new ArrayList<>(); list.add(6L); list.add(123L); list.add(1696120064304058370L); list.add(1696122438468800513L); userDao.deleteBatchIds(list); } 就成功删除了:2、 查询多条数据我们去查询id为1、2、3的用户@Test void testselect(){ List<Long> list=new ArrayList<>(); list.add(1L); list.add(2L); list.add(3L); userDao.selectBatchIds(list); } 查询结果为:三、逻辑删除(delete/update)删除操作业务问题:业务数据从数据库中丢弃逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中。1、添加字段数据库和实体类中添加deleted字段和属性。package com.example.domain; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @Data /*@TableName("tbl_user")*/ public class User { @TableId(type= IdType.ASSIGN_ID) private Long id; private String name; @TableField(value = "pwd",select = false) private String password; private Integer age; private String tel; private Integer deleted; @TableField(exist = false) private Integer online; } 2、添加@TableLogic注解// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.baomidou.mybatisplus.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) public @interface TableLogic { String value() default ""; String delval() default ""; } 查看源码我们可以发现可以设置两个属性值value和delval,分别表示未删除的值和删除的值,这个属性设置会直接影响查找数据和删除数据的SQL(后面会加where deleted)。@TableLogic(value = "0",delval = "1") private Integer deleted; 3、删除用户@Test void testDelete(){ userDao.selectById(1L); } 控制台输出和数据库更改:4、查询用户 我们逻辑删除了用户1,然后去查看一下能不能查找到用户1:@Test void testSelect(){ System.out.println(userDao.selectList(null)); } 我们可以看到自能查询到四条语句,用户名为1的未查询出来。 5、全局配置这个同样也可以进行全局配置。# 配置数据库的连接字符串 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/ku2022?characterEncoding=utf8 username: root password: "123456" driver-class-name: com.mysql.cj.jdbc.Driver main: banner-mode: off #不显示logo mybatis-plus: mapper-locations: classpath:mapper/*Mapper.xml configuration: # 配置打印 MyBatis-plus 执行的 SQL log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: banner: false #不显示logo db-config: id-type: assign_id table-prefix: tbl_ logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 两种方式稍微对比一下就知道对应的含义:

0
0
0
浏览量2017
武士先生

【Spring】——Spring简单 读和取

上期我们讲解了Spring的创建与使用,发现将Bean 注册到容器这一步中,如果Bean对象过多,在注册到容器时,我们有几个Bean对象就需要几行注册,在实际开发中这是非常麻烦的,我们需要有更简单的方法去实现这一过程,这便是本篇文章的主题——Spring简单 读和取。一、存储Bean对象[读]在Spring中我们可以使用注解存储和读取Bean对象,而其中我们有两种注解类型可以实现这个功能。类注解:@Controller(控制器存储)、@Service(服务存储) 、@Repository(仓库存储)、@Component(组件存储)、@Configuration(配置存储)。方法注解:@Bean。1、配置扫描路径但是在使用注解去进行存储和读取Bean对象之前,我们还需要进行配置扫描路径。在spring-config.xml中添加如下配置:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:content="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <content:component-scan base-package="com.demo.component"></content:component-scan> </beans> 2、类注解Ⅰ、@Controller(控制器存储)ArticleController类:package com.demo.component; import org.springframework.stereotype.Controller; @Controller// 将对象存储到 Spring 中 public class ArticleController { public String sayHi(){ return "Hello word"; } } 还是使用上篇讲的方法 去读取Bean对象:import com.demo.component.ArticleController; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; public class App { public static void main(String[] args) { //1、获取spring对象 ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml"); //2、从Spring中取出Bean对象 ArticleController articleController=(ArticleController) context.getBean("articleController"); //3、使用Bean(可选) System.out.println(articleController.sayHi());//输出Hello word } } Ⅱ、@Service(服务存储)ArticleController类:package com.demo.component; import org.springframework.stereotype.Service; @Service// 将对象存储到 Spring 中 public class ArticleController { public String sayHi(){ return "Hello word"; } } 还是使用上篇讲的方法 去读取Bean对象:import com.demo.component.ArticleController; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; public class App { public static void main(String[] args) { //1、获取spring对象 ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml"); //2、从Spring中取出Bean对象 ArticleController articleController=(ArticleController) context.getBean("articleController"); //3、使用Bean(可选) System.out.println(articleController.sayHi());//输出Hello word } } Ⅲ、@Repository(仓库存储)ArticleController类:package com.demo.component; import org.springframework.stereotype.Repository; @Repository// 将对象存储到 Spring 中 public class ArticleController { public String sayHi(){ return "Hello word"; } } 还是使用上篇讲的方法 去读取Bean对象:import com.demo.component.ArticleController; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; public class App { public static void main(String[] args) { //1、获取spring对象 ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml"); //2、从Spring中取出Bean对象 ArticleController articleController=(ArticleController) context.getBean("articleController"); //3、使用Bean(可选) System.out.println(articleController.sayHi());//输出Hello word } } Ⅳ、@Component(组件存储)ArticleController类:package com.demo.component; import org.springframework.stereotype.Component; @Component// 将对象存储到 Spring 中 public class ArticleController { public String sayHi(){ return "Hello word"; } } 还是使用上篇讲的方法 去读取Bean对象:import com.demo.component.ArticleController; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; public class App { public static void main(String[] args) { //1、获取spring对象 ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml"); //2、从Spring中取出Bean对象 ArticleController articleController=(ArticleController) context.getBean("articleController"); //3、使用Bean(可选) System.out.println(articleController.sayHi());//输出Hello word } } Ⅴ、@Configuration(配置存储)package com.demo.component; import org.springframework.context.annotation.Configuration; @Configuration// 将对象存储到 Spring 中 public class ArticleController { public String sayHi(){ return "Hello word"; } } 还是使用上篇讲的方法 去读取Bean对象:import com.demo.component.ArticleController; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; public class App { public static void main(String[] args) { //1、获取spring对象 ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml"); //2、从Spring中取出Bean对象 ArticleController articleController=(ArticleController) context.getBean("articleController"); //3、使用Bean(可选) System.out.println(articleController.sayHi());//输出Hello word } } 大家一路看下来,可能会吐槽一下:为什么全都是一样的代码啊?这有什么区别啊!为什么有这么多类注解?Spring框架有很多类注解是为了让开发者以更简洁、方便的方式来定义各种不同类型的Bean(如控制器、服务、存储库等),并且能够更容易地使用Spring的各种功能(如事务管理、缓存、安全性等)。虽然Spring框架中的注解很多,但大多数都有特定的功能和用途,使得开发者可以根据需求选择合适的注解来使用,也可以让程序员看到类注解之后,就能直接了解当前类的用途,比如:@Controller(控制器):业务逻辑层,用来控制用户的行为,它用来检查用户参数的有效性。@Servie(服务): 服务层,调用持久化类实现相应的功能。[不直接和数据库交互的,它类似于控制中心]@Repository (仓库):持久层,是直接和数据库进行交互的。通常每一个表都会对应一个 @Repository。@Configuration(配置):配置层,是用来配置当前项目的一些信息。@Component (组件) : 公共工具类,提供某些公共方法。程序的工程分层,调用流程如下:五大类注解的联系直接看@Controller 、@Service 、@Repository 、@Configuration 等注解的源码:@Service@Repository我们可以发现这些注解里面都有⼀个注解 @Component,说明它们是属于 @Component 的,是@Component的“子类”(其他源码也都类似,大家可以自行去查看查看他们的源码,理解更深刻哦!)。3、Bean 的命名规则连续按两下 Shift 进行搜索或者通过下图方式去打开搜索框在Classes中搜索 BeanName,打开我红色框选择的类划到最下面:我们就找到了Bean对象的命名方法,它使用的是 JDK Introspector 中的 decapitalize 方法,源码如下: public static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } // 如果第⼀个字⺟和第⼆个字⺟都为⼤写的情况,是把 bean 的⾸字⺟也⼤写存储了 if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))) { return name; } // 否则就将⾸字⺟⼩写 char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); } 看源码,可以发现获取Bean时 ,Bean的命名只有两种: 首字母和第二个字母都非大写,首字母小写来获取 Bean, 首字母和第二个字母都是大写,那么直接使用原 Bean 名来获取类名为:ArticleController正确命名方法:错误命名方法:类名为:AController正确命名方法:错误命名方法:4、方法注解Bean类注解是添加到某个类上的,而方法注解是放到某个方法上的。Ⅰ、方法注解要配合类注解使用Bean注解需要配合五大类注解使用。ArticleControllerpackage com.demo.component; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component// 将对象存储到 Spring 中 public class ArticleController { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "ArticleController{" + "id=" + id + ", name='" + name + '\'' + '}'; } @Bean//方法注解 public ArticleController acSet(){ ArticleController articleController=new ArticleController(); articleController.setId(1); articleController.setName("java"); return articleController; } } 使用ArticleController中的acSet方法import com.demo.component.ArticleController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { //1、获取spring对象 ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml"); //2、从Spring中取出Bean对象 ArticleController articleController=(ArticleController) context.getBean("acSet");//命名规则和获取Bean一样 //3、使用Bean(可选) System.out.println(articleController); } } 当我们把acSet方法的@Component注解删除时,就会报错:因此,在使用Bean注解时需要配合使用五大类注解,才能将对象正常的存储到 Spring 容器中Ⅱ、重命名 Bean可以通过设置 name 属性给 Bean 对象进行重命名操作。将acSet方法重命名为ac,并运行代码:我们可以注意到重命名的name名是使用大括号进行存储,其实这就是一个数组,一个 bean 可以有多个名字。aS:import com.demo.component.ArticleController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { //1、获取spring对象 ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml"); //2、从Spring中取出Bean对象 ArticleController articleController=(ArticleController) context.getBean("aS"); //3、使用Bean(可选) System.out.println(articleController);//输出:ArticleController{id=1, name='java'} } } 但是需要注意的是,如果进行了重命名原类名就无法再进行获取方法了!二、获取 Bean 对象(对象装配)[取]获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注入。1、依赖注入的常见方式 对象装配(对象注入)的实现方法以下 3 种:属性注入构造方法注入Setter 注入刚刚好这里有一篇有关Spring依赖注入的文章,写得很好,我就不重复造轮子了。面试突击77:Spring 依赖注入有几种?各有什么优缺点? - 掘金 (juejin.cn)Ⅰ、三种注入优缺点分析属性注入的优点是简洁,使同方便;缺点是只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)。构造⽅法注入是Spring 推荐的注入方式,它的缺点是如果有多个注⼊会显得比较臃肿,但出现这种情况你应该考虑一下当前类是否符合程序的单一职责的设计模式了,它的优点是通用性,在使用之前⼀定能把保证注⼊的类不为空。Setter方式是 Spring 前期版本推荐的注入方式,但通用性不如构造方法,所有 Spring 现版本已经推荐使用构造方法注入的方式来进行类注入了。2、@Resource:另一种注入关键字在进行类注入时,除了可以使用 @Autowired 关键字之外,我们还可以使用 @Resource 进行注入@Autowired 和 @Resource 的区别:出身不同:@Autowired 来自于 Spring,而@Resource 来自于 JDK 的注解;使用时设置的参数不同:相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean。@Autowired 可用于 Setter 注入、构造函数注入和属性注入,而@Resource 只能用于 Setter 注入和属性注入,不能用于构造函数注入。可以看到 @Resource是JDK自带的方法:在构造函数注入时, @Resource 会报错:其实在官方文档中并没有明确指出为什么构造方法不可以使用@Resource,可能是官方类加载顺序的问题或者循环引用的问题。(可以评论区讨论,给出你的看法)3、同一类型多个 @Bean 报错Userpackage com.demo.component; public class User { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "ArticleController{" + "id=" + id + ", name='" + name + '\'' + '}'; } } Userspackage com.demo.component; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class Users { @Bean public User user1() { User user = new User(); user.setId(1); user.setName("Java"); return user; } @Bean public User user2() { User user = new User(); user.setId(2); user.setName("MySQL"); return user; } } UserControllerpackage com.demo.Controller; import com.demo.component.User; import org.springframework.stereotype.Controller; import javax.annotation.Resource; @Controller public class UserController { @Resource private User user; public User getUser(){ return user; } } 运行APP就可以看到没有唯一Bean定义异常同一类型多个 Bean 报错处理 解决同一个类型,多个 bean 的解决方案有以下两个:使用@Resource(name="user1") 定义。使用@Qualifier 注解定义名称。使用@Resource(name="user1")使用@Qualifier 注解定义名称

0
0
0
浏览量2016
武士先生

初识SpringMVC

前篇我们讲了Spring日志,知道了日志的作用,日志怎么用以及通过lombok去进行更简单的日志输出,然后我们就基本讲完了Spring 相关知识,现在进入SpringMVC的学习。一、什么是SpringMVC🍭官方对于 Spring MVC 的描述是这样的:Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, "Spring Web MVC," comes from the name of its source module (spring-webmvc), but it is more commonly known as "Spring MVC". Spring Web MVC是基于Servlet API的原始Web框架,从一开始就包含在Spring框架中。其正式名称“Spring Web MVC”来自其源模块(Spring -webmvc)的名称,但更常见的名称是“Spring MVC”。Spring Web MVC :: Spring Framework从上述官方定义的描述我们可以提取两个关键信息:Spring MVC 是⼀个 Web 框架。Spring MVC 是基于 Servlet API 构建的。然而要真正的理解什么是 Spring MVC?我们首先要搞清楚什么是 MVC?1、什么是MVC?🍉MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分Model(模型) 是应用程序中用于处理应⽤程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。View(视图) 是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。Controller(控制器) 是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据, 控制用户输入,并向模型发送数据。2、MVC 和 Spring MVC 的关系🍉Spring MVC是基于MVC模式的Java Web开发框架,是Spring框架的一部分。Spring MVC提供了一种结构良好的开发模式,使得开发人员能够更好地组织和管理代码。它使用了MVC的概念,将应用程序的逻辑分离为模型、视图和控制器,并提供了一些额外的功能,如请求处理、表单验证、数据绑定等。是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架,当⽤户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项目就可以感知到用户的请求。因此,可以说Spring MVC是基于MVC模式的一种实现方式,它将MVC的概念应用于Web开发,并提供了一些与Web开发相关的功能和特性。二、为什么要学 Spring MVC?🍭现在绝大部分的 Java 项目都是基于 Spring(或 Spring Boot)的,而 Spring 的核心就是 Spring MVC。也就是说 Spring MVC 是 Spring 框架的核心模块,而 Spring Boot 是 Spring 的脚手架,因此 我们可以推断出,现在市⾯上绝⼤部分的 Java 项目约等于 Spring MVC 项⽬,这是我们要学 Spring MVC 的原因。SpringMVC的优点:🍓轻量级:Spring MVC是一个轻量级的框架,它只提供了基本的Web开发功能,没有过多的冗余功能,使得应用程序的开发和部署更加高效。灵活性:Spring MVC采用了基于注解的配置方式,使得开发者可以更灵活地定义控制器、请求映射和视图解析等,极大地简化了开发过程。松耦合:Spring MVC采用了MVC设计模式,将应用程序的不同层次分离开来,使得各个模块之间的耦合度降低,提高了代码的可维护性和可测试性。可扩展性:Spring MVC提供了丰富的扩展点和插件机制,开发者可以根据自己的需求进行扩展和定制,满足各种复杂的业务需求。高度集成:Spring MVC与Spring框架紧密集成,可以很容易地与其他Spring组件(如Spring Boot、Spring Security等)进行集成,提供了更完整的解决方案。强大的视图解析能力:Spring MVC提供了多种视图解析器,支持多种视图技术(如JSP、Thymeleaf、Freemarker等),使得开发者可以根据自己的喜好选择合适的视图技术。易于测试:Spring MVC采用了面向接口的编程方式,使得控制器和服务层的代码可以很容易地进行单元测试,提高了代码的质量和稳定性。在创建 Spring Boot 项⽬时,我们勾选的 Spring Web 框架其实就是 Spring MVC 框架,如下图所示:简单来说,咱们之所以要学习 Spring MVC 是因为它是⼀切项目的基础,我们以后创建的所有 Spring、Spring Boot 项目基本都是基于 Spring MVC 的。三、怎么学 Spring MVC?🍭学习 Spring MVC 我们只需要掌握以下 3 个功能:连接的功能:将用户(浏览器)和 Java 程序连接起来,也就是访问⼀个地址能够调用到我们的 Spring 程序。获取参数的功能:用户访问的时候会带⼀些参数,在程序中要想办法获取到参数。输出数据的功能:执行了业务逻辑之后,要把程序执行的结果返回给用户。对于 Spring MVC 来说,掌握了以上 3 个功能就相当于掌握了 Spring MVC。1、Spring MVC 创建和连接🍉Spring MVC 项目创建和 Spring Boot 创建项目相同(Spring MVC 使用 Spring Boot 的方式创建), 在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项目。 在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射,也就是浏览器连接程序的作用。Ⅰ、创建SpringMVC项目🍓使用Maven方式传统的创建SpringMVC(不过这已经是过时的方法)。使用Spring Boot添加Spring Web模块(Spring MVC)。创建步骤:创建之后,初始化完成:接下来,创建⼀个 TextController 类,实现⽤户到 Spring 程序的互联互通,具体实现代码如下:package com.example.mvcdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问]) @ResponseBody//告诉程序我返回的是一个数据而非页面 @RequestMapping("/text")//路由注册 public class TextController { @RequestMapping("/hi")//路由注册 public String sayHi(){ return "Hi,Spring MVC"; } } 运行起来:也可以使用@RestController代替@Controller+@ResponseBodypackage com.example.mvcdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; /*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问]) @ResponseBody//告诉程序我返回的是一个数据而非页面*/ @RestController//@Controller+@ResponseBody @RequestMapping("/text")//路由注册 public class TextController { @RequestMapping("/hi")//路由注册 public String sayHi(){ return "Hi,Spring MVC"; } } Ⅱ、@RequestMapping 注解介绍🍓@RequestMapping 是 Spring Web 应用程序中最常被用到的注解之⼀,它是用来注册接口的路由映射的。路由映射:所谓的路由映射指的是,当⽤户访问⼀个 url 时,将⽤户的请求对应到程序中某个类的某个方法的过程。@RequestMapping 即可修饰类,也可以修饰方法,当修饰类和方法时,访问的地址是类 + 方法。@RequestMapping 也可以直接修饰方法,代码实现如下:package com.example.mvcdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; /*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问]) @ResponseBody//告诉程序我返回的是一个数据而非页面*/ @RestController//@Controller+@ResponseBody /*@RequestMapping("/text")//路由注册*/ public class TextController { @RequestMapping("/hi")//路由注册 public String sayHi(){ return "Hi,Spring MVC"; } } 代码运行:Ⅲ、@RequestMapping 是 post 还是 get 请求?🍓我们先在浏览器看一下这是post请求还是get请求?可以看到这是get请求。下面使用 PostMan 测试⼀下,默认情况下使用注解 @RequestMapping 是否可以接收 GET 或 POST 请求?我们测试之后发现在现在版本的@RequestMapping 既支持GET方式的请求有支持PSOT方式的请求。GET:POST:GET方法和POST方法有什么区别🍒下面这篇文章讲解的十分详细了:面试突击71:GET 和 POST 有什么区别? - 掘金 (juejin.cn)指定 GET/POST 方法类型🍒package com.example.mvcdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; /*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问]) @ResponseBody//告诉程序我返回的是一个数据而非页面*/ @RestController//@Controller+@ResponseBody /*@RequestMapping("/text")//路由注册*/ public class TextController { @RequestMapping(value = "/hi",method= RequestMethod.POST)//路由注册 public String sayHi(){ return "Hi,Spring MVC"; } } 我们打开浏览器发现报错了,难道代码有问题?我们使用Postman看看GET:使用GET请求仍然报405,使用POST请求试试,发现可以访问:这是因为我们设置了方法为POST,即只能使用POST请求去访问。我们的浏览器之前已经看了。它是GET请求。Ⅳ、@GetMapping 和 PostMapping🍓get 请求的 3 种写法:🍒// 写法1 @RequestMapping("/hi") // 写法2 @RequestMapping(value = "/hi",method = RequestMethod.GET) // 写法3 @GetMapping("/hi") post 请求的 2 种写法:🍒// 写法1 @RequestMapping(value = "/hi",method = RequestMethod.POST) // 写法2 @PostMapping("/hi") 2、获取参数🍉Ⅰ、传递单个/多个参数🍓在 Spring MVC 中可以直接用方法中的参数来实现传参,比如以下代码:package com.example.mvcdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; /*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问]) @ResponseBody//告诉程序我返回的是一个数据而非页面*/ @RestController//@Controller+@ResponseBody /*@RequestMapping("/text")//路由注册*/ public class TextController { /*@RequestMapping(value = "/hi",method= RequestMethod.POST)//路由注册*/ /*@PostMapping("/hi")*/ @GetMapping("/hi") public String sayHi(String name){ return "Hi"+name; } } 不加参数,直接输出null添加name参数,输出:Hi 张三浏览器也是一样:如果我们参数名错误(不同)则传递不成功:如果我们传递了多个参数,其中有所需要的(参数名字相同),那它会自动匹配:多个参数也是一样传递,而且我们通过上面,也发现了参数的顺序并不影响参数的传递。 总结说明:当有多个参数时,前后端进行参数匹配时,是以参数的名称进行匹配的,因此参数的位置 是不影响后端获取参数的结果Ⅱ、传递对象🍓当参数个数过多时,可以进行传递对象,将参数封装成一个类。Person对象package com.example.mvcdemo.controller; import lombok.Data; @Data public class Person { private int id; private String name; private String password; } TextControllerpackage com.example.mvcdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; /*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问]) @ResponseBody//告诉程序我返回的是一个数据而非页面*/ @RestController//@Controller+@ResponseBody /*@RequestMapping("/text")//路由注册*/ public class TextController { /*@RequestMapping(value = "/hi",method= RequestMethod.POST)//路由注册*/ /*@PostMapping("/hi")*/ @GetMapping("/hi1") public String sayHi1(Person p){ return "Hi "+p.getId()+" "+p.getName()+" "+p.getPassword(); } } 运行代码+传递参数:注意事项🍒在Spring Boot(Spring MVC)中传参一定要是包装类型,而非基础类型。package com.example.mvcdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; /*@Controller//让框架启动的时候加载当前类(只有加载的类,别人才能使用[访问]) @ResponseBody//告诉程序我返回的是一个数据而非页面*/ @RestController//@Controller+@ResponseBody /*@RequestMapping("/text")//路由注册*/ public class TextController { /*@RequestMapping(value = "/hi",method= RequestMethod.POST)//路由注册*/ /*@PostMapping("/hi")*/ @GetMapping("/num") public String sayHi2(int num){ return "num="+num; } } 我们先正常传递参数:但是如果我们忘记传递或是没有传递(前后端工作人员沟通不及时时),则会报错,而且这是非常严重的。这个时候就很莫名其妙,也找不到错误。如果我们将int换成它的包装类时:正常传递参数可以正常显示:没有传递参数时它则会显示null,这就会很明显发现错误的来源。所以注意:参数类型应该设置为包装类Ⅲ、后端参数重命名(后端参数映射)🍓某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不⼀致,比如前端传递了⼀个 time 给后端,而后端又是用 createtime 字段来接收的,这样就会出现参数接收不到的情况,如果出现 这种情况,我们就可以使用 @RequestParam 来重命名前后端的参数值。具体示例如下,后端实现代码:@RequestMapping("/m4") public Object method_4(@RequestParam("time") String createtime) { System.out.println("时间:" + createtime); return "Hi "+createtime; } 代码运行:这就说明参数的重命名生效了。 还有需要注意的是使用了@RequestParam(),则这个参数是必须要传递的,我们可以看@RequestParam()源码:没有传递参数时:所以当这个重命名参数是非必传参数时,我们可以设置@RequestParam 中的required=false 来避免不传递时报错@RequestMapping("/m4") public Object method_4(@RequestParam(value = "time", required = false) String createtime) { System.out.println("时间:" + createtime); return "Hi "+createtime; }+ createtime); Ⅳ、@RequestBody 接收JSON对象🍓我们先来试试看接受对象的是否可以接收JSON对象:@GetMapping("/hi1") public String sayHi1(@RequestBody Person p){ return p.toString(); } 使用Postman 传递JSON对象传递的是 0 null null ,就发现传递不了。那我们传递JSON对象时应该任何传递?使用@RequestBody 注解。 @PostMapping("/hi1") public String sayHi1(@RequestBody Person p){ return "Hi "+p.getId()+" "+p.getName()+" "+p.getPassword(); } 不过当@RequestBody传递JSON格式对象时需要配合PostMapping一起使用,因为@RequestBody传递JSON格式对象时是Post类型传参。Postman:Ⅴ、获取URL中参数@PathVariable🍓后端实现代码:@PostMapping("/m6/{name}/{password}") public Object method_6(@PathVariable String name, @PathVariable String password) { return "name:" + name+" password:" + password; } 这样写就很简洁,SEO(Search Engine Optimization 是指搜索引擎优化)效果很好。 注意事项:@PostMapping("/m6/{name}/{password}") 中的 {password} 参数不能省略。 这是因为在Spring的路径映射中使用了占位符(即{})来表示可变的路径段。 /m6/{name}/{password}中的{name}和{password}都是路径变量,它们需要被具体的值替代才能匹配相应的请求路径。 参数的位置和个数都必须保持一致。Ⅵ、上传文件@RequestPart🍓@RequestMapping("/m9") public String upFile(@RequestPart("myfile") MultipartFile file) throws IOException { // ⽂件保存地址 String filePath = "C:\\Users\\lin\\Pictures\\JiangHai\\11.png"; // 保存⽂件 file.transferTo(new File(filePath)); return filePath + " 上传成功."; } 文件夹什么都没有:使用Postman进行上传文件:随便选择一张图片(文件名为myFile)上传成功:我们也可以打开这张图片但是我们发现我们把路径定死了,这在实际开发中是不可能的,那我们现在来写一个最终版的文件上传:@RequestMapping("/upfile") public String myUpFile(@RequestPart("myupfile") MultipartFile file) throws IOException { //根目录 String path ="C:\\Users\\lin\\Pictures\\JiangHai\\"; //根目录+唯一文件名(进行随机取名) path+=UUID.randomUUID().toString(); //根目录+唯一文件名+文件的后缀 ex: aaa.aaa.png path+=file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); file.transferTo(new File(path)); return path + " 上传成功"; } 再随便上传一张图片我们也可以上传一个.txt文件:上传成功:注意:字段myfile允许的最大大小为1048576字节(即上传的文件是有大小限制的)Ⅶ、获取Cookie/Session/header🍓获取 Request 和 Response 对象 🍒//Spring MVC(Spring Web) 内置了HttpServletRequest 和 HttpServletResponse @RequestMapping("/getparam") public String param10(HttpServletRequest request) { return request.getParameter("name"); } 通过获取Request对象获取参数:获取Cookie🍒@RequestMapping("/getck") public String getCookie( HttpServletRequest request) { // 获取所有 cookie 信息 Cookie[] cookies = request.getCookies(); for(Cookie item:cookies){ log.error(item.getName()+" "+item.getValue()); //log的使用需要添加@Slf4j注解 } return "get cookie!"; } 打开浏览器开发人员工具(F12) ,手动添加结果Cookie:浏览器访问 localhost:8080/getck控制台就会将我们的Cookie打印出来:上面我们是获取全部Cookie,我们也可以去获取指定的Cookie:@RequestMapping("/getck2") public String getCookie2(@CookieValue("zhangsan") String val) { return "Cookie Value: "+val; } 明明你在请求时没有加Cookie,为什么可以拿到呢?这是因为浏览器,浏览器自己实现了这个机制,浏览器会在你每一次访问网站时,将这个网站的所以Cookie传送给你的后端。 可以看下面:为什么浏览器会去实现这个机制呢?是因为HTTP协议是一种无状态协议,服务器无法知道用户之前的状态信息。 为了解决这个问题,Web应用使用了Cookie机制来跟踪和记录用户的状态。当用户首次访问一个网站时,服务器会在响应中设置一个或多个Cookie,并将它们发送到用户的浏览器。浏览器会将这些Cookie保存起来。 随后,当用户再次访问同一网站时,浏览器会将之前保存的Cookie附加到请求中,然后发送给服务器。这样,服务器可以通过读取Cookie中的信息来识别并恢复用户的状态,例如登录信息、用户偏好等。 因此,浏览器在每次访问网站时将所有与该网站相关的Cookie传送给后端,以便服务器能够根据这些Cookie识别用户并提供相应的服务。 需要注意的是,浏览器会根据Cookie的设置规则来决定是否发送Cookie,例如Cookie的过期时间、域名限制等。简洁获取 Header—@RequestHeader🍒@RequestMapping("/header") public String header(@RequestHeader("User-Agent") String userAgent) { return "userAgent:"+userAgent; } 浏览器:Session 存储和获取🍒Session 存储和 Servlet 类似,是使⽤ HttpServletRequest 中获取的,如下代码所示@RequestMapping("/setsess") public String setsess(HttpServletRequest request) { // 获取 HttpSession 对象,参数设置为 true 表示如果没有 session 对象就创建⼀个session HttpSession session = request.getSession(true); if(session!=null){ session.setAttribute("username","username"); } return "session 存储成功"; } 读Session①//读Session1 @RequestMapping("/getsess") public String sess(HttpServletRequest request) { // 如果 session 不存在,不会⾃动创建 HttpSession session = request.getSession(false); String username = "暂⽆"; if(session!=null && session.getAttribute("username")!=null){ username = (String) session.getAttribute("username"); return (String)session.getAttribute("username"); } return "暂无Session信息!"; } 读Session②(更简洁的方式)//读Session2 @RequestMapping("/getsess2") public String sess2(@SessionAttribute(value = "username",required = false) String username) { return "username:"+username; } 我们先去读Session,可以发现浏览器显示:暂无Session信息!然后我们去存储Session:再去读取Session就有了:当然Session是默认存在内存中的,如果当我们程序重新启动时,就没了,这是因为内存中的数据不具有持久性,无法跨越程序重启的边界。3、返回数据🍉Ⅰ、返回静态页面🍓创建前端页面 hello.html<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>hello,spring mvc</title> <script src="index.js"></script> </head> <body> <h1>Hello,Spring MVC.</h1> </body> </html> 创建控制器 RespController:package com.example.mvcdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/resp") public class RespController { @RequestMapping("/hi") public String sayHi(String name){ return "/hello.html"; } } Ⅱ、返回text/html🍓如果我们加上@ResponseBody,则显示:Ⅲ、返回 JSON 对象🍓@ResponseBody @RequestMapping("/m8") public HashMap<String, String> method_8() { HashMap<String, String> map = new HashMap<>(); map.put("Java", "Java Value"); map.put("MySQL", "MySQL Value"); map.put("Redis", "Redis Value"); return map; } 当你使用字典(键值对)类型时,Spring MVC会自动将其转换成JSON对象Ⅳ、请求转发或请求重定向🍓forward VS redirectreturn 不但可以返回⼀个视图,还可以实现跳转,跳转的方式有两种:forward :请求转发;redirect:请求重定向。请求转发和重定向的使用对比:// 请求重定向 @RequestMapping("/hello") public String hello(){ return "redirect:/hello.html"; } // 请求转发 @RequestMapping("/hello2") public String hello2(){ return "forward:/hello.html"; } forward(请求转发)和 redirect(请求重定向)的区别,举例来说,例如,你告诉你妈妈,你想吃辣条,如果你妈妈,说好,我帮你去买,这就是 forward 请求转发;如果你妈妈让你自己去买,那么就是请求 redirect 重定向。“转发”和“重定向”理解:在中国官⽅发布的内容越少事也越大, “转发”和“重定向”也是⼀样:字越少,责任越大 。转发是服务器帮转的,而重定向是让浏览器重新请求另⼀个地址。forward 和 redirect 具体区别如下:请求重定向(redirect)将请求重新定位到资源;请求转发(forward)服务器端转发。请求重定向地址发⽣变化,请求转发地址不发⽣变化。请求重定向与直接访问新地址效果⼀直,不存在原来的外部资源不能访问;请求转发服务器端转发有可能造成原外部资源不能访问。请求转发如果资源和转发的页面不在⼀个目录下,会导致外部资源不可访问 。

0
0
0
浏览量2022
武士先生

Spring事务和事务传播机制(2)

2、Spring 中设置事务隔离级别Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置,具体操作如下图所示:Ⅰ、MySQL 事务隔离级别有 4 种 1、READ UNCOMMITTED: 读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读2、READ COMMITTED: 读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL查询中,可能会得到不同的结果,这种现象叫做不可重复读。3、REPEATABLE READ:可重复读是MySQL 的默认事务隔离级别,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读(Phantom Read)。4、SERIALIZABLE: 序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。事务隔离级别脏读不可重复读幻读读未提交(READ UNCOMMITTED) √√√读已提交(READ COMMITTED)×√√可重复读(REPEATABLE READ)××√串行化(SERIALIZABLE)×××脏读:一个事务读取到了另一个事务修改的数据之后,后一个事务又进行了回滚操作,从而导致第一个事务读取的数据是错误的。 不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修0改了。 幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务有新增了一部分数据。在数据库中通过以下 SQL 查询全局事务隔离级别和当前连接的事务隔离级别:select @@global.tx_isolation,@@tx_isolation; 以上 SQL 的执⾏结果如下:Ⅱ、Spring 事务隔离级别有 5 种而Spring 中事务隔离级别包含以下 5 种:Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。从上述介绍可以看出,相⽐于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了一个Isolation.DEFAULT(以数据库的全局事务隔离级别为主)。Spring 中事务隔离级别只需要设置 @Transactional ⾥的 isolation 属性即可,具体实现代码如下@RequestMapping("/save") @Transactional(isolation = Isolation.SERIALIZABLE) public Object save(UserInfo user) { // 业务实现 } 四、Spring 事务传播机制1、事务传播机制是什么?Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的。2、为什么需要事务传播机制?事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证⼀个事务在多个调用方法间的可控性的(稳定性的)。例子:像新冠病毒⼀样,它有不同的隔离方式(酒店隔离还是居家隔离),是为了保证疫情可控,然而在每个人的隔离过程中,会有很多个执行的环节,比如酒店隔离,需要负责人员运送、物品运送、消杀原生活区域、定时核算检查和定时送餐等很多环节,而事务传播机制就是保证⼀个事务在传递过程中是可靠性的,回到本身案例中就是保证每个人在隔离的过程中可控的。事务隔离级别解决的是多个事务同时调用⼀个数据库的问题,如下图所示:而事务传播机制解决的是⼀个事务在多个节点(方法)中传递的问题,如下图所示:3、事务传播机制有哪些?Spring 事务传播机制包含以下 7 种:Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建⼀个新的事务。Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启自己的事务,且开启的事务相互独立,互不干扰。Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。以上 7 种传播行为,可以根据是否支持当前事务分为以下 3 类:以情侣关系为例来理解以上分类:4、Spring 事务传播机制使用和各种场景演示Ⅰ、支持当前事务(REQUIRED)以下代码实现中,先开启事务先成功插入一条用户数据,然后再执行日志报错,而在日志报错是发生了异常,观察 propagation = Propagation.REQUIRED 的执行结果。@Autowired private UserService userService; @Autowired private LogService logService; @Transactional// 声明式事务(自动提交) @RequestMapping("/insert") public Integer insert(UserInfo userInfo) { // 非空效验 if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) { return 0; } // 添加用户 int result = userService.add(userInfo); if (result>0){ //日志 logService.add(); } /*try { int num = 10 / 0; } catch (Exception e) { System.out.println(e.getMessage()); *//*throw e;*//* TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }*/ return result; } UserService 实现代码如下:@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional(propagation = Propagation.REQUIRED) public Integer add(UserInfo userInfo) { int result = userMapper.add(userInfo); System.out.println("用户添加:" + result); return result; } } LogService 实现代码如下:@RestController public class LogService { //捣乱,设置一个算数异常 @Transactional(propagation = Propagation.REQUIRED) public int add() { int num = 10 / 0; return 1; } } 执行了SQL语句,都是数据库中没有添加新用户,事务回滚了执行流程描述UserService 中的保存方法正常执行完成。LogService 保存日志程序报错,因为使用的是 Controller 中的事务,所以整个事务回滚。数据库中没有插入任何数据,也就是步骤1中的用户插入方法也回滚了。Ⅱ、不支持当前事务(REQUIRES_NEW)UserController 类中的代码不变,将添加用户和添加日志的方法修改为 REQUIRES_NEW 不支持当前事务,重新创建事务,观察执行结果:@RestController public class UserController { @Resource private UserService userService; @Resource private LogService logService; @RequestMapping("/save") @Transactional(propagation = Propagation.REQUIRED) public Object save(UserInfo userInfo) { // 插⼊⽤户操作 userService.save(userInfo); // 插⼊⽇志 logService.saveLog("⽤户插⼊:" + userInfo.getName()); return true; } } UserService 实现代码如下:@Service public class UserService { @Resource private UserMapper userMapper; @Transactional(propagation = Propagation.REQUIRED) public int save(UserInfo userInfo) { System.out.println("执⾏ save ⽅法."); return userMapper.save(userInfo); } } LogService 实现代码如下:@Service public class LogService { @Transactional(propagation = Propagation.REQUIRED) public int saveLog(String content) { // 出现异常 int i = 10 / 0; return 1; } } 程序执行结果:User 表中成功插入了数据,LogService 保存日志程序报错,但没影响 UserController 中的事务。Ⅲ、不支持当前事务,NEVER 抛异常UserController 实现代码:@RequestMapping("/save") @Transactional public Object save(UserInfo userInfo) { // 插⼊⽤户操作 userService.save(userInfo); return true; } UserService 实现代码:@Transactional(propagation = Propagation.NEVER) public int save(UserInfo userInfo) { System.out.println("执⾏ save ⽅法."); return userMapper.save(userInfo); } 程序执行报错,用户表未添加任何数据。Ⅳ、NESTED 嵌套事务UserController 实现代码如下:@RequestMapping("/save") @Transactional public Object save(UserInfo userInfo) { // 插⼊⽤户操作 userService.save(userInfo); return true } UserService 实现代码如下:@Transactional(propagation = Propagation.NESTED) public int save(UserInfo userInfo) { int result = userMapper.save(userInfo); System.out.println("执⾏ save ⽅法."); // 插⼊⽇志 logService.saveLog("⽤户插⼊:" + userInfo.getName()); return result; } LogService 实现代码如下:@Transactional(propagation = Propagation.NESTED) public int saveLog(String content) { // 出现异常 int i = 10 / 0; return 1; } 最终执行结果,用户表和日志表都没有添加任何数据。嵌套事务和加入事务有什么区别整个事务如果全部执行成功,二者的结果是⼀样的。如果事务执行到一半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上一个方法中执行的结果

0
0
0
浏览量2027
武士先生

SpringBoot统一功能处理

本章是讲Spring Boot 统⼀功能处理模块,也是 AOP 的实战环节,要实现的目标有以下 3 个:使用拦截器实现用户登录权限的统一验证;统⼀数据格式返回;统⼀异常处理。一、用户登录权限效验1、最初用户登录验证用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统⼀的用户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。@RestController @RequestMapping("/user") public class UserController { /** * 某⽅法 1 */ @RequestMapping("/m1") public Object method(HttpServletRequest request) { // 有 session 就获取,没有不会创建 HttpSession session = request.getSession(false); if (session != null && session.getAttribute("userinfo") != null) { // 说明已经登录,业务处理 return true; } else { // 未登录 return false; } } /** * 某⽅法 2 */ @RequestMapping("/m2") public Object method2(HttpServletRequest request) { // 有 session 就获取,没有不会创建 HttpSession session = request.getSession(false); if (session != null && session.getAttribute("userinfo") != null) { // 说明已经登录,业务处理 return true; } else { // 未登录 return false; } } // 其他⽅法。。。 } 从上述代码可以看出,每个方法中都有相同的用户登录验证权限,它的缺点是:每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也⼀样要传参调用和在方法中进行判断。添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成本和维护成本。这些用户登录验证的方法和接下来要实现的业务几何没有任何关联,但每个方法中都要写⼀遍。 所以提供⼀个公共的 AOP 方法来进行统⼀的用户登录权限验证迫在眉睫。2、Spring AOP 用户统⼀登录验证的问题说到统⼀的用户登录验证,我们想到的第⼀个实现方案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class UserAspect { // 定义切点⽅法 controller 包下、⼦孙包下所有类的所有⽅法 @Pointcut("execution(* com.example.demo.controller..*.*(..))") public void pointcut() { } // 前置⽅法 @Before("pointcut()") public void doBefore() { } // 环绕⽅法 @Around("pointcut()") public Object doAround(ProceedingJoinPoint joinPoint) { Object obj = null; System.out.println("Around ⽅法开始执⾏"); try { // 执⾏拦截⽅法 obj = joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("Around 方法结束执行"); return obj; } } 如果要在以上 Spring AOP 的切面中实现用户登录权限效验的功能,有以下两个问题:定义拦截的规则(表达式)非常难。(我们要对⼀部分方法进行拦截,而另⼀部分方法不拦截,如注册方法和登录方法是不拦截的,这样 的话排除方法的规则很难定义,甚至没办法定义)。在切面类中拿到 HttpSession 比较难那这样如何解决呢?Spring 拦截器对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理方法。将自定义拦截器加入WebMvcConfigurer 的 addInterceptors 方法中。具体实现如下:目录结构:Ⅰ、实现拦截器关键步骤:a.实现 HandlerInterceptor 接口b.重写 preHeadler 方法,在方法中编写自己的业务代码package com.example.demo.config; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class LoginInterceptor implements HandlerInterceptor { /** * 此方法返回一个 boolean,如果为 true 表示验证成功,可以继续执行后续流程如果是 false 表示验证失败,后面的流程不能执行了 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //用户登录业务判断 HttpSession session=request.getSession(false); if (session != null && session.getAttribute("userinfo") != null) { // 说明用户已经登录 return true; } // 可以调整到登录页面 or 返回一个 401/403 没有权限码 response.sendRedirect("/login.html"); // response.setStatus(403); return false; } } Ⅱ、将拦截器添加到配置文件中,并且设置拦截的规则package com.example.demo.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class AppConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") // 拦截所有请求 .excludePathPatterns("/user/login") // 排除的url地址(不拦截的url地址) .excludePathPatterns("/user/reg"); } } 其中:addPathPatterns:表示需要拦截的 URL,**表示拦截任意方法(也就是所有方法)。excludePathPatterns:表示需要排除的 URL。说明:以上拦截规则可以拦截此项目中的使用 URL,包括静态文件(图片文件、JS 和 CSS 等文件)。排除所有的静态资源: // 拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") // 拦截所有接⼝ .excludePathPatterns("/**/*.js") .excludePathPatterns("/**/*.css") .excludePathPatterns("/**/*.jpg") .excludePathPatterns("/login.html") .excludePathPatterns("/**/login"); // 排除接⼝ } UserController:package com.example.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { /* @GetMapping public String getMethod(){ return "执行GET请求!"; } @PostMapping public String postMethod(){ return "执行POST请求!"; }*/ @RequestMapping("/getuser") public String getUser() { System.out.println("执行了 get User~"); return "get user"; } @RequestMapping("/login") public String login() { System.out.println("执行了 login~"); return "login~"; } @RequestMapping("/reg") public String reg() { System.out.println("执行了 reg~"); return "reg~"; } } Ⅲ、启动项目:不拦截:拦截:http://localhost:8080/user/getuser为什么会显示重定向次数过多?这是因为这个login.html页面也被拦截了,所以它去访问时候就会一直重定向重定向去访问。解决方法:Ⅳ、拦截器实现原理正常情况下的调用顺序:然而有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图所示:所有的 Controller 执行都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出,如下图所示:而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,doDispatch 源码如下: protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } } 从上述源码可以看出在开始执行 Controller 之前,会先调用 预处理方法 applyPreHandle,而applyPreHandle 方法的实现源码如下:boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) { // 获取项⽬中使⽤的拦截器 HandlerInterceptor HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i); if (!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } return true; } 从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法,这样就和咱们前面定义的拦截器对应上了,如下图所示:此时用户登录权限的验证方法就会执行,这就是拦截器的实现原理。二、统⼀异常处理Ⅰ、实现统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知, 也就是执行某个方法事件,具体实现代码如下:package com.example.demo.config; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap; @ControllerAdvice @ResponseBody public class MyExHandler { /** * 拦截所有的空指针异常,进行统一的数据返回 */ @ExceptionHandler(NullPointerException.class) public HashMap<String, Object> nullException(NullPointerException e) { HashMap<String, Object> result = new HashMap<>(); result.put("code", "-1"); result.put("msg", "空指针异常:" + e.getMessage()); // 错误码的描述信息 result.put("data", null); return result; } @ExceptionHandler(Exception.class)//保底的默认异常 public HashMap<String, Object> exception(Exception e) { HashMap<String, Object> result = new HashMap<>(); result.put("code", "-1"); result.put("msg", "异常:" + e.getMessage()); // 错误码的描述信息 result.put("data", null); return result; } } Ⅱ、添加异常UserController @RequestMapping("/login") public String login() { Object obj = null; obj.hashCode(); System.out.println("执行了 login~"); return "login~"; } @RequestMapping("/reg") public String reg() { int num = 10 / 0; System.out.println("执行了 reg~"); return "reg~"; } Ⅲ、启动程序:login:reg:方法名和返回值可以自定义,其中最重要是 @ExceptionHandler(Exception.class) 注解。以上方法表示,如果出现了异常就返回给前端⼀个 HashMap 的对象,其中包含的字段如代码中定义的那样。 我们可以针对不同的异常,返回不同的结果。三、统一数据返回格式1.创建一个类,并添加 @ControllerAdvice2.实现ResponseBodyAdvice接口,并重写supports和beforeBodywrite (统一对象就是此方法中实现的)1、为什么需要统一数据返回格式?统一数据返回格式的优点有很多,比如以下几个:方便前端程序员更好的接收和解析后端数据接口返回的数据。降低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的。有利于项目统⼀数据的维护和修改。有利于后端技术部门的统⼀规范的标准制定,不会出现稀奇古怪的返回内容。2、统一数据返回格式的实现Ⅰ、实现统⼀的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现。package com.example.demo.config; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.util.HashMap; @ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice { @Autowired private ObjectMapper objectMapper; /** * 此方法返回 true 则执行下面 beforeBodyWrite 方法 * 反之则不执行 */ @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { HashMap<String, Object> result = new HashMap<>(); result.put("code", 200); result.put("msg", ""); result.put("data", body); // 需要特殊处理,因为 String 在转换的时候会报错 if (body instanceof String) { try { return objectMapper.writeValueAsString(result); } catch (JsonProcessingException e) { e.printStackTrace(); } } return result; } } 如果没有对Sting进行特殊处理,就会有异常出现:Ⅱ、添加实现类UserController@RequestMapping("/getnum") public Integer getNumber() { return new Random().nextInt(10); } @RequestMapping("/getuser") public String getUser() { System.out.println("执行了 get User~"); return "get user"; } Ⅲ、启动程序Ⅳ、总结上面只是以一种简单的方式来向大家介绍如何去统一数据返回格式,在实际工作中并不会这样去使用而是将统⼀的返回格式(包含:status、data、msg 字段)进行封装成一个类:根据不同操作成功和操作失败的情况,重写了不同的方法:package com.example.demo.common; import lombok.Data; import java.io.Serializable; /** * 统一数据格式返回 */ @Data public class AjaxResult implements Serializable {//支持序列化 // 状态码 private Integer code; // 状态码描述信息 private String msg; // 返回的数据 private Object data; /** * 操作成功返回的结果,需要 data */ public static AjaxResult success(Object data) { AjaxResult result = new AjaxResult(); result.setCode(200); result.setMsg(""); result.setData(data); return result; } //重载success public static AjaxResult success(int code, Object data) { AjaxResult result = new AjaxResult(); result.setCode(code); result.setMsg(""); result.setData(data); return result; } public static AjaxResult success(int code, String msg, Object data) { AjaxResult result = new AjaxResult(); result.setCode(code); result.setMsg(msg); result.setData(data); return result; } /** * 返回失败结果 */ public static AjaxResult fail(int code, String msg) { AjaxResult result = new AjaxResult(); result.setCode(code); result.setMsg(msg); result.setData(null); return result; } public static AjaxResult fail(int code, String msg, Object data) { AjaxResult result = new AjaxResult(); result.setCode(code); result.setMsg(msg); result.setData(data); return result; } }

0
0
0
浏览量2018
武士先生

Spring Boot日志文件

前篇我们 讲完了SpringBoot的配置文件,讲解了为什么学配置文件&配置文件的作用,还有两种配置文件的格式与使用。这篇来讲SpringBoot 日志文件,下面我们一起进入SpringBoot 日志文件的世界!一、日志的作用日志是程序的重要组成部分,想象一下,如果程序报错了,不让你打开控制台看日志,那么你能找到报错的原因吗?答案是否定的,写程序不是买彩票,不能完全靠猜,因此日志对于我们来说,最主要的用途就是排除和定位问题。Spring Boot日志文件用于记录应用程序的运行日志。它可以帮助开发人员在应用程序出现问题时进行故障排除和调试。以下是Spring Boot日志文件的几个用途:故障排除:当应用程序发生错误或异常时,日志文件可以提供有关错误发生的上下文信息,如错误堆栈跟踪、错误消息等。开发人员可以根据这些信息来定位和修复问题。调试:日志文件可以记录应用程序的运行过程中的详细信息,如请求参数、方法调用、返回结果等。这些信息可以帮助开发人员理解应用程序的运行流程,定位潜在的问题,并进行性能优化。监控和性能分析:通过分析日志文件,开发人员可以获取应用程序的运行状态和性能指标,如请求响应时间、吞吐量、并发请求数等。这些信息可以帮助开发人员监控应用程序的健康状况,并进行性能分析和优化。安全审计:日志文件可以记录应用程序的操作日志,如用户登录、数据修改等。这些信息可以用于安全审计和追踪用户行为。所以 Spring Boot日志文件是开发人员在应用程序运行过程中进行故障排除、调试、性能分析和安全审计的重要工具。1、日志真实使用案例:比如当我们去百度注册账号时,在注册时候百度不止要在后台添加 一条用户记录,同时也会给百度贴吧添加一条一模一样的用户记录,这样做的目的是为了实现一次注 册,多处使用的目的。不需要用户在两边系统注册了,等于在程序中以极低的成本实现的用户数据的同 步,但这样设计有一个致命的问题,用户在百度注册信息的时候,如果百度贴吧挂了,那么用户的注册 行为就会失败,因为用户在注册的时候需要同步注册到百度系统,但贴吧现在挂了,这个时候怎么办 呢?最简单的解决方案,百度账号在注册的时候,不管贴吧是否注册成功,都给用户返回成功,如果注册失败了,记录一下日志,等贴吧恢复正常之后,把日志给贴吧的管理人员,让他手动将注册失败的用户同步到贴吧系统,这样就最低成本的解决了问题。这就是日志的重要作用。二、日志怎么用Spring Boot 项目在启动的时候默认就有日志输出,如下图所示:以上内容就是 Spring Boot 输出的控制台日志信息。 通过上述日志信息我们能发现以下 3 个问题:Spring Boot 内置了日志框架(不然也输出不了日志)。默认情况下,输出的日志并⾮是开发者定义和打印的,那开发者怎么在程序中自定义打印日志呢?日志默认是打印在控制台上的,而控制台的日志是不能被保存的,那么怎么把日志永久的保存下来呢?下面我们一起来找寻这些问题的答案。1、自定义日志打印开发者自定义打印日志的实现步骤:在程序中得到日志对象。使用日志对象的相关语法输出要打印的内容Ⅰ、在程序中得到日志对象在程序中获取日志对象需要使用日志工厂 LoggerFactory:// 1.得到⽇志对象 private static Logger logger = LoggerFactory.getLogger(UserController.class); //⽇志⼯⼚需要将每个类的类型传递进去,这样我们才知道⽇志的归属类,才能更⽅便、更直观的定位 到问题类。 Logger 对象是属于 org.slf4j 包下的,不要导入错包。 常见的日志框架说明(了解)Ⅱ、使用日志对象打印日志 日志对象的打印方法有很多种,我们可以先使用 info() 方法来输出日志,如下代码所示// 2.使⽤⽇志打印⽇志 @Controller//当前类为控制器 @ResponseBody//返回的是数据,而非页面 public class UserController { private static final Logger logger= LoggerFactory.getLogger(UserController.class); @RequestMapping("Hi") public String sayHi(){ //写日志 // 2.使⽤⽇志打印⽇志 logger.info("--------------要输出⽇志的内容----------------"); return "Hi,Spring Boot"; } } 打印信息 :控制台打印:浏览器打印(使用的是生产环境,生产环境设置的端口号为:7777):2、日志的级别Ⅰ、日志级别有什么用?日志级别可以控制不同环境下,⼀个程序是否需要打印日志,如开发环境我们需要很详细的信息, 而生产环境为了保证性能和安全性就会输入尽量少的日志,而通过日志的级别就可以实现此需求。日志级别可以帮你筛选出重要的信息,比如设置日志级别为 error,那么就可以只看程序的报错日志了,对于普通的调试日志和业务日志就可以忽略了,从而节省开发者信息筛选的时间。Ⅱ、日志级别的分类与使用日志的级别分为:trace:微量,少许的意思,级别最低;debug:需要调试时候的关键信息打印;info:普通的打印信息(默认日志级别);warn:警告,不影响使用,但需要注意的问题;error:错误信息,级别较高的错误日志信息;fatal:致命的,因为代码异常导致程序退出执行的事件(软件或系统运行过程中发生了严重错误或异常,导致程序无法继续正常执行而必须终止的日志记录),我们是无法主动的去打印fatal日志。日志级别的顺序:越往上接收到的消息就越少,如设置了 warn 就只能收到 warn、error、fatal 级别的日志了。下面我们来看一下下面的代码,来更深入了解一下日志的级别:@Controller//当前类为控制器 @ResponseBody//返回的是数据,而非页面 public class UserController { private static final Logger logger= LoggerFactory.getLogger(UserController.class); @RequestMapping("Hi") public String sayHi(){ //写日志 // 2.使⽤⽇志打印⽇志 logger.trace("我是trace"); logger.debug("我是debug"); logger.info("我是info"); logger.warn("我是warn"); logger.error("我是error"); return "Hi,Spring Boot"; } } 控制台打印:我们发现只打印了三个,我们不是还打印了trace还有debug吗?这是因为日志设置的默认级别是info,所以只能打印info级别下面的日志。Ⅲ、日志级别设置日志级别配置只需要在配置文件中设置“logging.level”配置项即可:#开发模式 #设置默认端口号 server: port: 7777 #设置默认日志等级 logging: level: root: trace 我们设置默认级别为:trace。看控制台打印:我们看到设置为trace后,我们要求打印都打印了。但设置为trace之后,可能会导致日志文件过大或者打印输出过长,这可能会给查看和分析日志带来一些困难。为了解决这个问题,可以考虑以下几个方法:调整日志级别:将日志级别设置为更高的级别,例如将级别从trace调整为debug、info或者warn。这样可以减少不必要的日志输出,只保留关键信息。筛选日志输出:通过使用日志过滤器或者正则表达式,只打印特定关键字或者模式匹配的日志信息。这样可以减少无关的日志输出,只保留需要的部分。分割日志文件:设置日志文件大小限制或者定期将日志文件进行分割,可以防止单个日志文件过大。这样可以方便查看和管理日志文件。使用日志分析工具:使用专门的日志分析工具,可以对大量的日志进行快速搜索、过滤和分析。这样可以更方便地查找和定位问题。所以在通常,默认 默认级别为info即可,看warn日志和error日志即可。对于warn和error级别的日志消息,你也可以选择查看。 日志级别的设置是特别灵活的,我们还可以针对不同的目录设置不同的日志级别#开发模式 #设置默认端口号 server: port: 7777 #设置默认日志等级 logging: level: root: info com: example: springboard:info 有人说:我打印可以使用System.out.println()啊,为什么一定要使用日志呢?下面我们来比较一下Ⅳ、System.out.println() VS 日志灵活性:使用日志框架可以方便地控制日志的输出级别,从而在不同环境中灵活地开启或关闭日志输出。而使用System.out.println()则需要手动添加或删除代码。可配置性:日志框架可以通过配置文件来设置日志的输出格式、输出位置等信息,而System.out.println()则只能将日志输出到控制台。性能优化:日志框架会对日志进行缓冲和异步处理,以提高程序的性能。而System.out.println()会直接将日志输出到控制台,可能会对程序的性能造成一定的影响。可扩展性:使用日志框架可以方便地扩展和替换日志实现,例如切换到其他日志框架或自定义日志输出方式。而使用System.out.println()则需要手动修改代码。System.out.println() 不能被持久化(可接着看后面便知晓了)。3、日志持久化以上的日志都是输出在控制台上的,然而在生产环境上咱们需要将日志保存下来,以便出现问题之后追溯问题,把日志保存下来的过程就叫做持久化。想要将日志进行持久化,只需要在配置文件中指定日志的存储目录或者是指定日志保存文件名之后, Spring Boot 就会将控制台的日志写到相应的目录或文件下了。Ⅰ、配置日志文件的保存路径:路径:#开发模式 #设置默认端口号 server: port: 7777 #设置默认日志等级 logging: level: root: error com: example: springbootdemo: info # 设置⽇志⽂件的⽬录 file: path: C:\\logs 在C盘创建应该logs文件夹:路径+文件名: 我们可以自己定义.log的文件名:# 设置⽇志⽂件的⽂件名 logging: file: name: C:\\logs\\spring-1.log 文件名: #开发模式 #设置默认端口号 server: port: 7777 #设置默认日志等级 logging: level: root: error com: example: springbootdemo: info # 设置⽇志⽂件的⽬录 file: name: spring-1.log 如果直接指点文件名,那它就会直接在项目文件下直接生成.log文件:Ⅱ、进行日志持久化 @Controller//当前类为控制器 @ResponseBody//返回的是数据,而非页面 public class UserController { private static final Logger logger= LoggerFactory.getLogger(UserController.class); @RequestMapping("Hi") public String sayHi(){ //写日志 // 2.使⽤⽇志打印⽇志 logger.trace("我是trace"); logger.debug("我是debug"); logger.info("我是info"); logger.warn("我是warn"); logger.error("我是error"); System.out.println("我是System。"); return "Hi,Spring Boot"; } } 运行代码:打开控制台:打开logs:打开这spring.log:可以发现这和控制台打印的一模一样。但是没有打印 System.out.println(),这是因为日志的持久化只会保存日志信息,其他信息是不会去保存的。Ⅲ、日志是追加or覆盖?如果我们重新运行代码,这个spring.log里面的内容是被覆盖还是被追加呢?现在我们重新运行代码,打开spring.log看看:可以看到日志的内容是追加的,所以不用担心日志会被覆盖或者消失,但是如果一直把它放在一个.log文件里面,那它越来越大,这怎么办啊?这时候就应该去查看一下官方文档:官方文档可以看到,日志持久化文件最大为10M,当超过10M是它会重新创建一个新的持久化文件。还有其他持久化设置,感兴趣的可以去了解一下。三、更简单的日志输出—lombok每次都使用 LoggerFactory.getLogger(xxx.class) 很繁琐,且每个类都添加⼀遍,也很麻烦,这里讲⼀ 种更好用的日志输出方式,使用 lombok 来更简单的输出。添加 lombok 框架支持。使⽤ @slf4j 注解输出日志1、添加 lombok 依赖<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <optional>true</optional> </dependency> 但是我们在创建Spring Boot时就已经导入了Lombok框架了:pom.xml:Ⅰ、旧项目添加新依赖框架 安装EditStarters然后在pom.xml里面右键选择 Generate选择Edit Starters点击OK添加需要添加的依赖:添加完成之后,重新reload就行了。2、输出日志package com.example.springbootdemo; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller//当前类为控制器 @ResponseBody//返回的是数据,而非页面 @Slf4j public class ArticleController { @RequestMapping("art") public String sayHi(){ //写日志 // 2.使⽤⽇志打印⽇志 log.trace("我是trace"); log.debug("我是debug"); log.info("我是info"); log.warn("我是warn"); log.error("我是error"); return "Hi,Article"; } } 注意:使用 @Slf4j 注解,在程序中使用 log 对象即可输入日志,并且只能用 log 对象才能输出这是 lombok 提供的对象名 控制台打印:3、lombok原理解释lombok 能够打印日志的密码就在 target 目录里面,target 为项目最终执行的代码,查看 target 目录。如下:自己所写的代码:在target目录中,您可以找到编译后的项目代码可以看到lombok会直接给你生成注释对应的代码 ,这就特别省事了。Java 程序的运行原理Lombok 的作⽤如下图所示:4、lombok 更多注解说明基本注解组合注解⽇志注解

0
0
0
浏览量2019
武士先生

Spring Boot单元测试

Spring Boot 中进行单元测试是一个常见的做法,可以帮助你验证应用程序的各个组件是否按预期工作。所以我们有必要去学习一番!一、什么是单元测试?单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证的过程就叫单元测试。单元测试是开发者编写的一小段代码,用于检验被测代码的⼀个很小的、很明确的(代码)功能是否正确。执行单元测试就是为了证明某段代码的执行结果是否符合我们的预期。如果测试结果符合我们的预期,称之为测试通过,否则就是测试未通过(或者叫测试失败)。二、单元测试有哪些好处?方便、快捷测试一个功能模块(方法级别)在打包时会运行所以的单元测试,只有所有的单元测试都通过之后才能正常的打包,所以在这个过程中可以帮我们发现一些问题,减少问题的概率。使用单元测试可以在不污染数据库数据的情况下,来测试某个功能的正确性。(添加@Transactional注解即可)三、Spring Boot 单元测试使用Spring Boot 项目创建时会默认单元测试框架 spring-boot-test,而这个单元测试框架主要是依靠另⼀ 个著名的测试框架 JUnit 实现的,打开 pom.xml 就可以看到,以下信息是 Spring Boot 项目创建是自动添加的:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> 而 spring-boot-starter-test 的 MANIFEST.MF(Manifest 文件是用来定义扩展或档案打包的相关信息的)里面有具体的说明,如下信息所示:四、单元测试的实现步骤1.生成单元测试类在需要进行单元测试的类里面右键:UserMapperTest生成在text目录下:package com.example.ssmdemo1.mapper; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class UserMapperTest { @Test void getUserById() { } } 这个时候,此方法是不能调用到任何单元测试的方法的,此类只生成了单元测试的框架类,具体的业务代码要自己填充。2、添加单元测试代码Ⅰ、添加 Spring Boot 框架测试注解:@SpringBootTestpackage com.example.ssmdemo1.mapper; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest//表明当前单元测试是运行在Spring Boot环境中的 class UserMapperTest { @Test void getUserById() { } } Ⅱ、添加单元测试业务逻辑package com.example.ssmdemo1.mapper; import com.example.ssmdemo1.entity.Userinfo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest//1、表明当前单元测试是运行在Spring Boot环境中的 class UserMapperTest { //2、注入测试对象 @Autowired private UserMapper userMapper; @Test void getUserById() { //3、添加单元测试的业务代码 Userinfo userinfo=userMapper.getUserById(1); System.out.println(userinfo); } } 启动测试项目:我们进行单元测试, 后面需要去运行我们的项目,我们一定要将右上角重新切换过来:五、简单的断言说明方法说明assertEquals判断两个对象或两个原始类型是否相等assertNotEquals判断两个对象或两个原始类型是否不相等assertSame判断两个对象引用是否指向同一个对象assertNotSame判断两个对象引用是否指向不同的对象assertTrue判断给定的布尔值是否为 trueassertFalse判断给定的布尔值是否为 falseassertNull判断给定的对象引用是否为 nullassertNotNull判断给定的对象用是否不为 null断言:如果断言失败,则后面的代码都不会执行。package com.example.ssmdemo1.mapper; import com.example.ssmdemo1.entity.Userinfo; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest//1、表明当前单元测试是运行在Spring Boot环境中的 class UserMapperTest { //2、注入测试对象 @Autowired private UserMapper userMapper; @Test void getUserById() { //3、添加单元测试的业务代码 Userinfo userinfo=userMapper.getUserById(1); System.out.println(userinfo); //判断1是否等于2 简单断言 Assertions.assertEquals(1,2); } } 单元测试失败:单元测试失败,这时候我们去打包也会打包失败:打包失败:打包成功:

0
0
0
浏览量2019
武士先生

MyBatis查询数据库(1)

经过前⾯的学习咱们 Spring 系列的基本操作已经实现的差不多了,接下来,咱们来学习更重要的知识,将前端传递的数据存储起来,或者查询数据库里面的数据。一、MyBatis 是什么?🍭MyBatis 是⼀款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的 XML 或注解来配置 和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 简单来说MyBatis 是更简单完成程序和数据库交互的工具,也就是更简单的操作和读取数据库工具。二、为什么要学习 MyBatis? 🍭对于后端开发来说,程序是由以下两个重要的部分组成的:后端程序数据库而这两个重要的组成部分要通讯,就要依靠数据库连接工具,那数据库连接⼯具有哪些?比如之前我们学习的 JDBC,还有今天我们将要介绍的 MyBatis,那已经有了 JDBC 了,为什么还要学习 MyBatis? 这是因为 JDBC 的操作太繁琐了,我们回顾⼀下 JDBC 的操作流程:创建数据库连接池 DataSource通过 DataSource 获取数据库连接 Connection编写要执行带 ? 占位符的 SQL 语句通过 Connection 及 SQL 创建操作命令对象 Statement替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值使用 Statement 执行 SQL 语句查询操作:返回结果集 ResultSet,更新操作:返回更新的数量处理结果集释放资源三、怎么学MyBatis?🍭MyBatis 学习只分为两部分:配置 MyBatis 开发环境;使用 MyBatis 模式和语法操作数据库四、第⼀个MyBatis查询 🍭开始搭建 MyBatis 之前,我们先来看⼀下 MyBatis 在整个框架中的定位,框架交互流程图:MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。在面向对象编程语言中,将关系型数据库中的数据与对象建立起映射关系,进而自动的完成数据与对象的互相转换:将输入数据(即传入对象)+SQL 映射成原生SQL将结果集映射为返回对象,即输出对象ORM 把数据库映射为对象ORM 把数据库映射为对象:数据库表(table)--> 类(class)记录(record,行数据)--> 对象(object)字段(field) --> 对象的属性(attribute)⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。 也就是说使用 MyBatis 可以像操作对象⼀样来操作数据库中的表,可以实现对象和数据库表之间的转换,接下来我们来看 MyBatis 的使用吧。1、添加MyBatis框架支持🍉添加 MyBatis 框架支持分为两种情况:⼀种情况是对自己之前的 Spring 项目进行升级,另⼀种情况是创建⼀个全新的 MyBatis 和 Spring Boot 的项目,下面我们分别来演示这两种情况的具体实现Ⅰ、老项目添加MyBatis🍓<!-- 添加 MyBatis 框架 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <!-- 添加 MySQL 驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> 添加了 MyBatis 之后,为什么还需要添加 MySQL 驱动呢? MyBatis 就像⼀个平台(类似京东),而数据库相当于商家有很多种,不止有 MySQL,还有 SQL Server、DB2 等等.....因此这两个都是需要添加的。扩展:在老项目中快速添加框架,更简单的操作方式是使用EditStarters插件(前面已经说过了)Spring Boot日志文件Ⅱ、新项目添加MyBatis🍓如果是新项目创建 Spring Boot 项目的时候添加引用就可以了,如下图所示:我们创建完新的ssm项目,直接去启动时一定会报错, 因为你添加了数据库依赖而没有连接数据库(配置数据库的连接信息)下面我们就来配置数据库的连接信息。2、配置连接字符串和MyBatis🍉此步骤需要进行两项设置,数据库连接字符串设置和 MyBatis 的 XML 文件配置Ⅰ、配置连接字符串🍓如果是 application.yml 添加如下内容:# 数据库连接配置 spring: datasource: url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver application.propertiesspring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false spring.datasource.username=root #数据库密码 spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 注意事项:如果使用 mysql-connector-java 是 5.x 之前的使用的是“com.mysql.jdbc.Driver” ,如果是大于 5.x 使用的是“com.mysql.cj.jdbc.Driver” 。Ⅱ、配置 MyBatis 中的 XML 路径🍓MyBatis 组成2部分: 接口(表的使用操作方法,给程序其他类调用的) xml(实现接口,写具体SQL语句)MyBatis 的 XML 中保存是查询数据库的具体操作 SQL,配置如下:#mybatis xml 保存路径 # 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件 mybatis: mapper-locations: classpath:mapper/*Mapper.xml 五、添加业务代码🍭下面按照后端开发的工程思路,也就是下面的流程来实现 MyBatis 查询所有用户的功能:目录结构(需要在和启动类在同一个目录下,约定大于配置) :下面所需要使用的 MySQL代码:-- 创建数据库 drop database if exists mycnblog; create database mycnblog DEFAULT CHARACTER SET utf8mb4; -- 使用数据 use mycnblog; -- 创建表[用户表] drop table if exists userinfo; create table userinfo( id int primary key auto_increment, username varchar(100) not null, password varchar(32) not null, photo varchar(500) default '', createtime datetime default now(), updatetime datetime default now(), `state` int default 1 ) default charset 'utf8mb4'; -- 创建文章表 drop table if exists articleinfo; create table articleinfo( id int primary key auto_increment, title varchar(100) not null, content text not null, createtime datetime default now(), updatetime datetime default now(), uid int not null, rcount int not null default 1, `state` int default 1 )default charset 'utf8mb4'; -- 创建视频表 drop table if exists videoinfo; create table videoinfo( vid int primary key, `title` varchar(250), `url` varchar(1000), createtime datetime default now(), updatetime datetime default now(), uid int )default charset 'utf8mb4'; -- 添加一个用户信息 INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1); -- 文章添加测试数据 insert into articleinfo(title,content,uid) values('Java','Java正文',1); -- 添加视频 insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1); Ⅰ、添加实体类🍓先添加用户的实体类:package com.example.ssmdemo1.entity; import lombok.Data; import java.time.LocalDateTime; @Data public class Userinfo { //数据库中用户信息的七个变量 private Integer id; private String username; private String password; private String photo;//头像 private LocalDateTime createTime; private LocalDateTime updateTime; private Integer state; } Ⅱ、添加 mapper 接口🍓数据持久层的接口定义:package com.example.ssmdemo1.mapper; import com.example.ssmdemo1.entity.Userinfo; import org.apache.ibatis.annotations.Mapper; @Mapper//需要添加 @Mapper 注解 public interface UserMapper { Userinfo getUserById(Integer id); } Ⅲ、添加 UserMapper.xml🍓<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.ssmdemo1.mapper.UserMapper"> </mapper> 安装MybatisX插件实现快速导航:UserMapper.xml 查询所有用户的具体实现 SQL:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.ssmdemo1.mapper.UserMapper"> <select id="getUserById" resultType="com.example.ssmdemo1.entity.Userinfo"> select * from userinfo where id=${id} </select> </mapper> UserMapper接口添加  @param 注解package com.example.ssmdemo1.mapper; import com.example.ssmdemo1.entity.Userinfo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper//需要添加 @Mapper 注解 public interface UserMapper { Userinfo getUserById(@Param("id") Integer id); } 以下是对以上标签的说明:< mapper>标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接口的全限定 名,包括全包名.类名。< select>查询标签:是⽤来执⾏数据库的查询操作的:id:是和 Interface(接⼝)中定义的方法名称⼀样的,表示对接口的具体实现方法。resultType:是返回的数据类型,也就是开头我们定义的实体类Ⅳ、添加 Service🍓package com.example.ssmdemo1.service; import com.example.ssmdemo1.entity.Userinfo; import com.example.ssmdemo1.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { @Autowired private UserMapper userMapper; public Userinfo getUserById(Integer id){ return userMapper.getUserById(id); } } 注入可以注入普通对象也可以注入接口(Spring会直接去找它的实现类,然后引入使用) 。 如果接口有多个实现类?这个时候程序就会报错,因为程序不知道你要注入的是哪个类,这种问题 又回到了 同一个类型注入多次我应该怎么去解决: 【Spring】——Spring简单 读和取Ⅴ、添加 Controller🍓控制器层的实现代码如下:package com.example.ssmdemo1.controller; import com.example.ssmdemo1.entity.Userinfo; import com.example.ssmdemo1.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; //url 路径名直接全部小写即可 @RequestMapping("/getuserbyid") public Userinfo geUserById(Integer id){ if (id==null) return null; return userService.getUserById(id); } } 运行代码:

0
0
0
浏览量2017
武士先生

MybatisPlus(3)

一、查询投影查询投影是指在查询操作中,只选择需要的字段进行返回,而不是返回整个实体对象。这样可以减少网络传输的数据量,提高查询的效率。1、查询指点字段 @Test void textGetAll() { LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>(); List<User> userList = userDao.selectList(lqw); System.out.println(userList); } 正常查询是这样,会将所有字段都查询到然后当我们想查询部分字段时,这就叫查询投影。1、lambda格式我们只查询id、name、age三个字段: @Test void textGetAll() { LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>(); lqw.select(User::getId,User::getName,User::getAge); List<User> userList = userDao.selectList(lqw); System.out.println(userList); } 运行结果:可以看到这样确实只查询了三个字段,其他字段都显示为null。但是这种格式的写法只适用于lambda格式,如果不是lambda格式则是另一种写法。2、非lambda格式查询id、name、age、tel四个字段: @Test void textGetAll() { QueryWrapper<User> lqw=new QueryWrapper<>(); lqw.select("id","name","age","tel"); List<User> userList = userDao.selectList(lqw); System.out.println(userList); } 运行结果:2、查询条数(count) 当我们想查询数据条数时,应该怎么办?lambda格式写法可做不了程序条数。 @Test void textGetAll() { QueryWrapper<User> lqw=new QueryWrapper<>(); lqw.select("count(*)"); List<Map<String, Object>> userList = userDao.selectMaps(lqw); System.out.println(userList); } 运行结果:设置别名:可以看出这很好查出来了,共有四条数据,但是最下面结果会不会觉得丑了点,那我们给它设置一个别名: @Test void textGetAll() { QueryWrapper<User> lqw=new QueryWrapper<>(); lqw.select("count(*) as count"); List<Map<String, Object>> userList = userDao.selectMaps(lqw); System.out.println(userList); } 运行代码:3、分组查询我们来一个按电话分组查询 @Test void textGetAll() { QueryWrapper<User> lqw=new QueryWrapper<>(); lqw.select("count(*) as count, tel"); lqw.groupBy("tel"); List<Map<String, Object>> userList = userDao.selectMaps(lqw); System.out.println(userList); } user表:运行结果:MybatisPlus查询也并不是什么都可以查询,适用于任何情况,当有些情况实现不了时,还得老老实实的使用Mybatis时候的写法,写查询语句写Mapper。二、查询条件设置MyBatis-Plus提供了多种方式来设置查询条件,可以根据具体的需求选择适合的方式。1、匹配查询我们平时去进行登录操作时就会进行用户名和密码匹配,那在MyBatis-Plus中然后去进行查询呢? @Test void textGetAll() { LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>(); //等同于= lqw.eq(User::getName,"张三").eq(User::getPassword,"123456"); List<User> userList=userDao.selectList(lqw); //因为我们本来就是查询一个用户,使用可以使用selectOne替换上面这条语句 //User loginUser=userDao.selectOne(lqw); System.out.println(userList); } 运行测试代码:这里查询出来了张三用户,不过在当我们去使用这个操作时一般不会用密码原文,而是经过加密的密码。2、模糊匹配在MyBatis-Plus中进行模糊匹配,可以使用like方法来设置模糊查询条件。以下是使用like方法进行模糊匹配的示例: @Test void textGetAll() { LambdaQueryWrapper<User> lqw=new LambdaQueryWrapper<>(); //模糊查询 lqw.like(User::getName,"三"); List<User> userList=userDao.selectList(lqw); System.out.println(userList); } 查询结果:我们可以在控制台看到这个like中的百分号(%)是加在三的左右两边都有。likeRight的百分号就在右边在官网中还有很多条件构造器,感兴趣的同学可以前往官网学习:条件构造器 | MyBatis-Plus (baomidou.com)三、映射匹配兼容性MyBatis-Plus对于映射匹配的兼容性非常好,它与MyBatis框架完全兼容,并且提供了更多的便利功能。MyBatis-Plus使用的是MyBatis框架作为底层,它与MyBatis的映射配置文件(Mapper XML)和注解方式完全兼容。这意味着你可以继续使用MyBatis的映射方式,或者选择使用MyBatis-Plus提供的注解方式,二者可以灵活切换。1、表字段和编码属性设计不同步 在实际情况中肯定有这样的情况,就是设计数据库的人设计的字段名和后端写实体类的人设计的属性名不一样:那这个时候可以加一个@TableField注解 ,让我们来做这个字段的映射。package com.example.domain; import com.baomidou.mybatisplus.annotation.TableField; import lombok.Data; @Data public class User { private Long id; private String name; @TableField(value = "pwd") private String password; private Integer age; private String tel; } @TableField是MyBatis-Plus提供的注解之一,用于指定实体类中的字段与数据库表中的列的映射关系。使用@TableField注解可以对实体类的字段进行一些配置,包括字段名、是否为数据库表的主键、是否为自动填充字段等。以下是@TableField注解的常用属性:value:指定字段与数据库表的列名的映射关系,如果不指定,默认使用实体类字段名与数据库表的列名一致。exist:指定该字段是否为数据库表的列,如果设置为false,则该字段不参与数据库表的映射,默认为true。el:指定实体类字段与数据库列的映射关系的表达式,用于处理复杂的映射关系。fill:指定该字段是否为自动填充字段,可选值为FieldFill枚举类中的常量,如FieldFill.INSERT、FieldFill.UPDATE等。2、编程中添加了数据库中未定义的属性在User实体类中我们添加一个online字段,用来去判断用户是否在线,但是数据库中并不需要添加这个字段,那我们应该怎么去实现 让MybatisPlus去查询数据库。给online添加@TableField注解,设置属性exist为false即可。package com.example.domain; import com.baomidou.mybatisplus.annotation.TableField; import lombok.Data; @Data public class User { private Long id; private String name; @TableField(value = "pwd") private String password; private Integer age; private String tel; @TableField(exist = false) private Integer online; } 3、采用默认 查询开放了更多的字段查看权限在一般查询用户信息时,一般数据库中用户的密码信息是不会进行查询的,那这时候使用MybatisPlus应该如何去实现呢?package com.example.domain; import com.baomidou.mybatisplus.annotation.TableField; import lombok.Data; @Data public class User { private Long id; private String name; @TableField(value = "pwd",select = false) private String password; private Integer age; private String tel; @TableField(exist = false) private Integer online; } 如上面所示我们只需要给@TableField注解,设置select属性为false。可以看到设置之后就没有password字段的信息了。4、表名和编码开发设计不同步这个问题就是数据库表名和实体类的类名不一致时,应该怎么办呢?package com.example.domain; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @Data @TableName("tbl_user") public class User { private Long id; private String name; @TableField(value = "pwd",select = false) private String password; private Integer age; private String tel; @TableField(exist = false) private Integer online; } 数据库表名为tbl_user。还是一样可以查询到数据的。

0
0
0
浏览量2015