JavaWeb-SpringBoot(Tilas案例)
JavaWeb
对自己学习JavaWeb的过程进行记录.
1.Maven
Maven介绍
Maven是一款用于管理和构建Java项目的工具,是apache旗下的一个开源项目
Maven主要作用如下:
- 依赖管理: 方便快捷的管理项目依赖的资源(jar包)
- 项目构建: 标准化的跨平台(Linux,Windows,MacOS)的自动化项目构建方式
- 统一项目构建: 提供标准,统一的项目结构(目录结构)
-
依赖管理
只需要创建好
Maven项目后,在pom.xml配置依赖即可<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>上述就是在
pom.xml文件中加入lombok依赖.常见形式为
<dependency> <groupId>...</groupId> <artifactId>...</artifactId> <version>...</version> <scope>...</scope> <type>...</type> <classifier>...</classifier> <optional>...</optional> <exclusions> <exclusion> <groupId>...</groupId> <artifactId>...</artifactId> </exclusion> </exclusions> </dependency>标签名 说明 <groupId>依赖的组织名称(类似包名),用于唯一标识项目,如 org.springframework<artifactId>构件名称(项目名),用于唯一标识项目下的模块,如 spring-core<version>指定依赖的版本号,如 5.3.10<scope>依赖作用范围,常见值如下:- compile(默认)-test(仅在测试时有效)-provided(由容器提供)-runtime(运行时需要)-system(本地系统路径提供)<type>构件类型,默认是 jar,也可以是war、pom等<classifier>分类器,用于获取构件的某个特定构建,如 sources、javadoc等<optional>是否是可选依赖( true或false),可影响传递性依赖<exclusions>排除传递性依赖的子标签,用于避免冲突或冗余依赖 此时你可能有一个疑惑?我不知道lombok需要添加上述信息,在哪里寻找?网站如下:
只需要搜索需要的依赖,找到需要的版本,最下方就会有需要的配置文件
-
项目构建
一个
java项目的运行需要包含下面四个流程:编译->测试->打包->发布
如果需要开发的是一个大型的项目,由于开发使用的模块较多,如果还使用上述的步骤,开发的时候就会比较困难.
Maven制作了一套标准化的项目构建流程,直接使用Maven项目提供的指令,即可完成快速的项目构建 -
统一项目结构
Java的开发工具有很多,例如早期的Eclipse,MyEclipse,IDEA,但是不同的开发工具构建出的Java项目目录结构不一样,就导致不同开发工具创建的项目无法在不同的开发工具中使用使用
Maven构建的项目目录结构都是统一的,解决了这个问题Maven目录结构如下:
my-app/ # 项目根目录 ├── pom.xml # Maven 项目描述文件 └── src/ ├── main/ # 主要源码目录 │ ├── java/ # Java 源代码 │ ├── resources/ # 配置文件等资源 │ └── webapp/ # Web 应用资源(如 JSP、HTML、CSS 等,适用于 web 项目) └── test/ # 测试源码目录 ├── java/ # 测试用 Java 源代码 └── resources/ # 测试资源文件
单元测试
测试:是一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。
测试一般有四个阶段:单元测试、集成测试、系统测试、验收测试。
- 单元测试:就是针对最小的功能单元(方法),编写测试代码对其正确性进行测试。
- Junit:最流行的Java测试框架之一,提供了一些功能,方便程序进行单元测试。
首先在pom.xml加入JUnit依赖
maven repository 搜索 Junit
注意:JUnit单元测试类名命名规范为:
XxxTest[规范]。JUnit单元测试方法,必须声明为public void【规定】
例如:
@Test
public void testGetAge(){
Integer age = new UserService().getAge("412819200311226789");
System.out.println(age);
}
断言
Junit提供了一些辅助方法,用来帮我们确定被测试的方法是否按照预期的效果正常工作,这种方式成为断言。
主要是用Junit提供了一个测试的工具类Assertions,提供了很多的静态的测试方法
| 方法 | 说明 |
|---|---|
assertEquals(expected, actual) |
判断两个值是否相等(使用 equals() 方法) |
assertNotEquals(expected, actual) |
判断两个值是否不相等 |
assertSame(expected, actual) |
判断两个对象引用是否相同 |
assertNotSame(expected, actual) |
判断两个对象引用是否不同 |
assertNull(actual) |
判断对象是否为 null |
assertNotNull(actual) |
判断对象是否不为 null |
assertTrue(condition) |
判断条件是否为 true |
assertFalse(condition) |
判断条件是否为 false |
assertArrayEquals(expectedArray, actualArray) |
判断两个数组是否相等(按元素顺序) |
Junit案例
首先创建一个需要测试的方法
UserService.java
package com.itheima;
public class UserService {
public static void main(String[] args) {
System.out.println("Hello, World!"
);
}
public int add(int a,int b){
return a + b;
}
}
手动测试
UserServiceTest.java
package com.itheima;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class UserServiceTest {
UserService userService = new UserService();
@DisplayName("add测试")
@Test
public void addTest(){
Assertions.assertEquals(3,userService.add(1,2));
}
@ParameterizedTest
@ValueSource(ints = {1,2,3})
public void addTest2(int num){
Assertions.assertEquals(3,userService.add(1,num));
}
}
使用AI生成代码测试
UserServiceAiTest.java
package com.itheima;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class UserServiceAiTest {
UserService userService = new UserService();
@DisplayName("add测试")
@Test
public void addTest() {
Assertions.assertEquals(3, userService.add(1, 2));
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
public void addTest2(int num) {
Assertions.assertEquals(3, userService.add(1, num));
}
@Test
public void add_NegativeNumbers_ShouldReturnCorrectSum() {
Assertions.assertEquals(-3, userService.add(-1, -2));
}
@Test
public void add_ZeroAndPositiveNumber_ShouldReturnPositiveNumber() {
Assertions.assertEquals(5, userService.add(0, 5));
}
@Test
public void add_ZeroAndNegativeNumber_ShouldReturnNegativeNumber() {
Assertions.assertEquals(-5, userService.add(0, -5));
}
@Test
public void add_LargeNumbers_ShouldReturnCorrectSum() {
int a = Integer.MAX_VALUE;
int b = 0; // 为了防止溢出,我们选择0
Assertions.assertEquals(a, userService.add(a, b));
}
}
2.SpringBoot Web
入门
使用Idea或者https://start.spring.io/快速搭建一个初始的spring boot项目
在/java/com.itheima也就是SpringTalisApplication主程序所在的目录创建一个controller
HelloController
package com.itheima.testspringboot;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(String name){
return "Hello " + name + " ~";
}
}
官方骨架问题
使用国内阿里云提供的脚手架,https://start.aliyun.com
请求协议(request)
requestController.java
package com.itheima.testspringboot;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class requestController {
@RequestMapping("/request")
public String request(HttpServletRequest request){
// 获取请求方式
String method = request.getMethod();
System.out.println("请求方式: " + method);
// 获取请求完整路径
StringBuffer url = request.getRequestURL();
System.out.println("请求完整路径: " + url);
// 获取请求路径
String uri = request.getRequestURI();
System.out.println("请求文件路径: " + uri);
// 获取请求协议
String protocol = request.getProtocol();
System.out.println("请求协议: " + protocol);
// 获取请求参数
String queryString = request.getQueryString();
System.out.println("请求参数: " + queryString);
// 获取请求头
String header = request.getHeader("User-Agent");
System.out.println("请求头: " + header);
return "";
}
}
响应协议(response)
package com.itheima.testspringboot;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class responseController {
// 使用servlet提供 response 返回结果
@RequestMapping("/response")
public void response(HttpServletResponse response){
response.setStatus(401);
response.setHeader("name","Junglezt");
try {
response.getWriter().write("<h1>hello response</h1>");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 基于 spring 提供的 ResponseEntity 返回结果
@RequestMapping("/response2")
public ResponseEntity<String> response2(){
return ResponseEntity
.status(401)
.header("id","226")
.body("<h2>hello ResponseEntity</h2>");
}
}
SpringBoot Web案例
基于三层架构(MVC)开发Web程序案例,展示用户列表,读取的数据为user.txt文件
user.txt
1,daqiao,1234567890,大乔,22,2024-07-15 15:05:45
2,xiaoqiao,1234567890,小乔,18,2024-07-15 15:12:09
3,diaochan,1234567890,貂蝉,21,2024-07-15 15:07:16
4,lvbu,1234567890,吕布,28,2024-07-16 10:05:15
5,zhaoyun,1234567890,赵云,27,2024-07-16 11:03:28
6,zhangfei,1234567890,张飞,31,2024-07-16 11:03:29
7,guanyu,123567890,关羽,34,2024-07-16 12:05:12
8,liubei,1234567890,刘备,37,2024-07-16 15:03:28
开发流程
-
准备工作
- 创建
SpringBoot项目,勾选Web依赖、lombok依赖 - 在
resources中引入user.txt文件 - 定义一个实体类,用来封转用户信息
pojo/User.java
package com.itheima.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDateTime; @Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String username; private String password; private String name; private Integer age; private LocalDateTime updatetime; } - 创建
-
开发服务端程序,接收请求,读取文本数据并响应
实操代码
- 首先准备前端页面
static/user.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
h1 {
color: #333;
text-align: center;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
th {
background-color: #f5f5f5;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
tr:hover {
background-color: #f1f1f1;
}
</style>
</head>
<body>
<div id="app">
<h1>用户列表</h1>
<table>
<thead>
<tr>
<th>序号</th>
<th>用户名</th>
<th>密码</th>
<th>姓名</th>
<th>年龄</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
<tr v-for="(user, index) in users" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.password }}</td> <!-- 密码打码显示 -->
<td>{{ user.name }}</td>
<td>{{ user.age }}</td>
<td>{{ user.updatetime }}</td>
</tr>
</tbody>
</table>
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="module">
import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
createApp({
data() {
return {
users: []
}
},
mounted() {
this.search();
},
methods: {
async search() {
let result = await axios.get('http://192.168.202.1:8080/list');
// console.log(result.data)5
this.users = result.data;
},
}
}).mount('#app');
</script>
</body>
</html>
- 定义Controller层代码
controller/UserController.java
package com.itheima.controller;
import cn.hutool.core.io.IoUtil;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import com.itheima.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Array;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static java.lang.System.in;
@RestController
public class UserController {
// 不使用三层架构实现
// @RequestMapping("/list")
// public List<User> list() throws FileNotFoundException {
//// 1.加载并读取user.txt文件
// InputStream in = new FileInputStream(new File("E:\\source\\java\\springboot-web-01\\src\\main\\resources\\user.txt"));
//
//// System.out.println(this.getClass().getClassLoader().getResourceAsStream("user.txt").toString());
//// InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
// ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
//
//// 2.解析用户数据封装为 User 对象 -> List
// List<User> userList = lines.stream().map(line -> {
// String[] parts = line.split(",");
// Integer id = Integer.parseInt(parts[0]);
// String username = parts[1];
// String password = parts[2];
// String name = parts[3];
// Integer age = Integer.parseInt(parts[4]);
// LocalDateTime updatetime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// return new User(id, username, password, name, age, updatetime);
// }).toList();
//
//// 3.返回json数据
//// 使用 @RestController 注解,默认会将对象自动转换为 json 格式返回
// return userList;
// }
// 使用三层架构实现
//// 方法1 属性注入
// @Autowired
// private UserService userservice;
//// 方法2 构造器注入
// private final UserService userservice;
//// @Autowired // 如果当前类中只存在一个构造函数,Autowired注解可以省略
// public UserController(UserService userservice) {
// this.userservice = userservice;
// }
// 方法3 setter 注入
private UserService userservice;
@Autowired
public void setUserservice(UserService userservice) {
this.userservice = userservice;
}
@RequestMapping("/list")
@CrossOrigin(origins = "*")
public List<User> list2() throws FileNotFoundException {
List<User> userlist = userservice.findall();
return userlist;
}
}
- 定义UserService接口
service/UserService.java
package com.itheima.service;
import com.itheima.pojo.User;
import java.io.FileNotFoundException;
import java.util.List;
public interface UserService {
public List<User> findall() throws FileNotFoundException;
}
- 实现UserService接口,并调用Dao层读取user.txt数据,处理后返回给Controller层
service/impl/UserServiceImpl.java
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.io.FileNotFoundException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
@Service
//@Component // 将当前类交给 ioc 容器管理
public class UserServiceImpl implements UserService {
// private UserDao userdao = new UserDaoImpl();
// 使用 spring ioc 注入
@Autowired // 应用程序运行时,会自动查找UserDao类型的bean对象,并复制给userdao成员变量
private UserDao userdao;
@Override
public List<User> findall() throws FileNotFoundException {
ArrayList<String> lines = userdao.list();
List<User> userList = lines.stream().map(line -> {
String[] parts = line.split(",");
Integer id = Integer.parseInt(parts[0]);
String username = parts[1];
String password = parts[2];
String name = parts[3];
Integer age = Integer.parseInt(parts[4]);
LocalDateTime updatetime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return new User(id, username, password, name, age, updatetime);
}).toList();
return userList;
}
}
- 定义UserDao接口
dao/UserDao.java
package com.itheima.dao;
import com.itheima.pojo.User;
import java.io.FileNotFoundException;
import java.util.ArrayList;
public interface UserDao {
ArrayList<String> list() throws FileNotFoundException;
}
- 实现UserDao接口,读取user.txt文件并返回
dao/impl/UserDaoImpl.java
package com.itheima.dao.impl;
import cn.hutool.core.io.IoUtil;
import com.itheima.dao.UserDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@Repository // Repository 将当前类交给 ioc 容器管理
//@Component // 将当前容器交给 ioc 容器处理
public class UserDaoImpl implements UserDao {
@Override
public ArrayList<String> list() throws FileNotFoundException {
InputStream in = new FileInputStream(new File("E:\\source\\java\\springboot-web-01\\src\\main\\resources\\user.txt"));
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
return lines;
}
}
3.JDBC
JDBC: (Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API
要连接不同的数据库,需要下载不同数据库厂商提供的JDBC实现,例如使用mysql是需要在Maven repository找到对应版本的pom.xml即可
JDBC连接主要有四个步骤
- 导入
jdbc驱动 - 连接数据库
- 创建
statement对象 - 执行
sql语句
下方就是Mysql JDBC的基础案例
查询
package jdbc.jdbcTest;
import java.sql.*;
public class Example01 {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/users?serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
try {
// 导入mysql jdbc驱动
Class.forName("com.mysql.jdbc.Driver");
// 使用 DriverManager.getConnection 连接数据库
Connection conn = DriverManager.getConnection(url, username, password);
// 使用 createStatement 创建 Statement 对象
Statement stmt = conn.createStatement();
// 使用 statement 对象执行 sql 语句
String sql = "select * from users";
ResultSet result = stmt.executeQuery(sql);
while(result.next()) {
int id = result.getInt("id");
String name = result.getString("name");
String email = result.getString("email");
Date birthday = result.getDate("birthday");
// System.out.println("id: " + id + " name: " + name + " email: " + email + " phone: " + birthday);
System.out.println(id + " " + name + " " + email + " " + birthday);
}
} catch (ClassNotFoundException e) {
System.out.println("找不到驱动程序");
} catch (SQLException e) {
System.out.println("数据库连接失败");
}
}
}
增加
package jdbc.jdbcTest;
import java.sql.*;
public class Example02 {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/users?serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
try {
// 导入mysql jdbc驱动
Class.forName("com.mysql.jdbc.Driver");
// 使用 DriverManager.getConnection 连接数据库
Connection conn = DriverManager.getConnection(url, username, password);
// 使用 createStatement 创建 Statement 对象
Statement stmt = conn.createStatement();
// 使用 statement 对象执行 sql 语句
String sql = "insert into users(id,name,password,email,birthday) values(5,'王六','8888','333@qq.com','2004-02-22')";
int result = stmt.executeUpdate(sql);
if (result > 0) {
System.out.println(result + "成功插入数据");
}
} catch (ClassNotFoundException e) {
System.out.println("找不到驱动程序");
} catch (SQLException e) {
System.out.println("数据库连接失败");
}
}
}
删除
package jdbc.jdbcTest;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Example03 {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/users?serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
try {
// 导入mysql jdbc驱动
Class.forName("com.mysql.jdbc.Driver");
// 使用 DriverManager.getConnection 连接数据库
Connection conn = DriverManager.getConnection(url, username, password);
// 使用 createStatement 创建 Statement 对象
Statement stmt = conn.createStatement();
// 使用 statement 对象执行 sql 语句
String sql = "delete from users where name='王六'";
int result = stmt.executeUpdate(sql);
if (result > 0) {
System.out.println(result + "删除成功");
}
} catch (ClassNotFoundException e) {
System.out.println("找不到驱动程序");
} catch (SQLException e) {
System.out.println("数据库连接失败");
}
}
}
增加(使用预编译PreparedStatement)
package jdbc.jdbcTest;
import java.sql.*;
public class Example04 {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/users?serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
try {
// 导入mysql jdbc驱动
Class.forName("com.mysql.jdbc.Driver");
// 使用 DriverManager.getConnection 连接数据库
Connection conn = DriverManager.getConnection(url, username, password);
// 使用 createStatement 创建 Statement 对象
//Statement stmt = conn.createStatement();
String sql = "insert into users(id,name,password,email,birthday) values(?,?,?,?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 4);
pstmt.setString(2, "John");
pstmt.setString(3, "123456");
pstmt.setString(4, "john@gmail.com");
pstmt.setString(5, "2024-12-16");
int result = pstmt.executeUpdate();
if (result > 0) {
System.out.println("Insert successful");
}
} catch (ClassNotFoundException e) {
System.out.println("找不到驱动程序");
} catch (SQLException e) {
System.out.println("数据库连接失败");
}
}
}
JDBCUtils
package jdbc.jsj.utils;
import java.sql.*;
/**
* 连接数据库、释放资源
*/
public class JDBCUtils {
// 连接数据库
public static Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/users";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
// 释放sql查询时需要关闭三个对象
public static void release(Connection conn, PreparedStatement pstmt, ResultSet rs) {
// 关闭ResultSet对象
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
rs = null;
}
// 关闭执行sql语句的preparedStatement对象
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
pstmt = null;
}
// 关闭数据库连接对象
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
// 执行增删改操作不需要使用PreparedStatement对象
public static void release(Connection conn, PreparedStatement pstmt) {
// 关闭执行sql语句的preparedStatement对象
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
pstmt = null;
}
// 关闭数据库连接对象
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
UserDao(实例)
package jdbc.jsj.DAO;
import jdbc.jsj.domain.User;
import jdbc.jsj.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
//实现增删改查
/**
* 基本功能:
* 1. 增加用户
* 2. 删除用户(根据id)
* 3. 修改用户(根据id)
* 4. 查询用户(根据id)
* 5. 查询所有用户
*/
public class UserDao {
// 1. 增加用户
public boolean insertUser(User user){
Connection conn = null;
PreparedStatement pstmt = null;
// 1.连接数据库
try {
conn = JDBCUtils.getConnection();
// 2.定义sql语句,获取sql语句执行对象PreparedStatement
String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)";
pstmt = conn.prepareStatement(sql);
// 3.指定增加用户信息
pstmt.setString(1,user.getName());
pstmt.setString(2,user.getPassword());
pstmt.setString(3,user.getEmail());
pstmt.setString(4,user.getBirthday());
// 4.执行sql语句
int resp = pstmt.executeUpdate();
return resp > 0;
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JDBCUtils.release(conn,pstmt);
}
}
// 2.删除用户
public boolean deleteUser(int id){
Connection conn = null;
PreparedStatement pstmt = null;
try {
// 1. 连接数据库
conn = JDBCUtils.getConnection();
// 2. 定义sql语句,获取prepareStatement执行sql一句对象
String sql = "delete from users where id=?";
pstmt = conn.prepareStatement(sql);
// 3. 设置删除参数id
pstmt.setInt(1,id);
// 4. 执行sql语句
int resp = pstmt.executeUpdate();
// 根据返回结果返回true/false
return resp > 0;
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JDBCUtils.release(conn,pstmt);
}
}
// 3.更改信息
public boolean updateUser(User user,int id){
Connection conn = null;
PreparedStatement pstmt = null;
// 1.连接数据库
try {
conn = JDBCUtils.getConnection();
// 2. 定义sql语句,获取执行sql语句对象preparedStatement
String sql = "update users set id=?,name=?,password=?,email=?,birthday=? where id=?";
pstmt = conn.prepareStatement(sql);
// 3. 设置更改数据
pstmt.setInt(1,user.getId());
pstmt.setString(2,user.getName());
pstmt.setString(3,user.getPassword());
pstmt.setString(4,user.getEmail());
pstmt.setString(5,user.getBirthday());
// 根据id更改用户信息
pstmt.setInt(6,id);
// 4.执行sql语句
int resp = pstmt.executeUpdate();
return resp > 0;
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JDBCUtils.release(conn,pstmt);
}
}
// 4. 查询指定用户
public User getUserById(int id){
Connection conn = null;
PreparedStatement pstmt = null;
// 1.连接数据库
try {
conn = JDBCUtils.getConnection();
// 2. 定义sql语句,获取执行sql语句对象preparedStatement对象
String sql = "select * from users where id=?";
pstmt = conn.prepareStatement(sql);
// 3. 设置查询数据
pstmt.setInt(1,id);
// 4. 执行sql语句获取返回结果ResultSet对象
ResultSet rs = pstmt.executeQuery();
// 5. 处理并返回ResultSet对象
while(rs.next()){
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
user.setBirthday(rs.getString("birthday"));
return user;
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JDBCUtils.release(conn,pstmt);
}
return null;
}
// 5. 查询所有用户
public ArrayList<User> getAllUser(){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
String sql = "select * from users";
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
ArrayList<User> userList = new ArrayList<>();
while(rs.next()){
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
user.setBirthday(rs.getString("birthday"));
userList.add(user);
}
return userList;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
JDBCUtils.release(conn,pstmt,rs);
}
}
// 6.根据用户名和密码查询用户
public User getUserByNamePwd(String username,String pwd){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
String sql = "select * from users where name=? and password=?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1,username);
pstmt.setString(2,pwd);
rs = pstmt.executeQuery();
while(rs.next()){
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
user.setBirthday(rs.getString("birthday"));
return user;
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally{
JDBCUtils.release(conn,pstmt,rs);
}
return null;
}
}
4. Mybatis
MyBatis是一款优秀的持久层框架,终于简化JDBC的开发。
使用Mybatis查询所有用户信息
- 准备工作:
- 创建Springboot工程,引入Mybatis相关依赖
- 准备数据库表user、实体类User
- 配置Mybatis(在application.properties中数据库连接信息)
- 编写
Mybatis程序:编写Mybatis的持久层接口,定义SQL(注解/XML)
1.mybatis数据库下创建t_student表
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50726 (5.7.26)
Source Host : localhost:3306
Source Schema : mybatis
Target Server Type : MySQL
Target Server Version : 50726 (5.7.26)
File Encoding : 65001
Date: 26/05/2025 15:24:47
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_student
-- ----------------------------
DROP TABLE IF EXISTS `t_student`;
CREATE TABLE `t_student` (
`sid` int(11) NOT NULL AUTO_INCREMENT,
`sname` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
`sage` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`sid`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of t_student
-- ----------------------------
INSERT INTO `t_student` VALUES (1, '彭于晏', 23);
INSERT INTO `t_student` VALUES (2, 'Lili', 20);
INSERT INTO `t_student` VALUES (3, 'Jim', 21);
INSERT INTO `t_student` VALUES (4, '小红', 18);
2.创建pojo/User实体类
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int sid;
private String sname;
private int sage;
}
3.配置Mybatis(application)
#配置数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
#配置Mybatis日志输出
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#配置数据库连接池为Druid(默认使用Hikari)
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
注意:上述如果要配置连接池为Druid德鲁伊,需要在pom.xml文件中导入
4. 创建Mapper接口
Mapper/UserMapper.java
package com.itheima.Mapper;
import com.itheima.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper // 应用程序在运行时,会自动的为该接口创建一个实现类对象(代理对象),自动的将该实现对象注入IOC中 - bean
public interface UserMapper {
/**
* 查询所有用户信息
*/
// @Select("select * from t_student")
public List<User> findall();
@Delete("delete from t_student where sid = #{id}")
public Integer delUser(Integer id);
@Insert("insert into t_student (sid, sname, sage) values (#{sid},#{sname},#{sage})")
public void addUser(User user);
@Update("update t_student set sname = #{sname},sage = #{sage} where sid = #{sid}")
public void updateUser(User user);
/**
* 根据用户名和年龄查询用户信息
*/
@Select("select * from t_student where sname = #{sname} and sage = #{sage}")
// public User findByNameAndAge(@Param("sname") String sname, @Param("sage") int sage);
// 基于官方声称的springboot项目,可以不添加 Param 注解
public User findByNameAndAge(String sname, int sage);
}
5.在SpringBoot中测试Mapper接口
SpringBootTest
package com.itheima;
import com.itheima.Mapper.UserMapper;
import com.itheima.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest // springboot单元测试的注解
class SpringbootMybatisApplicationTests {
// 注入UserMapper
@Autowired
private UserMapper userMapper;
@Test
public void testFindAll() {
List<User> userList = userMapper.findall();
userList.forEach(System.out::println);
}
@Test
public void testDelUser() {
Integer result = userMapper.delUser(4);
System.out.println("受影响的行数:" + result);
}
@Test
public void addUser(){
User user = new User(4,"小红",18);
userMapper.addUser(user);
}
@Test
public void updateUser(){
User user = new User(1,"彭于晏",23);
userMapper.updateUser(user);
}
@Test
public void findByNameAndAge(){
User user = userMapper.findByNameAndAge("小红", 18);
System.out.println(user);
}
}
XML映射配置
- 在
Mybatis中,既可以通过注解配置SQL语句,也可以通过XML配置文件配置SQL语句。 - 默认规则:
- XML映射文件的名称与Mapper接口名称一致,并且将XMl映射文件和Mapper接口放置在相同包下(同包同名)
- XML映射文件的namespace属性为Mapper接口全限定名一致。
- XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致。
java/com.itheima.Mapper/UserMapper.java
package com.itheima.Mapper;
import com.itheima.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserMapper {
/**
* 查询所有用户信息
*/
// @Select("select * from t_student")
public List<User> findall();
}
resources/com.itheima.Mapper/UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.Mapper.UserMapper">
<select id="findall" resultType="com.itheima.pojo.User">
select * from t_student
</select>
</mapper>
配置XML映射文件位置
配置application.properties
#指定XMl映射文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml
5.SpringBoot配置文件
在上述中,默认使用的是SpringBoot默认的配置文件application.properties配置文件,但是在大型项目配置文件过多,代码很多表现很臃肿,导致层级结构不清晰
所以推荐使用yml、yaml进行文件配置
例如下方是application.properties和application.yml的区别
application.properties
#配置数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
很明显可以看出yaml格式文件结构比较清晰
6.RestFul风格
REST(REpresentational State Transfer),表述行状态装换,它是一种软件架构风格。
🧾传统风格 API 示例
| 操作 | 方法 | URL | 描述 |
|---|---|---|---|
| 获取用户列表 | GET | /getUsers |
获取所有用户信息 |
| 获取用户信息 | GET | /getUser?id=123 |
获取指定 ID 的用户信息 |
| 创建用户 | POST | /createUser |
新建一个用户 |
| 更新用户 | POST | /updateUser |
更新用户信息 |
| 删除用户 | POST | /deleteUser |
删除用户 |
🌐 RESTful 风格 API 示例
| 操作 | 方法 | URL | 描述 |
|---|---|---|---|
| 获取用户列表 | GET | /users |
获取所有用户信息 |
| 获取用户信息 | GET | /users/123 |
获取指定 ID 的用户信息 |
| 创建用户 | POST | /users |
新建一个用户 |
| 更新用户 | PUT | /users/123 |
更新指定 ID 的用户信息 |
| 删除用户 | DELETE | /users/123 |
删除指定 ID 的用户 |
7. Tilas案例
在处理前端的同方式的请求参数,SpringBoot提供了不同的方式获取前端传参
环境准备
-
创建
SpringBoot工程,并引入web开发起步依赖、mybatis、mysql驱动、lombok -
创建数据库表
dept,在application.yml中配置数据库的基本信息dept数据库.sql
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for dept -- ---------------------------- DROP TABLE IF EXISTS `dept`; CREATE TABLE `dept` ( `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID, 主键', `name` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '部门名称', `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `name`(`name`) USING BTREE ) ENGINE = MyISAM AUTO_INCREMENT = 17 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of dept -- ---------------------------- INSERT INTO `dept` VALUES (1, '学工部', '2023-09-25 09:47:40', '2023-09-25 09:47:40'); INSERT INTO `dept` VALUES (2, '教研部', '2023-09-25 09:47:40', '2023-10-09 15:17:04'); INSERT INTO `dept` VALUES (3, '咨询888', '2023-09-25 09:47:40', '2025-04-16 11:59:39'); INSERT INTO `dept` VALUES (4, '就业部', '2023-09-25 09:47:40', '2023-09-25 09:47:40'); INSERT INTO `dept` VALUES (5, '人事部', '2023-09-25 09:47:40', '2023-09-25 09:47:40'); INSERT INTO `dept` VALUES (15, '行政部', '2023-11-30 20:56:37', '2023-11-30 20:56:37'); SET FOREIGN_KEY_CHECKS = 1;application.yml
spring: application: name: spring-tailis datasource: url: jdbc:mysql://127.0.0.1:3306/talis driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root -
准备基础代码结构,并引入实体类
Dept以及统一的响应结果封装类Result分别创建
controller、mapper、pojo、service、service/impl包并创建
controller/DeptController.javamapper/DeptMapper.javaservice/DeptService.javaservice/impl/DeptServiceImpl.java
pojo/Dept.java
package com.itheima.pojo; import lombok.Data; import java.time.LocalDateTime; @Data public class Dept { private Integer id; private String name; private LocalDateTime createTime; private LocalDateTime updateTime; }pojo/Result.java
package com.itheima.pojo; import lombok.Data; @Data public class Result { private Integer code;; private String msg; private Object data; public static Result success(){ Result result = new Result(); result.code = 1; result.msg = "success"; return result; } public static Result success(Object object){ Result result = new Result(); result.data = object; result.code = 1; result.msg = "success"; return result; } public static Result error(){ Result result = new Result(); result.code = 0; result.msg = "error"; return result; } public static Result error(Object object){ Result result = new Result(); result.data = object; result.code = 0; result.msg = "error"; return result; } }
列表查询
请求方式: Get
接口地址:/depts
采用MVC开发模式
注意:下方代码写了代码基础模板,含有@Autowired注入,后续将只写方法代码,不写外层类
分析数据库查询语句
select id,name,create_time,update_time from dept order by update_time desc
Controller代码
@RequestMapping("/depts")
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping
public Result list(){
System.out.println("查询全部的部门数据");
List<Dept> deptList = deptService.findall();
return Result.success(deptList);
}
}
Service接口和实现
public interface DeptService {
List<Dept> findall();
}
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public List<Dept> findall() {
return deptMapper.findAll();
}
}
Mapper接口
@Select("select id,name,create_time,update_time from dept order by update_time desc")
List<Dept> findAll();
由于数据库中使用
create_time下划线命名方式,在Java的Dept实体类中使用的是createTime驼峰命名方式,在实体类和数据库列映射时可能出现偏差,导致数据库查询出错。
解决方案:
-
开启一个
mybatis配置文件即可完成create_time == createTimeapplication.yml
mybatis: configuration: # 将mybatis日志输出到控制台 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启驼峰命名开关 map-underscore-to-camel-case: true -
SQL语句中
as别名@Select("select id,name,create_time createTime,update_time updateTime from dept order by update_time desc") -
手动结果映射
@Results({ @Result(column = "create_time",property = "createTime"), @Result(column = "update_time",property = "updateTime") })
删除接口
请求方式: Delete
接口地址: /dept/delete?id=1
通过RESTful风格路径,删除接口案例学习获取前端GET传参数据
Controller
-
前端使用
Delete -
Get传参
id=1 -
有三种方式获取参数
-
原生Servlet获取请求参数
// @DeleteMapping("/delete") public Result delete(HttpServletRequest request){ String idStr = request.getParameter("id"); int id = Integer.parseInt(idStr); System.out.println("删除部门的id:" + id); return Result.success(); -
使用spring提供的 @RequestParam 注解获取请求参数
一但声明了
@RequestParam注解,那么这个参数必须存在,否则会报错(默认required为true)@DeleteMapping("/delete") public Result delete(@RequestParam(value = "id",required = false) Integer id) { System.out.println("删除部门的id:" + id); return Result.success(); } -
省略 @RequestParam 注解,直接使用参数名称获取请求参数
条件: 请求参数名和方法参数名称相同即可
@DeleteMapping public Result delete(Integer id){ System.out.println("删除部门id:" + id); deptService.deleteById(id); return Result.success(); }
-
Service
service/DeptService.ava
void deleteById(Integer id);
service/impl/DeptServiceImpl.java
/**
* 根据id查询部门
* @param id
*/
@Override
public void deleteById(Integer id) {
deptMapper.deleteById(id);
}
Mapper
mapper/DeptMapper.java
/**
* 根据id删除部门
* @param id
*/
@Delete("delete from dept where id = #{id}")
void deleteById(Integer id);
新增部门
请求方式: Post
接口地址: /dept
Controller
/**
* 新增部门
*/
// 使用 @RequestBody 注解,将请求体中的json数据封装到 dept 对象中
// 注: @RequestBody 要求请求体中的键名要和 dept 对象的属性名一致
@PostMapping
public Result add(@RequestBody Dept dept){
System.out.println("新增部门" + dept);
deptService.add(dept);
return Result.success();
}
Service
/**
* 新增部门
*/
@Override
public void add(Dept dept) {
// 补全基础属性 - createTime, updatetime
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.add(dept);
}
Mapper
/**
* 新增部门
* @param dept
*/
@Insert("insert into dept (id, name, create_time, update_time) values (#{id},#{name},#{createTime},#{updateTime})")
void add(Dept dept);
修改部门
修改部门点击修改时会根据当前班级的用户id查询一次数据库,在编辑页面显示,所以修改部门需要两次数据库查询
- 点击编辑按钮,查询当前编辑用户信息并返回
- 修改用户信息,点击提交更改用户信息
首先实现根据id查询部门
请求方法: Get
请求路径: /depts/{id}
Controller
/**
* 修改部门,根据id修改
*/
// @GetMapping("/depts/{id}")
// public Result edit(@PathVariable("id") Integer deptid){
// System.out.println("修改部门的id: " + deptid);
// return Result.success();
// 若deptid的名称和路径参数的名称一致,可以直接使用@PathVariable注解获取路径参数
@GetMapping("/{id}")
public Result edit(@PathVariable Integer id){
System.out.println("修改部门的id: " + id);
Dept dept = deptService.getById(id);
return Result.success(dept);
}
// 如果请求路径为 /depts/{id}/{name}
// 可以使用 @PathVariable("id") Integer deptid,@PathVariable("name") 获取
Service
/**
* 根据id查询部门
* @param id
* @return
*/
@Override
public Dept getById(Integer id) {
return deptMapper.getById(id);
}
Mapper
/**
* 根据id查询部门
*
* @param id
* @return
*/
@Select("select id,name,create_time,update_time from dept where id = #{id}")
Dept getById(Integer id);
接着实现更改用户信息
请求方法: Put
请求地址: /depts
Controller
@PutMapping
public Result update(@RequestBody Dept dept){
System.out.println("修改的部门: " + dept);
deptService.update(dept);
return Result.success();
}
Service
/**
* 更新部门
* @param dept
*/
@Override
public void update(Dept dept) {
// 1.设置更新时间
dept.setUpdateTime(LocalDateTime.now());
// 2.调用mapper
deptMapper.update(dept);
}
Mapper
/**
* 更新部门
* @param dept
*/
@Update("update dept set name = #{name}, update_time = #{updateTime} where id = #{id}")
void update(Dept dept);
8.Logback日志
程序中的日志,是用来记录应用程序的运行信息、状态信息、错误信息等。
常用的日志技术:
-
JUL(java.util.logging):这是JavaSE平台提供的官方日志框架。配置相对简单,但不够灵活,性能较差。
-
Log4j:一个流行的日志框架,提供了灵活的配置选项,支持多种输出目标。
-
Logback:基于
log4j升级而来,提供了更多的功能和配置选项,性能优于Log4j(同一作者) -
Slf4j(Simple Logging Facade for Java):简单日志门面,提供了一套日志操作的标准接口及抽象类,允许应用程序使用不同的底层日志框架。
准备工作
-
引入
Logback依赖(springboot项目中该依赖已传递),配置logback.xml<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50}: 最长50个字符(超出.切割) %msg:日志消息,%n是换行符 --> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- 日志输出级别 --> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration> -
记录日志:定义日志记录对象,记录日志
private final static Logger log = LoggerFactory.getLogger(LogTest.class)
LogTest.java
package com.itheima;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
public class LogTest {
// 定义日志记录对象
private final static Logger log = LoggerFactory.getLogger(LogTest.class);
@Test
public void testLog(){
// System.out.println(LocalDateTime.now() + " : 开始计算...");
log.debug("开始计算...");
int sum = 0;
int[] nums = {1, 5, 3, 2, 1, 4, 5, 4, 6, 7, 4, 34, 2, 23};
for (int num : nums) {
sum += num;
}
log.info("计算结果为: " + sum);
// System.out.println("计算结果为: "+sum);
// System.out.println(LocalDateTime.now() + "结束计算...");
log.debug("结束计算...");
}
}
配置文件解析
配置文件: logback.xml
-
该配置文件是对Logback日志框架输出的日志进行控制的,可以来配置输出的格式、位置及日志开关等
-
常用的两种输出日志的位置: 控制台、系统文件
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">...</appender>
<!-- 系统文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">...</appender>
- 开启日志(ALL),关闭日志(OFF)
<!-- 日志输出级别 -->
<root level="ALL">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
完整日志案例
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%logger显示日志记录器的名称, %msg表示日志消息,%n表示换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</encoder>
</appender>
<!-- 系统文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志文件输出的文件名, %i表示序号 -->
<FileNamePattern>D:/tlias-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
<!-- 最多保留的历史日志文件数量 -->
<MaxHistory>30</MaxHistory>
<!-- 最大文件大小,超过这个大小会触发滚动到新文件,默认为 10MB -->
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</encoder>
</appender>
<!-- 日志输出级别 -->
<root level="ALL">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
日志级别
| 日志级别 | 说明 |
|---|---|
| TRACE | 最详细的日志,用于追踪程序运行的细节 |
| DEBUG | 调试级别日志,记录开发和调试过程中的信息 |
| INFO | 运行时正常状态的信息,如启动、关闭等 |
| WARN | 潜在的问题或警告,不影响程序运行 |
| ERROR | 错误信息,程序可能无法继续运行 |
| OFF | 关闭日志记录 |
-
级别从低到高为:TRACE < DEBUG < INFO < WARN < ERROR < OFF。
-
一旦设置了某个级别,例如
INFO,那么它只会记录INFO及以上级别(WARN、ERROR),不会记录DEBUG和TRACE。 -
OFF表示完全关闭日志记录。
使用
要在开发SpringBoot项目中使用,只需要在类上方加入@Slf4j注解,下方直接使用log.{level}()记录登记即可
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {
@GetMapping
public Result page(EmpQueryParam empQueryParam){
// 使用 Logback 记录日志
log.info("分页查询: {}",empQueryParam);
PageResult<Emp> pageResult = empService.page(empQueryParam);
return Result.success(pageResult);
}
}
建议一般设置日志登记为
Debug或者是Info级别
9.员工列表查询
请求路径:/emps
请求方式:GET
接口描述:该接口用于员工列表数据的条件分页查询
请求参数:
/emps?name=张&gender=1&begin=2007-09-01&end=2022-09-01&page=1&pageSize=10
响应格式:
{
"code": 1,
"msg": "success",
"data": {
"total": 2,
"rows": [
{
"id": 1,
"username": "jinyong",
"password": "123456",
"name": "金庸",
"gender": 1,
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
"job": 2,
"salary": 8000,
"entryDate": "2015-01-01",
"deptId": 2,
"deptName": "教研部",
"createTime": "2022-09-01T23:06:30",
"updateTime": "2022-09-02T00:29:04"
},
{
"id": 2,
"username": "zhangwuji",
"password": "123456",
"name": "张无忌",
"gender": 1,
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
"job": 2,
"salary": 6000,
"entryDate": "2015-01-01",
"deptId": 2,
"deptName": "教研部",
"createTime": "2022-09-01T23:06:30",
"updateTime": "2022-09-02T00:29:04"
}
]
}
}
准备工作:
- 准备数据库表 emp、emp_expr
- 准备实体类 EMP、EmpExpr
- 准备三层架构的基本代码结构: EmpController、EmpService/EmpServiceImpl、EmpMapper
数据表
-- 员工表
create table emp(
id int unsigned primary key auto_increment comment 'ID,主键',
username varchar(20) not null unique comment '用户名',
password varchar(32) default '123456' comment '密码',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别, 1:男, 2:女',
phone char(11) not null unique comment '手机号',
job tinyint unsigned comment '职位, 1 班主任, 2 讲师 , 3 学工主管, 4 教研主管, 5 咨询师',
salary int unsigned comment '薪资',
image varchar(255) comment '头像',
entry_date date comment '入职日期',
dept_id int unsigned comment '部门ID',
create_time datetime comment '创建时间',
update_time datetime comment '修改时间'
) comment '员工表';
INSERT INTO emp VALUES
(1,'shinaian','123456','施耐庵',1,'13309090001',4,15000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2000-01-01',2,'2023-10-20 16:35:33','2023-11-16 16:11:26'),
(2,'songjiang','123456','宋江',1,'13309090002',2,8600,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2015-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:37'),
(3,'lujunyi','123456','卢俊义',1,'13309090003',2,8900,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2008-05-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:39'),
(4,'wuyong','123456','吴用',1,'13309090004',2,9200,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2007-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:41'),
(5,'gongsunsheng','123456','公孙胜',1,'13309090005',2,9500,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2012-12-05',2,'2023-10-20 16:35:33','2023-10-20 16:35:43'),
(6,'huosanniang','123456','扈三娘',2,'13309090006',3,6500,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2013-09-05',1,'2023-10-20 16:35:33','2023-10-20 16:35:45'),
(7,'chaijin','123456','柴进',1,'13309090007',1,4700,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2005-08-01',1,'2023-10-20 16:35:33','2023-10-20 16:35:47'),
(8,'likui','123456','李逵',1,'13309090008',1,4800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2014-11-09',1,'2023-10-20 16:35:33','2023-10-20 16:35:49'),
(9,'wusong','123456','武松',1,'13309090009',1,4900,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2011-03-11',1,'2023-10-20 16:35:33','2023-10-20 16:35:51'),
(10,'linchong','123456','林冲',1,'13309090010',1,5000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2013-09-05',1,'2023-10-20 16:35:33','2023-10-20 16:35:53'),
(11,'huyanzhuo','123456','呼延灼',1,'13309090011',2,9700,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2007-02-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:55'),
(12,'xiaoliguang','123456','小李广',1,'13309090012',2,10000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2008-08-18',2,'2023-10-20 16:35:33','2023-10-20 16:35:57'),
(13,'yangzhi','123456','杨志',1,'13309090013',1,5300,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2012-11-01',1,'2023-10-20 16:35:33','2023-10-20 16:35:59'),
(14,'shijin','123456','史进',1,'13309090014',2,10600,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2002-08-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:01'),
(15,'sunerniang','123456','孙二娘',2,'13309090015',2,10900,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2011-05-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:03'),
(16,'luzhishen','123456','鲁智深',1,'13309090016',2,9600,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2010-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:05'),
(17,'liying','12345678','李应',1,'13309090017',1,5800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2015-03-21',1,'2023-10-20 16:35:33','2023-10-20 16:36:07'),
(18,'shiqian','123456','时迁',1,'13309090018',2,10200,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2015-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:09'),
(19,'gudasao','123456','顾大嫂',2,'13309090019',2,10500,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2008-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:11'),
(20,'ruanxiaoer','123456','阮小二',1,'13309090020',2,10800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2018-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:13'),
(21,'ruanxiaowu','123456','阮小五',1,'13309090021',5,5200,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2015-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:15'),
(22,'ruanxiaoqi','123456','阮小七',1,'13309090022',5,5500,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2016-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:17'),
(23,'ruanji','123456','阮籍',1,'13309090023',5,5800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2012-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:19'),
(24,'tongwei','123456','童威',1,'13309090024',5,5000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2006-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:21'),
(25,'tongmeng','123456','童猛',1,'13309090025',5,4800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2002-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:23'),
(26,'yanshun','123456','燕顺',1,'13309090026',5,5400,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2011-01-01',3,'2023-10-20 16:35:33','2023-11-08 22:12:46'),
(27,'lijun','123456','李俊',1,'13309090027',2,6600,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2004-01-01',2,'2023-10-20 16:35:33','2023-11-16 17:56:59'),
(28,'lizhong','123456','李忠',1,'13309090028',5,5000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2007-01-01',3,'2023-10-20 16:35:33','2023-11-17 16:34:22'),
(30,'liyun','123456','李云',1,'13309090030',NULL,NULL,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2020-03-01',NULL,'2023-10-20 16:35:33','2023-10-20 16:36:31'),
(36,'linghuchong','123456','令狐冲',1,'18809091212',2,6800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2023-10-19',2,'2023-10-20 20:44:54','2023-11-09 09:41:04');
-- 员工工作经历信息
create table emp_expr(
id int unsigned primary key auto_increment comment 'ID, 主键',
emp_id int unsigned comment '员工ID',
begin date comment '开始时间',
end date comment '结束时间',
company varchar(50) comment '公司名称',
job varchar(50) comment '职位'
)comment '工作经历';
导入实体类
pojo/Emp.java
package com.itheima.pojo;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class Emp {
private Integer id; //ID,主键
private String username; //用户名
private String password; //密码
private String name; //姓名
private Integer gender; //性别, 1:男, 2:女
private String phone; //手机号
private Integer job; //职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师
private Integer salary; //薪资
private String image; //头像
private LocalDate entryDate; //入职日期
private Integer deptId; //关联的部门ID
private LocalDateTime createTime; //创建时间
private LocalDateTime updateTime; //修改时间
}
pojo/EmpExpr.java
package com.itheima.pojo;
import lombok.Data;
import java.time.LocalDate;
/**
* 工作经历
*/
@Data
public class EmpExpr {
private Integer id; //ID
private Integer empId; //员工ID
private LocalDate begin; //开始时间
private LocalDate end; //结束时间
private String company; //公司名称
private String job; //职位
}
准备三层架构,创建实体类,加上注解,这些不再叙述
准备sql语句
// 查询所有员工信息,以及该员工所属的部门名称
select e.*, d.name from emp e left join dept d on e.dept_id = d.id
原始分页查询
通过分析,需要进行两条SQL语句
- 查询总记录数
- 分页查询
sql语句如下
查询总记录数
select count(*) from emp e left join dept d on e.dept_id = d.id
分页查询
select e.*, d.name deptName from emp e left join dept d on e.dept_id = d.id
order by update_time desc
limit 0,10;
根据响应格式创建PageResult返回封装类
pojo/PageResult.java
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PageResult<T> {
private Long total;
private List<T> rows;
}
Mapper
先开发Mapper接口
- 查询总记录数
- 分页查询
/**
* 查询总记录数
* @return
*/
@Select("select count(*) from emp e left join dept d on e.dept_id = d.id")
public Long count();
/**
* 分页查询
*/
@Select("select e.*, d.name deptName from emp e left join dept d on e.dept_id = d.id " + "order by update_time desc limit #{start},#{pageSize};")
public List<Emp> list(Integer start, Integer pageSize);
上述中,分页查询会返回一个name列,由于结构文档需要名称为deptName,所以使用别名更改为deptName
deptName是在dept表中,并不属于pojo/Emp.java实体类属性,但是最后返回结果封装给了Emp类,所以需要在pojo/Emp.java加入一个属性
pojo/Emp.java
private String deptName;
Controller
- 接受参数
- 调用Service
- 响应结果
@GetMapping
public Result page(@RequestParam(defaultValue = "1")Integer page,
@RequestParam(defaultValue = "10") Integer pageSize){
log.info("分页查询: {},{}",page,pageSize);
PageResult<Emp> pageResult = empService.list(page,pageSize);
return Result.success(pageResult);
}
Service
- 调用Mapper接口,查询总记录数
- 调用Mapper接口,查询结果列表
- 封装PageResult对象返回
/**
* 分页查询,原始方法
* @param page
* @param pageSize
* @return
*/
@Override
public PageResult<Emp> list(Integer page, Integer pageSize) {
//1. 调用Mapper接口,查询总记录数
Long total = empMapper.count();
//2. 调用Mapper接口,查询结果列表, -1 是因为 mysql的limit从0开始
Integer start = (page - 1) * pageSize;
List<Emp> rows = empMapper.list(start, pageSize);
//3. 封装结果到PageResult对象并返回
return new PageResult<Emp>(total, rows);
}
PageHelper插件分页查询
PageHelper是第三方提供的Mybatis框架中用来实现分页的插件,用来简化分页操作,提高开发效率。
- 使用步骤
- 引入PageHelper依赖
- 定义Mapper接口的查询方法(无需考虑分页)
- 在Service方法中实现分页查询
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
Mapper
@Select("select e.*, d.name deptName from emp e left join dept d on e.dept_id = d.id order by update_time desc")
public List<Emp> list();
Service
/**
* 使用PageHelper插件进行用户信息的分页查询
* @param page
* @param pageSize
* @return
*/
@Override
public PageResult<Emp> list(Integer page, Integer pageSize) {
// 1.设置分页参数
PageHelper.startPage(page,pageSize);
// 2.执行查询
List<Emp> empList = empMapper.list();
// 3.解析查询结果
Page<Emp> p = (Page<Emp>) empList;
return new PageResult<Emp>(p.getTotal(),p.getResult());
}
Controller不用更改
多条件分页查询
虽然完成了分页查询,该接口需要提供用户名、性别、最近修改时间进行查询
Controller
@GetMapping
public Result page(@RequestParam(defaultValue = "1")Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
String name, Integer gender,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
log.info("分页查询: {},{},{},{},{},{}",page,pageSize,name,gender,begin,end);
PageResult<Emp> pageResult = empService.page(page,pageSize,name,gender,begin,end);
return Result.success(pageResult);
}
Service
/**
* 动态条件分页查询
* @param page
* @param pageSize
* @param name
* @param gender
* @param begin
* @param end
* @return
*/
@Override
public PageResult<Emp> page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end) {
// 1.设置分页参数
PageHelper.startPage(page, pageSize);
// 2.执行查询
List<Emp> empList = empMapper.page(name, gender, begin, end);
// 3.解析查询结果并返回
Page<Emp> p = (Page<Emp>) empList;
return new PageResult<>(p.getTotal(), p.getResult());
}
Mapper
/**
* 动态条件分页查询
* @param name
* @param gender
* @param begin
* @param end
* @return
*/
List<Emp> page(String name, Integer gender, LocalDate begin, LocalDate end);
EmpMapper.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.itheima.mapper.EmpMapper">
<select id="page" resultType="com.itheima.pojo.Emp">
select e.*, d.name from emp e left join dept d on e.dept_id = d.id
<where>
<if test="name != null and name != ''">
e.name like concat('%',#{name},'%')
</if>
<if test="gender != null and gender != ''">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entry_date between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
</mapper>
多条件分页查询改造
上述中,在Controler接受参数处定义了很多的参数传递,由于参数较多,可以将请求参数封装到一个实体类中,方便代码编写,逻辑更清晰
创建EmpQueryParam实体类,封装请求参数
pojo/EmpQueryParam.java
package com.itheima.pojo;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
public class EmpQueryParam {
private Integer page = 1;
private Integer pageSize = 10;
String name;
Integer gender;
@DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate begin;
@DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate end;
}
Controller
/**
* 分页查询,使用Mybatis动态条件查询
* @param empQueryParam
* @return
*/
@GetMapping
public Result page(EmpQueryParam empQueryParam){
log.info("分页查询: {}",empQueryParam);
PageResult<Emp> pageResult = empService.page(empQueryParam);
return Result.success(pageResult);
}
Service
/**
* 使用PageHelper插件进行用户信息的分页查询
* @param empQueryParam
* @return
*/
@Override
public PageResult<Emp> page(EmpQueryParam empQueryParam) {
// 1.设置分页参数
PageHelper.startPage(empQueryParam.getPage(), empQueryParam.getPageSize());
// 2.执行查询
List<Emp> empList = empMapper.page(empQueryParam);
// 3.解析查询结果并返回
Page<Emp> p = (Page<Emp>) empList;
return new PageResult<>(p.getTotal(), p.getResult());
}
Mapper
/**
* 使用Mapper配置文件来实现分页查询
* @param empQueryParam
* @return
*/
List<Emp> page(EmpQueryParam empQueryParam);
EmpMapper.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.itheima.mapper.EmpMapper">
<select id="page" resultType="com.itheima.pojo.Emp">
select e.*, d.name from emp e left join dept d on e.dept_id = d.id
<where>
<if test="name != null and name != ''">
e.name like concat('%',#{name},'%')
</if>
<if test="gender != null and gender != ''">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entry_date between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
</mapper>
10.新增员工信息
新增员工需要增加员工信息和员工的工作经历信息,分别在两个表中存储,下方是接口需要系统的数据
请求路径:/emps
请求方式:POST
接口描述:该接口用于添加员工的信息
请求数据样例:
{
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-03-07-37-38222.jpg",
"username": "linpingzhi",
"name": "林平之",
"gender": 1,
"job": 1,
"entryDate": "2022-09-18",
"deptId": 1,
"phone": "18809091234",
"salary": 8000,
"exprList": [
{
"company": "百度科技股份有限公司",
"job": "java开发",
"begin": "2012-07-01",
"end": "2019-03-03"
},
{
"company": "阿里巴巴科技股份有限公司",
"job": "架构师",
"begin": "2019-03-15",
"end": "2023-03-01"
}
]
}
准备数据
创建pojo/EmpExpr.java
package com.itheima.pojo;
import lombok.Data;
import java.time.LocalDate;
/**
* 工作经历
*/
@Data
public class EmpExpr {
private Integer id; //ID
private Integer empId; //员工ID
private LocalDate begin; //开始时间
private LocalDate end; //结束时间
private String company; //公司名称
private String job; //职位
}
在pojo/Emp.java增加工作经历,最后返回结果封装到Emp实体类
package com.itheima.pojo;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {
// ......(之前的代码)
private List<EmpExpr> exprList;
}
Controller
@PostMapping
public Result add(@RequestBody Emp emp){
log.info("新增员工: {}",emp);
empService.add(emp);
return Result.success();
}
Service
/**
* 添加 用户信息 和 工作经历
* @param emp
*/
// 开启事务回滚
@Transactional(rollbackFor = {Exception.class})
@Override
public void add(Emp emp) {
// 1.新增用户基本信息
// 用户并有传递创建时间和修改时间,需要手动设置
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
empMapper.add(emp);
// 验证事务回滚
// Integer num = 1/0;
// 2.新增用户工作经历
List<EmpExpr> exprList = emp.getExprList();
if(!CollectionUtils.isEmpty(exprList)){
// 遍历集合,为 empId 赋值
exprList.forEach(empExpr ->{
empExpr.setEmpId(emp.getId());
});
empExprMapper.add(exprList);
}
}
Service层需要注意的是,新增用户工作经历时,前端并没有传入新增用户的id值,需要在新增用户基本信息时的id进行插入,可以使用Spingboot提供的主键返回注解,将id返回见下方
Mapper层注解Options(useGeneratedKeys = true, keyProperty = "id")
Mapper
mapper/EmpMapper.java
// Mybatis提供 主键返回,执行后
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into emp(username, name, gender, phone, job," +
" salary, image, entry_date, dept_id," +
" create_time, update_time)" +
" values (#{username}, #{name}, #{gender}, #{phone},#{job}, " +
" #{salary}, #{image}, #{entryDate}, #{deptId}, " +
"#{createTime}, #{updateTime})")
void add(Emp emp);
mapper/EmpExprMapper.java
package com.itheima.mapper;
@Mapper
public interface EmpExprMapper {
/**
* 新增员工工作经历
* @param exprList
*/
void add(List<EmpExpr> exprList);
}
EmpExprMapper.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.itheima.mapper.EmpExprMapper">
<!-- collection 集合名称-->
<!-- item 集合遍历出来的元素-->
<!-- separator 每次遍历使用的分隔符-->
<!-- open 遍历开始拼接的片段-->
<!-- close 遍历结束拼接的片段-->
<insert id="add">
insert into emp_expr(emp_id, begin, end, company, job) values
<foreach collection="exprList" item="expr" separator=",">
(#{expr.empId},#{expr.begin},#{expr.end},#{expr.company},#{expr.job})
</foreach>
</insert>
</mapper>
11.事物管理
介绍
例子: 上述新增员工信息中,若保存员工的基本信息成功了,保存工作经历失败了,怎么办?
就会造成数据库数据不完整、不一致。
事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
操作
事务控制主要三步操作:开启事务、提交事务/回滚事务
-- 开启事务
start transaction;
# 执行员工插入的sql语句
# ......
# ......
-- 提交事务
commit;
-- 回滚事务
rollback;
注意: 进行事务管理需要mysql数据库中数据表类型是InnoDB类型,使用下方命令查询/修改当前数据库类型
-- 查看当前表使用的存储引擎
SHOW TABLE STATUS LIKE 'your_table_name';
-- 查看数据库中所有表的引擎
SELECT table_name, engine
FROM information_schema.tables
WHERE table_schema = 'your_database_name';
-- 修改表的存储引擎为 InnoDB
ALTER TABLE your_table_name ENGINE = InnoDB;
由于在日常项目总进行事务管理,需要执行SQL语句,非常麻烦,于是SpringBoot提供了事务管理的的注解:
- 注解:
@Transactional - 作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
- 位置:业务(service)层的方法上、类上,接口上;推荐放到业务层方法上,避免不必要的资源浪费
由于默认设置日志级别为Info,JDBC连接默认使用的是Debug级别日志,所以需要单独配置显示日志
application.yml
logging:
level:
# 由于将日志级别设置为info不会显示debug日志,事务管理需要显示,所以仅显示事务管理的debug日志
org.springframework.jdbc.support.JdbcTransactionManager: debug
例如可以参考上方新增员工信息的Service层代码
/**
* 添加 用户信息 和 工作经历
* @param emp
*/
// 开启事务回滚
@Transactional(rollbackFor = {Exception.class})
@Override
public void add(Emp emp) {
// 1.新增用户基本信息
// 用户并有传递创建时间和修改时间,需要手动设置
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
empMapper.add(emp);
// 验证事务回滚
// Integer num = 1/0;
// 2.新增用户工作经历
List<EmpExpr> exprList = emp.getExprList();
if(!CollectionUtils.isEmpty(exprList)){
// 遍历集合,为 empId 赋值
exprList.forEach(empExpr ->{
empExpr.setEmpId(emp.getId());
});
empExprMapper.add(exprList);
}
}
上述代码中其实直接使用@Transactional注解就可以完成事务管理的操作。
所以为什么要加入rollbackFor = {Exception.class}参数?
-
rollbackFor执行出现何种异常,回滚事务 -
Exception.class表示所有异常
在直接使用
@Transcational注解时:默认出现RuntimeException才会回滚事务若手动会抛出异常,或出现其他类型异常,事务还是会进行提交,所以建议加上
rollbackFor = {Exceptin.class},遇到所有异常,回滚事务
12.文件上传
- 文件上传:是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程
- 文件上传在项目中应用非常广泛,我们经常发微博、微信朋友圈都用到了文件上传功能
文件上传
文件上传通常前端为一个form表单
<form action="/upload" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="name"> <br>
年龄:<input type="text" name="age"> <br>
图像:<input type="file" name="file"> <br>
<input type="submit" value="上传文件" name="submit">
</form>
这个表单具备上传文件所需的三个关键要素:
method="post":使用 POST 方法提交数据。enctype="multipart/form-data":编码类型用于文件上传。<input type="file" name="file">:用于选择文件的输入框。
本地文件上传
首先准备上传页面
resources/static/upload.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文件上传示例</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; background: #f5f5f5; display: flex;
justify-content: center; align-items: center; height: 100vh; }
.container { background: #fff; padding: 2rem; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); width: 320px; }
h1 { text-align: center; margin-bottom: 1.5rem; font-size: 1.5rem; }
form label { display: block; margin: 0.8rem 0 0.4rem; font-weight: bold; }
form input[type="text"],
form input[type="number"],
form button { width: 100%; padding: 0.6rem; border: 1px solid #ccc;
border-radius: 4px; }
form button { margin-top: 1.2rem; background: #007bff; color: #fff;
border: none; cursor: pointer; }
/* 隐藏原生 file input */
input[type="file"] { display: none; }
/* 自定义 file label 按钮 */
.custom-file-label { display: block; width: 100%; padding: 0.6rem;
background: #28a745; color: white; border-radius: 4px;
text-align: center; cursor: pointer; margin-top: 0.4rem; }
.file-name { margin-top: 0.4rem; font-size: 0.9rem; color: #555; }
</style>
</head>
<body>
<div class="container">
<h1>上传您的文件</h1>
<form action="/upload" method="POST" enctype="multipart/form-data">
<label for="username">用户名:</label>
<input type="text" id="username" name="name" required />
<label for="age">年龄:</label>
<input type="number" id="age" name="age" min="0" max="120" required />
<label for="file">选择文件:</label>
<label class="custom-file-label" for="file">点击选择文件</label>
<input type="file" id="file" name="file" required />
<div class="file-name" id="file-name">未选择文件</div>
<button type="submit">上传</button>
</form>
</div>
<script>
const fileInput = document.getElementById('file');
const fileNameDiv = document.getElementById('file-name');
fileInput.addEventListener('change', () => {
const name = fileInput.files.length > 0 ? fileInput.files[0].name : '未选择文件';
fileNameDiv.textContent = name;
});
</script>
</body>
</html>
创建UploadController
controller/UploadController.java
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String name, Integer age, MultipartFile file) throws IOException {
log.info("文件上传: {},{},{}",name,age,file);
// 获取上传文件名
String fileName = file.getOriginalFilename();
// 获取上传文件后缀名
// 使用substring截取字符串,使用lastIndexOf找到最后一个.的索引开始截取
String suffix = fileName.substring(fileName.lastIndexOf(".")); // .png
// 放置同名文件上传覆盖,使用UUID对图片进行重命名
String newFileName = UUID.randomUUID().toString() + suffix;
// 保存文件
file.transferTo(new File("E:/source/" + newFileName));
return Result.success();
}
}
注意:在SpringBoot项目中,默认单个文件上传大小不能超过1MB,超过需要调整配置文件
**application.yml
spring:
......
......
servlet:
multipart:
# 最大单个文件大小
max-file-size: 5MB
# 最大请求大小(包含表单和文件上传数据)
max-request-size: 10MB
阿里云OSS
准备
阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
参考阿里云官方文档
创建aliyun账号,创建Bucket,生成AccessKey
在本地环境变量设置AccessKey
# 设置
setx OSS_ACCESS_KEY_ID "YOUR_ACCESS_KEY_ID"
setx OSS_ACCESS_KEY_SECRET "YOUR_ACCESS_KEY_SECRET"
# 查看
echo %OSS_ACCESS_KEY_ID%
echo %OSS_ACCESS_KEY_SECRET%
添加依赖
# aliyunOSS依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
# JABX依赖
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
文件上传Demo
在测试目录创建AliyunDemo,从官方文档复制
AliyunDemo.java
package com.itheima;
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import java.io.ByteArrayInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class AliyunDemo {
public static void main(String[] args) throws Exception {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "https://oss-cn-beijing.aliyuncs.com";
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填写Bucket名称,例如examplebucket。
String bucketName = "junglezt";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String objectName = "test.png";
// 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
String region = "cn-beijing";
// 创建OSSClient实例。
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
OSS ossClient = OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(credentialsProvider)
.clientConfiguration(clientBuilderConfiguration)
.region(region)
.build();
try {
// 填写字符串。
String filePath = "D:\\图片\\【哲风壁纸】唯美背景-性感.png";
Path path = Paths.get(filePath);
byte[] content = Files.readAllBytes(path);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new ByteArrayInputStream(content));
// 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
// ObjectMetadata metadata = new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// metadata.setObjectAcl(CannedAccessControlList.Private);
// putObjectRequest.setMetadata(metadata);
// 上传字符串。
PutObjectResult result = ossClient.putObject(putObjectRequest);
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}
上述代码中,前面的变量填写基本信息,后面的代码重要的如下:
bucketName为变量配置的jungleztObjectName为上传后的文件名content为上传文件的内容(需要文件字节流)
// 前面的代码
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "https://oss-cn-beijing.aliyuncs.com";
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填写Bucket名称,例如examplebucket。
String bucketName = "junglezt";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String objectName = "test.png";
// 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
String region = "cn-beijing";
......
......
// 后面的代码
// 填写字符串。
String filePath = "D:\\图片\\【哲风壁纸】唯美背景-性感.png";
Path path = Paths.get(filePath);
byte[] content = Files.readAllBytes(path);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new ByteArrayInputStream(content));
新增员工信息(文件上传案例)
请求路径:/upload
请求方式:POST
接口描述:上传图片接口
请求参数:
参数格式:multipart/form-data
参数说明:
| 参数名称 | 参数类型 | 是否必须 | 示例 | 备注 |
|---|---|---|---|---|
| file | file | 是 |
响应数据样例:
{
"code": 1,
"msg": "success",
"data": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-0400.jpg"
}
在使用aliyunOSS上传文件时,前后端需要以下步骤
- 接收前端上传的图片
- 将图片存储起来(OSS)
- 返回图片访问的URL
案例实现步骤:
- 引入阿里云OSS文件上传工具类(根据官方代码改造)
- 上传文件接口开发
Utils
使用AI根据Demo类改造
package com.itheima.utils;
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
@Component
public class AliyunOSSOperator {
private String endpoint = "https://oss-cn-beijing.aliyuncs.com";
private String bucketName = "junglezt";
private String region = "cn-beijing";
public String upload(byte[] content, String fileName) throws com.aliyuncs.exceptions.ClientException {
// 从环境变量读取accessKey
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 设置上传文件名规则
// 获取当前年月,例如 202504
String folderName = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
// 获取文件后缀(扩展名)
String extension = "";
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex >= 0) {
extension = fileName.substring(dotIndex);
}
// 生成随机 UUID 文件名
String uuidFileName = UUID.randomUUID().toString().replace("-", "") + extension;
// 拼接最终路径
String ObjectName = folderName + "/" + uuidFileName;
// 创建OSSClient实例。
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
OSS ossClient = OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(credentialsProvider)
.clientConfiguration(clientBuilderConfiguration)
.region(region)
.build();
try {
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, ObjectName, new ByteArrayInputStream(content));
// 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
// ObjectMetadata metadata = new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// metadata.setObjectAcl(CannedAccessControlList.Private);
// putObjectRequest.setMetadata(metadata);
// 上传字符串。
PutObjectResult result = ossClient.putObject(putObjectRequest);
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + ObjectName;
}
}
该文件上传工具类需要传入文件字节流和文件名称,通过文件上传后,会返回一个图片的访问地址
Controller
@Slf4j
@RestController
public class UploadController {
@Autowired
private AliyunOSSOperator aliyunOSSOperator;
@PostMapping("/upload")
public Result upload(MultipartFile file) throws IOException, ClientException {
log.info("文件上传: {}", file);
String image_url = aliyunOSSOperator.upload(file.getBytes(), file.getOriginalFilename());
log.info("图片上传成功,图片地址为:{}",image_url);
return Result.success(image_url);
}
}
通过Controller获取文件字节流和文件名,将参数传入Utils工具类进行文件上传,将返回的image_url地址返回给前端
13.配置文件程序优化
@Value注解
上述在进行AliyunOss进行文件上传时,需要配置Bucket和endpoint等地址,为了方便项目的管理与维护,这些配置一般建议统一配置到application.yaml配置文件,需要使用@Value注解来实现
具体案例如下
原先的数据
@Component
public class AliyunOSSOperator {
private String endpoint = "https://oss-cn-beijing.aliyuncs.com";
private String bucketName = "junglezt";
private String region = "cn-beijing";
}
优化后的数据
@Component
public class AliyunOSSOperator {
// 通过@Value注解获取属性值
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
@Value("${aliyun.oss.region}")
private String region;
}
application.yml增加下方代码
#阿里云OSS
aliyun:
oss:
endpoint: https://oss-cn-beijing.aliyuncs.com
bucketName: junglezt
region: cn-beijing
@ConfigurationProperties
问题分析:虽然上述配置信息都配置到了application.yml文件中,但是每个属性都需要@Value进行注入一次,如果该配置需要注入的位置较多,也是不太方便代码的维护。
这时可以使用到另外一个注解:@ConfigurationProperties
原先的数据
@Component
public class AliyunOSSOperator {
private String endpoint = "https://oss-cn-beijing.aliyuncs.com";
private String bucketName = "junglezt";
private String region = "cn-beijing";
}
优化后的数据
创建Utils/AliyunOSSProperties.java实体类
package com.itheima.utils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
// prefix = "aliyun.oss" 表示读取application.yml文件中的 aliyun.oss 节点下的属性
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSProperties {
private String endpoint;
private String bucketName;
private String region;
}
在配置文件application.yml添加配置信息
#阿里云OSS
aliyun:
oss:
endpoint: https://oss-cn-beijing.aliyuncs.com
bucketName: junglezt
region: cn-beijing
修改源代码如下
//通过@ConfigurationProperties注解获取属性值
@Autowired private AliyunOSSProperties aliyunOSSProperties;
// 在类中的方法中使用get方法获取属性值
public String upload(byte[] content, String fileName) throws com.aliyuncs.exceptions.ClientException {
String endpoint = aliyunOSSProperties.getEndpoint();
String bucketName = aliyunOSSProperties.getBucketName();
String region = aliyunOSSProperties.getRegion();
}
总结
上述两种程序配置优化方式,都需要在application.yml文件中配置,根据配置使用的次数
- 配置使用较少:
@Value - 配置使用较多:
@ConfigurationProperties
14.删除员工信息
请求路径:/emps
请求方式:DELETE
接口描述:该接口用于批量删除员工的数据信息
请求参数样例:
/emps?ids=1,2,3
响应数据样例:
{
"code":1,
"msg":"success",
"data":null
}
进行员工删除时,含有单个员工删除和多个员工删除,在接口开发时,需要思考开发几个删除接口?
其实只需要开发一个批量删除的删除即可,因为单独的员工删除算是一个独特的批量员工删除
SQL语句分析
员工信息包含员工基本信息和员工的工作经历信息,所以需要两条sql语句
-- 删除员工基本信息
delete from emp where id in (1,2,3);
-- 删除员工工作经历信息
delete from emp_expr where emp_id in (1,2,3);
Controller
需要获取前端ids=1,2,3逗号分割方式的传参,以下两种方式
第一种:
@DeleteMapping
public Result delete(Integer[] ids){
log.info("删除员工: {}", Arrays.toString(ids));
return Result.success();
}
第二种:
// 使用@RequestParam配合List获取
@DeleteMapping
public Result delete(@RequestParam List<Integer> ids){
log.info("删除员工: {}", ids);
empService.delete(ids);
return Result.success();
}
@RequestParam List<Integer> ids这种格式可以获取下方两种方式的传参
GET /emps?ids=1,2,3GET /items?ids=1&ids=2&ids=3
Service
// 开启事务回滚
@Transactional(rollbackFor = {Exception.class})
@Override
public void delete(List<Integer> ids) {
// 1.批量删除用户信息
empMapper.delete(ids);
// 2.批量删除用户工作经历
empExprMapper.delete(ids);
}
Mapper
Mapper/EmpMapper.java
/**
* 根据 id 列表,批量删除用户
* @param ids
*/
void delete(List<Integer> ids);
EmpMapper.xml
<!-- 批量删除员工基本信息 (1,2,3) -->
<delete id="delete">
delete from emp where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
mapper/EmpExprMapper.java
/**
* 批量删除员工工作经历
* @param empIds
*/
void delete(List<Integer> empIds);
EmpExprMapper.xml
<!-- 批量删除员工工作经历信息 (1,2,3) -->
<delete id="delete">
delete from emp_expr where emp_id in
<foreach collection="empIds" item="empId" separator="," open="(" close=")">
#{empId}
</foreach>
</delete>
15.修改员工信息
分析:在点击编辑按钮修改员工信息的时候,需要查询一次当前修改用户的信息,方便修改,也就是根据当前编辑用户的id查询并返回当前用户的信息
那么修改员工信息分为两个步骤:
- 查询当前员工信息
- 修改员工信息
根据id查询当前员工信息
请求路径:/emps/{id}
请求方式:GET
接口描述:该接口用于根据主键ID查询员工的信息
请求参数样例:
/emps/1
响应数据样例:
{
"code": 1,
"msg": "success",
"data": {
"id": 2,
"username": "zhangwuji",
"password": "123456",
"name": "张无忌",
"gender": 1,
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
"job": 2,
"salary": 8000,
"entryDate": "2015-01-01",
"deptId": 2,
"createTime": "2022-09-01T23:06:30",
"updateTime": "2022-09-02T00:29:04",
"exprList": [
{
"id": 1,
"begin": "2012-07-01",
"end": "2019-03-03"
"company": "百度科技股份有限公司"
"job": "java开发",
"empId": 2
},
{
"id": 2,
"begin": "2019-3-15",
"end": "2023-03-01"
"company": "阿里巴巴科技股份有限公司"
"job": "架构师",
"empId": 2
}
]
}
}
Controller
/**
* 根据id查询员工信息
* @param id
* @return
*/
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id){
log.info("根据id查询员工信息: {}", id);
Emp emp = empService.getById(id);
return Result.success(emp);
}
Service
@Override
public Emp getById(Integer id) {
return empMapper.getById(id);
}
Mapper
/**
* 根据id查询用户信息,含用户工作经历
* @param id
* @return
*/
Emp getById(Integer id);
EmpMapper.xml
<!-- 定制resultMap -->
<resultMap id="empResultMap" type="com.itheima.pojo.Emp">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="name" property="name"/>
<result column="gender" property="gender"/>
<result column="phone" property="phone"/>
<result column="job" property="job"/>
<result column="salary" property="salary"/>
<result column="image" property="image"/>
<result column="entry_date" property="entryDate"/>
<result column="dept_id" property="deptId"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<collection property="exprList" ofType="com.itheima.pojo.EmpExpr">
<id column="ee_id" property="id"/>
<result column="ee_empid" property="empId"/>
<result column="ee_begin" property="begin"/>
<result column="ee_end" property="end"/>
<result column="ee_company" property="company"/>
<result column="ee_job" property="job"/>
</collection>
</resultMap>
<select id="getById" resultMap="empResultMap">
select
e.*,
ee.id ee_id,
ee.emp_id ee_empid,
ee.begin ee_begin,
ee.end ee_end,
ee.company ee_company,
ee.job ee_job
from emp e left join emp_expr ee on e.id = ee.emp_id
where e.id = #{id}
</select>
修改员工信息
请求路径:/emps
请求方式:PUT
接口描述:该接口用于修改员工的数据信息
请求数据样例:
{
"id": 2,
"username": "zhangwuji",
"password": "123456",
"name": "张无忌",
"gender": 1,
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
"job": 2,
"salary": 8000,
"entryDate": "2015-01-01",
"deptId": 2,
"createTime": "2022-09-01T23:06:30",
"updateTime": "2022-09-02T00:29:04",
"exprList": [
{
"id": 1,
"begin": "2012-07-01",
"end": "2015-06-20"
"company": "中软国际股份有限公司"
"job": "java开发",
"empId": 2
},
{
"id": 2,
"begin": "2015-07-01",
"end": "2019-03-03"
"company": "百度科技股份有限公司"
"job": "java开发",
"empId": 2
},
{
"id": 3,
"begin": "2019-3-15",
"end": "2023-03-01"
"company": "阿里巴巴科技股份有限公司"
"job": "架构师",
"empId": 2
}
]
}
响应数据样例:
{
"code":1,
"msg":"success",
"data":null
}
Controller
@PutMapping
public Result update(@RequestBody Emp emp){
log.info("修改员工信息: {}", emp);
empService.update(emp);
return Result.success();
}
Service
@Override
public void update(@RequestBody Emp emp) {
// 1.根据id更改用户信息
emp.setUpdateTime(LocalDateTime.now());
empMapper.update(emp);
// 2. 根据id修改工作经历
// 2.1根据id删除用户原有工作经历
empExprMapper.delete(Arrays.asList(emp.getId()));
// 2.2根据id添加工作经历
List<EmpExpr> exprList = emp.getExprList();
if(!CollectionUtils.isEmpty(exprList)){
exprList.forEach(empExpr -> empExpr.setEmpId(emp.getId()));
empExprMapper.add(exprList);
}
}
Mapper
/**
* 修改员工信息
* @param emp
*/
void update(Emp emp);
EmpMapper.xml
<!-- set标签会自动生成set关键字; 自动删除更新字段后多余的,-->
<update id="update">
UPDATE emp
<set>
<if test="username != null and username != ''">username = #{username},</if>
<if test="name != null and name != ''">name = #{name},</if>
<if test="gender != null">gender = #{gender},</if>
<if test="phone != null and phone != ''">phone = #{phone},</if>
<if test="job != null">job = #{job},</if>
<if test="salary != null">salary = #{salary},</if>
<if test="image != null and image != ''">image = #{image},</if>
<if test="entryDate != null">entry_date = #{entryDate},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</set>
WHERE id = #{id}
</update>
16.全局异常处理器
使用
@RestControllerAdvice修饰类使用
@ExceptionHandler修饰异常方法
exception/GlobalExceptionHandler.java
package com.itheima.exception;
import com.itheima.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public Result handlerException(Exception e){
log.error("捕获到异常: {}", e);
// log.error("捕获到异常: {}", e.getMessage()); // 仅获取报错的异常信息
return Result.error("出错啦,请联系管理员~");
}
@ExceptionHandler
public Result handlerDuplicateKeyException(DuplicateKeyException e){
log.error("捕获到异常: {}", e);
String message = e.getMessage();
int i = message.indexOf("Duplicate entry");
String errMsg = message.substring(i);
String[] s = errMsg.split(" ");
return Result.error(s[2] + "已存在");
}
}
17.员工职位人数统计
请求路径:/report/empJobData
请求方式:GET
接口描述:统计各个职位的员工人数
请求参数:
无
响应数据样例:
{
"code": 1,
"msg": "success",
"data": {
"jobList": ["教研主管","学工主管","其他","班主任","咨询师","讲师"],
"dataList": [1,1,2,6,8,13]
}
}
准备JobOption实体类
pojo/JobOption.java
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JobOption {
private List jobList;
private List dataList;
}
Controller
@GetMapping("/empJobData")
public Result getEmpJobData(){
log.info("统计员工职位人数");
JobOption jobOption = reportService.getEmpJobData();
return Result.success(jobOption);
}
Service
/**
* 查询员工职位人数
* @return
*/
@Override
public JobOption getEmpJobData() {
// 1.调用EmpMapper查询员工职位,职位人数
List<Map<String, Object>> list = empMapper.countEmpJobData(); // map: pos=班主任, num = 1
// 2.组装结果,并返回
List<Object> jobList = list.stream().map(dataMap -> dataMap.get("pos")).toList();
List<Object> dataList = list.stream().map(dataMap -> dataMap.get("num")).toList();
return new JobOption(jobList, dataList);
}
Mapper
/**
* 查询员工职位人数
* @return
*/
@MapKey("pos") // 防止mybatisx报错
List<Map<String, Object>> countEmpJobData();
EmpMapper.xml
<!-- 统计员工职位人数-->
<select id="countEmpJobData" resultType="java.util.Map">
select
(case job when 1 then '班主任'
when 2 then '讲师'
when 3 then '学工主管'
when 4 then '教研主管'
when 5 then '咨询师'
else '其他' end) pos,
count(*) num
from emp group by job order by num;
</select>
18.员工性别统计
请求路径:/report/empGenderData
请求方式:GET
接口描述:统计员工性别信息
请求参数:
无
响应数据样例:
{
"code": 1,
"msg": "success",
"data": [
{"name": "男性员工","value": 5},
{"name": "女性员工","value": 6}
]
}
Controller
@GetMapping("/empGenderData")
public Result getEmpGenderData(){
log.info("统计员工性别人数");
List<Map<String, Object>> genderList = reportService.getEmpGenderData();
return Result.success(genderList);
}
Service
/**
* 查询员工性别人数
* @return
*/
@Override
public List<Map<String, Object>> getEmpGenderData() {
return empMapper.getEmpGenderData();
}
Mapper
/**
* 查询员工性别人数
* @return
*/
@MapKey("gender") // 防止mybatisx报错
List<Map<String, Object>> getEmpGenderData();
EmpMapper.xml
<!-- 查询员工性别人数-->
<select id="getEmpGenderData" resultType="java.util.Map">
select
(
if(gender = 1,'男性员工','女性员工')
) name,
(count(*)) value
from emp group by gender;
</select>
19.登录接口
请求路径:/login
请求方式:POST
接口描述:该接口用于员工登录Tlias智能学习辅助系统,登录完毕后,系统下发JWT令牌。
请求数据样例:
{
"username": "jinyong",
"password": "123456"
}
响应数据样例:
{
"code": 1,
"msg": "success",
"data": {
"id": 2,
"username": "songjiang",
"name": "宋江",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJzb25namlhbmciLCJleHAiOjE2OTg3MDE3NjJ9.w06EkRXTep6SrvMns3w5RKe79nxauDe7fdMhBLK-MKY"
}
}
准备工作
准备LoginInfo响应结果实体类
pojo/LoginInfo.java
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginInfo {
private Integer id;
private String username;
private String name;
private String token;
}
准备JwtUtil工具类
utils/JwtUtil.java
package com.itheima.utils;
public class JwtUtil {
// 签名密钥
private static final String SECRET_KEY = "SnVuZ2xlenQ=";
// 过期时间:12小时(毫秒)
private static final long EXPIRATION = 12 * 60 * 60 * 1000;
/**
* 生成JWT令牌
*
* @param claims 要封装在token中的数据
* @return 返回生成的JWT字符串
*/
public static String generateJwt(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims) // 设置自定义声明
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) // 设置过期时间
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 使用HMAC-SHA算法签名
.compact(); // 构建JWT字符串
}
/**
* 解析JWT令牌
*
* @param token 要解析的JWT字符串
* @return 返回解析后的Claims对象
*/
public static Claims parseJwt(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY) // 设置签名密钥
.parseClaimsJws(token) // 解析JWT
.getBody(); // 获取payload部分
}
}
后续详细介绍JwtToken
Controller
package com.itheima.controller;
@Slf4j
@RestController
@RequestMapping("/login")
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping
public Result login(@RequestBody Emp emp){
log.info("登录信息: {}",emp);
LoginInfo loginInfo = empService.login(emp);
// 1.如果返回数据不为空
if(loginInfo != null){
return Result.success(loginInfo);
}
// 2.返回空数据
return Result.error("用户名或密码错误");
}
}
Service
/**
* 用户登录
* @param emp
* @return
*/
@Override
public LoginInfo login(Emp emp) {
// 1.调用EmpMapper查询用户
Emp e = empMapper.getByUsernameAndPassword(emp);
// 2.判断该用户是否存在,若存在,返回封装用户信息
if(e != null){
log.info("登录用户信息: {}",e);
// 生成jwt token
Map<String,Object> claims = new HashMap<>();
claims.put("id",e.getId());
claims.put("username",e.getUsername());
String jwt = JwtUtil.generateJwt(claims);
return new LoginInfo(e.getId(),e.getUsername(),e.getName(),jwt);
}
// 3.不存在返回空
return null;
}
Mapper
@Select("select id,username,name from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);
20.会话技术介绍
现在前端常用的会话技术如下:
CookieSession令牌(Jwt)
Cookie 会话
1. 原理
- 响应头:
Set-Cookie - 请求头:
Cookie
2. 优缺点
✅ 优点:
- HTTP 协议原生支持,兼容性强
❌ 缺点:
- 移动端 App 无法使用 Cookie
- 不安全,用户可以手动禁用 Cookie
- Cookie 不能跨域使用
Session 会话
1. 原理
- Session 的底层依赖于 Cookie(通过
Set-Cookie与Cookie实现标识传递)
2. 优缺点
✅ 优点:
- 数据存储在服务端,更加安全
❌ 缺点:
- 在服务器集群环境下,Session 共享困难(需额外处理)
- 仍然依赖 Cookie,因此继承了 Cookie 的部分缺点
令牌会话
优点 ✅
- 支持 PC 端与移动端,适应性强
- 能有效解决服务器集群环境下的认证问题
- 减轻服务器端的存储压力(无状态)
缺点 ❌
- 需要开发者自行实现令牌机制(如生成、验证、刷新等)
21.Jwt令牌
介绍
全称:
JSON Web Token(https://jwt.io/)
定义了一种简洁的、自包含的格式,用于在通信双方以json的数据格式安全的传输信息
组成:
- 第一部分:Hedaer(头),记录令牌类型签名算法等。例如:
{"alg":"HS256","type","JWT"} - 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:
{"id":"1","username":"Tom"} - 第三部分: Signature(签名),放置Token被篡改、确保安全性。将header、payload融入,并加入指定密钥,通过指定签名算法计算而来。
具体形式如下:前两段使用Base64编码
eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImV4cCI6MTc0ODYwODQ5MH0.ZNBWg8-otaJyXtvfLVZMFKVIlI1F3DgsVrogVzoHTWA
想要使用Jwt需要引入依赖
<!-- jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
Jwt测试
package com.itheima;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtTest {
/**
* 生成JWT令牌
*/
@Test
public void testGenerateJwt(){
// 定义Map对象
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id",1);
dataMap.put("username","admin");
// SnVuZ2xlenQ= 为 Junglezt的Base64编码
String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "SnVuZ2xlenQ=")// 签名算法
.addClaims(dataMap) // 添加数据
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 设置过期时间
.compact();// 生成JWT
System.out.println(jwt);
}
/**
* 解析JWT令牌
*/
@Test
public void testParseJwt(){
String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImV4cCI6MTc0NzEwNTc2OH0.EEDQSTuVKAI3iHevDnRioJqJibAkJkWE273YLwfDPcw";
Claims claims = Jwts.parser().setSigningKey("SnVuZ2xlenQ=")
.parseClaimsJws(jwt) // 解析JWT令牌
.getBody(); // 获取令牌中自定义信息
System.out.println(claims);
}
}
JwtUtils
package com.itheima.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
// 签名密钥
private static final String SECRET_KEY = "SnVuZ2xlenQ=";
// 过期时间:12小时(毫秒)
private static final long EXPIRATION = 12 * 60 * 60 * 1000;
/**
* 生成JWT令牌
*
* @param claims 要封装在token中的数据
* @return 返回生成的JWT字符串
*/
public static String generateJwt(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims) // 设置自定义声明
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) // 设置过期时间
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 使用HMAC-SHA算法签名
.compact(); // 构建JWT字符串
}
/**
* 解析JWT令牌
*
* @param token 要解析的JWT字符串
* @return 返回解析后的Claims对象
*/
public static Claims parseJwt(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY) // 设置签名密钥
.parseClaimsJws(token) // 解析JWT
.getBody(); // 获取payload部分
}
}
提供了一个生成和解析Jwt令牌的方法
22.Filter过滤器
介绍
Filter过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一。
过滤器可以把对自愿的请求拦截下来,从而实现一些特殊的功能。
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
Filter快速入门
- 定义Filter:定义一个类,实现Filter接口,并实现其所有方法。
- 配置Filter:Filter类上加
@WebFilter注解,配置拦截路径。引导类上加@ServletComponentScan开启Servlet组件支持。
filter/DemoFilter.java
package com.itheima.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
//@WebFilter("/*") // 拦截所有请求
@Slf4j
public class DemoFilter implements Filter {
@Override
// 初始化方法,启动时执行
public void init(FilterConfig filterConfig) throws ServletException {
log.info("init Filter初始化方法");
}
@Override
// 过滤方法,每次请求时执行
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("拦截请求");
// 放行请求
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
// 销毁方法,关闭时执行
public void destroy() {
log.info("销毁方法");
}
}
Filter令牌校验
filter/TokenFilter.java
package com.itheima.filter;
@WebFilter(urlPatterns = "/*")
@Slf4j
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 1.获取请求路径
String requestURI = request.getRequestURI();
// 2.判断路径是否包含/login,如果是,放行
if(requestURI.contains("/login")){
log.info("登录操作,放行...");
filterChain.doFilter(request,response);
return;
}
// 3.获取请求头中的token
String token = request.getHeader("token");
// 4.判断token是否为空,返回401状态码
if(token == null || token.isEmpty()){
log.info("token为空");
response.setStatus(401);
return;
}
// 5.校验token准确性,不通过返回401状态码
try{
JwtUtil.parseJwt(token);
} catch (Exception e) {
log.info("token校验不通过");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// 6.放行
log.info("token校验通过,放行...");
filterChain.doFilter(request,response);
}
}
23.Interceptor拦截器
介绍
- 拦截器是一种动态拦截方法调用机制,类似于过滤器。Spring框架中提供,主要用来动态拦截控制器方法的执行。
- 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
Interceptor快速入门
- 定义拦截器,实现
HandlerInterceptor接口,并实现其所有方法。 - 注册拦截器
interceptor/DemoInterceptory.java
package com.itheima.interceptor;
@Slf4j
@Component
public class DemoInterceptor implements HandlerInterceptor {
// 在controller执行之前执行,return true表示放行,false表示拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("执行preHandle...");
return false;
}
// controller执行之后,渲染视图之前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("执行postHandle...");
}
// 渲染视图之后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("执行afterCompletion");
}
}
config/WebConfig.java
package com.itheima.config;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private DemoInterceptor demoInterceptor;
// 拦截器注册
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截所有请求
registry.addInterceptor(demoInterceptor).addPathPatterns("/**");
}
}
Interceptor令牌校验
定义拦截器
interceptor/TokenInterceptor.java
package com.itheima.interceptor;
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求路径
String requestURI = request.getRequestURI();
// 2.判断路径是否包含/login,如果是,放行
if(requestURI.contains("/login")){
log.info("登录操作,放行...");
return true;
}
// 3.获取请求头中的token
String token = request.getHeader("token");
// 4.判断token是否为空,返回401状态码
if(token == null || token.isEmpty()){
log.info("token为空");
response.setStatus(401);
return false;
}
// 5.校验token准确性,不通过返回401状态码
try{
JwtUtil.parseJwt(token);
} catch (Exception e) {
log.info("token校验不通过");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 6.放行
log.info("token校验通过,放行...");
return true;
}
}
注册拦截器
config/Webconfig.java
package com.itheima.config;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
// 拦截器注册
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截所有请求
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
}
}
24.SpringAOP
AOP: Aspect Oriented Proggramming(面向切面编程、面相方面编程),可简单理解为就是面向特定的方法编程。
场景:案例中部分业务方法运行较慢,定位执行耗时较长的方法,此时需要统计每一个业务方法的执行耗时。
原始解决方案: 如果在默认场景,我们需要在每一个业务逻辑代码之前记录代码执行的时间,然后再业务逻辑之后再次记录代码执行的时间。
如果只有一两个业务方法还好,若是在项目中,几百上千个业务方法,就需要使用SpringAOP技术,减少重复代码,提高开发效率,方便业务维护。
AOP基础
统计所有业务层方法的执行耗时
- 导入依赖:在
pom.xml中引入AOP依赖
<!-- spring-aop 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 编写AOP程序:针对于对顶的业务方法根据业务需要进行编程
aop/RecordTimeAspect.java
package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect // 标识当前是一个AOP类
@Slf4j
@Component
public class RecordTimeAspect {
// 针对于 com.itheima.service.impl 包下的所有方法
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
// 1. 获取方法运行开始时间
long begin = System.currentTimeMillis();
// 2. 执行原始的方法
Object result = pjp.proceed();
// 3. 记录方法的结束时间,计算耗时
long end = System.currentTimeMillis();
// getSignature() 获取方法签名(方法执行的完整路径还有传参)
log.info("方法{} 执行耗时:{}ms", pjp.getSignature(),end - begin);
return result;
}
}

浙公网安备 33010602011771号