0、注解问题
- @RestController注解是@Controller + @ResponseBody 合体,相当于给整个类的每个方法加了一个@Responsebody。不提供URL映射设置。使用这个注解,还必须使用@RequestMapping才可以,因为真正匹配路径的是RequestMapping.
- 使用@RestController这个注解,就不能返回jsp,html页面,视图解析器无法解析jsp,html页面
2、Spring-Boot
在之前我们学习了Spring、SpringMVC、Mybatis等开发框架,但是如果要将这些框架整合到WEB项目中则需要做大量的配置(很复杂),比如说applicationContext.xml以及servlet-MVC.xml、mybatis.cfg.xml文件等等,光有这些文件还不够,还需要在web.xml文件中进行一系列的配置。
以上的这些操作是比较麻烦的,在这种背景下,SpringBoot产生了,SpringBoot的出现就简化了上面配置管理,让开发人员把更多的精力放到业务上,而不是花大量的时间在配置文件的整合处理上,这是Spring的一个新的开始,是一个里程碑(SpringBoot的出现)。
但是SpringBoot的开发还对使用者有一定要求的,至少你要能熟悉的使用maven,以及之前的基础知识要牢固。
SpringBoot中大量的沿用了前面的ssm中的一些相关的注解。
2.1.Spring-boot重新开发
使用idea搭建第一个从SpringBoot开发环境。
- 1.新建项目
- 2.我们要选择一个SpringInitializr
- 6.之后选择Finish之后静静等待IEAD加载索引搭建工程。OK,一个完整的SpringBoot项目的结构我们已经搭建好了。
- 7.删除不必要的文件
7、maven环境的配置
在我们SpringBoot项目中后期肯定需要不停的往后添加需要使用到的依赖,需要依赖那就肯定要用到maven,现在SpringBoot使用默认的maven的setting.xml文件,当下载jar的时候是从国外仓库下载比较慢,我们要改成自己的maven的setting
8、此时环境就搭建好了
- Tomcatinitializedwithport(s):8080(http):SpringBoot自带了
- Tomcat,也就是不需要我们自己安装Tomcat和相关的配置了,端口号默认是8080·
- Tomcatstartedonport(s):8080(http)withcontextpath’':项目的根路径就是直接localhost:8080
9.如果包的结构修改了,可能导致启动类的所在的包也不是最初的,此时要进行修改。
2.2.启动类注解的分析
在SpringBoot中很多注解和Spring、SpringMVC是一样的,也就是沿用了Spring和SpringMVC的很多注解。
package cn.qf.first_boot;
/**
* *@EnableAutoConfiguration简单点说就是SpringBoot根据依赖中的jar包,
* *自动选择实例化某些配置,配置类必须有@Configuration注解。
* *说白了,还是实例化对象,只是实例化的是依赖包中的类。
* */
@SpringBootApplication //复合注解
//@EnableAutoConfiguration
@ComponentScan(basePackages = {"cn.qf"})
public class FirstBootApplication {
public static void main(String[] args) {
SpringApplication.run(FirstBootApplication.class, args);
}
}
发现了SpringBoot程序要运行需要一个注解
“@EnableAutoConfiguration”
和一个主方法,具备这两个特性的类被叫做SpringBoot的主程序类(使用来启动项目)。
复合注解:
@SpringBootApplication 等价于
@EnableAutoConfiguration
@ComponentScan + 其它
总结:
@ComponentScan:主要就是定义扫描的包,从中找出标识了需要装配的类自动装配到spring的bean容器中,该注解默认会扫描该类所在的包下所有的配置类(比如说打上注解@Controller/@Service),相当于之前的<context:component-scan>
@Configuration:等同于spring的XML配置文件,也就是这个类的功能具备原始的Spring的xml配置文件的功能
但是要注意的地方是:使用了@SpringBootApplication之后,所有的控制器必须在主程序所在包的子包下,这也是Spring官方建议的操作。
2.3自动加载的配置
在开发中需要不停的修改代码,但是修改之后需要重启项目,之后修改过的才会生效,这一点我们在从学习Spring到现在都一直是这处理的,这样很麻烦,那么SpringBoot提供了一个自动加载的机制但是需要导入相关的开发包。
- 1.导入自动加载的的开发包,拷贝到pom文件中(相当于是一个工具)
<!-- 自动加载 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
- 2.修改更新生效时机
OnUpdateaction里面有四个选项(一般选Updateclassesandresources):
Updateresources:如果发现有更新,而且更新的是资源文件(.jsp,.xml等,不包括java文件),就会立刻生效
Updateclassesandresources:如果发现有更新,只要java文件或者resources中的文件有修改,就会立刻生效
- 3.将idea修改为自动编译设置
-
4、修改idea的注册信息(选择运行时自动编译)
ctrl+shift+alt+/就可以进入设置
配置成功之后修改了代码之后保存,只需要等待一会就能自动加载,不再需要我们重启项目
2.4取得内置对象
在SpringBoot中取得内置对象和SpringMVC是一样的,可以直接传递request内置对象给控制器的方法,之后在根据该对象取得其他的内置对象
- 取得内置对象
/**
* 获取内置对象
* @param request:
* @param response:
* @return java.lang.String
**/
@RequestMapping("http")
public String test02(HttpServletRequest request, HttpServletResponse response){
//获取Session对象
System.out.println(request.getSession().getId());
//获取Application (ServletContext)内置对象
System.out.println(request.getSession().getServletContext().getRealPath("/"));
return "获取内置对象成功!";
}
总结:
1、会配置自动加载
2、在SpringBoot中取得内置对象和原来在SpringMVC中是一样的。
2.5Junit测试
之前的测试有两种方案:一种是在主方法中去运行,一种启动项目从浏览器发送请求进行访问测试,这样的测试比较麻烦而且不同时对多个方法进行测试,现在SpringBoot中出现一个Junit测试的功能,可以批量测试多个方法。
在以前我们使用过Junit测试,但是之前的测试是在接口的方法上进行测试,今天我们使用SpringBoot可以在任意任意类的任意方法上实现测试。
- 1.导入测试的开发包
<!-- junit测试的开发包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- scope:是指定了能使用测试注解(@Test)的包 -->
</dependency>
- test.java下的测试包
package cn.qf.first_boot.test;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Test01 {
@Test
public void test1(){
System.out.println("测试工具1!");
}
@Test
public void test2(){
System.out.println("测试工具2!");
}
}
总结:
1、优点:可以批量测试方法(一次可以测多个)
2、不足:被测试的方法不能有返回值
2.6.映射路径的配置
映射路径配置和之前的SpringMVC配置是一样的,需要使用到的注解:
- @Controller:表示一个控制器·
- @RequestMapping:定义映射路径,可以任何类型的请求
- @ResponseBody:表示将方法返回的数据转换成json格式之后之后再返回给前端。
1、定义一个控制器有可能控制器中的所有方法都要求返回值为json进行处理,此时就需要在每个方法上加上@ResponseBody”注解。
package cn.qf.controller;
import cn.qf.pogo.Emp;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//@Controller
@RestController //符合注解
public class TestController {
/**
* 获取内置对象
* @param request:
* @param response:
* @return java.lang.String
**/
@RequestMapping("http")
public String test02(HttpServletRequest request, HttpServletResponse response){
//获取Session对象
System.out.println(request.getSession().getId());
//获取Application (ServletContext)内置对象
System.out.println(request.getSession().getServletContext().getRealPath("/"));
return "获取内置对象成功!";
}
@RequestMapping("add")
public Object addEmp(){
System.out.println("添加对象成功!");
return "添加对象成功";
}
@RequestMapping("get")
public Object getEmp(){
Emp emp = new Emp();
emp.setEmpno(1001);
emp.setEname("Admin");
emp.setJob("Java开发工程师");
emp.setSal(6660.00);
return emp;
}
}
如果你确定了你的控制器中的所有方法的返回值都是json格式,在以后的前后端开发的模式中基本上返回的都是json格式,那么可以用另外一个注解来简化@ResponseBody注解的重复使用。
2、使用@RestController注解代替“@Controller”本质就是一个复合注解(@RestController=@Controller+@Resbonsebody)
2.7.传递参数
在SpringMVC中已经了解过,就是关于前端参数传递的问题。
普通方式传递:在地址后面+“?”+参数名=参数值&参数名=参数值
-
传统传参方式: 地址?k=v & k2=v2 k3= v3
@GetMapping
-
restful风格传递:路径/参数内容/参数内容/参数内容(不存在参数名称了)
这种方式不需要在地址栏写参数名称的信息,只需要参数的内容即可
@GetMapping("restful/{cp}/{ls}/{kw}")
public Object listEmp(@PathVariable("cp") Integer cp,
@PathVariable("ls") Integer ls,
@PathVariable("kw") String kw){
System.out.println("当前页:"+cp);
return true;
}
总结:
1、可以使用@RestController代替@Controller来实现@ResponseBody注解的简化。
2、第二种传参方式专业的说法就是restful风格的传参要会使用。
2.8.项目的打包处理
如果我们要将开发项目部署到生产环境,那么需要在开发工具中将项目打包发布到云端服务器(比如阿里云),打包的类型常见的有两种,一种war包,这种形式在传统的开发中使用比较多,一种是jar,这种形式是现在的主流,微服务开发就是要jar的形式进行部署。
war(ssm+tomcat实现的项目)包发布到服务器,服务器必须先安装好web容器(比如Tomcat),我们也可以将项目打成jar发到服务器直接运行,这是微服务开发经常要做的一件事。
-
1.对SpringBoot项目打包发布
需要下载相关的插件(打包要用到的插件)后面打成的jar需要启动,既然要启动就得知道我们SpringBoot的启动类程序,可以pom问价中中指定。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<mainClass>cn.qf.first_boot.FirstBootApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 2.点击右侧栏的MavenProjects,打开Lifecycle,在双击clean,清除target缓存
然后双击package,IDEA会自动开始把项目所有需要的文件和依赖包到打成jar文件的形式,完成后左侧目录栏里的target文件下会出现我们要打包的jar
-
之后将打好的包改名demo.jar,放到d盘的demo文件夹下使用如下的命名运行程序(把1-boot-demo-0.0.1-SNAPSHOT.jar修改为demo.jar)
注意:启动项目之前要将工具中部署的项目服务停止,因为它会占用端口号,导致你即将启动项目无法成功。
java -jar demo.jar
- 从浏览器访问项目
注意:
1、使用java-jarxxxx.jar的时候可能会出现找不到主类的情况
总结:
1、将项目打成jar发布到服务器,这是微服务开发经常要做的事,其实上微服务就是多个这个这样的jar运行,相互之间通过rpc协作运行的。
2、现在打包发布比之前的更为方便,省去了tomcat的部署等操作。
3、打包的方式有很多,但是这种方式是最简单的方式。
4、正常情况打包的时候默认会把所有的配置文件一起打包的,如果你的不可以就加上如下的配置。
2.9.端口配置
真正的项目发布的时候(生产环境),端口一般是设置为80端口,现在使用的服务器是SpringBoot内嵌的tomcat,在SpringBoot中如果需要指定端口号则需要创建一个配置文件,之后在配置文件中就可以指定端口号了,而且该配置文件需要在资源目录下定义(src/main/resources)
注意:如果你使用的是idea,那么在创建项目的时候该文件已经给你准备好了
如果地址中不写端口号,默认就是80端口。
2.10.使用jetty容器
在项目的部署的时候可以使用不同的web容器,我们之前默认使用的是Tomcat,但是如果是微服务的项目部署可以选择使用jetty运行,这是我们接触的一个新的web容器,使用很简单,只需要在pom文件中增加jetty的相关开发包即可
- 1.配置jetty的开发包 【JettyWebServer】
<!-- 配置jetty为web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 不使用默认的tomcat -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
总结:
1、比较Tomcat和jetty单纯比较Tomcat和Jetty的性能意义不是很大,只能说在某些使用场景下它们的表现各有差异,因为它们面向的使用场景不尽相同。从架构上来看Tomcat在处理少数连接上更有优势(并发访问比较少的场景),也就是连接的生命周期如果比较短,而且连接次数少,Tomcat的性能比较优。而Jetty则恰好相反,Jetty可以同时处理大量连接并且长时间的保持这些连接,这种情况下jetty就比较有优势了,例如,一些Web聊天应用非常适合用Jetty服务器,淘宝的Web旺旺就如果是web的聊天工具就可以使用jetty比如说聊天室等等。
2.11.任务调度
在SpringBoot中进行任务调度的配置,在之前我们学习Spring的时候配置任务调度有两种(一种是注解[需要在配置文件中开启任务调度注解驱动支持],一种是配置文件中配置)方式,发现了两种方式都离不开配置文件。但是我们使用SpringBoot之后就可以完全离开配置文件实现任务调度功能的开发。
- 启动类中开启任务调度****
@EnableScheduling 开启事务功能
/**
* *@EnableAutoConfiguration简单点说就是SpringBoot根据依赖中的jar包,
* *自动选择实例化某些配置,配置类必须有@Configuration注解。
* *说白了,还是实例化对象,只是实例化的是依赖包中的类。
* */
//@EnableAutoConfiguration
@SpringBootApplication //复合注解
@ComponentScan(basePackages = {"cn.qf"})
@EnableScheduling //开启任务调度
public class FirstBootApplication {
public static void main(String[] args) {
SpringApplication.run(FirstBootApplication.class, args);
}
}
- 注解实现配置 @Schenduled
/**
* 任务调度 :
* 1. 启动类中开启任务调度:@EnableScheduling
* 2.任务调度所在类生成bean:@Component
* 3.任务方法中设置任务调度:@Scheduled(cron="*****?")
* */
@Component
public class Task_A {
@Scheduled(cron = "* * * * * ?")
public void scheduling01(){
System.out.println("任务调度:Scheduling:"
+new SimpleDateFormat("yy-MM-dd hh:mm:ss").format(new Date()));
}
}
配置从后往前的“?”和“*”分别表示“年、月、日、时、分、秒”
1.cron=“*****?”:表示每一年中的每秒执行一次
2…cron=“***1*?”:表示每一年每月的1号的每一秒都执行一次任务
3.cron=“0001*?”:表示每一年的每月的一号的0时0分0秒执行任务
4.cron=“003**?”:表示每一年的每月每日凌晨3点0分0秒执行任务
5.cron=“0031*?”:表示每一年的每月一号凌晨3点0分0秒执行任务
2.12.yml配置文件的使用
“application.properties”其实就是SpringBoot的配置文件,但是其中另外一种格式的配置文件也是SpringBoot使用比较多的,它叫做yml配置文件,这种配置的文件是以.yml结尾的。
很多人要使用yml呢?主要原因有如下:yml支持注释;不必强求逗号括号等符号;通过缩进来区分层级,视觉上清晰很多。
- 将之前的application.properties改为application.yml
# 应用名称
# spring.application.name=first_boot
# 应用服务 WEB 访问端口
# server.port=80
spring:
application:
name: first_boot
messages: #resourceFile_Config
basename: static.i18n.messages #ResourceFile_path
server:
port: 80
2.13国际化资源
在开发中资源文件是不可少的,因为基本上所有的信息提示都定义在资源文件中,还有可能将路径信息也定义到资源文件中,为了方便管理我们将资源文件定义在“src/main/resources/static/i18n”目录下(i18n)【internationalization】
你的项目可能是在不同的国家区域进行使用,此时Spring会自动判断你的电脑的本地系统来决定要读取哪个文件中的内容。
-
1、创建“src/main/resources/static/i18n”目录
-
2、创建三个资源文件分别是:
essages_en_US.properties
messages_zh_CN.properties
messages.properties
add.success={0} 添加成功(默认)
add.error={0} 添加失败(默认)
(美国以及其他的一些西方国家,他们电脑系统是英文环境的)
- 3、此时需要在yml配置文件中进行如下的配置,指定资源文件所在的路径我们要使用spring程序去读取,所以需要在配置中以spring的节点出现
spring:
application:
name: first_boot
messages: #resourceFile_Config
basename: static/i18n/messages #ResourceFile_path
Spring在查找资源文件的之后会自动添加一个messages此时资源文件已经定义好了,接下来就是使用。
-
4、定义一个BaseController的父类
Spring和SpringMVC中就可以将一些公共的操作放到父类中(BaseController),现在要定义的是控制层的父类。
package cn.qf.first_boot.controller;
@Controller //表示控制器
public class BaseController {
/**
* 注入资源文件的bean: 配置文件中配置生成的
* */
@Autowired
MessageSource messageSource;
/**
* messageSource资源对象获取数据的方法
* @param key: 对应到资源文件中的key值
* @param args: 要传递到资源文件中占位符对应的值要传递到资源文件中占位符对应的值
* @return java.lang.String
**/
public String getMsg(String key,String...args){
return messageSource.getMessage(key,args, Locale.getDefault());
}
}
- 5.子类Controller中实现
package cn.qf.first_boot.controller;
@Controller
@RequestMapping("/test2")
public class TestController02 extends BaseController{
@GetMapping("getMsg")
@ResponseBody
public String getMessage(){
String msg = super.getMsg("add.success", "获取资源");
System.out.println(msg);
return msg;
}
}
- 6、报错问题
你传递的key值是否在资源文件中存在
你的配置文件中指定资源文件的路径是否正确
- 7、中文乱码问题
# 应用名称
# spring.application.name=first_boot
# 应用服务 WEB 访问端口
# server.port=80
spring:
application:
name: first_boot
messages: #resourceFile_Config
basename: static/i18n/messages #ResourceFile_path
encoding: GB2312 #中文乱码配置
传递多个参数的实现
add.success= {0} {1} 成功
add.error= {0} {1} 失败
-Controller层
package cn.qf.first_boot.controller;
@Controller
@RequestMapping("/test2")
public class TestController02 extends BaseController{
@GetMapping("getMsg")
@ResponseBody
public String getMessage(){
String msg = super.getMsg("add.success", "获取","资源");
System.out.println(msg);
return msg;
}
//测试传递多个参数获取资源文件
@GetMapping("addEmp")
@ResponseBody
public String addEmp(){
String msg = super.getMsg("add.error","添加","emp");
System.out.println(msg);
return msg;
}
}
如果没有对应系统环境的资源文件则使用默认的资源文件。
总结:
1、要学会使用yml配置文件,因为很多开发中都在使用,尽量少在里面写中文注释(英文可以)
2、理解和配置国际化资源
2.14thymeleaf模板引擎(模板语言)
在之前的开发中我们使用了jsp实现数据在页面的渲染,如果要在jsp页面中将数据完整的显示需要使用到EL表达式+JSTL标签完成,这种形式其实就很形象的比喻:把模板给你定义好,你只需要按照模板格式语法填充数据即可(把后端查询的数据填充到模板中即可),所以又称之为模板语言
除了jsp还有一些其他类似的模板语言比如说FreeMaker、Velocity,以及我们接下来要讲的thymeleaf,thymeleaf是官方推荐使用的语言(如果你要在springboot项目中使用模板语言就选择thymeleaf)。
1.导入thymeleaf开发包
<!-- 需要导入支持thymeleaf的开发包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2、修改控制层中的方法
在之前使用jsp开发的时候也需要从控制器中跳转到jsp页面,方法的返回值类型是ModelAndView或者是String,那么今天同样的需要从控制器中跳转到页面,但是此时使用的页面就是普通的*.html。
@Controller
@RequestMapping("/test2")
public class TestController02 extends BaseController{
@GetMapping("getEmp")
//@ResponseBody--注解掉后不返回Json类型数据。则返回的是地址
public String getEmp(Model model){
Emp emp = new Emp();
emp.setEmpno(1);
emp.setEname("小刘");
emp.setJob("java");
emp.setSal(1234.22);
//保存数据在Model中
model.addAttribute("empInfo",emp);
/** 在控制器中跳转路径的时候默认前缀是templates后缀是.html */
return "page/emp";
}
}
3.模板语言HTMl的使用位置
要保存模板语言对象的html则需要在源代码目录下的
resoutces–“templates”目录下创建.HTML文件
- 还需要导入
<!–使用jsp的时候需要在头部导入jstl的标签库–>
<!–使用thymeleaf的时候需也要在头部导入thymeleaf的标签库–>
< html xmlns:th= “ http: // www.thymeleaf.org ” >
- 在使用thymeleaf的时候必须要注意标签需要有“/”作为完结。
<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf模板语言的使用</title>
</head>
<body>
<h1>雇员信息</h1>
<h3 th:text="'编号'+${empInfo.getEmpno()}"></h3>
<h3 th:text="'姓名'+${empInfo.ename}"></h3>
<h3 th:text="'职位'+${empInfo.job}"></h3>
<h3 th:text="'薪资'+${empInfo.sal}"></h3>
</body>
</html>
总结
1、理解模板引擎语言的概念
2、要能搭建thymeleaf的开发环境
3、我们的静态资源保存到static目录下
4、templates目录下的HTMl,只能通过控制器来访问
5、在控制器中跳转路径的时候默认前缀是templates后缀是.html
6、注意:很多时候你的thymeleaf导入之后依然不能使用,出错,告诉你找不到模板(新建项目基本都可以解决)。
2.15路径的处理@{路径}
在使用jsp的时候如果要取得项目的根路径作为整个页面的基础路径,使用的操作是request.getContextPath()进行获取。
那么在thymeleaf中直接使用“@{/目录/目录/…/资源名称}”这样的方式取得。
1.static文件中定义一个js文件并且引用到html中。
window.onload=function () {
alert("js导入:Hello Thymeleaf!!!");
}
- 此时表示js静态资源引用成功。
<head>
<meta charset="UTF-8">
<title>Thymeleaf模板语言的使用</title>
<script type="text/javascript" th:src="@{/js/emp.js}"></script>
</head>
- 使用**@{/xx/xx}**的方式访问控制器
<!DOCTYPE html>
<html lang="en">
<!--使用Thymeleaf的时候需要在头部导入Thymeleaf的标签库-->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf模板语言的使用</title>
<!-- js静态资源引用 -->
<script type="text/javascript" th:src="@{/js/emp.js}"></script>
</head>
<body>
<!-- 使用Thymeleaf实现超链接的跳转地址获取 -->
<a href="/test2/addEmp">添加Emp</a><br> <br>
<a th:href="@{/test2/addEmp}">Thymeleaf方式添加Emp</a>
</body>
</html>
2.16 对象的输出时间格式
在jsp中可以使用el表达式和jstl标签实现对象属性的值的输出,在thymeleaf中也可以实现。
<h1>雇员信息</h1>
<h3 th:text="'编号'+${empInfo.getEmpno()}"></h3>
<h3 th:text="'姓名'+${empInfo.ename}"></h3>
<h3 th:text="'职位'+${empInfo.job}"></h3>
<h3 th:text="'薪资'+${empInfo.sal}"></h3>
<h3 th:text="'入职时间 :'+${empInfo.hiredate}"></h3>
<!--
编号1
姓名小刘
职位java
薪资1234.22
入职时间 :Thu Nov 10 19:22:09 CST 2022 【格式不对】
-->
ht:text=${#dates.format(时间数据,’ 时间格式 ')}
<h3 th:text="'入职日期:'+${#dates.format(empInfo.hiredate,'yy-MM-dd hh:mm:ss')}"></h3>
2.16逻辑处理(了解)
在jsp中也有逻辑处理(但是不常用),同样的thymeleaf中也有对应的逻辑处理语法。在thymeleaf中的逻辑关系
>(gt ) , <(lt),>=(ge),<=(le),==(eq), !=(neq)
<!-- 使用逻辑处理 -->
<p th:if="${empInfo.sal>8000}">高收入</p>
<p th:if="${empInfo.sal < 8000}">低收入</p>
- 可以使用switch进行多条件判断
th:swith=“${数据值}”
th:case=“xx”
<!-- 使用 switch 进行多条件判断-->
<p th:switch="${empInfo.sal}">
<span th:case="5000">
还需加油
</span>
<span th:case="8000">
继续努力
</span>
<span th:case="*">
工资情况不清楚
</span>
</p>
如果没有满足的条件,在Java中switch中是有default进行处理,但是现在thymeleaf中没有default,而是使用“*”代替
要注意的是在使用th:case="*"的时候不要在“*”的两端加上单引号。
总结:
1、本次课的重点是有:对象的属性值输出(和之前的el表达式很相似)、各种逻辑运算、以及switch的使用。
2.16 thymeleaf迭代集合
在开发中数据很可能会以集合的形式出现,最常见的集合是Set、List、Map集合,在jsp中也能实现数据的迭代输出(也就是对集合的迭代),在thymeleaf中同样可以,而且更为简洁。
1.迭代List个和set集合
@Controller
@RequestMapping("/test2")
public class TestController02 extends BaseController{
/* thymeleaf迭代List集合 */
@GetMapping("getList")
public ModelAndView getList(){
List<Emp> list = new ArrayList<>();
for(int i=1;i<=10;i++){
Emp emp = new Emp();
emp.setEmpno(i);
emp.setSal(i*100.00);
emp.setEname("员工"+i);
list.add(emp);
}
ModelAndView modelAndView = new ModelAndView("page/emp");
modelAndView.addObject("empList",list);
return modelAndView;
}
- List集合的遍历
th:each=“别名 :${List集合 }”
<!-- 遍历List集合 -->
<table border="1" align="center">
<thead>
</thead><tr> <th>编号</th><th>员工号</th><th>工资</th> </tr>
</thead>
<tbody>
<tr> <th>1</th><th>2</th><th>3</th> </tr>
<tr th:each=" emps:${empList}">
<td th:text="${emps.empno}" />
<td th:text="${emps.ename}"/>
<td th:text="${emps.sal}"/>
</tr>
</tbody>
</table>
以上是基本的操作,还有更为强大的功能,比如说可以自动生成数据的编号,还可以判断当前数据的编号是偶数还是奇数。
- 使用更为强大的功能
<tableborder="1"><tr><th>序号</th><th>编号</th><th>姓名</th><th>职位</th><th>薪资</th><th>入职日期</th><th>偶数</th><th>奇数</th></tr><trth:each="emp,empStat:${emps}"><tdth:text="${empStat.index+1}"></td><tdth:text="${emp.empno}"/><tdth:text="${emp.ename}"/><tdth:text="${emp.job}"/><tdth:text="${emp.sal}"/><tdth:text="${#dates.format(emp.hiredate,'yyyy-MM-dd')}"/><tdth:text="${empStat.even}"/><tdth:text="${empStat.odd}"/></tr></table>
如果是Set集合,也不需要做任何改变,和List的迭代规则是一样的
- 遍历Map结合
th:eath=" 别名:${Map集合} "
${别名.key}
${别名.value}
<!-- 遍历Map集合 -->
<h3>
<span th:each="map:${testMap}">
<p th:if="${map.key=='name'}">
<span th:text="${map.key}"/>
---->{<span th:each="namelist:${map.value}" th:text="${namelist}+','" />}
</p>
<p th:if="${map.key!='name'}">
<span th:text="${map.key}"/>
---->
<span th:text="${map.value}"/>
</p>
</span>
</h3>
/* thymeleaf遍历Map集合 */
@GetMapping("getMap")
public String getMap(Model model){
ArrayList<Object> list = new ArrayList<>();
HashMap<String, Object> map = new HashMap<>();
list.add("12");
list.add("13");
list.add("苏苏");
map.put("name",list);
map.put("sex",19);
map.put("job","java高级工程师");
model.addAttribute("testMap",map);
return "page/emp";
}
2.17 thymeleaf处理其它数据
thymeleaf的数据处理功能还是很强大的,List和Set集合数据,除了这些数据之外还可以处理字符串类型数据,以及日期格式数据(日期格式化)等等。
- 字符串处理
<pth:text="'替换字字符:'+${#strings.replace('www.mbzvip.cn','.','^')}"></p><pth:text="'小写转大写:'+${#strings.toUpperCase('www.MBZVIP.cn')}"></p><pth:text="'大写转小写:'+${#strings.toLowerCase('www.MBZVIP.cn')}"></p><pth:text="'截取字符串:'+${#strings.substring('www.MBZVIP.cn',0,4)}"></p>
总结:
1、本次课的重点是使用thymeleaf对List、Set、Map集合进行迭代
2、重点是对各种常见数据的处理
3、thymeleaf的讲解我们就到此,以后如果遇到了其他新的知识我们再补充,但是thymeleaf是很简单的,学习成本也比较低,所以你一定要掌握。
3、Springboot文件上传
在实际的开发中很多项目都需要涉及到文件的上传,在SpringMVC中经常使用到MultipartFile接口来实现文件的上传,在SpringBoot中也可以继续使用该接口实现上传。
3.1 定义上传的父类(BaseController)
package cn.qf.first_boot.controller;
@Controller //表示控制器
public abstract class BaseController {
/**
* 生成新的文件名
* @param file:
* @return java.lang.String
**/
public String createNewFileName(MultipartFile file){
//获取后缀
String originalFileName = file.getOriginalFilename();
String[] split = originalFileName.split("\\.");
String ext = split[split.length - 1];
//生成新的文件名
return UUID.randomUUID().toString().replace("-","")+"."+ext;
}
//子类确定文件夹名称
public abstract String getDir();
/**
* 实现文件保存
* @param newFileName:
* @param file:
* @param request:
* @return java.lang.String
**/
public String saveFile(String newFileName, MultipartFile file, HttpServletRequest request)throws Exception{
//取得上传地址(项目的部署路径+文件夹+文件名称)
String path = request.getSession().getServletContext().getRealPath("/")
+getDir()+newFileName;
System.out.println(path);
//判断子类文件夹是否存在
File pathFile = new File(path);
if(!pathFile.getParentFile().exists()){
pathFile.getParentFile().mkdirs();
}
//实现文件上传
file.transferTo(pathFile);
//返回相对路径
return getDir()+newFileName;
}
}
3.2 定义上传的控制器(子类Controller)
package cn.qf.first_boot.controller;
@Controller
@RequestMapping("/test2")
public class TestController02 extends BaseController{
//子类定义的文件夹名称
@Override
public String getDir() {
return "empImages/";
}
@RequestMapping("upload")
public String upload(MultipartFile file, HttpServletRequest request,Model model)throws Exception{
//取得文件的新名称
String newFileName = super.createNewFileName(file);
//调用父类保存文件
String path = super.saveFile(newFileName, file, request);
System.out.println("======"+path);
model.addAttribute("src","/"+path);
return "page/emp";
}
}
3.3 定义上传表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<form action="/test2/upload" method="POST" enctype="multipart/form-data">
<fieldset>
<legend> 文件上传 </legend>
姓名:<input type="text" name="name"><br><br>
照片:<input type="file" name="file"><br> <br>
<input type="submit" value="提交">
<input type="reset" value="重置">
</fieldset>
</form>
</body>
</html>
3.4 照片回显
<!DOCTYPE html>
<html lang="en">
<!--使用Thymeleaf的时候需要在头部导入Thymeleaf的标签库-->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf模板语言的使用</title>
<!-- js静态资源引用 -->
<script type="text/javascript" th:src="@{/js/emp.js}"></script>
</head>
<body>
<h3 th:text="${src}" ></h3>
<img th:src="${src}" height="300" width="500">
</body>
</html>
上传的文件的大小超过默认允许的最大值,默认最大值是10兆,如果要上传超过10M的文件则需要配置,之前传统的SpringMVC是在xml配置文件中去进行配置的,现在直接在application.yml中配置,也可以直接通过配置类完成配置。
3.5 application.yml中配置上传大小
spring:
application:
name: first_boot
messages: #resourceFile_Config
basename: static/i18n/messages #ResourceFile_path
encoding: GB2312 #中文乱码配置
servlet:
multipart:
enabled: true #表示上传时使用当前配置
max-file-size: 5MB #单个文件的最大值是5M
max-request-size: 50MB #多个文件的总的最大值是50M
除了以上的配置形式之外还有一种很常用的配置,是在类中进行配置。
在实际的开发中如果是文件上传最终会生成新的文件名字,并且保存到指定目录,上传成功之后还需要将文件的地址信息保存到数据库,需要保存到数据库的信息最基本的文件名字了。所以在父类中上传的方法需要一个返回值,这个返回值就是文件名字。
总结:
1、实现上传的方法一般都是在父类中去定义的。
2、我们上面是把文件上传到项目部署路径下的磁盘中,还用一种常用的方式,
文件上传到自己公司的文件存储服务器或者是第三方的文件存储服务器(oss)
4.使用阿里云的oss实现文件上传
在开发中很多时候图片是上传到图片服务器的,图片服务器有自己搭建的也有第三方的,比如说oss就是阿里云的对象存储服务器(可以进行文件对象的管理)。
4.1 开通oss服务
进入阿里云官网(https://www.aliyun.com/)
西南1成都:
Bucket名称**:qf-lcj-bucket
Endpoint : oss-cn-chengdu.aliyuncs.com
密钥(AccessKey/SecretKey):
access Key ID : LTAI5tGMuuhBm2edGcPP67BE
Access Key Secret : pzPbgP8Ga4ehrZutMoHcQLmUiOnZOa
访问域名和数据中心https://help.aliyun.com/document_detail/31837.htm?spm=a2c4g.11186623.0.0.243d1cd54xAgEI#concept-zt4-cvy-5db2、
1、注册
实名认证直接使用支付宝扫码也可以进行认证的,如果你是在校还没毕业的学生也可以进直接扫码进行学生认证(人个实名认证+学生认证)
3、将鼠标移至产品,单击对象存储OSS,打开OSS产品详情页面。
4、在OSS产品详情页,单击立即开通。
5、开通服务后,在OSS产品详情页单击管理控制台直接进入OSS管理控制台界面。您也可以单击位于官网首页右上方菜单栏的控制台,进入阿里云管理控制台首页,然后单击左侧的对象存储OSS菜单进入OSS管理控制台界面。
6.创建Bukect
获取accessKeyID和AccessKeySecret
4.2 内部实现
- 添加相关依赖
<!-- 阿里云oss服务依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.5.0</version>
</dependency>
-
oss上传工具类
这个工具类是官方提供的,不需要们自己去写,直接使用就行了,里面的代码你能理解就行,暂时以方法为单位去理解即可。
package cn.qf.first_boot.util;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.Bucket;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.engine.ISSEThrottledTemplateWriterControl;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.jar.JarOutputStream;
/**
* @className: OSSutil
* @Author: ich liebe Dich
* @Date: 2022/11/12 15:18
* @Version: 1.0
*/
public class OSSutil {
//阿里云API的内或外网域名(https://help.aliyun.com/document_detail/31837.htm?spm=a2c4g.11186623.0.0.243d1cd54xAgEI#concept-zt4-cvy-5db)
public static String ENDPOINT = "http://oss-cn-chengdu.aliyuncs.com";
//阿里云API的密钥Access Key ID
public static String ACCESS_KEY_ID = "LTAI5tGMuuhBm2edGcPP67BE";
//阿里云API的密钥Access Key Secret
public static String ACCESS_KEY_SECRET = "pzPbgP8Ga4ehrZutMoHcQLmUiOnZOa";
//阿里云PAI的bucket名称
public static String BACKET_NAME = "qf-lcj-bucket";
//阿里云API的文件名称
public static String FOLDER = "images/";
/**
* 获取阿里云OSS客户端对象
*
* @return com.aliyun.oss.OSSClient
**/
public static OSSClient getOSSClient() {
return new OSSClient(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
}
public static String createBucketName(OSSClient ossClient, String bucketName) {
//存储空间
final String bucketNames = bucketName;
if (!ossClient.doesBucketExist(bucketName)) {
//创建存储空间
Bucket bucket = ossClient.createBucket(bucketName);
System.out.println("创建空间成功!");
return bucket.getName();
}
return bucketNames;
}
/**
* 删除存储空间buckName
*
* @param ossClient: oss对象
* @param bucketName: 存储空间
**/
public static void deleteBucket(OSSClient ossClient, String bucketName) {
ossClient.deleteBucket(bucketName);
System.out.println("删除" + bucketName + "Bucket成功!");
}
/**
* 创建模拟文件夹
*
* @param ossClient: oss连接
* @param bucketName: 存储空间
* @param folder: 模拟文件夹名 eg:"qj_nanjing/"
* @return java.lang.String
**/
public static String createFolder(OSSClient ossClient, String bucketName, String folder) {
//文件名
final String keySuffixWithSlash = folder;
//判断文件是否存在,不存在则创建
if (!ossClient.doesObjectExist(bucketName, keySuffixWithSlash)) {
//创建文件
ossClient.putObject(bucketName, keySuffixWithSlash, new ByteArrayInputStream(new byte[0]));
System.out.println("创建文件夹成功!");
//得到文件夹名称
OSSObject object = ossClient.getObject(bucketName, keySuffixWithSlash);
String fileDir;
}
return keySuffixWithSlash;
}
/**
* 上传图片至OSS
*
* @param ossClient: oss客户端连接对象
* @param file: 上传文件(文件全路径如:D:\\image\\cake.jpg)
* @param bucketName: 存储空间
* @param fileName:
* @param folder:模拟文件夹名如"qj_nanjing/"
* @return java.lang.String
**/
public static String uploadObject20ss(OSSClient ossClient, MultipartFile file, String bucketName, String fileName, String folder) {
String resultStr = null;
try {
//以输入流的形式上传文件
InputStream is = file.getInputStream();
//文件大小
Long fileSize = file.getSize();
if (fileSize > 0) {
//创建上传Object的Metadata
ObjectMetadata metadata = new ObjectMetadata();
//上传的文件的长度
metadata.setContentLength(is.available());
//指定该Object被下载时的网页的缓存行为
metadata.setCacheControl("no-cache");
//指定该Object下设置Header
metadata.setHeader("Pragma", "no-cache");
//指定该Object被下载时的内容编码格式
metadata.setContentEncoding("utf-8");
//文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成,
//如果没有扩展名则填默认值application/octet-stream
metadata.setContentType(getContentType(fileName));
//指定该Object被下载时的名称(指示MINME用户代理如何显示附加的文件,打开或下载,及文件名称)
metadata.setContentDisposition("filename/filesize=" + fileName + "/" + fileSize + "Byte.");
//上传文件(上传文件流的形式)
PutObjectResult putResult = ossClient.putObject(bucketName, folder + fileName, is, metadata);
//解析结果
resultStr = putResult.getETag();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("上传阿里云OSS服务器异常." + e.getMessage());
}
return resultStr;
}
/**
* 通过文件名来判断并获取OSS服务文件上传是文件的contentType
*
* @param fileName: 文件名
* @return java.lang.String
**/
public static String getContentType(String fileName) {
//文件的后缀名
String fileExtension = fileName.substring(fileName.lastIndexOf("."));
if (".bmp".equalsIgnoreCase(fileExtension)) {
return "image/bmp";
}
if (".gif".equalsIgnoreCase(fileExtension)) {
return "image/gif";
}
if (".jpeg".equalsIgnoreCase(fileExtension) || ".jpg".equalsIgnoreCase(fileExtension) || ".png".equalsIgnoreCase(fileExtension)) {
return "image/jpeg";
}
if (".html".equalsIgnoreCase(fileExtension)) {
return "text/html";
}
if (".txt".equalsIgnoreCase(fileExtension)) {
return "text/plain";
}
if (".vsd".equalsIgnoreCase(fileExtension)) {
return "application/vnd.visio";
}
if (".ppt".equalsIgnoreCase(fileExtension) || "pptx".equalsIgnoreCase(fileExtension)) {
return "application/vnd.ms-powerpoint";
}
if (".doc".equalsIgnoreCase(fileExtension) || "docx".equalsIgnoreCase(fileExtension)) {
return "application/msword";
}
if (".xml".equalsIgnoreCase(fileExtension)) {
return "text/xml";
}
//默认返回类型return"image/jpeg";
return "image/jpeg";
}
}
- 上传照片Controller并回显
package cn.qf.first_boot.controller;
import cn.qf.first_boot.util.OSSutil;
import com.aliyun.oss.OSSClient;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.BaseStream;
@RestController
@RequestMapping("OSS")
public class OSSController extends BaseController {
@PostMapping("upload")
public ModelAndView upload(String name, MultipartFile file, HttpServletRequest request) throws Exception {
ModelAndView modelAndView = new ModelAndView();
//取得文件名
String newFiletName = createNewFileName(file);
if (file.getSize() > 0 && file != null) {
String result = OSSutil.uploadObject20ss(OSSutil.getOSSClient(), file, OSSutil.BACKET_NAME, newFiletName, OSSutil.FOLDER);
System.out.println("上传返回值为:" + result);
//照片回显
String path="http://"+OSSutil.BACKET_NAME+"."+OSSutil.ENDPOINT.replace("http://","")+"/"+OSSutil.FOLDER+newFiletName;
modelAndView.addObject("src",path);
modelAndView.setViewName("page/emp");
return modelAndView;
}
return modelAndView;
}
@Override
public String getDir() {
return "testDir/";
}
}
5.Http状态码统一处理
比如说找不到路径就是404状态码,访问的路径对应的方法只支持post,但是我们用get提交,此时会出现405状态码,服务端出错的时候就是500状态。
在之前的项目中错误的处理一般是在web.xml文件中进行配置,本次课使用SpringBoot处理三种状态码为例:404(找不到资源)、405(请求不被允许)、500请求错误的处理(服务器内部的代码出现错误了)
-
定义3个错误页面
3个静态资源放在 ” src/main/resources/static “ 下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>error404</title>
</head>
<body>
<h1>访问资源不存在!(404)</h1>
</body>
</html>
.
.
.
- 定义控制器
该控制器的作用是出指定的状态码负责转发到对应的错误页面。
package cn.qf.first_boot.controller;
@Controller
public class ErrorController {
//handle处理404错误
@RequestMapping("error404")
public String handle404(){
return "redirect:http://localhost/errorPage/error404.html";
}
//handle 处理 405错误
@RequestMapping("error405")
public String handle405(){
return "redirect:http://localhost/errorPage/error405.html";
}
//handle处理500错误
@RequestMapping("error500")
public String handler500(){
return "redirect:http://localhoset/errorPage/error500.html";
}
}
- 错误状态码处理的配置类configuration
package cn.qf.first_boot.config;
//@Configuration:就等价于之前在xml文件中的配置了(也就是等价于之前的配置文件)
@Configuration
public class ErrorConfig implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
//处理404状态码的对象
ErrorPage error404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error404");
//处理404状态码的对象
ErrorPage error405 = new ErrorPage(HttpStatus.METHOD_NOT_ALLOWED, "/error405");
//处理500状态码的对象
ErrorPage error500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error500");
//将上面的三个对象添加到registry对象中
registry.addErrorPages(error404,error405,error500);
}
}
总结:以上的操作实际上没有针对异常进行捕获,而是根据响应的状态码进行不同的处理的,那么如要针对不同的异常进行捕获呢?这就要用到全局异常捕获
6.全局异常处理
对不同的状态码进行了统一处理,但不是针对异常进行处理,现在要针对不同异常的类型进行处理,那就是完全基于注解实现(本质上就是AOP编程)。
- 定义一个全局异常处理类
package cn.qf.first_boot.controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
//@ControllerAdvice:控制器增强处理(之前我们在学习aop的时候都是方法级别的增强处理)
public class ExceptionController {
//表示该方法可以处理Exception以及其子类类型的异常
@ExceptionHandler(value = Exception.class)
@ResponseBody
public String handlerException(Exception exception){
//把异常信息返回给前端
return exception.getMessage();
}
}
- 测试各种异常
package cn.qf.first_boot.controller;
@RestController //符合注解
//@Controller
//ResponseBody
@RequestMapping("test1")
public class TestController {
@RequestMapping("exception")
public String exception(){
try{
int number = 1/0;
}catch (Exception e){
throw new ArithmeticException("你出现了算数异常!");
}
try {
String str = null;
str.split("x");
} catch (Exception e) {
throw new NullPointerException("空指针!");
}
try {
Integer.parseInt("xxx");
}catch (Exception e){
throw new NumberFormatException("格式化异常!");
}
return "无异常!";
}
7.SpringBoot拦截器的实现
在开发中可以使用拦截进行表单的数据验证(也可以使用第三方工具实现验证)、日志记录(对数据的操作进行日志记录)、登陆授权检测、实现慢查询等操操作,在SpringBoot中的拦截器和SpringMVC是同样的支持方式,只不过配置方式不一样。
- 定义普通控制器(准备)
package cn.qf.first_boot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/** 拦截器测试控制器 */
@Controller
@RequestMapping("interceptor")
public class InterceptorController {
//@RequestMapping("test1")
@GetMapping("test1")
public ModelAndView interceptorTest(){
System.out.println("正在访问目标方法。。。。。");
//设置跳转路径
ModelAndView modelAndView = new ModelAndView("page/interceptor");
modelAndView.addObject("msg","拦截器interceptor介入的测试!");
return modelAndView;
}
}
- templates目录下的page/interceptor.html (采用了thymeleaf模板语言)
<!DOCTYPE html>
<html lang="en">
<!--使用Thymeleaf的时候需要在头部导入Thymeleaf的标签库-->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>SpringBoot-interceptor</title>
</head>
<body>
<h3 th:text="'拦截器interceptor测试方法响应消息:'+${msg}"></h3>
</body>
</html>
- 定义拦截器
package cn.qf.first_boot.interceptors;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
/** 统计目标方法执行的时长*/
//保存开始时间的变量
private static long startTime;
//保存结束时间的变量
private static long endTime;
//声明一个本地线程对象(为了数据的安全,保存到该对象中的数据只能当前线程访问)
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
//preHandle:执行目标方法之前触发
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//取得目标方法对象及名称
HandlerMethod methodHandle = (HandlerMethod) handler;
String methodName = methodHandle.getMethod().getName();
System.out.println("preHandle======methodName:"+methodName);
//取得当前时间毫秒数
startTime = System.currentTimeMillis();
threadLocal.set(startTime);
return true;
}
/**
* 执行完目标方法,响应前端数据之前
* */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//取得目标方法中的modelAndView
if(modelAndView!=null)
modelAndView.addObject("msg","interceptor拦截!");
//取得目标方法的名称(可以通过handlerMethod可以获取的目标方法的相关信息)
HandlerMethod handlerMethod = (HandlerMethod) handler;
String methodName = handlerMethod.getMethod().getName();
System.out.println("postHandler====methodName:"+methodName);
}
/**
* 渲染(响应)结束之后触发(请求完全执行完毕)
* */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//取得目标方法的相关信息
HandlerMethod handlerMethod = (HandlerMethod) handler;
String methodName = handlerMethod.getMethod().getName();
System.out.println("afterCompletion=====methodName"+methodName);
endTime=System.currentTimeMillis();
System.out.println("执行该方法总共耗时:"+(endTime-threadLocal.get()));
}
}
在之前我们需要使用拦截器就要在SpringMVC的配置文件中进行相关的配置,现6在使用SpringBoot之后交给配置类。
- 定义配置类
package cn.qf.first_boot.config;
import cn.qf.first_boot.interceptors.MyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//使用配置类替代原来的配置文件(等价于原来的dispatcher-servlet.xml)
@Configuration //配置类
public class SpringBootConfig implements WebMvcConfigurer {
@Bean //创建拦截器类的bean对象
public MyInterceptor getInterceptor(){
return new MyInterceptor();
}
/**
* 配置拦截器,配置类需要实现的接口
* */
@Override
public void addInterceptors(InterceptorRegistry registry) {
//定义拦截器的拦截路径
registry.addInterceptor(this.getInterceptor()).addPathPatterns("/interceptor/*");
}
}
- 访问“ localhost/interceptor/test1 ” --结果:
正在访问目标方法。。。。。
postHandler====methodName:interceptorTest
afterCompletion=====methodNameinterceptorTest
执行该方法总共耗时:3
8.SpringBoot整合Mybatis
8.1 创建项目
SpringBoot整合Mybatis,我们以记录操作数据的整个过程的详细信息为需求背景来整合mybatis,实现对数据操作(记录操作的电脑的ip地址/操作时间/用7户的编号等等)进行跟踪记录,本次要用到的技术aop开发,灵活使用反射+注解
- 新建项目
国内地址:https://start.aliyun.com
8.2 操作日志生成的实现
- 定义日志实体类
package cn.qf.pojo;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* @className: Log
* @Author: ich liebe Dich
* @Date: 2022/11/11 15:45
* @Version: 1.0
*/
@Data
@ToString
@Accessors(chain = true) //支持链式编程
public class Log implements Serializable {
private Long lid; //日志编号
private String desc;//日志的描述
private String logType;//日志的类型(update/login/delete/)
private String requestUrl; //请求操作的路径
private String getRequestType; //请求类型(post/get/put)
private String username; //执行当前操作的用户(必须登录)
private Date createTime;//操作日期
}
- 在这里我们要定义自己的注解信息,定义注解的信息的原因是可以灵活的在需要记录日志的方法上使用(注解在实际开发中的使用)。
package cn.qf.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) //使用元注解,定义当前的注解的使用范围
@Retention(RetentionPolicy.RUNTIME) //表示注解生效范围
public @interface LogAnnotation {
String desc() default "增加数据";
String type() default "inset";
}
如果要记录日志则要使用到面向切面的开发,就是aop,因为要在操作目标方法之前获取操作人的信息,操作完毕之后也要获取时间等信息,这就是对切点的增强操作。
- 导入AOP的开发依赖
<!-- AOP开发依赖:动态代理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 自动加载 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
- 定义日志的业务层Service
package cn.qf.service;
import cn.qf.pojo.Log;
import java.util.List;
public interface LogService {
/**
* 记录日志信息(最终调用dao层方法保存到数据库)
* */
boolean addLog(Log log);
/**
* 获取所以日志性息
* @return java.util.List<cn.qf.pojo.Log>
**/
List<Log> getAllLogs();
}
//impl---------------------------
package cn.qf.service.impl;
import cn.qf.pojo.Log;
import cn.qf.service.LogService;
import org.springframework.stereotype.Service;
import java.util.List;
ersion: 1.0
@Service
public class LogServiceImpl implements LogService {
//todo 注入dao层bean
@Override
public boolean addLog(Log log) {
System.out.println(log);
System.out.println("添加日志成功!");
return false;
}
@Override
public List<Log> getAllLogs() {
System.out.println("查询所有日志log!!");
return null;
}
}
8.2 日志处理(定义切面ASpect、切点aspectPoint)
- 定义切面类
package cn.qf.utils;
/**
* Emp操作的log日志切面类Aspect
*/
@Aspect
@Component //创建bean注入到ioc容器
public class EmpLogAspect {
//创建本地线程保存时间,保存线程安全
private static final ThreadLocal<Date> THREAD_LOCAL = new ThreadLocal<>();
@Autowired
LogService logService;
@Autowired //request对象Spring已经内置在ioc容器中
HttpServletRequest request;
/**
* 配置切点的方法
* 原始的配置方法 :@Pointcut(” execution(* * ..cn.qf.service*..*(..)) “)
* 新配置:@Pointcut("@annotation(cn.qf.annotations.LogAnnotation)")
* --配置了@LogAnnnotation注解的方法都会被AOP
* */
//@Pointcut("execution(* * ..cn.qf.controller*..*(..))")
@Pointcut("@annotation(cn.qf.annotations.LogAnnotation)") //自定义注解配置为切点Pointcut
public void aspectPoint(){} //不用写任何实现
/**
* 切入点方法执行之前,可记录访问目标方法时间
* @param joinPoint: joinPoint:包含了目标方法的所有信息的对象
**/
@Before("aspectPoint()")
public void doBefore(JoinPoint joinPoint){
System.out.println("【即将执行目标方法】:"+joinPoint.getSignature().getName()
+"....."+new SimpleDateFormat("yy-MM-dd HH:mm:ss").format(new Date()));
THREAD_LOCAL.set(new Date());
}
/**
* 切入点方法执行后:获取到日志信息并保存到数据库
* @param joinPoint: joinPoint:包含了目标方法的所有信息的对象
**/
@After("aspectPoint()")
public void doAfter(JoinPoint joinPoint) throws Exception {
//保存用户名
String username = null;
if(request.getSession().getAttribute("user")==null)
username="李四";
//取得注解信息
String desc = getAnnotationInfos(joinPoint).get("desc").toString();
String type = getAnnotationInfos(joinPoint).get("type").toString();
//创建日志对象
Log log = new Log();
log.setUsername(username)
.setLogType(request.getMethod())
.setDesc(desc)
.setLogType(type)
.setRequestUrl(request.getRequestURL().toString())
.setGetRequestType(request.getMethod())
.setCreateTime(new Date());
//todo 将日志持久化到数据库
System.out.println(log);
}
/**
* 取得目标方法的注解信息,并保存到map集合中
* @param joinPoint:
* @return java.util.Map<java.lang.String,java.lang.Object>
**/
public Map<String,Object> getAnnotationInfos(JoinPoint joinPoint)throws Exception{
Map<String,Object> annotationInfors = new HashMap<>();
//取得目标类的名称
String targetClassName = joinPoint.getTarget().getClass().getName();
//取得目标方法的名称
String targetMethodName = joinPoint.getSignature().getName();
/*
//取得目标方法的参数
Object args[] = joinPoint.getArgs();
//取得目标类的所有方法
Method[] methods = Class.forName(targetClassName).getMethods();
*/
//使用反射取得目标方法
Method method = Class.forName(targetClassName).getMethod(targetMethodName);
//获取方法的注解信息(自定义的注解)
String desc = method.getAnnotation(LogAnnotation.class).desc();
String type = method.getAnnotation(LogAnnotation.class).type();
//保存入map中
annotationInfors.put("desc",desc);
annotationInfors.put("type",type);
return annotationInfors;
}
}
- 测试(Controller)层
package cn.qf.controller;
@RestController
@RequestMapping("emp")
public class EmpController {
@GetMapping("add")
@LogAnnotation(desc = "增加雇员",type = "insetType")
public String addEmp(){
return "增加雇员成功!";
}
@RequestMapping("delete")
@LogAnnotation(desc = "删除雇员",type = "deleteType")
public String deleteEmp(){
return "删除雇员成功!";
}
}
- 以上的日志信息还没有添加到数据库,只是做了一个伪操作,但是核心的是注解和反射的运用、以及切面类的开发定义。
- 本次案例用到的技术点还是很核心的,比如说:反射注解等等
8.3整合Mybatis
1、导入 mybatis 整合的开发包
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2、自己在数据库中设计一张数据表
3、要在 application.yml 文件中进行相关的配置
比如数据库连接配置。
# 应用名称
spring.application.name=log-boot
# 应用服务 WEB 访问端口
server.port=80
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=cn.qf.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://192.168.226.128:3306/blue?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=***
spring.datasource.password=***
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/log? useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
mybatis:
type-aliases-package: cn.mbz.entity
mapper-locations: classpath:mappers/**/*.xml
4、定义数据层接口
package cn.mbz.dao;
import cn.mbz.entity.Log;
import java.util.List;
public interface LogDAO {
/**
* 插入日志信息
* @param log
* @return
*/
int insert(Log log);
/**
* 查询日志信息
* @return
*/
List<Log> selectLogList();
}
5、拷贝映射文件(在你之前的 demo 中自己去找)
<?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="cn.qf.empModel.mapper.EmpMapper">
<!--命名空间为EmpNs,后⾯操作数据的时候要使⽤到-->
<!--在需要2级缓存的映射文件中进行配置 -->
<cache eviction="LRU" flushInterval="1000" size="500" readOnly="true"></cache>
<select id="selectEmpById" resultType="cn.qf.vo.Emp">
SELECT * FROM emp WHERE empno =#{0}
</select>
<!--动态sql更新-->
<update id="updateEmpById" parameterType="Emp">
UPDATE emp SET ename=#{ename}
<if test="job!=null">
,job=#{job}
</if>
<if test="sal!=null">
,sal=#{sal}
</if>
<where>
<choose>
<when test="sal!=null">
and sal < 0
</when>
<when test="job!=null">
1=2;
</when>
<otherwise>
empno=#{empno}
</otherwise>
</choose>
</where>
</update>
<delete id="deleteEmpById" >
delete * from emp where id=#{0}
</delete>
<insert id="insertEmp" parameterType="Emp" useGeneratedKeys="true" keyProperty="empno" keyColumn="empno">
INSERT INTO emp(empno,ename,job,sal,hiredate,deptno,comm,mgr)
values (#{empno},#{ename},#{job},#{sal},#{hiredate},#{deptno},#{comm},#{mgr})
</insert>
<!--模糊分页查询-->
<select id="selectEmpAllSplit" resultType="cn.qf.vo.Emp">
select * from emp where 1=1
<if test="kw!=null">
and ename like ${kw}
</if>
<if test="column!=null">
order by ${column} ${sort}
</if>
<if test="start!=null">
LIMIT ${start},${ls}
</if>
</select>
<!--查询所有Emp-->
<select id="selectAll" resultType="Emp">
select * from emp where 1=1
</select>
<!--实现数据量的统计-->
<select id="selectCount" resultType="Integer">
select count(*) from emp where 1=1
<if test="kw!=null">
and ename LIKE ${kw}
</if>
</select>
<!--根据部门deptNo查询emps-->
<select id="selectEmpsByDeptno" resultType="Emp">
select * from emp where deptno=#{0}
</select>
<!-- 批量删除 -->
<delete id="deleteByIdList">
delete from emp WHERE empno IN
<foreach collection="list" open="(" separator="," close=")" item="empno">
#{empno}
</foreach>
</delete>
</mapper>
7、定义业务层(调整业务层实现类)
7、定义业务层(调整业务层实现类)
package cn.mbz.service.impl;
import cn.mbz.dao.LogDAO;
import cn.mbz.entity.Log;
import cn.mbz.service.LogService;
import org.aspectj.lang.annotation.After;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDAO logDAO;
@Override
public boolean addLog(Log log) {
System.out.println(log);
return logDAO.insert(log)>0;
}
@Override
public List<Log> getAllLogs() {
System.out.println("查询日志成功!");
return null;
}
}
8.启动类包扫描注解
package cn.mbz;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "cn.mbz.dao")
public class LogBootApplication {
public static void main(String[] args) {
SpringApplication.run(LogBootApplication.class, args);
}
}
9、mybatis 中 sql 语句日志打印输出(调整配置文件)
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/log?useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.pn.boot.pojo
【properties 文件的配置】
mybatis.configuration.log-impl
=org.apache.ibatis.logging.stdout.StdOutImpl
10、日期格式的转换
【2022-07-05T6:00.00.000+00:00】日期是西方格式,对象转换成 json 的时候导致的,我们可以放弃Spring 原有的 json 转换工具,我们选择使用 fastjson(阿里巴巴提供的)
package cn.qf.configs;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
//配置 json 的转换器
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建一个新的转换器添加到 converters(fastjson 提供的)
FastJsonHttpMessageConverter messageConverter = new
FastJsonHttpMessageConverter();
// 配置转换的规则
FastJsonConfig fastJsonConfig = new FastJsonConfig();
//配置全局日期格式
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
//设置字符编码
fastJsonConfig.setCharset(Charset.forName("utf-8"));
//设置 mime 类型 直接交给转换器来进行设置
List<MediaType> mediaTypeList = new ArrayList<MediaType>();
mediaTypeList.add(MediaType.APPLICATION_JSON); //application/json
messageConverter.setSupportedMediaTypes(mediaTypeList);
messageConverter.setFastJsonConfig(fastJsonConfig);
//将转换器添加 converters
converters.add(messageConverter);
}
}
9.集成swagger
10.1 问题描述
随着互联网技术的发展,现在的网站应用架构基本都由原来的后端渲染(比如 jsp 和thymeleaf),变成了:前端渲染、前后端分离的形态,而且前端技术和后端技术在各自的道路上越走越远。 前端和后端的唯一联系,变成了 API 接口;API 文档变成了前后端开发人员联系的纽带,变得越来越重要,swagger 就是一款让你更好的书写 API 文档的框架,而且 swagger 可以完全模拟 http 请求,入参出参和实际情况差别几乎为零。
没有 API 文档工具之前,大家都是手写 API 文档的(维护起来相当困难),在什么地方书写的都有,有在 confluence 上写的,有在对应的项目目录下 readme.md 上写的,每个公司都有每个公司的玩法,无所谓好坏。但是能称之为“框架”的,估计也只有 swagger 了
10.2 创建项目加入依赖
<!--swagger starter-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
10.3 创建 Swagger 配置类
package com.qf.utils;//package com.qf.utils;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* 定义接口信息
*/
public ApiInfo getInfo(){
return new ApiInfoBuilder()
.title("管理系统的说明文档")
.description("此系统可以完成对美容院内的人员相关信息的管理")
.contact(new Contact("yi","http://localhost:8080","2223420215@qq.com"))
.termsOfServiceUrl("http://localhost:8080")
.license("yi")
.version("1.1.0")
.build();
}
/**
* swagger的配置界面
* @return
*/
public Docket configUi(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getInfo())
.select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/error").negate())
.paths(PathSelectors.any())
.build();
}
}
10.4 修改 yml 文件
#【properties 风格】
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
#由于 Spring Boot 2.6.x 请求路径与 Spring MVC 处理映射匹配的默认策略从 AntPathMatcher 更改为PathPatternParser。所以需要设置 spring.mvc.pathmatch.matching-strategy 为 ant-path-matcher 来改变它。
spring:
mvc:
pathmatch:
matching-strategy:ant_path_matcher
常用注释注解
//方法上
@ApiOperation(value = "获取车位区域",notes = "")
//类上
@Api(tags = "车位管理")
//实体类字段上
@ApiModelProperty(value = "车辆品牌")
10.mybatis-plus 简介
官网:http://mp.baomidou.com/
MyBatis-Plus (opens new window() 简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生(单表操作的王者)。
原始的 mybatis 需要写 sql,现在有了 mybatis-plus,基本上可以不写 sql(在操作单表数据的时候就可以完全不写 sql),但是如果是连表查询还是要写 sql 语句。
注意:并不是 mybatis 不使用了,不是使用 mybatis-plus 取代了 mybatis,而是在不改变 mybaits 的使用的基础上进行了增强。
补充:以前有一个映射框架叫做 hibernate,这个框架是全自动的框架,就是可以不写 sql 语句,但是牺牲了性能。
1.1 特性
-
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
-
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
-
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
-
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
-
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
-
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
-
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )2
-
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
-
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
-
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
-
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
-
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
4.1 引入依赖
spring-boot-starter、spring-boot-starter-test
添加:mybatis-plus-boot-starter、MySQL、lombok、
在项目中使用 Lombok 可以减少很多重复代码的书写。比如说 getter/setter/toString
等方法的编写 如果在创建项目的时候已经选择了相关的依赖则不需要重复去添加如下的依赖
了。
<dependencies>
<!--mybatis-plus-->
<dependency>
<groupId>com.mobaijun</groupId>
<artifactId>mybatis-plus-spring-boot-starter</artifactId>
<version>1.0.3</version>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
5.基本配置
在 application.properties 配置文件中添加 MySQL 数据库的相关信息:
# 应用名称
spring:
application:
name: mybatis-demo
datasource:
url: jdbc:mysql://localhost:3306/demo?serverTimezone=GMT%2B8
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
【properties】
# 应用名称
spring.application.name=mybatis-plus-mk-demo
# 应用服务 WEB 访问端口
server.port=8080
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=1234
注意:mysql8 以上 driver 和 url 的变化
1、这里的 url 使用了 ?serverTimezone=GMT%2B8 后缀,因为 Spring Boot 2.1 集成了 8.0 版本
的 jdbc 驱动,这个版本的 jdbc 驱动需要添加这个后缀,否则运行测试用例报告如下错误:
java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or
represents more
2、这里的 driver-class-name 使用了 com.mysql.cj.jdbc.Driver ,在 jdbc 8 中 建议使用这个驱
动,之前的 com.mysql.jdbc.Driver 已经被废弃,否则运行测试用例的时候会有 WARN 信息
6.1 主类
在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹
注意:扫描的包名根据实际情况修改
@SpringBootApplication
//@MapperScan("cn.qf.mybatisplus.mapper") 如果EmpMapper注入失败是需要扫描数据层的
// 该注解转移到mybatisPlus配置类上去
public class MybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusApplication.class, args);
}
}
7.2 配置日志
#【yml 风格下的配置】
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#【properties 风格下】
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImp
8.主键生成策略
之前在学习数据库的时候如果主键是数字类型,则可以让其自增长,现在我们可以直接使用 mybatis-plus 实现我们自己的主键的值。
(1)ID_WORKER
MyBatis-Plus 默认的主键策略是:ID_WORKER 全局唯一 ID
(2)自增策略
要想主键自增需要配置如下主键策略
|-需要在创建数据表的时候设置主键自增
|-主键的字段类型必须是数字
|-实体字段中配置 @TableId(type = IdType.AUTO)
@TableId(value="empno",type=IdType.AUTO)
private Integer empno;
(3)观察 IdType 的源码
public enum IdType {
AUTO(0),//自动增长,但是字段必须是数字类型
NONE(1),// 若手工指定了则用指定的的策略,否则用第三种策略
INPUT(2),//表示要你主动设置(如果对应字段设置不能为 null,需要自己给定值)
ASSIGN_ID(3), //数字类型的字符串如 :1542717258367205377
ASSIGN_UUID(4);// 随机字符的类型如:dd9709b24851e9e5704c5e2add6aa231
}
如果说每一张数据表的主键值都需要自动生成,则对应的每个实体都要去指定生成策略,这种就比较麻烦,我们可以在配置文件进行全局的主键生成策略配置。
(4)要想影响所有实体的配置,可以设置全局主键配置
#【修改 application.yml 文件】
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: assign_id
#【propert6ies 风格的配置】
mybatis-plus.global-config.db-config.id-type=assign_id
//【需要设置主键策略的类的属性只需要加上注解就行】
@Data
public class Emp {
@TableId
private String empno;
}
9.数据更新
@Test //查询结果为 结果集类型应该是多行多列的
void testSelectBatchIds(){ /** 快速生成list集合 */
List<Emp> emps = empMapper.selectBatchIds(Arrays.asList("7521", "7788", "7900"));
/**
* List集合的三种遍历方式
* */
//方式一:iterator迭代器遍历
Iterator<Emp> iterator = emps.iterator();
while (iterator.hasNext()){
Emp next = iterator.next();
System.out.println(next);
}
//方式二:函数式接口 【jdk.1.8】
emps.forEach(new Consumer<Emp>() {
@Override
public void accept(Emp emp) {
System.out.println(emp);
}
});
//方式三:方法的应引用 【jdk.1.8】
emps.forEach(emp -> System.out.println(emp));
}
/** 条件查询需要【QueryWrapper】类封装查询条件 */
@Test
//查询薪资大于1500的
void selectByCondition(){
//需要使用到一个新的类来封装查询条件
QueryWrapper<Emp> queryWrapper = new QueryWrapper();
//封装查询条件
queryWrapper.gt("sal", 1500);
//进行带条件查询
List<Emp> emps = empMapper.selectList(queryWrapper);
emps.forEach(emp -> System.out.println(emp));
}
/** gt() or() eq() */
@Test //查询薪资大于1500或者职位是clerck的雇员信息
void testSelectByCondition(){
//要用到一个新的类
QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
//查询薪资大于1500或者职位是clerck的雇员的信息
queryWrapper.gt("sal",15000).or().eq("job","clerk");
//进行查询
List<Emp> emps = empMapper.selectList(queryWrapper);
emps.forEach(emp -> System.out.println(emp));
}
/** and条件查询:分开加入条件 */
@Test //查询薪资大于1500并且职位不是经理的
void testSelectByCondition2(){
QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("sal",1500);
queryWrapper.ne("job","manager");
List<Emp> emps = empMapper.selectList(queryWrapper);
emps.forEach(emp -> System.out.println(emp));
}
/** 多条件查询 + 排序查询 */
@Test //查询薪资大于1500并且职位不是经理的,按照薪资降序排列
void testSelectByCondition3(){
//创建封装条件的类
QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
//薪资大于1500
queryWrapper.gt("sal",1500);
//职位不是经理
queryWrapper.ne("job","manager");
//按照薪资降序处理--按照工资降序
queryWrapper.orderByDesc("sal");
//进行查询
List<Emp> emps = empMapper.selectList(queryWrapper);
emps.forEach(emp -> System.out.println(emp));
}
/** 模糊查询 LIKE 枚举类型查询 in/notin */
@Test //查询名字带A并且编号不在7788,7963,7521范围的雇员的信息。
void testSelectByCondition4(){
//封装条件类
QueryWrapper<Emp> queryWrapper = new QueryWrapper();
queryWrapper.like("ename","a");
queryWrapper.notIn("empno",Arrays.asList("7788","7369","7521"));
//查询
List<Emp> emps = empMapper.selectList(queryWrapper);
emps.forEach(emp -> System.out.println(emp));
}
10.自动填充
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录创建时间,更新时间等。我们可以使用 MyBatis Plus 的自动填充功能,完成这些字段的赋值工作:
(2)在的要自动填充的字段上添加对应的注解
//主键生成策略
@TableId(value = "empno",type = IdType.NONE)
private String empno;
//自动填充
/**
* 实现元对象处理器接口要实现数据的自动填充则还需要使用到一个接口,
* 这个接口是MetaObjectHandler接口,
* 可以在实现类中指定实体类的哪些属性需要填充指定的数
* */
@TableField(fill= FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 乐观锁进行更新时需要用到的版本号version ---仅支持updateById 与 update(entity实体,wrapper)
* 元对象处理器接口添加version的insert默认值
* */
@TableField(fill = FieldFill.INSERT) //inset操作时自动填充数值
@Version() //作为乐观锁的版本号!!
private Integer version;
/**
* 将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录。
* */
@TableField(fill = FieldFill.INSERT) //创建时自动填充---需要在元对象处理器接口添加的insert值
@TableLogic(value = "1",delval = "0") //1数据正常状态 0数据删除状态
private Integer deleted;
(3)实现元对象处理器接口
要实现数据的自动填充则还需要使用到一个接口,这个接口是 MetaObjectHandler 接
口,可以在实现类中指定实体类的哪些属性需要填充指定的数据。
package cn.qf.mybatisplus.utils;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import javax.xml.crypto.Data;
import java.util.Date;
/**
* @className: MyMetaObjectHandler
* @Author: ich liebe Dich
* @Date: 2022/11/15 14:22
* @Version: 1.0
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入数据时会执行的方法
@Override
public void insertFill(MetaObject metaObject) {
//如果在插入数据的时候遇到了createTime属性则使用newDate()进行填充
this.setFieldValByName("createTime", new Date(),metaObject);
//如果在插入数据的时候遇到了updateTime属性则使用newDate()进行填充
this.setFieldValByName("updateTime",new Date(),metaObject);
//版本号的数据自动插入(1表示存在,0:已经表示逻辑删除)
this.setFieldValByName("version",1,metaObject);
//添加逻辑删除状态字段column的初始值自动填充
this.setFieldValByName("deleted",1,metaObject);
}
//修改数据时会执行的方法
@Override
public void updateFill(MetaObject metaObject) {
//如果更新数据的时候遇到了明年updateTime则使用当前时间填充
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
注: 自动填充失败可能出现的原因:
1.数据表实体类 注解 @TableName(“对应表名称”)
2.检查Mapper层继承BaseMapper<实体类> 并且 类上注解@Mapper
3.检查metaObjectHandler实现类要于实体类属性名称一致
11.条件查询(select)
我们在之前 mp 的第一个 demo 就是查询,但是之前的查询的没有根据条件去查询,接下我们要分析带条件的查询操作。
11.1 根据 id 查询记录
查询到的结果集是单行多列的类型
@Test
void testSelectById(){
Emp emp = empMapper.selectById("7521");
System.out.println(emp);
}
@Test
void testSelectBatchIds() {
List<Emp> emps = empMapper.selectBatchIds(Arrays.asList("7521","7788","7900"));
emps.forEach(emp -> System.out.println(emp));
}
@Test
void testSelectByCondtion5() {
QueryWrapper<Emp> empQueryWrapper = new QueryWrapper<>();
empQueryWrapper.select("date_format(hiredate,'%Y-%m') AS month,COUNT(*) AS 人数,SUM(sal) AS sal").between("date_format(hiredate,'%Y-%m')", "1981-01",
"1981-12").groupBy("date_format(hiredate,'%Y-%m')");
List<Map<String, Object>> maps = empMapper.selectMaps(empQueryWrapper);
maps.forEach(map -> {//取得月份
String month = map.get("month").toString();//取得人数
String 人数 = map.get("人数").toString();
System.out.println(map.toString());
System.out.println(month+"--->"+人数);
});
}
- 使用了MyBatis-Plus的LambdaQueryWrapper进行条件查询
首先创建了一个LambdaQueryWrapper对象,然后通过调用eq方法设置了查询条件,即员工的用户名和密码与传入的参数相等。最后通过employeeService的getOne方法根据条件查询数据库,返回符合条件的员工信息。
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
/**
* LambdaQueryWrapper 进行条件查询
* */
LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Employee::getUsername,employee.getUsername());
lambdaQueryWrapper.eq(Employee::getPassword,password);
Employee empDb = employeeService.getOne(lambdaQueryWrapper);
13.分页查询
(1)创建配置类
此时可以删除主类中的 @MapperScan 扫描注解
package cn.qf.mybatisplus.configs;
/**
* @className: MybatisPlusConfig
* @Author: ich liebe Dich
* @Date: 2022/11/15 20:05
* @Version: 1.0
*/
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* MyBatisPlus自带分页插件,只要简单的配置即可实现分页功能。
* */
@Configuration //配置类 Spring中的注解
@EnableTransactionManagement //开启事务管理
@MapperScan("cn.qf.mybatisplus.mapper") //mapper数据层的包扫描注解
public class MybatisPlusConfig {
//注册一个bean,这个bean是mp的一个插件
@Bean /**分页查询需要用到的bean*/
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//Interceptor拦截器实例 Filter过滤器
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//添加一个表示乐观锁处理的拦截器对象
/* mybatisPlusInterceptor.addInnerInterceptor(
new OptimisticLockerInnerInterceptor() );*/
mybatisPlusInterceptor.addInnerInterceptor(
new OptimisticLockerInnerInterceptor());
/** 添加分页插件 */
mybatisPlusInterceptor.addInnerInterceptor(
new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
(2)测试 selectPage 分页
测试:最终通过 page 对象获取相关数据
@Test
void testSelectPage(){
//需要使用一个新的类,该类用来辅助完成分页查询
//1.表示当前页 2.表示每页显示五条数据
Page<Emp> page = new Page<>(1,3);
//查询条件的组装
QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
queryWrapper.like("ename","a");
//只要执行了下面的代码,则查询到的数据就会保存到上面创建的empPag中了
empMapper.selectPage(page,queryWrapper);
//取得查询的数据
List<Emp> emps = page.getRecords();
emps.forEach(emp -> System.out.println(emp));
System.out.println("总页数:"+page.getPages());
System.out.println("当前页:"+page.getCurrent());
System.out.println("每页显示的数据量:"+page.getSize());
System.out.println("总数据量:"+page.getTotal());
System.out.println("是否还有上一页:"+page.hasPrevious());
System.out.println("是否还有下一页:"+page.hasNext());
}
1、分页查询需要一个插件,这个插件对应的对象需要在 mybatis-plus 的配置文件中去增加
14.业务层的 CRDU
之前我们是基于 mapper 层进行操作,但是我们在实际的开发中都是通过控制层去调用 mapper 层的,现在思考一个问题,我们如何从从控制层操作到 mybatis-plus 的的 mapper 层呢?
其实上 mybatis-plus 也考虑到了这种情况,所以他连 service层都帮我们实现了。
mapper 层的结构图如下:
15.实现乐观锁
在数事务的概念的时候分析过锁的概念,数据库是支持锁表和锁行,锁表的机制叫做表锁,锁行的机制叫做行锁,行锁和表锁的特征就是认为数据一定会出现安全风险,所以锁一直存在,这种锁叫做悲观锁。
和悲观锁对应的就是乐观锁,乐观锁是通过数据的版本号实现的,在更新之前查询一次数据的版本号,在更新的时候再查询一次版本号,两次的查询做对比,如有相同则更新数据,更新之后并且版本好+1,如果不同则不更新,这就是乐观锁的原理。
主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线
程安全的数据更新
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
(1)数据表中添加 version 字段(emp 数据表表)
(2)实体类添加 version 字段(Emp.java 实体类)
/**
* 乐观锁进行更新时需要用到的版本号version ---仅支持updateById 与 update(entity实体,wrapper)
* 元对象处理器接口添加version的insert默认值
* */
@TableField(fill = FieldFill.INSERT) //inset操作时自动填充数值
@Version() //作为乐观锁的版本号!!
private Integer version;
(3) 元对象处理器接口添加 version 的 insert 默认值
修改实现类:MyMetaObjectHandler
//插入数据时会执行的方法
@Override
public void insertFill(MetaObject metaObject) {
//如果在插入数据的时候遇到了createTime属性则使用newDate()进行填充
this.setFieldValByName("createTime", new Date(),metaObject);
//如果在插入数据的时候遇到了updateTime属性则使用newDate()进行填充
this.setFieldValByName("updateTime",new Date(),metaObject);
//版本号的数据自动插入(1表示存在,0:已经表示逻辑删除)
this.setFieldValByName("version",1,metaObject);
//添加逻辑删除状态字段column的初始值自动填充
this.setFieldValByName("deleted",1,metaObject);
}
特别说明:
-
支持的数据类型只有 int,Integer,long,Long,Date,Timestamp,LocalDateTime
-
整数类型下 newVersion = oldVersion + 1
-
newVersion 会回写到 entity 中,我们这里的实体类就是 Emp 类
-
mybatis-plus 中的乐观锁仅支持 updateById(id) 与 update(entity, wrapper) 方法
-
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
上面的配置还不够,需要增加一个配置类,这个配置类的作用是注入一个支持乐观锁插
件的一个 bean。
(4)在 MybatisPlusConfig 中注册 Bean
/**
* MyBatisPlus自带分页插件,只要简单的配置即可实现分页功能。
* */
@Configuration //配置类 Spring中的注解
@EnableTransactionManagement //开启事务管理
@MapperScan("cn.qf.mybatisplus.mapper") //mapper数据层的包扫描注解
public class MybatisPlusConfig {
//注册一个bean,这个bean是mp的一个插件
@Bean /**分页查询需要用到的bean*/
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//Interceptor拦截器实例 Filter过滤器
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//添加一个表示乐观锁处理的拦截器对象
/* mybatisPlusInterceptor.addInnerInterceptor(
new OptimisticLockerInnerInterceptor() );*/
mybatisPlusInterceptor.addInnerInterceptor(
new OptimisticLockerInnerInterceptor());
/** 添加分页插件 */
mybatisPlusInterceptor.addInnerInterceptor(
new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
3.删除数据
/**
* 将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录。
* */
@TableField(fill = FieldFill.INSERT) //创建时自动填充---需要在元对象处理器接口添加的insert值
@TableLogic(value = "1",delval = "0") //1数据正常状态 0数据删除状态
private Integer deleted;
//添加逻辑删除状态字段column的初始值自动填充
this.setFieldValByName("deleted",1,metaObject);
全局配置
在springboot的配置文件中增加配置(deleted的值如果1表示逻辑删除,如果0表示没有逻辑删除)
mybatis-plus:
configuration:
log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
id-type:assign_id
logic-delete-value:0 #被删除状态
logic-not-delete-value:1 #正常状态
4、代码生成器
- 方式一
导入依赖:
<!-- 代码生成器的依赖2个 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
生成器代码:
package cn.qf.mybatisplus;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.jupiter.api.Test;
public class CodeGenerator {
@Test
public void run() {
// 1、创建代码生成器对象
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
//指定生成的代码和包存放的地方
//D:\Code_Program_333\SpringBoot_MyBatisPlus\springboot-mybatisplus
gc.setOutputDir("D:\\Code_Program_333\\SpringBoot_MyBatisPlus\\springboot-mybatisplus" + "/src/main/java/");
gc.setAuthor("Liuchangjiang");//作者
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(true); // 不覆盖之前的
gc.setServiceName("%sService");//生成的业务层接口不需要前面有一个I
gc.setIdType(IdType.ID_WORKER);//主键策略的设置(字符串类型的数字)
gc.setDateType(DateType.ONLY_DATE);//设置日期类型
// gc.setSwagger2(true);//是否开启swagger配置
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://192.168.226.128:3306/Test?serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("Lcj1024..");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
// pc.setParent("cn.mbz");//指定父包名称
pc.setParent("testCodeGenerator");//指定父包名称
// pc.setModuleName("uservice"); //指定模块名称(在分布式的项目中才会指定)
pc.setController("controller");//控制层包名
pc.setEntity("pojo");//实体类包名
pc.setService("service");//业务层包名
pc.setMapper("mapper");//dao层包名
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("emp");//数表的名称(就是指定要为哪一张数据表生成包和代码)
//数据表映射到实体类的命名策略,比如数据表为t_emp--->tEmp
strategy.setNaming(NamingStrategy.underline_to_camel);
//生成实体类的时候去掉表的前缀(比如表名为t_emp_info--->empInfo)
//strategy.setTablePrefix(pc.getModuleName() + "_");
strategy.setTablePrefix("t_"); //去掉表前面的t
//生成实体类的时候的字段的命名策略(遵循驼峰原则比如 e_name--->eName)
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);//开启lombok插件的使用
strategy.setRestControllerStyle(true);//restful风格的api配置
strategy.setControllerMappingHyphenStyle(true);// url中驼峰转字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}
- 方式二:
-
pom.xml导入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency>
-
代码生成器
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import java.util.*;
public class CodeGenerator {
public static void main(String[] args) {
getCode();
}
public static void getCode() {
//TODO 修改为自己的数据库信息
String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "123456";
//TODO 修改为自己的表名
List<String> tables = List.of("user");//需要生成对应代码的表名的集合
FastAutoGenerator.create(url, username, password)
//全局配置----------------------------------------------------------------------------------------
.globalConfig(builder -> {
builder
.author("蒾酒")//TODO 修改为自己名称
.outputDir(System.getProperty("user.dir") + "\\src\\main\\java")// 输出路径(写到java目录)
// .enableSwagger() //开启swagger,会自动添加swagger相关注解
.commentDate("yyyy-MM-dd");//日期格式
})
//包名配置--------------------------------------------------------------------------------------------
.packageConfig(builder ->
builder.parent("com.mijiu")//TODO 修改为自己项目的路径
// .moduleName("practice")//模块名,设置该项会在输出路径上增加一层模块名目录
.entity("entity")
.service("service")
.serviceImpl("service.impl")
.controller("controller")
.mapper("mapper")
.xml("mapper/xml")
)
//策略配置-----------------------------------------------------------------------------------------
.strategyConfig(builder -> {
builder
.addInclude(tables)// 需要生成代码对应的表,若需要生成全部表则注释该行解放下一行
// .addInclude("all")//生成全部表
// .addTablePrefix("p_")//表前缀过滤,例如“p_”开头的表不会生成对应代码
//实体策略配置
.entityBuilder()
.enableFileOverride()// TODO 开启覆盖已生成的entity文件,关闭则注释本行
.enableLombok()// 自动添加lombok注解@Getter @Setter
.logicDeleteColumnName("deleted")// 指定逻辑删除字段名自动为其添加逻辑删除字段注解
.enableTableFieldAnnotation()//启用表字段注解@TableField
//Mapper策略配置
.mapperBuilder()
.enableBaseResultMap() // 生成通用的resultMap
.superClass(BaseMapper.class)
.formatMapperFileName("%sMapper")//mapper文件后缀,如UserMapper
// .enableFileOverride()// TODO 开启覆盖已生成的mapper文件,关闭则注释本行
.formatXmlFileName("%sMapper")//xml文件后缀,如UserMapper.xml
//Service策略配置
.serviceBuilder()
// .enableFileOverride()//TODO 开启覆盖已生成的service文件,关闭则注释本行
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl")
//Controller策略配置
.controllerBuilder()
.enableHyphenStyle() // 映射路径使用连字符格式
.formatFileName("%sController")
// .enableFileOverride()// TODO 开启覆盖已生成的controller文件,关闭则注释本行
.enableRestStyle();//启用rest风格自动添加@RestController
}).execute();
}
}
240709、补充内容
1. 配置文件格式
问题导入
框架常见的配置文件有哪几种形式?
1.1 修改服务器端口
http://localhost:8080/books/1 >>> http://localhost/books/1
SpringBoot提供了多种属性配置方式
- application.properties
server.port=80
- application.yml
server:
port: 81
- application.yaml
server:
port: 82
1.2 自动提示功能消失解决方案
操作步骤:
1.3 SpringBoot配置文件加载顺序
- application.properties > application.yml > application.yaml
注意事项:
- SpringBoot核心配置文件名为application
- SpringBoot内置属性过多,且所有属性集中在一起修改,在使用时,通过提示键+关键字修改属性
2. yaml
问题导入
什么是yaml,和properties有什么区别?
- YAML(YAML Ain’t Markup Language),一种数据序列化格式
- 优点:
- 容易阅读
- 容易与脚本语言交互
- 以数据为核心,重数据轻格式
- YAML文件扩展名
- .yml(主流)
- .yaml
2.1 yaml语法规则
- 大小写敏感
- 属性层级关系使用多行描述,每行结尾使用冒号结束
- 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
- 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
- #表示注释
- 核心规则:数据前面要加空格与冒号隔开
2.2 yaml数组数据
- 数组数据在数据书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔
2.3 yaml数据读取
- 使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
-
封装全部数据到Environment对象
-
== 自定义对象封装指定数据【常用】 ==
public class Enterprise {
private String name;
private Integer age;
private String tel;
private String[] subject;
//自行添加getter、setter、toString()等方法
}
- 自定义对象封装数据警告解决方案
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
3. 多环境开发配置
问题导入
在实际开发中,项目的开发环境、测试环境、生产环境的配置信息是否会一致?如何快速切换?
3.1 多环境启动配置
- yaml文件多环境启动
- properties文件多环境启动
#主启动配置文件 application.properties
spring.profiles.active=pro
#环境分类配置文件 application-pro.properties
server.port=80
#环境分类配置文件 application-dev.properties
server.port=81
#环境分类配置文件application-test.properties
server.port=82
3.2 多环境启动命令格式
- 带参数启动SpringBoot
java –jar springboot.jar --spring.profiles.active=test
java –jar springboot.jar --server.port=88
java –jar springboot.jar --server.port=88 --spring.profiles.active=test
- 参数加载优先顺序
3.3 多环境开发控制
Maven与SpringBoot多环境兼容(步骤)
①:Maven中设置多环境属性
<profiles>
<profile>
<id>dev_env</id>
<properties>
<profile.active>dev</profile.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>pro_env</id>
<properties>
<profile.active>pro</profile.active>
</properties>
</profile>
<profile>
<id>test_env</id>
<properties>
<profile.active>test</profile.active>
</properties>
</profile>
</profiles>
②:SpringBoot中引用Maven属性
③:执行Maven打包指令
- Maven指令执行完毕后,生成了对应的包,其中类参与编译,但是配置文件并没有编译,而是复制到包中
- 解决思路:对于源码中非java类的操作要求加载Maven对应的属性,解析${}占位符
④:对资源文件开启对默认占位符的解析
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>utf-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
</configuration>
</plugin>
</plugins>
</build>
- Maven打包加载到属性,打包顺利通过
4. 配置文件分类
问题导入
SpringBoot的配置文件可以放在项目的哪些地方?
java –jar springboot.jar --spring.profiles.active=test --server.port=85 --server.servlet.context-path=/heima --server.tomcat.connection-timeout=-1 ... ...
-
SpringBoot中4级配置文件
1级: file :config/application.yml 【最高】
2级: file :application.yml
3级:classpath:config/application.yml
4级:classpath:application.yml 【最低】
-
作用:
1级与2级留做系统打包后设置通用属性
3级与4级用于系统开发阶段设置通用属性