Servlet【理解为主】
web 服务器,就是一个运行 web 项目的容器
- Tomcat
- Apache
- 各大公司有自己做的
- ...
servlet 是运行在服务器上的程序,作用
- 接收请求
- 做出响应
演示
- 创建 web 项目图片

- 写 servlet 程序
- servlet 是封装好的程序,所以导包
XML
<!-- 引入servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 引入jsp依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
- 接收请求
- 做出响应
- 服务端一般是三层架构
- 控制层(servlet)
- 业务层(service)
- 持久层(dao)
- 部署到 tomcat

- 启动
- 前端
- html,css
记住 web 开发流程 **[重点]**

记住项目结构 [重点]


记住 tomcat 端口,默认是 8080
SpringBoot
servlet 比较麻烦后来使用 ssm 框架
- Spring , 容器,帮你管理整个的对象
- SpringMVC, web 框架,封装了 servlet
- Mybatis , 封装了 jdbc
一、引言 - SpringBoot 之前
1.1 初始化配置
为了使用 SSM 框架去开发,准备 SSM 框架的模板配置。
1.2 整合第三方框架
为了 Spring 整合第三方框架,单独的去编写 xml 文件。
1.3 后期维护
后期 SSM 项目后期 xml 文件特别多,维护 xml 文件的成本是很高的
1.4 部署工程
SSM 工程部署也是很麻烦,依赖第三方的容器
1.5 敏捷式开发
基于 Java 的 SSM 开发方式是很笨重,而现在的 python,php,NodeJS 的敏捷式开发已经盖过 Java 一头
二、SpringBoot 介绍

SpringBoot 是由 Pivotal 团队研发的,SpringBoot 并不是一门新技术,只是将之前常用的 Spring,SpringMVC,data-jpa 等常用的框架封装到了一起,帮助你隐藏这些框架的整合细节 ,实现敏捷开发。约定大于配置
SpringBoot 就是一个工具集。官网:https://spring.io/projects/spring-boot
SpringBoot 特点:
- SpringBoot 项目不需要模板化的配置。
- SpringBoot 中整合第三方框架时,只需要导入相应的 starter 依赖包,就自动整合了。
- SpringBoot 默认只有一个.properties 或者.yml 的配置文件,不推荐使用 xml。
- SpringBoot 工程在部署时,采用的是 jar 包的方式,内部自动依赖 Tomcat容器,提供了多环境的配置。
- 后期要学习的微服务框架 SpringCloud 需要建立在 SpringBoot 的基础上。
看完回答几个问题?
- 什么是框架?
- 没有框架,原始操作,一砖一砖的盖,麻烦,慢
- 框架,快速搭建主体,节省时间搞业务
- java 常用框架有哪些?
- ssm
- ssm 分别是什么框架,干什么的?
- SpringBoot 是干什么的?
- 和 SSM 什么关系?
- SpringBoot 为什么好用?
三、SpringBoot 快速入门
3.1 快速构建 SpringBoot
3.1.1 选择构建项目的类型

注意:根据网络状况,可能会提示无法连接。如果不能连接,使用http://start.springboot.io(或者http://start.aliyun.com)
3.1.2 指定 SpringBoot 版本和需要的依赖
| 指定 SpringBoot 版本和需要的依赖 |
|---|
![]() |
3.1.3 导入依赖
xml
XML
<!-- 如果没有该依赖,就手动cv下方的依赖,到pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3.1.4 编写了 Controller
Controller 就是之前的 Servlet, 用于接收请求做出响应
如果使用的是start.aliyun.com创建的项目,它会自带一些测试接口在 demos 包下,直接删除它
创建一个 controller 包,单独写 controller 类

java
java
@Controller // 声明该类是控制层,就可以接收请求做出响应
public class TestController {
@RequestMapping("/test") // 接收请求路径
public String test(){
return "ok.html"; // 请求转发,跳转页面
}
@RequestMapping("/user/login") // 接收请求路径
public String test(){
return "/ok.html"; // 请求转发,跳转页面
}
}
3.1.5 编写页面
在 resources/static 下创建 ok.html 页面ps: 默认的固定格式,只能在 resources 下,且只能是 static

3.1.6 测试
springboot 自带 tomcat, 不需要我们管 web 服务器的事
springboot 框架让 web 开发回归到原始的 java 方式,以 main 运行启动
springboot 项目创建成功后,会自动创建一个有 main 方法的类,我们称为主类或者叫主启动类
主类 --> 点击启动项目
| 启动成功效果 |
|---|

|---|
| |
SpringBoot 项目启动成功后,不会主动打开浏览器,需要自己手动打开浏览器测试

补充:一些问题
pom.xml 依赖报红,即下载失败,重启 idea, 再次下载,主要原因网络。多试几次
图片就是清空缓存重启
第一次启动项目,一定点击主类,不要直接点右上角按钮
不要点击 idea 的 html 页面的右上角的浏览器标志
主类必须放在最外层,如下是不行的
主类一定要放在最外层,像下面这样
小技巧:改变项目结构展示效果
四、Spring
介绍...balabalaspring 的核心功能**++Spring 是一个容器,帮助管理 bean(豆)对象生命周期 (创建对象,使用对象,对象销毁等),++**
bean , 又称 javabean 就是对象
bean 翻译:豆子
java 翻译:爪哇岛,盛产咖啡豆
... 联想...
Spring 框架中主要的功能有
- IOC
- AOP
- MVC ---> 将来会单独学,就是 springmvc 技术
- Dao (JDBCTemplate) 等 ---> 不学,后期会学习 mybatis 操作数据库

所以现在学习 spring 的重点
管理对象技术: IOC
切面编程技术: AOP
4.1 IOC+DI [重点]
4.1.0 引言
使用 servlet 写的登录操作,都是自己手动 new 对象


认为上面这样直接 new 对象,不方便扩展,耦合度高....
Spring 框架的作用就是不需要我们像上面这样 new 对象
4.1.1 介绍
IOC 是 Spring 框架的核心功能之一,IOC(inversion of control)控制反转
控制: 控制创建对象的能力 (以前是自己需要什么对象,自己创建)
反转:原来创建对象是自己做,反转就是将创建对象的能力交给 SpringIOC (控制反转):
DI(dependency injection) 依赖注入,即 属性赋值
Spring 的 ioc, 帮助我们++创建对象,是通过注解实现的++, 常用的创建对象的注解
- @Controller 在控制层代码上使用
- @Service 在业务层代码上使用
- @Repository 在数据层代码上使用
- @Component 在其他代码上使用
--> 以上四个注解都是创建对象用的,只不过建议 是各层用对应的
属性赋值 DI (依赖注入的注解)
- @Autowired
- @Resource
4.1.2 演示 1
需求:项目中控制层 Controller 中需要使用到业务层 service 对象来处理业务
例如 AdminController 中需要创建 AdminService 对象使用,使用 IOC+DI 完成
AdminService 和 AdminServiceImpl
java
public interface AdminService {
void showIOC();
}
@Service // 创建对象,并放入到spring容器中
public class AdminServiceImpl implements AdminService {
@Override
public void showIOC() {
System.out.println("------ 调用AdminServiceImpl --------");
System.out.println("------ 调用AdminServiceImpl[结束] --------");
}
}
AdminController
java
@Controller //创建对象,并放入到spring容器中
public class AdminController {
// 没有IOC时,是自己手动创建对象
// private AdminService adminService = new AdminServiceImpl();
// 有IOC时,是从spring容器中获取对象
@Autowired
private AdminService adminService;
@RequestMapping("/show/ioc")
public void showIOC(){
System.out.println("------ 调用AdminController --------");
// 调用业务层,这个对象是从spring容器中获取赋值给这个接口的,运行看子类
adminService.showIOC();
System.out.println("------ 调用AdminController[结束] --------");
}
}
练习:在 AdminService 中使用 AdminDao 对象
创建 dao, 和 dao.impl 包以及 AdminDao,AdminDaoImpl
java
public interface AdminDao {
void showIOC();
}
java
@Repository // 创建对象,放入到spring容器中
public class AdminDaoImpl implements AdminDao {
@Override
public void showIOC() {
System.out.println("------ 调用AdminDaoImpl --------");
}
}
业务层注入 Dao 层对象
java
@Service // 创建对象,并放入到spring容器中
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminDao adminDao;
@Override
public void showIOC() {
System.out.println("------ 调用AdminServiceImpl --------");
// 调用dao层
adminDao.showIOC();
System.out.println("------ 调用AdminServiceImpl[结束] --------");
}
}
发请求测试
总结:
创建对象 spring 创建 一个注解搞定 控制反转 IOC
用对象 的话 用 @AutoWired 依赖注入 DI
4.1.3 演示 @Autowired 依赖注入 (属性赋值) 的原理
@Autowired 给属性赋值,是如何绑定属性赋值的?是按照类型注入的
但是如果 spring 容器中有多个同类型的对象时,@Autowired 注入就会失败
AdminService adminSerbvice = new AdServiceImp1 ();
AdminService adminSerbvice = new AdServiceImp2 ();

如果 spring 容器中有多个同类型的对象时,如何解决?
此时通过对象名来确定指定注入 (赋值) 哪个对象.
@Qualifier("as2")对象名是什么?默认通过 @Controller,@Service 等这些创建的对象,++对象名默认是类名首字母小写,++
也可以通过在 @Controller,@Service 等这些注解后设置对象名,例如 @Service ("as1")


扩展: @Autowired 和 @Resource 什么异同?
@Autowired是 Spring 专属,默认按类型匹配,需配合其他注解处理多 Bean 场景;@Resource是 Java 标准注解,默认按名称匹配,自带属性更灵活、通用性更强。
4.1.4 演示 3
演示 @Component 注解创建对象
假如有个类 Admin, 现在需要该类对象,就可以在该类上加上 @Component 注解,并在其他地方使用 @Autowired 注入

4.2 AOP [熟悉]
说明
AOP: 面向切面编程
aspect 切面
OOP: 面向对象编程
即 AOP 就是一种特殊的编程写法,简单说就是抽取了一些代码,可以复用!
比如
- 事务管理
- 日志记录
- 权限控制
- ....
将这些重复性代码,抽取出来形成 ' 切面 ', 由这个切面帮这些 "目标类" 完成这些额外工作。背后的技术实现:代理模式.
编写
切面依赖
XML
<!--aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建切面类写切面 / 增强 / 通知方法指定目前类路径
java
package com.qf.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**切面类
*/
@Component
@Aspect
public class MyAspect {
/**
* 前置通知: 在目标方法执行之前执行
* 一般是权限校验
*
* @ Before就是前置通知,其中写的目标路径
* 即对谁产生效果
*/
@Before("execution(* com.qf.service.impl.*.*(..))")
public void before() {
System.out.println("---- 前置通知(权限校验) ----");
}
/**
* 后置通知: 在目标方法执行之后执行
* 一般是记录日志
*/
@After("execution(* com.qf.service.impl.*.*(..))")
public void after() {
System.out.println("---- 后置通知(记录日志) ----");
}
/**
* 环绕通知: 目标方法执行前后都执行
* 一般是事务管理
* ProceedingJoinPoint 目前方法对象
*/
@Around("execution(* com.qf.service.impl.*.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("---- 环绕通知(事务管理)前-开启事务 ----");
try {
// 执行目标方法
joinPoint.proceed();
System.out.println("---- 环绕通知(事务管理)后,提交事务 ----");
} catch (Throwable throwable) {
System.out.println("---- 环绕通知(事务管理)后,回滚事务 ----");
}
}
}
启动测试,访问任何一个请求都可以生效

五、SpringMVC
SpringMVC其实是 spring 框架中关于 web,webmvc 开发的一个技术
spring 核心 IOC,AOP,Web开发
5.1 MVC
m: model
v: view
c: controller
没有 mvc 时,全部一个类做完,不符合单一职责图片

有了 mvc 架构设计图片

MVC 架构:根据不同的事情由不同的类去处理,内部单一职责
- Model: 模型类,例如封装数据的实体类,业务模型 (Service), 数据层 (Dao)
- View: 视图,展示数据的.HTML,JSP
- Controller: 控制器,控制整个流程走向。决定是否能接收请求,调用哪个业务,跳转哪个页面,Servlet
MVC 框架 (SpringMVC 框架) 特点
- 封装了 Servlet
- 接收请求方便
- 接收请求数据方便
- 响应数据方便

5.2 搭建环境
- 创建 springboot 项目
- 导入 spring-boot-starter-web 这个依赖,即可开始开发 webmvc 的代码
5.2 接收请求【重点】
即前后如何通过路径匹配想要在 Controller 层接收请求,只需要
- 创建一个类,类上加 @Controller 注解
- 设计一个方法
- 给方法添加请求映射【路径】的注解
- @RequestMapping 或
- @GetMapping 或 @PostMapping 等
java
package com.qf.controller;
import org.springframework.stereotype.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.RequestMethod;
/**
* 演示接收请求
*/
@Controller
@RequestMapping("/test")
public class Demo1Controller {
/**
* 以前servlet接收请求,需要创建一个类,继承HttpServlet,才能接收一个请求
* 即再有一个请求,就需要再创建一个类,继承HttpServlet,才能接收
* ...麻烦!!!
*
* 现在springmvc接收请求,只需要在一个类中,添加一个方法,即可接收一个请求
*/
/**
* 方法上@RequestMapping 匹配前端发出的请求路径,
* 默认可以get和post请求
* 也可以在类上添加@RequestMapping,
* 类上的@RequestMapping 是前缀和方法上的@RequestMapping 拼接的
*
* /test/t1
*/
@RequestMapping("/t1")
public String test1(){
System.out.println("test1方法执行了");
return "/ok.html";
}
/**
* 注解@RequestMapping内部是可以指定接收哪个请求
* @RequestMapping(value="/t2",method = RequestMethod.GET)
* 这样,就只能接收get请求,不能接收post请求
* 如果发送post请求,就会报错405
*/
@RequestMapping(value="/t2",method = RequestMethod.GET)
public String test2(){
System.out.println("test2方法执行了");
return "/ok.html";
}
/**
* @RequestMapping(value="/t2",method = RequestMethod.GET)
* 一般会简化注解使用@GetMapping("/t2")
*/
@GetMapping("/t3")
public String test3(){
System.out.println("test3方法执行了");
return "/ok.html";
}
@PostMapping("/t4")
public String test4(){
System.out.println("test4方法执行了");
return "/ok.html";
}
}
思考如何发出请求 测试?
- 浏览器地址栏,手输入,get 方式
- a 标签,默认发出的 get 请求
- form 表单,指定 method 发出 get 或 post 请求
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>hello word!!!</h1>
<!-- href是请求后端地址-->
a标签发送get请求: <a href="/test/t1">/test1</a> <br>
表单发送请求1,post方式,路径/test/t1
<form action="/test/t1" method="post">
<input type="submit" value="发送post请求">
</form>
表单发送请求2,get方式,路径/test/t1
<form action="/test/t1" method="get">
<input type="submit" value="发送get请求">
</form>
<hr>
表单发送请求3,post方式,路径/test/t2
<form action="/test/t2" method="post">
<input type="submit" value="发送post请求">
</form>
表单发送请求4,get方式,路径/test/t2
<form action="/test/t2" method="get">
<input type="submit" value="发送get请求">
</form>
<hr>
表单发送请求5,get方式,路径/test/t3
<form action="/test/t3" method="get">
<input type="submit" value="发送get请求">
</form>
表单发送请求6,post方式,路径/test/t4
<form action="/test/t4" method="post">
<input type="submit" value="发送post请求">
</form>
</body>
</html>
5.3 参数绑定 (接收请求数据) 【重点】
所谓参数绑定,就是前端发请求中的数据,可以直接在 Controller 的方法参数中接收。即前端请求数据和后端方法参数绑定.
5.3.1 简单类型参数绑定 [重点]
简单类型指,常用的几种类型:基本类型 + String+Date前端页面
html
<h3>测试-接收简单类型数据</h3>
<!--a标签发送get请求,拼接数据,记住拼接方法-->
<!--url?key=value&key=value-->
<a href="/basic?id=1&username=张三&score=10.0&birthday=2020-01-01&sex=1">请求携带数据-基本类型</a>
<hr>
<form action="/basic" method="get">
id<input type="text" name="id"><br>
username<input type="text" name="username"><br>
score<input type="text" name="score"><br>
birthday<input type="date" name="birthday"><br>
sex<input type="radio" name="sex" value="1">男
<input type="radio" name="sex" value="2">女<br>
<input type="submit" value="基本类型">
</form>
api 工具测试

postman

后端接收
java
@Controller
public class Demo2Controller {
/**
* 演示1: 接收简单类型数据
* 接收数据,只需要在方法的参数列表设计参数即可,数据类型对应
* 接收的前端的name值参数名和方法的参数名保持一致即可
* -----
* 小问题: springmvc框架日期,默认只能接收yyyy/MM/dd格式的日期
* 但是如果前端传递的是yyyy-MM-dd格式的日期,则会报错
* 解决: 加注解@DateTimeFormat(pattern = "yyyy-MM-dd"),指定解析模版
*/
@GetMapping("/basic")
public String basic(int id,
String username,
double score,
@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday,
int sex){
System.out.println(id);
System.out.println(username);
System.out.println(score);
System.out.println(birthday);
System.out.println(sex);
return "/ok.html";
}
}
5.3.2 使用对象接收表单数据 [非常重要]
场景:注册 / 添加 / 更新
实体类,要求实体类的属性和前端 name/key 一致
java
@Data // 提供getter/setter/toString/equals/hashCode等方法
public class User {
private int id;
private String username;
private String password;
private double score;
@DateTimeFormat(pattern = "yyyy-MM-dd")// 解析日期格式
private Date birthday;
private int sex;
}
@Data 注解需要导入下方依赖
xml
XML
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
前端
html
<h2>对象数据绑定</h2>
<form action="/obj" method="get">
id<input type="text" name="id"><br>
username<input type="text" name="username"><br>
password<input type="text" name="password"><br>
score<input type="text" name="score"><br>
birthday<input type="date" name="birthday"><br>
sex<input type="radio" name="sex" value="1">男
<input type="radio" name="sex" value="2">女<br>
<input type="submit" value="对象类型">
</form>
api 工具测试.

后端
java
/**
* 演示2: 接收对象类型数据
* 场景: 前端发送的数据量大,可以封装到对象中
* 前提: 前端发送的数据,必须和对象的属性名保持一致
*/
@GetMapping("/obj") // 整个项目中请求路径不能重复
public String obj(User user){
System.out.println(user);
return "/ok.html";
}
5.3.2 使用对象接收 JSON 格式数据 [重点]
一般使用 post 请求,向后端发送 json
前端使用 axios 发送 json
html
<!--演示使用axios 发送post请求,且是json数据到后端-->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<button id="btn">点击-aixos发送</button>
<script>
var btn = document.getElementById("btn");
btn.onclick=function (){
console.log("点击事件生效")
// axios发送post请求
axios.post('/obj2', {
id: 2,
username: '奥特曼',
password: '123123',
score:59.9,
birthday:"2000-01-01",
sex:1
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
}
</script>
接口工具发送 json 数据

后端接收 json
java
/**
* 接收json格式数据
* 必须使用对象,且对象的属性名必须和json数据的key保持一致
* 且需要使用@RequestBody注解,将json数据转换为对象
*/
@PostMapping("/obj2")
public String obj2(@RequestBody User user){
System.out.println(user);
// 使用post请求后,默认跳转页面方式是转发,需要重定向跳转,redirect:/ok.html
return "redirect:/ok.html";
// 这样可以解决问题,但是一般不会这样用
// 因为后续是要返回json数据,而不是页面,此处就不用纠结对错...
}
这里主要强调:前端如果是以 json 的形式将数据发到后端,那么后端使用对象接收前需要使用 注解 @RequestBody
5.3.3 接收数组类型的数据
场景:批量删除需要同时接收多个 id, (前端是复选框的)
delete from tb_user where id in (1,2,3,4)
前端
html
<h2>数组绑定</h2>
<form action="/array" method="get">
<input type="checkbox" name="ids" value="1">1
<input type="checkbox" name="ids" value="2">2
<input type="checkbox" name="ids" value="3">3
<input type="checkbox" name="ids" value="4">4
<input type="submit" value="数组类型">
</form>

后端
java
/**
* 自动绑定: 要求前端的请求中的参数要和方法参数名(数组名)一致
*/
@GetMapping("/array")
public String array(int[] ids){
System.out.println("ids = " + Arrays.toString(ids));
return "ok.html";
}
5.3.4 List 集合
List 集合使用场景与数组是一样的前端
html
<h2>List绑定</h2>
<form action="/list" method="get">
<input type="checkbox" name="skill" value="Java">Java
<input type="checkbox" name="skill" value="HTML">HTML
<input type="checkbox" name="skill" value="Linux">Linux
<input type="submit" value="List类型">
</form>
SpringMVC 默认是不支持直接封装 List 的,解决方案:
- 参数前加注解@RequestParam
java
@GetMapping("/list")
public String list(@RequestParam List<String> skill){
System.out.println("skill = " + skill);
return "ok.html";
}
5.3.5 前端发送数据,后端使用 Map 接收数据
Map 是键值对,键和值一一映射.
跟 Java 对象很类似,属性和属性值一一对应.
所以什么时候需要 / 可以使用 Map 类型来接收参数呢?
- 凡是可以用对象接收的都可以使用 Map
SpringMVC 默认不支持直接将参数封装进 Map, 需要使用 @RequestParam
前端
html
<h2>搜索</h2>
<form action="/map" method="get">
部门<input type="text" name="dept"> <br>
手机号 <input type="text" name="phone"> <br>
日期 <input type="date" name="date"> <br>
<input type="submit" value="搜索">
</form>
后台
java
/**
* 演示使用map接收数据
* 需要使用@RequestParam注解,将map接收数据
*/
@GetMapping("/map")
public String search(@RequestParam HashMap<String,Object> map){
System.out.println(map);
return "/ok.html";
}
5.3.6 路径参数 @PathVariable
参考这个路径
这个路径中 weixin_39641494 是用户编号,131625212 是文章 id /myinfo/9527
@GetMapping ("/{userid}/article/details/{aid}
html
https://www.baidu.com/s?tn=68018901_3_dg&ie=UTF-8&wd=RESTful
https://blog.csdn.net/article?uid=weixin_39641494&aid=131625212
RESTful风格如下
https://blog.csdn.net/weixin_39641494/article/details/131625212
前端
html
<h2>演示: 接收路径中的数据</h2>
<a href="/blog/weixin_39641494/article/details/131625212">
查看文章详情
</a>
后端
java
/**
* 接收路径参数
* 需要使用@PathVariable注解,将路径参数接收封装到参数列表的变量
* 需要{}中变量名和方法的参数名保持一致
*/
@GetMapping("/csdn/{uid}/article/details/{aid}")
public String csdn(@PathVariable String uid, @PathVariable String aid){
System.out.println("用户id = " + uid);
System.out.println("文章id = " + aid);
return "/ok.html";
}
5.4 页面跳转 [了解] 不用!!!
controller 层类中方法的返回值,定义成 String, 然后 return 中写的就是跳转的页面路径,这个页面必须放在 resources/static/ 下

这样的跳转方式是请求转发,专业写法是

还可以改成重定向

区别是什么?
- 直观的效果:
- 请求转发后,浏览器上路径是不会变的
- 重定向后,浏览器上路径会变的
- 本质原因
- 请求转发是服务器内部动作
- 重定向是浏览器动作
说明:请求转发和重定向技术,在后续前后端分离的开发模式中,已经用不到
因为,前后端分离就是指,后端项目中只有后端 java 代码,不存在页面。即也不需要我们跳转页面...
5.5 会话技术 session
会话:前后端交互会话管理:管理前后端交互过程的数据会话管理技术: session, 基于 cookie
面试题:
- session 原理,基于 cookie.
- 第一次请求时,会在后端创建 session 对象,然后随着响应,将 sessionId 存储到 cookie 响应到浏览器
- 后续每次再请求会带上 sessionId, 找到之前这个 session 对象
- session 和 cookie 的区别
- cookie 是浏览器技术,将数据存储在浏览器
- session 是服务端技术,将数据存储在服务器
想要使用,只需要在 Controller 层方法的参数列表中直接定义该变量即可使用
java
package com.qf.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
/**
* 演示使用 session
*/
@Controller
@RequestMapping("/session")
public class Demo4Controller {
/**
* 想要使用 session 必须要在方法的参数中添加 HttpSession 类型的参数
* @param session
* @return
*/
@GetMapping("/t1")
public String test1(HttpSession session){
System.out.println("---- t1 -----");
System.out.println("sessionId = " + session.getId());
// session是存储数据,类似于 map
// 经验: session登录状态
session.setAttribute("name","张三");
return "/ok.html";
}
@GetMapping("/t2")
public String test2(HttpSession session){
System.out.println("---- t2 -----");
System.out.println("sessionId = " + session.getId());
String user = (String) session.getAttribute("name");
System.out.println("从session取出的 = " + user);
return "/ok.html";
}
/**
* 销毁 session
* ps: 说明,session如果不主动销毁,默认保存会话结束. 常见的就是浏览器关闭会话结束.
*/
@GetMapping("/t3")
public String test3(HttpSession session){
System.out.println("---- t3 -----");
// 销毁session
session.invalidate();
return "/ok.html";
}
}
主要记住用法
- 对象 HttpSession
- 存储方法 session.setAttribute ("key",value)
- 取出数据 session.getAttribute ("key")
- 销毁对象 session.invalidate ()场景:一般来说,HttpSession 用的多,常用于存储用户的登录信息
5.6 响应:返回 json 数据【重点】
后续工作项目,都是前后端分离开发,前后端使用 JSON 数据交互
- 前端发送 json, 使用 axios 技术 (类似于 ajax),vue 中就使用 axios 发送请求
- 后端接收 json, 然后响应给前端 json

前端发送 json 等 vue 时候再演示现在演示响应 JSON 数据 , 非常简单,方法加上 @ResponseBody 即可,就会将任何解析为 json 返回
java
package com.qf.controller;
import com.qf.model.User;
import com.qf.util.R;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.*;
/**
* 演示响应json
* 将后台的数据以json形式返回给前端
*/
@Controller
public class Demo4Controller {
/**
* 控制层代码要想返回/响应给前端json数据,只需要
* 1) 设计方法的返回值为对应类型(String,对象,Map,List)
* 2) 最后在方法上加上@ResponseBody
* ---------------------------------------------
* 真正写项目时,前后端交互的json格式要固定,一般
* {
* code:200,
* msg:"xxx",
* data:{}
* }
* --- 那么就会在java项目中定义该类型的类R
* --- 前端拿到json后,会获取其中数据
* if(json.code == 200) {
* json.data
* } else {
* alert(json.msg)
* }
*/
@GetMapping("/json")
@ResponseBody // 该注解就会把响应的数据当json返回给前端
public String testJson(){
/**
* json格式
* {k:v,k:v}
* {"id":1,"username":"zs"}
*/
String jsonStr = "{\"id\":1,\"username\":\"zs\"}";
return jsonStr;
}
@GetMapping("/json2")
@ResponseBody
public User testJson2(){
// 假设这是从数据库查出的数据,并且封装成对象
User user = new User( );
user.setId(2);
user.setScore(1.1);
user.setPassword("123456");
user.setUsername("老王");
user.setBirthday(new Date( ));
/**
* {
* id:2,
* score:1.1,
* password:"123456",
* username:"老王",
* birthday:
* }
*/
return user;
}
@GetMapping("/json3")
@ResponseBody
public Map<String, Object> testJson3(){
HashMap<String, Object> map = new HashMap<>( );
map.put("id",1);
map.put("username","老李");
map.put("password","123456");
map.put("score",2.2);
map.put("birthday",new Date( ));
return map;
}
@GetMapping("/json4")
@ResponseBody
public List<User> testJson4(){
ArrayList<User> list = new ArrayList<>( );
list.add(new User());
list.add(new User());
list.add(new User());
/**
* [
* {},
* {},
* {}
* ]
*/
return list;
}
}
以上这些响应的 json, 但是格式不统一真正开发时,是团队协作
- 200 成功
- 400 失败
- 500 连接超时
- 300 未登录
- msg是响应的提示信息
- data是后端返回给前端的数据
json
java
{
code:200,
msg:"成功|失败",
data:{}
}
前后端交互,定义的类,用于统一返回封装数据返回 JSON
java
package com.qf.utils;
import lombok.Data;
/**
* 这个R类,是用来封装响应数据的,一般用于返回json数据
* 这个类名R,是response的缩写,表示响应数据
* 也可以Result
* 若依项目AjaxResult
* ---------------
* 这个对象一般包含以下属性:
* code: 状态码,一般200表示成功,400表示失败
* msg: 提示信息
* data: 响应数据
*/
@Data
public class R {
/**
* 一般200表示成功,400表示失败
* 也可以自定义一些
* 600,请求超时
* 700,权限不足
* 800,数据不存在
* .....
*/
private Integer code;
private String msg;
private Object data;
public static R ok(){
R r = new R();
r.setCode(200);
r.setMsg("成功");
return r;
}
public static R ok(Integer code,String msg,Object data){
R r = new R();
r.setCode(code);
r.setMsg(msg);
r.setData(data);
return r;
}
public static R ok(Object data){
R r = new R();
r.setCode(200);
r.setMsg("成功");
r.setData(data);
return r;
}
public static R fail(String msg){
R r = new R();
r.setCode(400);
r.setMsg(msg);
return r;
}
public static R fail(){
R r = new R();
r.setCode(400);
r.setMsg("失败");
return r;
}
}
java
java
/**
* 最终版
*/
@GetMapping("/t4")
@ResponseBody
public R test4() {
// R r = new R();
// r.setCode(200);
// r.setMsg("成功");
// r.setData("hello");
// return R.ok(new User());
return R.ok(2000,"查询用户成功",new User());
// return R.fail();
}
补充:如果该类中所有方法都返回 json, 那就需要在每个方法上都要加 @ResponseBody 注解,有点麻烦,
此时可以直接将 @Controller 换成 @RestController, 以后方法默认返回 json, 就不需要加 @ResponseBody
java
// @Controller
// @ResponseBody // 整个类中的所有方法,都返回json
// 以上两个注解,可以简写成一个
@RestController
public class Demo5Controller {
}
5.7 拦截器
拦截器拦的请求,主要拦截请求判断一些数据,决定要不要放行
保安...使用步骤
- 编写自定义拦截器类
- 实现接口 HandlerInterceptor
- 重写拦截方法 preHandler ()
- 配置拦截器
- 自己定义一个配置类,实现 WebMvcConfigurer 接口,重写方法 addInterceptors
- 另外,该配置类上加了一个非常重要的注解
@Configuration创建拦截器包 interceptor, 自定义拦截器类
java
package com.qf.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义拦截器
* Handler单词是处理器,其实就是Controller中的方法
*/
@Component//创建对象
public class MyInterceptor implements HandlerInterceptor {
/**
* 重点关注这个方法,是前置拦截器,返回true表示放行,返回false表示不放行
* @param request 请求对象,可以获取请求的信息(数据,路径,方法,请求头,session...)
* @param response 响应对象
* @param handler 就是目标Controller的方法对象
* @return 返回true表示放行,返回false表示不放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle方法被调用");
return true;
}
}
创建配置文件的包 config, 其中创建拦截器配置类
java
package com.qf.config;
import com.qf.interceptor.MyInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 自定义拦截器配置类
*/
@Configuration // 这个注解表示当前类是一个配置类,会被Spring扫描到
public class MyInterceptorConfig implements WebMvcConfigurer {
@Autowired // 注入
private HandlerInterceptor myInterceptor;
// 向容器注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(myInterceptor).addPathPatterns("/**");
// registry.addInterceptor(myInterceptor).addPathPatterns("/json/**");
// 拦截所有请求,除了/session/**
registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/session/**");
}
}
发请求测试即可
实战演示:身份信息认证设置身份认证拦截器,对所有请求拦截,判断有没有登录,登录过就放行,没有登录就返回错误提示
设置登录。退出接口,用于存储 / 销毁 session
java
package com.qf.controller;
import com.qf.utils.R;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/user")
public class UserController {
//登录
@RequestMapping("/login")
public R login(HttpSession session){
// 登录成功,将用户信息存储到session中
session.setAttribute("user","lisi");
return R.ok();
}
// 退出登录
@RequestMapping("/logout")
public R logout(HttpSession session){
// 退出登录,将session中的用户信息删除
session.invalidate();
return R.ok();
}
}
设置拦截器,判断有无登录信息,决定要不要
java
package com.qf.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.qf.utils.R;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.PrintWriter;
@Component
public class AuthInterceptor implements HandlerInterceptor {
/**
* 拦截请求,判断有无登录,如果没有登录,则返回json,R,提示没有登录
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
if (user == null){ // 没有登录信息,不放行,还有返回错误信息
// 设置响应格式为json
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
// 将R对象转换为json字符串,使用fastjson工具,需要导入依赖
R r = new R();
r.setCode(500);
r.setMsg("没有登录,禁止访问");
String jsonString = JSONObject.toJSONString(r);
// 这个将字符串写出到浏览器
writer.write(jsonString);
return false;
}
return true; // 登录成功,放行
}
}
上面需要 fastjson 工具类,所有需要导包
xml
XML
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version> <!-- 使用最新版本 -->
</dependency>
拦截器配置类
java
package com.qf.config;
import com.qf.interceptor.AuthInterceptor;
import com.qf.interceptor.MyInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
*自定义拦截器配置类
*/
@Configuration // 这个注解表示当前类是一个配置类,会被Spring扫描到
public class MyInterceptorConfig implements WebMvcConfigurer {
@Autowired // 注入
private MyInterceptor myInterceptor;
@Autowired
private AuthInterceptor authInterceptor;
// 向容器注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(myInterceptor).addPathPatterns("/**");
// registry.addInterceptor(myInterceptor).addPathPatterns("/json/**");
// 拦截所有请求,除了/session/**
registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/session/**");
registry.addInterceptor(authInterceptor).addPathPatterns("/**").excludePathPatterns("/user/**");
}
}
测试即可
- 先不登录,访问任意路径查看效果

- 登录后,再访问

- 退出登录,销毁 session, 再测试
5.8 全局异常处理
SpringBoot 中有一个 @ControllerAdvice 的注解,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用 @ExceptionHandler 注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。
java
package com.qf.utils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice // 异常处理类,这个注解可以捕获Controller层抛出的异常
public class MyExceptionHandler {
/**
* 处理Controller层抛出的异常
* 方法的参数,就是捕获到的异常对象
*/
// @ExceptionHandler(RuntimeException.class) //指定捕获异常
@ExceptionHandler // 捕获任意异常
// @ResponseBody
public R handleException(Exception e){
R r = new R();
r.setCode(500);
r.setMsg("系统错误,请联系管理员,"+e.getMessage());
// 捕获到异常,可以做很多事情
// 可以返回json提示
// 也可以跳转一个页面
// 可以记录日志
// 可以发送邮件
// 可以发送短信
// 可以记录到数据库
return r;
}
}
测试发请求,在 Controller 中故意写错代码,测试即可
补充:接口工具使用
目前情况,写好了 controller 层接口,写 html 页面,写 a 标签或者表单测试,--> 这样不专业!!!
因为项目是前后端分离,没有前端前端项目,没法写页面测试都要使用接口测试工具!
接口:不是 interface, 是前后端对接的接口,即 controller-service-dao 一套
idea插件
安装

一般需要重启一次idea
测试发请求

postman工具
-
安装
-
注册
-
汉化
参考文档: https://apifox.com/apiskills/postman-chinese/
1将汉化包解压到 Postman 安装目录到 resources 下
2Windows系统,进入Postman安装地址/版本/resources目录。
3找安装地址的方式:
桌面找到 Postman应用程序 -> 右键 -> 打开文件所在位置 -> 进入app-..*/resources。
默认安装地址:C:/Users/用户名/AppData/Local/Postman。
4 解压汉化版到/resources下
基本使用


高端使用1
- 设计集合,存储每次的请求

测试完,点击保存到这个集合

高端使用2:
- 设计环境变量
即抽取一些变量,重复使用,比如说抽取 每次请求前的服务器地址http://localhost:8080 , 重复使用
- 创建变量


- 使用变量

六、Mybatis
6.1 之前的 JDBC 的使用缺点?
- 大量的代码重复
- 加载驱动,获得连接,获得执行语句,执行 sql, 关闭连接
- 手动加载驱动,创建连接 (Connection), 关连接
- 封装数据麻烦 (ORM)
- 效率不高 (没有缓存)
6.2 Mybatis 的介绍

MyBatis 本是 apache 的一个开源项目
iBatis,2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为**MyBatis** 。2013 年 11 月迁移到Github。iBATIS 一词来源于 "internet" 和 "abatis" 的组合,是一个基于 Java 的持久层 (Dao)框架。用于操作数据库。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL 、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs (Plain Ordinary Java Object, 普通的 Java 对象) 映射成数据库中的记录。且有缓存机制,可以提高查询效率。
Mybatis 是一个**
半ORM框架,可以消除 JDBC 的代码和步骤,让开发者只关注SQL**本身。
- ORM 是对象关系映射,是指数据库表和 java 实体类一一对应.
- 半 ORM 框架,还是需要写 SQL, 由框架帮你完成映射
- 完全 ORM 框架,连 SQL 都不需要写,只需要遵循 ORM 的要求,就会自动生成 SQL 完成映射 (Hibernate,JPA,MybatisPlus 等)
6.3 xml 方式整合 Mybatis [重点]
xml 方式在编写复杂 SQL 时,更适合
6.3.1 环境
mybatis 和 druid 的 springboot 环境下的依赖 (别忘了 web 依赖)
xml
XML
<!-- 小辣椒 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- druid 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
准备数据库
sql
sql
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',
`username` varchar(10) DEFAULT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
`create_time` date DEFAULT NULL COMMENT '注册时间',
`money` double(10,2) DEFAULT NULL COMMENT '账户余额',
`sex` int(1) DEFAULT NULL COMMENT '性别 1男2女',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8
实体类
java
@Data
public class User {
private int id;
private String username;
private String password;
private String phone;
private Date createTime;
private double money;
private int sex;
}
6.3.2 编写接口和映射文件
接口就是我们之前的 Dao 层接口,Mybatis 习惯叫做 Mapper, 所以先创建 mapper 包,然后再在其中创建接口文件
java
public interface UserMapper {
User findUserById(int id);
}
以前是写接口的实现类,现在 mybatis 的接口实现功能由 xml 文件来实现了在 resources / 下创建 m
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace名称空间,根据接口的全限定名,来关联接口和xml文件-->
<mapper namespace="com.qf.day34mybatis.mapper.UserMapper">
<!--前面接口中定义的方法,这里要写标签,然后写sql-->
<!--根据需求,写不同的标签,
查询 <select>
更新 <update>
删除 <delete>
插入 <insert>
-->
<!--根据id查询用户-->
<!--
id: 是前面接口的方法名
resultType: 结果类型,即语句的返回值类型,查询的结果会封装到这个类型的对象中
-->
<!--resultType,是要封装的类型的路径,比较长,如果配置type-aliases-package,可以省略包名-->
<select id="getUserById" resultType="User">
<!--原来?的地方,现在mybatis写的#{},内部写变量名-->
select * from tb_user where id = #{id}
</select>
</mapper>
6.3.3 yml 文件
SpringBoot 项目,默认配置文件类型是 properties 改成后缀为 yml 的文件
yaml
XML
server:
port: 8080
# 连接数据库的信息
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb5?serverTimezone=UTC&useSSL=false&characterEncoding=utf8&useUnicode=true
username: root
password: root
# 数据库连接池
type: com.alibaba.druid.pool.DruidDataSource
# mybatis配置
mybatis:
# 扫描映射文件
mapper-locations: classpath:mapper/*.xml
# 配置别名扫描的包
type-aliases-package: com.example.entity
configuration:
# 开启驼峰映射配置
map-underscore-to-camel-case: true
# 打印执行过程的sql信息
logging:
level:
com.com.example.dao: DEBUG
6.3.4 扫描 mapper
主类扫描 mapper
java
@SpringBootApplication
@MapperScan("com.qf.mapper") // 扫描mapper接口,创建代理对象
public class TestSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(TestSpringbootApplication.class, args);
}
}
6.3.5 测试
创建 controller,service , 按以前三层架构,依次调用即可// Controller
java
package com.qf.day34mybatis.controller;
import com.qf.day34mybatis.entity.User;
import com.qf.day34mybatis.service.UserService;
import com.qf.day34mybatis.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 根据id查询用户
@GetMapping("/{id}")
public R getUserById(@PathVariable Integer id) {
User user = userService.getUserById(id);
if (user == null) {
return R.fail("用户不存在");
}
return R.ok(user);
}
}
// 业务层 (接口 + 实现类)
java
public interface UserService {
User getUserById(Integer id);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserById(Integer id) {
return userMapper.getUserById(id);
}
}
启动项目,发出请求localhost:8080/user/1即可
完整项目结构

6.3.6 接口工具测试
idea 下载 Cool Request 插件

6.4 CRUD [核心]
6.4.1 查询
6.4.1.1 单条件查询
详情看入门演示【特别注意:ORM 时字段名要和实体类属性一致,否则封装失败】
对象关系映射(Object Relational Mapping)
6.4.1.2 查询全部
设计查询接口方法
java
public interface UserMapper {
User findUserById(int id);
List<User> findAll();
}
映射文件xml
XML
<!-- 一个标签,就是一个SQL执行的语句 -->
<!-- 【注意】虽然查询返回集合,但是返回类型此处还要写集合中存储的类型 -->
<!-- 【或者这样理解】虽然返回集合,此处定义的是查询返回要封装的实体类类型 -->
<select id="findAll" resultType="User">
select * from tb_user
</select>
一样的设计 Controller,Service 调用即可,启动项目测试
java
// Server
public interface UserServer {
List<User> findAll();
}
// ServerImpl
@Service
public class UserServerImpl implements UserServer {
@Autowired
private UserDao userDao;
public List<User> findAll(){
return (List<User>) userDao.findAll();
}
}
// Controller
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserServer userServer;
@RequestMapping("/findAll")
public List<User> findAll(){
return userServer.findAll();
}
}
6.4.1.3 多参数查询
需求: 通过用户名和密码查询接口方法
java
public interface UserMapper {
User findUserByLogin(String username,String password);
}
映射文件xml
XML
<select id="findUserByLogin" resultType="com.qf.model.User">
<!-- 默认是不支持传多个参数,传入多个参数时,需要如下操作 -->
<!--
方案1: #{}内按顺序写param1,param2,....或者agr0,arg1
但是此种方式不建议
-->
select * from tb_user
where username = #{param1} and password = #{param2}
</select>
(解决方案 2)(推荐) 接口方法(参数加注解)
java
public interface UserMapper {
User findUserByLogin(
@Param("username") String username,
@Param("password") String password);
}
映射文件xml
XML
<select id="findUserByLogin" resultType="User">
<!-- 默认是不支持传多个参数,传入多个参数时,需要如下操作(2选1) -->
<!--
方案2: 1)在接口方法参数前添加注解@Param并设置注解参数
2)在#{}内写注解中的值
-->
select * from tb_user
where username = #{username} and password = #{password}
</select>
6.4.1.4 Map 参数查询
需求:查询时,就要传递分页数据,又要传递模糊查询关键词,此时就可以使用 Map 来封装参数 (即请求中参数有点多....)接口方法
java
public interface UserMapper {
// Mapper接口传递map参数
List<User> search(HashMap<String, String> map);
}
映射文件xml
XML
<!--接口虽然返回的List,但是此处resultType写的是User-->
<!--此处是模拟搜索,先固定查询条件(查询用户名和密码都包含指定字符串的用户)-->
<select id="search" resultType="User">
select * from tb_user
where username like '%${username}%' and password like '%${password}%'
<!--传递是map,${}或者#{} 写的是map的key-->
</select>
强调:前面使用 map 传递参数,${} #{} 内部需要根据 map 的 key 来取值:::
#{}vs${}的区别:
#{}:预编译参数,会自动转义 ,能防止 SQL 注入,推荐使用 (需配合CONCAT函数拼接通配符)。${}:直接字符串替换 ,有 SQL 注入风险,仅适用于确定无注入风险的场景(比如固定参数)。
java
// 搜索,模拟传递Map参数
@GetMapping("/search")
public R search(@RequestParam HashMap<String,String> map) {
List<User> list = userService.search(map);
return R.ok(list);
}
6.4.1.5 对象参数
比如登录,前端传递 (用户名 + 密码)(手机号 + 验证码) 等等
- 可以会传递多个参数,上面讲过可以在 Mapper 接口的方法中设计注解来解决或者使用 Map 封装数据
- 但是,常见的方案是用对象接收前端数据,向业务层 / 持久层传递对象!接口方法
java
public interface UserMapper {
User loginV2(User user);
}
映射文件
XML
<select id="loginV2" resultType="com.qf.day34mybatis.entity.User">
select * from tb_user
where username = #{username} and password = #{password}
<!--#{}是对象的属性名-->
</select>
controller
java
/**登录,使用对象接收
* 前端发送json格式数据
*/
@PostMapping("/loginv2")
public R loginV2(@RequestBody User user){
User user2 = userService.loginV2(user);
if (user2 == null) {
return R.fail("登录失败");
}
return R.ok(user2);
}
6.4.2 增加
需求:如下场景

前端使用 JSON 发送,后端使用对象接收还得使用 @RequestBody 配合
controller
java
/**
* 添加用户
* 添加/更新/登录 发post请求
* 前端的数据格式是json
*/
@PostMapping("/add")
public R addUser(@RequestBody User user) {
System.out.println("UserController.addUser收到数据,user"+user);
// 调用业务层
// 增删改,可以不要返回值
// 也可以要返回值---> int / boolean
userService.addUser(user);
return R.ok();
}
service
java
public interface UserService {
void addUser(User user);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public void addUser(User user) {
userMapper.addUser(user);
}
}
Mapper 接口方法
java
public interface UserMapper {
void addUser(User user);
}
Mapper 映射文件
xml
XML
<!--插入,增删改标签内不需要定义返回值类型,但是想返回只需要接口的方法定义返回值类int boolean-->
<insert id="addUser">
insert into tb_user
(username , password,phone,create_time,money,sex)
values (#{username},#{password},#{phone},#{createTime},#{money},#{sex})
</insert>
测试

6.4.3 修改
6.4.3 修改
需求场景:如下暂定:需求就是只要是传递过来的数据都会修改 (全量更新)
java
// 更新
@PostMapping("/edit")
public R editUser(@RequestBody User user){
userService.updateById(user);
return R.ok();
}
service....接口方法
java
public interface UserMapper {
void updateById(User user); // 修改方法的参数是对象
}
映射文件
xml
XML
<update id="updateById">
<!-- 对象参数,#{}内属性名 -->
update tb_user set username=#{username},password=#{password},
phone= #{phone},create_time=#{createTime},money=#{money},sex=#{sex}
where id = #{id}
</update>
测试

6.4.4 删除 (自学)
需求:场景如下

在前端,设置删除按钮,点击发出请求,请求中携带这条数据的 id
接口方法
java
int deleteById (int id);
映射文件 xml
XML
<delete id="deleteById">
delete from tb_user where id = #{id}
</delete>
6.5 动态 SQL【重要!!!】
帮助我们拼接 SQL
MyBatis 动态 SQL 是一个模板适配所有查询场景 ,替代上面 4 条原生 SQL,核心靠
<where>和<if>标签实现「条件动态拼接」
XML
// 所有查询场景
select * from tb_user where username = ? and sex = ?
select * from tb_user
select * from tb_user where username= ?
select * from tb_user where sex = ?
// 该模板会根据传入的参数(用户名 / 性别)自动拼接条件,适配所有查询场景
<select id="findUserByCondition" resultType="User">
select * from tb_user
<where>
<if test="username != null and username != ''">
and username = #{username} <!-- 建议用#{参数名},比?更直观,MyBatis自动转? -->
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
</where>
</select>
常见的动态 SQL 语法
- sql 片段
- where+if
- set+if
- foreach
- trim
- choose-when-otherwise
6.5.1 sql 片段
抽取重复 sql 语句
使用<sql id="">标签定义重复的 sql 语句
在下方的 sql 语句中使用 **<include refid="">**将其引入

6.5.2 where+if [重点]
场景:搜索

测试

Controller
java
// 搜索2,模拟传递Map参数
@GetMapping("/search2")
public R searchV2(@RequestParam HashMap<String,String> map) {
List<User> list = userService.searchV2(map);
return R.ok(list);
}
service ...
java
public interface UserService {
List<User> searchV2(HashMap<String, String> map);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public List<User> searchV2(HashMap<String, String> map) {
return userMapper.searchV2(map);
}
}
mapper
java
public interface UserMapper {
List<User> searchV2(HashMap<String, String> map);
}
模拟的效果是
- 根据用户名,密码,手机号 模糊搜索
- 根据注册时间 范围搜索
xml
XML
<!--
sql片段,id唯一名字
-->
<sql id="selectUserField">
select
id,username , password,phone,create_time,money,sex
from tb_user
</sql>
<!--模糊搜索,演示动态sql where + if-->
<select id="searchV2" resultType="User">
<include refid="selectUserField"/>
<!--where标签,如果下方有条件,会自动产出where关键词,如果没有任何条件,where就消失-->
<!--where会自动去掉多于的and-->
<where>
<!--取出map的键所对应值,判断有无数据-->
<if test="username != null and username != ''">
and username like '%${username}%'
</if>
<if test="phone != null and phone != ''">
and phone like '%${phone}%'
</if>
<if test="password != null and password != ''">
and password like '%${password}%'
</if>
<if test="beginTime != null">
<!-- 大于>运算不能使用,需要写成 >-->
and create_time > #{beginTime}
</if>
<if test="endTime != null">
<!--小于< 运算,需要写成 <-->
and create_time < #{endTime}
</if>
</where>
</select>
特别注意: xml 文件中,
大于 > 运算不能使用,需要写成 >
小于 < 运算,需要写成**<**
6.5.3 set+if
场景:动态更新 (部分更新)
之前更新是全量更新,只要没有传递数据,即认为要置为 null
其实我们认为,不传递数据是不修改,保持原样!
controller
java
/**
* 更新2 动态更新,即没有传递的数据,即不更新保持原样
*/
@PostMapping("/edit2")
public R editUser2(@RequestBody User user){
userService.updateById2(user);
return R.ok();
}
动态更新 mapper xml
| 传入参数情况 | 最终拼接的 SQL(简化版) | 效果说明 |
|---|---|---|
| 仅传 id=1、username="zhangsan" | update tb_user set username = ? where id = ? |
只更新用户名,其他字段不变 |
| 传 id=1、password="123456"、sex=1 | update tb_user set password = ?, sex = ? where id = ? |
只更新密码和性别 |
| 传 id=1、所有字段都有值 | update tb_user set username=?, password=?, phone=?, create_time=?, sex=?, money=? where id=? |
所有字段都更新,无多余逗号 |
| 仅传 id=1、无其他字段 | 拼接为 update tb_user where id = ?(语法错误) |
XML
<!--动态更新-->
<update id="updateById2">
update tb_user
<!--set标签, 1)会产生set关键词, 2) 取出多于最后一个,逗号-->
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="password != null and password != ''">
password = #{password},
</if>
<if test="phone != null and phone != ''">
phone = #{phone},
</if>
<!--createTime是Date类型,不能和 字符串'' 判断-->
<if test="createTime != null">
create_time = #{createTime},
</if>
<!--sex是Integer类型,不能和 字符串'' 判断-->
<if test="sex != null">
sex = #{sex},
</if>
<if test="money != null">
money = #{money},
</if>
</set>
where id = #{id}
</update>
6.5.4 foreach
场景
- 批量删除
- delete from tb_user where id in (1,2,3);
- 批量插入 (自学)

测试

Controller
java
// 批量删除
@GetMapping("/batch/del")
public R batchDel(@RequestParam List<Integer> ids) {
userService.delBatch(ids);
return R.ok();
}
service.....
批量删除
--> mapper xml
XML
<!--批量删除-->
<delete id="delBatch">
delete from tb_user where id in
<!--collection是要遍历的集合,此处必须叫list
item 就是遍历得到的数据 -->
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
6.5.6 动态插入
MyBatis 实现
tb_user表的动态插入功能,核心价值是:根据传入
User对象的非空字段,动态拼接插入的「字段列表」和「值列表」,只插入有值的字段,替代固定字段的原生插入 SQL(如**
insert into tb_user (username, sex) values(#{username}, #{sex})),**适配多场景插入需求。
标签:
<trim>(通用动态拼接标签)
<trim>是 MyBatis 最灵活的动态标签,通过 3 个属性解决拼接语法问题,这里用了两个<trim>分别处理「字段列表」和「值列表」:
| 属性 | 作用 | 示例(第一个 <trim>) |
|---|---|---|
prefix |
给拼接后的片段加「前缀」 | prefix="(" → 字段列表开头加左括号 ( |
suffix |
给拼接后的片段加「后缀」 | suffix=")" → 字段列表结尾加右括号 ) |
suffixOverrides |
剔除片段最后多余的指定字符(这里是 ,) |
自动删掉最后一个字段后的逗号,避免 (username, sex,) 语法错误 |
XML
insert into tb_user (username, sex) values(#{username}, #{sex})
<insert id="insertUser" parameterType="com.example.entity.User">
INSERT INTO tb_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null and username != ''">
username,
</if>
<if test="sex != null and sex != ''">
sex,
</if>
<if test="age != null">
age,
</if>
<if test="email != null and email != ''">
email,
</if>
<if test="phone != null and phone != ''">
phone,
</if>
<if test="createTime != null">
create_time,
</if>
</trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
<if test="username != null and username != ''">
#{username},
</if>
<if test="sex != null and sex != ''">
#{sex},
</if>
<if test="age != null">
#{age},
</if>
<if test="email != null and email != ''">
#{email},
</if>
<if test="phone != null and phone != ''">
#{phone},
</if>
<if test="createTime != null">
#{createTime},
</if>
</trim>
</insert>
xml
XML
<!-- 1. 定义插入语句,id 为方法名,parameterType 指定参数类型 -->
<insert id="insertUser" parameterType="com.example.entity.User">
<!-- 2. 固定的 INSERT INTO 表名部分 -->
INSERT INTO tb_user
<!-- 3. 第一个 <trim> 标签:处理字段列表 -->
<!--
prefix="(" :在内容前添加左括号
suffix=")" :在内容后添加右括号
suffixOverrides=",":移除末尾多余的逗号
-->
<trim prefix="(" suffix=")" suffixOverrides=",">
<!-- 4. 判断 username 字段是否非空且非空字符串 -->
<!-- 如果条件成立,添加 "username," 到字段列表 -->
<if test="username != null and username != ''">
username,
</if>
<!-- 5. 判断 sex 字段是否非空且非空字符串 -->
<if test="sex != null and sex != ''">
sex,
</if>
<!-- 6. 判断 age 字段是否非空(age 可能是 Integer/Long 类型) -->
<if test="age != null">
age,
</if>
<!-- 7. 判断 email 字段是否非空且非空字符串 -->
<if test="email != null and email != ''">
email,
</if>
<!-- 8. 判断 phone 字段是否非空且非空字符串 -->
<if test="phone != null and phone != ''">
phone,
</if>
<!-- 9. 判断 createTime 字段是否非空 -->
<if test="createTime != null">
create_time, <!-- 注意:数据库字段名是蛇形命名 create_time -->
</if>
</trim> <!-- 第一个 <trim> 结束,此时会生成类似 (username, sex, age) 的字段列表 -->
<!-- 10. 第二个 <trim> 标签:处理 VALUES 部分 -->
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
<!-- 11. 对应 username 字段的值 -->
<!-- 判断条件与字段列表的 username 判断保持一致 -->
<if test="username != null and username != ''">
#{username}, <!-- MyBatis 参数占位符 -->
</if>
<!-- 12. 对应 sex 字段的值 -->
<if test="sex != null and sex != ''">
#{sex},
</if>
<!-- 13. 对应 age 字段的值 -->
<if test="age != null">
#{age},
</if>
<!-- 14. 对应 email 字段的值 -->
<if test="email != null and email != ''">
#{email},
</if>
<!-- 15. 对应 phone 字段的值 -->
<if test="phone != null and phone != ''">
#{phone},
</if>
<!-- 16. 对应 create_time 字段的值 -->
<if test="createTime != null">
#{createTime}, <!-- Java 对象属性名是驼峰命名 createTime -->
</if>
</trim> <!-- 第二个 <trim> 结束,此时会生成类似 (?, ?, ?) 的值列表 -->
</insert> <!-- 整个插入语句结束 -->
6.6 多表联查 【重点】
表关系: 一对一,一对多,多对多
- 1vs1 丈夫表 --> 妻子表
- 1 vs n 用户 --> 车辆 / 房产
- n vs n 老师 / 商品 --> 学生 / 订单
多表联查的 SQL
- 内连接
- select * from 表 1 inner join 表 2 on 表 1. 字段 = 表 2. 字段
- select * from 表 1 , 表 2 where 表 1. 字段 = 表 2. 字段
- 外连接
- select * from 表 1 left join 表 2 on 表 1. 字段 = 表 2. 字段
6.6.1 OneToOne
需求: 实现一对一查询,查询订单以及对应的用户信息
数据: tb_user 表,tb_order 表
关系:
- 用户 ---> 订单 (1 VS N) 一个用户有多个订单
- 订单 ---> 用户 (1 VS 1) 一个订单只会属于一个人
tb_user 表
sql
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',
`username` varchar(10) DEFAULT NULL COMMENT '用户名',
`password` varchar(10) DEFAULT NULL COMMENT '密码',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
`money` double(10,2) DEFAULT NULL COMMENT '账户余额',
`create_time` date DEFAULT NULL COMMENT '注册时间',
`sex` int(11) DEFAULT 1 COMMENT '1男2女'
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
tb_order 表
sql
CREATE TABLE `tb_order` (
`oid` int(11) NOT NULL AUTO_INCREMENT,
`detail` varchar(255) DEFAULT NULL,
`price` double(10,2) DEFAULT NULL,
`uid` int(11) DEFAULT NULL,
PRIMARY KEY (`oid`)
);
实体类
java
@Data
public class Order {
private Integer oid;
private String detail;
private Double price;
private Integer uid;
}
思考 1: 查询订单以及关联的用户,sql 怎么写?
sql
-- 根据订单id 查询订单信息和关联的用户
select *
from tb_order o,tb_user u
where oid = 1
and o.uid = u.id
思考 2: 这个 sql 结果如何映射封装到对象?

但是上面的实体类 (Order.java 和 User.java), 只有订单信息或者只有用户信息,但是我们要查询的是既有订单又有用户!上面的类就无法展现以及封装全部数据,所以 **<font style="color:#DF2A3F;">需要扩展类 (即包含 Order 又包含 User)</font>**
java
@Data
public class OrderVO extends Order {
private User user;
}
OrderMapper.java 接口文件
java
public interface OrderMapper {
OrderVO findOrderWithUserByOid(Integer oid);
}
OrderMapper.xml 映射文件
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.day34mybatis.mapper.OrderMapper">
<!--resultMap标签, 结果映射,将列和属性关联
id是唯一标识,type最终要封装的VO类-->
<resultMap id="orderWithUser" type="orderVO">
<!--主键设置id标签,column是sql中列,property是要封装对象中的属性名-->
<id column="oid" property="oid"/>
<!--其他列使用result-->
<result column="detail" property="detail"/>
<result column="price" property="price"/>
<result column="uid" property="uid"/>
<!--vo中的嵌套对象,需要association标签来指定嵌套封装的对象-->
<!--property就是嵌套对象的属性-->
<association property="user" javaType="User">
<id column="id" property="id"/>
<!--其他列使用result-->
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="phone" property="phone"/>
<result column="create_time" property="createTime"/>
<result column="money" property="money"/>
<result column="sex" property="sex"/>
</association>
</resultMap>
<!--resultType不能封装嵌套对象的,需要使用resultMap-->
<select id="findOrderWithUserByOid" resultMap="orderWithUser">
select *
from tb_order o,tb_user u
where oid = #{orderId}
and o.uid = u.id
</select>
</mapper>
测试
OrderController 调用 OrderService
OrderService 调用 OrderMapper

总结:与之前单表查询区别
- 创建 Vo 类
- xml 中结果映射改成手动映射
6.6.2 OneToMore
需求:一对多,查询用户关联查询出所有的订单
SQL 如下
sql
# 根据用户id,查询用户以及其所有订单
select *
from tb_user u,tb_order o
where u.id = o.uid
and u.id = 1
目的是查询用户以及关联多个订单

User 类不够展现全部数据,那么就创建扩展类 UserVO,UserVO 类继承 User 就可以存储用户信息,还需要再 UserVO 类中添加 Order 类来存储信息,但是!!不是一个 Order 类,因为是一对多,一个用户关联多个订单,所以要设置 List<Order>
User 扩展实体类
sql
@Data
public class UserVO extends User{
private List<Order> orderList;
}
UserMapper.java 接口
sql
public interface UserMapper {
UserVO findUserWithOrdersById(int id);
}
UserMapper.xml 映射文件
sql
<!-- 一对多 -->
<resultMap id="userWithOrdersResultMap" type="UserVO">
<!-- 封装User对象 -->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="phone" property="phone"/>
<result column="create_time" property="createTime"/>
<result column="money" property="money"/>
<result column="sex" property="sex"/>
<!-- 一对多关联映射使用collection标签 -->
<!-- property是UserVO类中关联的属性 -->
<!-- 不是javaType,是ofType,是指定集合中存储的数据类型 -->
<collection property="orderList" ofType="Order">
<id column="oid" property="oid"/>
<result column="order_time" property="orderTime"/>
<result column="order_desc" property="orderDesc"/>
<result column="uid" property="uid"/>
</collection>
</resultMap>
<!-- 多表联查,另外的属性不会自动封装,需要使用resultMap -->
<select id="findUserWithOrdersById" resultMap="userWithOrdersResultMap">
SELECT
*
FROM
tb_user u
LEFT JOIN tb_order o ON u.id = o.uid
WHERE
u.id = #{id}
</select>
6.6.3 关联查询总结
- 实体类要设置扩展类 VO 以用于封装多表的数据
- 一对一,类中套类
- 一对多,类中套集合
- 正常封装使用 resultMap
- 一对一封装使用 association
- 一对多封装使用 collection
6.8 SpringBoot 整合分页助手
关于分页有些数据
- 默认访问首页,即默认当前页是 pageNum= 1
- 数据有总条数,total = select count (*)
- 页面大小 / 每页展示多少条数据,pageSize = 10
- 总页数,pageCount = total /pageSize (需要注意除不尽情况)
sql
-- total共7条
select count(*) from tb_user
-- 每页多少条数据: pageSize 3条
-- 总页数pageCount
pageCount = total % pageSize == 0? total/pageSize :(total/pageSize)+1
-- 当前页pageNum=1
-- 查询第1页
select * from tb_user limit 0,3
-- 查询第2页
select * from tb_user limit 3,3
-- 查询第pageNum页
select * from tb_user limit (pageNo-1)*pageSize,pageSize
-- 查询第3页面
select * from tb_user limit 6,3
<font style="color:#DF2A3F;">现在使用的是分页助手 - pagehelper</font>
- 原理拦截 sql, 帮助我们拼接 limit
6.8.1 导入依赖
xml
<!-- pageHelper依赖-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
SpringBoot 会自动完成配置,我们直接写代码
6.8.2 测试使用
测试是在 Controller 中直接设置开启分页即可
特别注意!!!无需改动 sql, 即不需要自己写 limit, 分页工具自己会拼接
sql
/**
* 使用分页查全部
* pageNum 当前页码
* pageSize 页面大小
* 这两个参数需要前端发请求带过来
*/
@GetMapping("/m2")
public R testMybatis2(int pageNum,int pageSize) {
// 使用步骤
// 1 先设置分页信息
PageHelper.startPage(pageNum,pageSize);
// 2 正常执行查询
List<User> list = mapper.findAll( );
// 3 通过查询返回的list创建出分页信息,PageInfo内包含所有分页数据,可以点入源码查看
PageInfo<User> info = new PageInfo<>(list);
System.out.println("当前面"+info.getPageNum());
System.out.println("页面大小"+info.getPageSize() );
System.out.println("总条数"+info.getTotal() );
System.out.println("总页数"+info.getPages() );
System.out.println("数据"+info.getList());
return R.ok(info);
}
注意,mapper 层面还是之前的查询全部 findAll,sql 语句还是 select * from tb_user, 不需要写其他的
因为 Mybatis 的 PageHelper 插件会自动帮助拼接 limit 等关键词
6.9 事务
复习
事务:是一组原子操作,即这组操作不可再分,保证这这组全部成功或全部失败
xml
开启事务
执行一系列...sql
如果成功,提交事务
如果失败,回滚事务
方法加 **@Transactional 注解 ** 即可,一般加在业务层
sql
@Override
@Transactional // 加上注解,该方法执行时就有事务控制
public int deleteById(int id) {
int i = mapper.deleteById(id);
System.out.println(1/0 );
// 无事务管理时,即使会报错抛异常,但是上面删除还会成功执行
// 但是有事务管理时,如果有报错异常抛出,上面的删除会回滚回去
return i;
}
该注解加在业务层方法上,那么该方法被事务管理
如果加业务层类上,那么该类的所有方法被事务管理
补充: @Transactional 注解的参数说明
事务的传播机制 propagation
- 传播机制:并发访问时事务和事务之间的相互影响的机制
- REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常见的传播行为,适用于大多数情况。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。适用于对事务无要求的情况,可与现有事务一起执行,也可独立执行。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。用于要求调用方必须在事务中调用该方法。
- REQUIRES_NEW:每次都会创建一个新的事务,并且暂停当前事务(如果存在)。如果没有现有事务,则只是简单地创建一个新事务。用于要求每次调用都使用自己的独立事务。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。用于在不需要事务保证的情况下执行方法。
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。用于确保方法不在事务中执行。
- NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新事务。嵌套事务是现有事务的一个真正子事务,可以独立提交或回滚,但是只有在外部事务提交时才能被提交。
隔离级别 isolation:
- 读未提交 read_uncommitted
- 读已提交 read_committed
- 可重复读 (mysql 默认) repeatable_read
- 串行化 serializable

超时 timeout: 事务并发访问,如果有行锁,表锁等锁上数据,其他事务的等待时间
- 默认以连接的数据源为准 (mysql 默认是 50 秒)
可以通过 sql 查询
show variables like 'innodb_lock_wait_timeout'
只读 readOnly:
指定异常回滚 rollbackFor: 指定某个异常出现时才回滚
- 默认的回滚异常是RuntimeException
5 一些事务失效的场景【面试题】
1)@Transactional 修饰的方法没有使用 public 修饰符2)同一类中,一个非事务方法中直接调用(或者通过 this 调用)另一个启用事务的方法3)启用事务的方法中抛出非运行时异常4)启用事务的方法中捕获异常并自己处理
6.10 缓存 (cache)【面试】
缓存主要目的是为了
**<font style="color:#DF2A3F;">提高查询效率</font>**. 缓存其实就是一个内存空间,存储在程序的某个地方,存储查询结果数据.mybatis 支持缓存的,且有两级缓存
- 一级缓存
- 二级缓存
| 无缓存:用户在访问相同数据时,需要发起多次对数据库的直接访问,导致产生大量IO、读写硬盘的操作,效率低下 |
|---|
![]() |
| **有缓存:**首次访问时,查询数据库,将数据存储到缓存中;再次访问时,直接访问缓存,减少IO、硬盘读写次数、提高效率 |
|---|
![]() |
6.10.1 一级缓存
MyBatis 的一级缓存是默认的. 无需配置,自动实现.
默认的一级缓存是SqlSession级别
- 是指同一个SqlSession 发起的多次查询同一条数据,会使用缓存.
SqlSession 的理解,可以理解为一次前端请求内查询多次
ps: SqlSession 会话,就是一次正常的 mybatis 执行 sql 的过程

ps: Mybatis 内部存储缓存使用的是一个 HashMap 对象,key 为 hashCode + sqlId + sql 语句。而 value 值就是从查询出来映射生成的 java 对象。
6.10.1.1 命中缓存
sql
@GetMapping("/user/{id}")
@Transactional // 【重点】需要开启事务才会生效一级缓存,因为mysql默认每句话都是独立的事务,即每句话都是独立的SqlSession,那么就不符合一级缓存的要求
// 前端每次请求 到Mybatis都会重新创建SqlSession,即SqlSession不一样,不会命中缓存
// 只有同一个请求中,查询多次会命中缓存
public R findUserById(@PathVariable int id) {
User user = userService.findUserById(id);// 第一次查
System.out.println(user );
System.out.println("-------------" );
User user2 = userService.findUserById(id);// 第二次查
System.out.println(user2 );
return R.ok(user);
}

第二次直接从缓存取值,没有经过数据库查询,直接出结果

6.10.1.2 清空缓存
在更新 (更新,删除,插入) 数据后,会清空缓存,下次重新查最新的数据。避免脏读
sql
@GetMapping("/user/{id}")
@Transactional
public R findUserById(@PathVariable int id) {
User user = userMapper.findUserById(id);// 查一次
System.out.println(user );
System.out.println("-------------------" );
userMapper.deleteById(3);// 中间执行删除,会清空缓存
System.out.println("-------------------" );
User user2 = userMapper.findUserById(id);// 再查一次
System.out.println(user2 );
return R.ok(user);
}
6.10.2 二级缓存
**<font style="color:#DF2A3F;background-color:#FBDE28;">二级缓存是 Mapper 级别,</font>** 比 SqlSession 级别范围更大.
- SqlSession 级别是指,同一次的请求查询才会生效
- Mapper 级别是指,不论查询多少次多少请求,只要查询的是同一个 Mapper 中的同一个数据即可生效
需要在 mapper.xml 中设置 caceh 即可
- <cache/>
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
6.10.2.1 使用二级缓存
- 需要在 mapper 中设置 caceh 即可

xml
<!-- 默认是指定下面类的,只不过可以省略-->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"></cache>
- 实体类需要序列化,实现 Serializable 接口,要不然会报错

java
sql
// 两次请求,查询的是同一个mapper下的同一个方法的同一个参数的同一条数据
@GetMapping("/one")
@Transactional
public R findUserById(){
User user = userService.findUserById(1);
if (user != null) {
return R.ok(user);
}
return R.fail();
}
@GetMapping("/one2")
@Transactional
public R findUserById2(){
User user = userService.findUserById(1);
System.out.println(user );
if (user != null) {
return R.ok(user);
}
return R.fail();
}
6.10.2.2 清空缓存
sql
@GetMapping("/user/{id}")
public R findUserById(@PathVariable int id) {
User user = userMapper.findUserById(id);
System.out.println(user );
System.out.println("-------------------" );
userMapper.deleteById(2);// 删除会清空缓存
System.out.println("-------------------" );
User user2 = userMapper.findUserById(id);
System.out.println(user2 );
return R.ok(user);
}
总结:问题
- 什么是缓存?有什么好处?
- mybatis 有没有缓存?
- 一级 二级什么区别?
- 一级二级是如何设置缓存?
- 缓存的流程?
6.11 注解方式整合 Mybatis [了解]
注解方式在编写配置简单,简单 SQL 推荐使用
sql
create table `emp` (
`id` int auto_increment,
`name` varchar(255),
`age` int ,
`birthday` date ,
primary key (`id`)
) ;
sql
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class Emp {
private int id;
private String name;
private int age;
private Date birthday;
}
6.11.1 Mapper 接口
sql
public interface EmpMapper {
@Select("select * from emp")
List<Emp> findAll();
}
6.11.2 添加 Mybatis 注解
针对增删改查:@Insert,@Delete,@Update,@Select
还是需要在启动类中添加 @MapperScan 注解
sql
public interface EmpMapper {
@Select("select * from emp where id = #{id}")
public Emp getEmpById(int id); // 查一个
@Insert("insert into emp (name,age,birthday) values (#{name},#{age},#{birthday})")
public int insertEmp(Emp emp);// 增
@Delete("delete from emp where id = #{id}")
public int deleteEmpById(int id);//删
@Update("update emp set name=#{name},age=#{age},birthday=#{birthday} where id=#{id}")
public int updateEmpById(Emp emp);//改
}
6.11.4 测试,查看日志
sql
@SpringBootApplication
@MapperScan("com.taotie.testspringboot.mapper")
public class TestSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(TestSpringbootApplication.class, args);
}
}
// ============================================
@RestController
public class TestMybatisController {
@Autowired
private EmpMapper empMapper;
@GetMapping("/m2")
public R testMybatis2() {
List<Emp> list = empMapper.findAll( );
return R.ok(list);
}
}
6.12 #{} 和 ${}【面试】
#{},是jdbc中的预处理语句,会将#{}地方变成?,然后再给?赋值,如果是数字直接赋值,如果是字符串会自动拼接引号

${} 是 jdbc 中处理语句,是直接取值拼接,无论是数字还是字符串都是直接取值拼接,如下

模糊查询,$ 的一个应用场景 xml
sql
<select id="findUserByLikeName" resultType="com.qf.entity.User">
select
<include refid="userFields"/>
from
tb_user
<!--此时这样写,预处理语句会自动拼接引号,导致出现 '%'admin'%' 这样的情况,报错-->
<!--where username like '%#{username}%'-->
<!--方案1: 使用${},直接取值-->
<!--where username like '%${name}%'-->
<!--方案2: 使用#{}-->
where username like concat('%',#{name},'%')
</select>
七、配置文件【重点】
7.1 SpringBoot 的配置文件格式
springboot 是 约定大于配置, 几乎是不需要配置文件就可以帮助完成配置,
但是有些默认不好用的时候,也可以通过配置文件改变配置
比如:端口,访问路径,文件上传大小,jdbc 连接信息
SpringBoot 的配置文件
- 文件名必须是 application
- 位置,resources 下
- 格式支持 properties 和 yml。
特殊的,有时也会见到名为 bootstrap.yml 文件
- 更推荐使用 yml 文件格式:
- yml 文件,会根据换行和缩进帮助咱们管理配置文件所在位置
- yml 文件,相比 properties 更轻量级一些
- K: V 表示一对键值对 (冒号:后一定有一个空格)
- 严格以空格的缩进来控制层级关系; 只要是左对齐的都是属于一个层级的数据
- 属性和值大小写敏感.
yml 文件的劣势:
- 严格遵循换行和缩进
- 在填写 value 时,一定要在:后面跟上空格
配置文件的作用
- 修改 SpringBoot 的配置的默认值:
- 比如默认启动的 Tomcat 的端口是 8080, 可以修改为 8081
| properties | yml |
|---|---|
服务器配置示例:server.port=8081 ![]() |
服务器配置示例:server:port: 8081 ![]() |

配置文件的位置:
- 一般默认都是放在 resources / 下
- 也有其他位置的,暂且不讨论
7.2 多环境配置 / 切换 [了解]
实际开发中,有三种环境:develop test production
- 开发环境 dev- 程序员日常开发所需 (本地)
- 测试环境 test- 项目的集成测试
- 生产环境 prod- 最终项目部署的环境,真实环境
SpringBoot 支持多环境的配置。只需要根据环境需要,编写多个配置文件,通过配置属性选择使用哪个环境使用步骤:
多环境的配置文件命名:**application-**环境名 .yml
在总文件 application.yml 中通过属性:spring.profiles.active: 环境名
Spring 框架中「启用的环境配置集」
单词 基础含义 Spring 语境下的专业含义 spring春天;弹簧 特指 Spring 框架(无额外翻译) profiles个人资料(复数) 「环境配置集」(多套配置文件的抽象) active活跃的、启用的、生效的 启用的、当前生效的

7.3 获取配置文件数据 [常用]
场景:
- 加密盐值
- 秘钥
- token 令牌信息
- 项目自定义信息等等
解释:将 yml 配置的值,赋值给对应的类
yaml
# yml中的示例
aliyun:
accessKey: ATYSBD23B1N44
accessSecret: 123456
java
java
// 哪里需要获得yml中的数据,哪里只需要加@Value注解取值即可
@RestController
public class UserController {
// 注意${} , 其中写的是yml中key
@Value("${aliyun.accessKey}")
private String accessKey;
@GetMapping("/user")
public R findUserById(){
System.out.println(accessKey );
return R.ok();
}
这样写,其实就是将这些数据抽取到 yml 中定义,而不是直接写在代码中,目的就是为了后期方便维护
7.4 热加载
热加载 / 热部署:实现不停机更新代码导入依赖 xml
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
新版 idea (2022 版以后) 在设置里找]
File->Settings->Advanced Settings 中找到 Compiler 并勾选 Allow auto-make to start even if developed application is currently running

测试,以 Debug 方式启动项目 , 改动代码按ctrl+F9 , 就会自动重启项目刷新

八、SpringBoot 整合日志框架 [重要]
8.0 日志
作用:
- 方便调试
- 记录运行信息
- 记录异常信息
现在如何实现记录日志的呢
- sout 这个输出语句
弊端
- 无论什么情况,只要到此处输出语句一定执行,不能有选择的可控输出
- 只能输出到控制台
- 信息不完整
- 输出语句要删掉
8.1 常见的日志框架
- slf4j ( Simple Logging Facade for Java)
SLF4J 本身不实现日志功能,只定义一套统一的日志接口(门面)
slf4j 只是一个日志标准,并不是日志系统的具体实现。它用于提供日志操作的接口,提供获取日志对象的方法
- **log4j **apache 实现的一个开源日志组件
- **logback ** 相对于 logback,有更好的特性,springboot 默认使用 logback
- log4j2 是 log4j 的升级版本,拥有更好的性能,支持异步日志
- commons-logging
注意:slf4j 属于日志接口,log4j、logback、log4j2 属于日志实现
4 是 for 的谐音 / 简写
(Java 生态的通用惯例:用数字 4 替代单词 for,比如 Log4j = Log for Java
- Log4j :Log for Java,是具体的日志实现(真正负责日志打印、输出到文件 / 控制台);
- SLF4J :Simple Logging Facade for Java,是日志门面(接口),本身不打印日志,只对接 Log4j/Logback 等实现。
8.2 日志的等级
日志级别按照从低到高为:ALL < TRACE < DEBUG
< INFO < WARN < ERROR <FATAL < OFF程序会打印高于或等于所设置级别的日志,设置的 日志等级越高,打印出来的日志就越少
All:最低等级,会输出所有日志记录
Trace:追踪,就是程序推进一下
Debug:调试日志
Info:消息日志,可用于输出应用程序的运行过程
Warn:输出警告级别的日志
Error:输出错误信息日志
Fatal:输出每个严重的错误日志.(致命的 /重大的)
OFF:最高等级的,用于关闭所有日志记录
8.3 整合 Log4j2
8.3.0 依赖
Spring Boot 默认使用 LogBack,但是我们没有看到显示依赖的 jar 包,其实是因为所在的 jar 包 spring-boot-starter-logging 都是作为 spring-boot-starter-web 或者 spring-boot-starter 依赖的一部分。
如果这里要使用 Log4j2,需要从 spring-boot-starter-web 中排除 spring-boot-starter-logging 依赖,同时显示声明使用 Log4j2 的依赖 jar 包,具体如下:
pom.xml
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 去掉springboot默认配置 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
再单独引入 log4j2 的依赖 xml
XML
<!-- 引入log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
8.3.1 编写配置文件
在 resources 下面新建 log4j2.xml,输入以下内容:
XML
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!-- status log4j2内部输出自身的日志信息的级别,可以不设置,没太大用 -->
<!-- configuration中主要包括有 Properties、Appenders、Loggers标签 -->
<Configuration status="fatal" monitorInterval="30">
<!--打印在本地,根据具体存储地址填写 ./logs是当前项目名下的位置-->
<Properties>
<Property name="baseDir" value="./logs"/>
<!--
常见的配置如下:
- %d{yyyy-MM-dd HH:mm:ss.SSS} : 日志生成时间,输出格式为"年-月-日 时:分:秒.毫秒"
- %p : 日志输出格式
- %c : 类名
- %m : 日志内容,即 logger.info("message")
- %n : 换行符
- %T : 线程号
- %L : 日志输出所在行数
- %M : 日志输出所在方法名
-->
<Property name="pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} %5p: --- [%15.15t] %-30.30logger{1} : %m%n</Property>
</Properties>
<!-- 输出源,常见的主要有Console、RollingFile、File 三种子节点
Console:用于定义输出到控制台的Appender
RollingFile:定义指定方式触发新的Appender
-->
<Appenders>
<!-- 输出到控制台 -->
<Console name="console" target="SYSTEM_OUT">
<PatternLayout>
<pattern>${pattern}</pattern>
</PatternLayout>
</Console>
<!-- 输出到本地磁盘,形成日志文件 -->
<RollingFile name="localFileLog" fileName="${baseDir}/debug.log"
filePattern="${baseDir}/debug_%i.%d{yyyy-MM-dd}.log">
<!-- 过滤器 -->
<Filters>
<!-- 限制日志级别在debug及以上在info以下 -->
<ThresholdFilter level="debug"/>
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<!-- 日志格式 -->
<PatternLayout>
<pattern>${pattern}</pattern>
</PatternLayout>
<!-- 策略 -->
<Policies>
<!-- 每隔一天转存 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<!-- 文件大小,如果太大,可能不会按照模板生成,可以改成10KB试试 -->
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<!-- 这个level可以控制输出的级别-->
<Root level="debug">
<AppenderRef ref="console"/>
<!--<AppenderRef ref="localFileLog"/>-->
</Root>
</Loggers>
</Configuration>
8.3.2 代码中使用日志
直接在项目的文件内使用日志对象即可无论 Controller,Service,Mapper, 项目的任何位置都可以使用
java
@RestController // 组合注解 = @Controller + @ResponseBody,说明:
// 1. 这是一个控制器,处理HTTP请求;
// 2. 方法返回值自动转为JSON(而非跳转视图)
@Slf4j // Lombok核心注解:自动生成以下代码(无需手动写):
// private static final org.slf4j.Logger log =
// org.slf4j.LoggerFactory.getLogger(TestLogController.class);
// 对比注释里的Log4j创建方式,@Slf4j是SLF4J(日志门面)的简化,更通用
// 用了这个注解,代码中只需要直接使用log对象
public class TestLogController {
// 导入包import org.apache.logging.log4j.LogManager;
// 导入包import org.apache.logging.log4j.Logger;
// private Logger logger = LogManager.getLogger(TestLogController.class);
@GetMapping("/log")
public R log() {
log.debug("");
// 下面这些需要配合上方成员变量logger对象
// {} 是拼接参数
// logger.debug("这是debug日志,接收id = {},{}",id,"zs");
// logger.info("这是info日志,接收id = {}",id);
// logger.warn("这是warn日志,接收id = {}",id);
// logger.error("这是error日志,接收id = {}",id);
log.debug("这是debug日志,接收id = {},{}",id,"zs");
log.info("这是info日志,接收id = {}",id);
log.warn("这是warn日志,接收id = {}",id);
log.error("这是error日志,接收id = {}",id);
return R.ok( );
}
}
日志占位符 {}

注意:实际开发中,不允许使用输出语句定位问题!要用日志:::
九、SpringBoot 整合 Knife4j
9.1 接口文档
开发中一般写接口文档比较麻烦,需要定义++访问路径++ ,++请求方式++ ,++请求参数++ ,请求列表,请求参数实例,++返回结果++ ,返回结果类型,返回体结构,返回结构实例,返回参数列表,++返回状态码++列表等等等等等....
关键还会经常随着需求的更新而改变.....还有,就是这玩意儿真的手写起来太费劲,且不好分工,前端说这东西是后端写的,后端是这东西是前端写的.....
这是一个手写的 word 版的接口文档....
| ** 某公司的 api 接口文档截图** |
|---|
![]() |
9.2 接口文档工具
正是有这样那样的问题,才催生了这些接口文档工具,可以方便的生成接口文档,还可以当接口测试工具去测试,还可以导出成 pdf,word,markdown, 太舒服了常见的工具:
- Swagger (丝袜哥 er)
- Knife4j (乃夫 for j)它们之间的关系是:
- Swagger 是一个开源框架,用于设计、构建和文档化 API。它提供了一组工具和规范,可以生成具有交互式界面的 API 文档。Swagger 可以通过注解或配置文件来定义 API 的元数据,包括请求和响应的数据结构、参数、路径等。它还支持自动生成客户端代码和执行 API 测试。
- Knife4j 是一个基于 Swagger 的增强工具,为 Swagger 文档提供了更直观、美观和易于使用的界面。它通过自定义样式和感知能力来改进 Swagger 生成的文档。Knife4j 提供了一些额外的功能,如开发者友好的文档展示、在线测试工具、接口权限管理等。它可以轻松集成到 Spring Boot 等框架中,提供更好的 API 文档展示和管理体验。
因此,Knife4j 可以看作是 Swagger 的一个扩展和增强工具,通过提供更好的 UI 和功能来改进 Swagger 生成的 API 文档。它使用 Swagger 的核心功能和规范,并在此基础上进行了定制和改进,提供更好的用户体验和开发者工具。
swagger的界面


Knife4j的界面


9.3 整合 Knife4j
9.3.1 依赖xml
XML
<!-- 接口文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
9.3.2 yml 配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://ip:3306/test_springboot?serverTimezone=UTC&useSSL=false
username: root
password: ***
type: com.alibaba.druid.pool.DruidDataSource
mvc:
pathmatch: # Springfox使用的路径匹配是基于AntPathMatcher的
# 所以需要配置此参数
matching-strategy: ant_path_matcher
9.3.3 Controller 接口加注解
java
@RestController
@RequestMapping("/api/dept/")
@Api(tags = "部门接口")
public class TestKnife4jController {
@Autowired
private DeptMapper mapper;
@GetMapping("/list")
@ApiOperation(value = "查询所有部门")
public ResultData test(){
List<Dept> list = mapper.findAll( );
return ResultData.ok( list );
}
@GetMapping("/{id}")
@ApiOperation(value = "根据部门编号查询部门")
public ResultData findById(@PathVariable String id){
Dept dept = mapper.findById(id);
return ResultData.ok( dept );
}
@PostMapping("/save1")
@ApiOperation(value = "插入部门-表单")
public ResultData save1(Dept dept){
mapper.save(dept);
return ResultData.ok( );
}
@PostMapping("/save2")
@ApiOperation(value = "插入部门-json")
public ResultData save2(@RequestBody Dept dept){
mapper.save(dept);
return ResultData.ok( );
}
}
9.3.4 配置 [重点]
java
package com.qf.config;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
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.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration // 开启配置
@EnableSwagger2 // 启动Swagger2
public class Knife4jConfiguration {
@Bean
public Docket defaultApi2() {
String groupName = "1.0版本";
Docket docket = new Docket(DocumentationType.OAS_30)
// 是否启用Swagger
.enable(true)
.apiInfo(new ApiInfoBuilder()
.title("这是Taotie-Test-knife4j API ")
.description("这是项目描述")
.termsOfServiceUrl("服务器URL")
.contact(new Contact("饕餮", null, "qiushiju0828@163.com"))
.version("1.0")
.build())
//分组名称
.groupName(groupName)
.select()
// 这里指定Controller扫描包路径,没有加注解的接口方法也会生成接口文档
.apis(RequestHandlerSelectors.basePackage("com.qf.controller"))
// 这里指定只有加了注解的才会生成接口文档
//.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
return docket;
}
}

9.3.5 启动项目,访问接口文档


可以测试


特别注意:
- Knife4j 依赖用的时候再添加
- 否则,只是添加依赖,没有写配置文件,项目会报错:::
9.4 其他注解
- @Api:修饰整个类,描述 Controller 的作用
java
@Api(tags ="用户管理API")
public class UserController {}
- @ApiOperation:描述一个类的一个方法,或者说一个接口
java
@ApiOperation(value="获取用户详细信息", notes="根据id来获取用户详细信息")
public ResultData<User> findUserById(Integer id){}
- @ApiModel:用对象来接收参数 ,修饰类
- @ApiModelProperty:用对象接收参数时,描述对象的一个字段例如:
java
@ApiModel(description = "用户实体类")
public class User {
@ApiModelProperty(name="id", value="用户id")
private Integer id;
@ApiModelProperty(value="用户姓名")
private String name;
- @ApiResponse:HTTP 响应其中 1 个描述
- @ApiResponses:HTTP 响应整体描述,一般描述错误的响应
java
// 针对响应状态 修饰方法
@ApiResponses({
@ApiResponse(code=500, message = "服务器异常")
})
- @ApiIgnore:使用该注解忽略这个 API@ApiError :发生错误返回的信息 @ApiParam:单个参数描述,用在控制器的方法上 @ApiImplicitParam:一个请求参数,用在方法上
java
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Integer", paramType = "path")
java
@ApiImplicitParams({
@ApiImplicitParam(),
@ApiImplicitParam()
})
- 针对返回值,使用泛型表示
java
@ApiModel
public class R {
@ApiModelProperty(value = "返回数据状态",notes = "200成功 500失败")
private int code;
private String msg;
@ApiModelProperty(value = "返回数据",notes = "可以是具体的对象,也可以是null")
private Object data;
十 总结
知识点重点 (完全掌握)
Spring
- ioc , 控制反转,创建对象
- @Controller,@Service,@Component
- di, 依赖注入,属性赋值
- @Autowired,@Value
SpringMVC - web层,控制层框架,主要功能是请求响应相关的代码
如何请求匹配路径 @GetMapping @PostMapping @RequestMapping
如何接收请求数据的.前端name和后端参数名一致/后端对象属性名一致
特殊类型数据(List,Map)需要@RequestParam
特殊类型数据 JSON,需要@RequestBody
如何响应
默认是返回String,值是页面字符串 例如: return "ok.html"
前后端分离开发时,后端响应json数据,就在方法上加@ResponseBody注解
- 如果所有方法都加@ResponseBody注解,那么我们就可以在类上把@Controller改成@RestController即可
mybatis - jdbc - 用来操作数据库的
知道ORM --> 表/字段;类/属性
知道编码流程,熟练crud
表
实体类
Mapper接口,定义crud方法
Mapper映射文件,定义crud标签
配置yml文件
主类配置@MapperScan
动态sql
场景: 条件查询 where+if
场景: 条件更新 set+if
场景: 批量删除/插入 foreach
多表联查
其他东西都是锦上添花(用到了,会 CV)
spring-aop
springmvc的会话,拦截器,全局异常
mybatis缓存,事务,注解开发,分页
SpringBoot 多环境切换,获得配置文件数据,热加载,接口工具
整合日志框架
aop+自定义注解实现日志记录
编码上熟悉架构
熟悉springboot整合ssm需要的jar包/依赖
熟系三层架构编码风格











