事务管理、文件上传、员工管理

Spring 事务管理

Created: 8/19/2025 15:12
Updated: 8/29/2025 16:03
Exported: 8/29/2025 16:05
Link: https://chatgpt.com/c/68a423e1-3a30-832d-8144-ee740f9322fb

Prompt:Spring事务管理

Response:

Spring 的**事务管理(Transaction Management)**是企业应用开发里非常核心的功能之一,尤其是在涉及数据库操作时。它的主要目标是:保证数据的一致性、完整性,并简化开发者对事务的处理。下面我给你系统性梳理一下:


1. 什么是事务

事务(Transaction)是一组操作的集合,要么全部执行成功,要么全部回滚失败。数据库事务满足 ACID 特性

  • A(Atomicity,原子性):事务是最小的操作单位,不可再分。
  • C(Consistency,一致性):事务执行前后,数据必须处于一致状态。
  • I(Isolation,隔离性):并发事务之间相互隔离。
  • D(Durability,持久性):事务提交后数据必须持久化。

2. Spring 事务管理的方式

Spring 支持两类事务管理:

  1. 编程式事务管理
    • 开发者自己在代码里使用 TransactionTemplatePlatformTransactionManager 来控制事务的开始、提交和回滚。
    • 灵活但冗余,常用于非常复杂的场景。
  2. 声明式事务管理(推荐)
    • 基于 AOP(切面编程),通过 @Transactional 注解或 XML 配置声明事务。
    • 简单、清晰、主流用法。

3. 核心接口

Spring 事务抽象了底层事务实现,常用接口:

  • PlatformTransactionManager:事务管理器的顶层接口。常见实现:
    • DataSourceTransactionManager:基于 JDBC。
    • JpaTransactionManager:基于 JPA。
    • HibernateTransactionManager:基于 Hibernate。
  • TransactionDefinition:定义事务的属性(隔离级别、传播行为、超时、是否只读)。
  • TransactionStatus:事务的运行状态。

4. @Transactional 注解的常用属性

1
2
3
4
5
6
7
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.READ_COMMITTED, // 隔离级别
timeout = 30, // 超时时间(秒)
readOnly = false, // 是否只读
rollbackFor = Exception.class // 指定回滚的异常
)
  • 传播行为(propagation):定义方法在调用事务时如何参与事务。
    • REQUIRED(默认):有事务就加入,没有就新建。
    • REQUIRES_NEW:总是新建事务。
    • MANDATORY:必须在事务中执行,否则报错。
    • NESTED:嵌套事务。
  • 隔离级别(isolation):解决脏读、不可重复读、幻读。
    • READ_UNCOMMITTED
    • READ_COMMITTED(常用)
    • REPEATABLE_READ
    • SERIALIZABLE(最高,性能差)

5. Spring 声明式事务示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class UserService {

@Autowired
private UserRepository userRepository;

@Transactional(rollbackFor = Exception.class)
public void createUser(User user) {
userRepository.save(user);
if (true) {
throw new RuntimeException("模拟异常,触发回滚");
}
}
}

6. Spring 事务注意点

  1. @Transactional 只对 public 方法有效
    因为底层用代理(JDK 动态代理或 CGLIB)实现。
  2. 自调用(同类方法互调)事务失效
    因为没有经过代理增强。
  3. 默认只对 RuntimeException 回滚
    若要对 checked 异常回滚,需要指定 rollbackFor
  4. 多数据源事务
    需要用 JTA 或分布式事务框架(如 Seata)。

7. 典型应用场景

  • 用户注册时插入多张表(用户表、角色表、日志表)。
  • 电商下单时:扣库存、减余额、生成订单,任何一步失败就整体回滚。

要不要我帮你画一个 Spring 声明式事务管理的执行流程图(从 @TransactionalPlatformTransactionManager 再到数据库)?这样会更直观。

Prompt:事务的传播行为控制的是什么

Response:

好问题 👍

事务的传播行为(Propagation)控制的是:
👉 当一个方法在调用时遇到已经存在的事务,它该如何处理自己的事务边界。

换句话说,就是 方法 A 有事务,方法 B 也需要事务,当 A 调用 B 时,B 的事务要不要加入 A 的事务?还是要新开一个事务?还是直接报错?


常见传播行为解析

Spring 定义了 7 种传播行为(Propagation 枚举):

1. REQUIRED(默认)

  • 有事务:加入当前事务。
  • 没有事务:新建一个事务。
    🔹 最常用的,保证调用方和被调用方在一个事务中。

2. REQUIRES_NEW

  • 有事务:挂起当前事务,新建一个事务。
  • 没有事务:新建一个事务。
    🔹 典型场景:日志记录。主事务失败回滚,但日志也要单独提交。

3. NESTED

  • 有事务:在当前事务中再开启一个“子事务”(用保存点实现)。外部回滚会导致子事务回滚,但子事务失败不会影响外部事务。
  • 没有事务:新建事务。
    🔹 适合需要部分回滚的业务。

4. MANDATORY

  • 有事务:加入当前事务。
  • 没有事务:抛出异常。
    🔹 强制要求必须在事务中调用。

5. SUPPORTS

  • 有事务:加入当前事务。
  • 没有事务:以非事务方式运行。
    🔹 常用于读操作(可有可无)。

6. NOT_SUPPORTED

  • 有事务:挂起当前事务,以非事务方式运行。
  • 没有事务:非事务运行。
    🔹 用于不希望有事务的操作,比如查询缓存。

7. NEVER

  • 有事务:抛出异常。
  • 没有事务:以非事务方式运行。
    🔹 强制要求不能在事务里运行。

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;

@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
// 开启事务
saveOrder();

// 调用支付
paymentService.pay();

// 如果 pay 出错,这里会整体回滚
}
}
  • 如果 pay() 标注的是 REQUIRED
    → 加入到 createOrder() 的事务,一起提交或回滚。
  • 如果 pay() 标注的是 REQUIRES_NEW
    → 单独事务,createOrder() 回滚不会影响 pay()
  • 如果 pay() 标注的是 NESTED
    → 内部事务失败可以单独回滚,外部事务成功仍可提交。

✅ 总结一句话:
传播行为控制的是事务方法在嵌套调用时,是否复用、挂起、还是新建事务。


要不要我帮你画一个 事务传播行为的对比表格(包含是否复用事务、新建事务、挂起事务的情况)?这样查起来会很方便。

Prompt:springboot项目中实现文件上传方法

Response:

在 Spring Boot 项目中实现文件上传其实很简单,Spring 已经帮我们封装好了上传的接口。下面我给你分步骤说明,并提供示例代码。


1. 基础依赖

Spring Boot 的 spring-boot-starter-web 里已经包含了上传所需的依赖,不需要额外引。
如果你用的是 Maven,保证有这个依赖即可:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. application.yml 配置

Spring Boot 默认支持上传文件,可以配置上传的大小限制:

1
2
3
4
5
spring:
servlet:
multipart:
max-file-size: 10MB # 单个文件大小限制
max-request-size: 20MB # 总请求大小限制

3. Controller 代码示例

使用 MultipartFile 接收上传文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@RestController
@RequestMapping("/upload")
public class FileUploadController {

@PostMapping
public String upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "上传失败,文件为空!";
}

// 获取文件名
String fileName = file.getOriginalFilename();
// 保存路径(这里存到本地 D:/upload/)
String filePath = "D:/upload/";
File dest = new File(filePath + fileName);

// 确保目录存在
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}

try {
// 保存文件
file.transferTo(dest);
return "上传成功,文件路径:" + dest.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
return "上传失败!";
}
}
}

4. 前端调用方式

  • 使用 Postman / curl 测试:
1
curl -F "file=@C:/Users/xxx/Desktop/test.png" http://localhost:8080/upload
  • HTML 表单测试:
1
2
3
4
<form method="post" enctype="multipart/form-data" action="http://localhost:8080/upload">
<input type="file" name="file"/>
<button type="submit">上传</button>
</form>

5. 进阶:多文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@PostMapping("/multi")
public String uploadMulti(@RequestParam("files") MultipartFile[] files) {
StringBuilder result = new StringBuilder();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
String fileName = file.getOriginalFilename();
try {
file.transferTo(new File("D:/upload/" + fileName));
result.append(fileName).append(" 上传成功;");
} catch (IOException e) {
result.append(fileName).append(" 上传失败;");
}
}
}
return result.toString();
}

📌 总结:

  • MultipartFile 接收上传文件
  • transferTo() 保存到指定目录
  • 配置 spring.servlet.multipart.max-file-sizemax-request-size 控制大小

Prompt:参数配置化是什么

Response:

这个问题问得很好 👍

参数配置化,通俗地说,就是 把程序中写死(hardcode)的参数提取出来,通过配置文件、数据库或者环境变量来管理,从而做到参数可修改、可扩展,而不用改动代码、重新编译部署。


🔑 为什么要做参数配置化

  1. 避免硬编码:比如 URL、用户名密码、限流阈值、文件路径等如果写在代码里,修改一次就要重新打包、上线。
  2. 提升灵活性:只改配置文件,不动代码,就能快速调整参数。
  3. 支持多环境:开发、测试、生产环境的参数不同,可以通过配置文件区分。
  4. 方便运维:参数变化交给配置管理(如 Nacos、Apollo、Spring Cloud Config),减少开发介入。

⚙️ 在 Spring Boot 中的典型用法

1. 配置文件 application.yml / application.properties

1
2
3
4
oss:
endpoint: oss-cn-shenzhen.aliyuncs.com
bucket-name: my-bucket
folder: upload/

读取方式:

1
2
3
4
5
6
7
8
@Component
@ConfigurationProperties(prefix = "oss")
public class OssProperties {
private String endpoint;
private String bucketName;
private String folder;
// getter/setter
}

这样,OSS 工具类不需要硬编码 endpoint、bucketName,而是从配置文件读取,后期切换环境只改配置文件。


2. @Value 注解

1
2
@Value("${oss.endpoint}")
private String endpoint;

直接从配置文件取值。


3. 动态配置中心(进阶)

结合 Nacos / Apollo / Spring Cloud Config,可以做到配置热更新,运维在配置中心修改参数,服务无需重启就能生效。


📌 举个例子

假设你写了一个“最大上传文件大小”的限制:

❌ 硬编码方式:

1
2
3
if (file.getSize() > 10 * 1024 * 1024) { // 10MB
throw new RuntimeException("文件过大");
}

✅ 配置化方式:

1
2
file:
max-size: 10MB
1
2
@Value("${file.max-size}")
private String maxFileSize;

这样,想改成 20MB 时,不需要改代码,只需改配置文件


🔖 总结一句话

参数配置化就是把系统运行时需要的可变参数抽离到配置层,避免硬编码,使系统更灵活、可维护、可扩展。


要不要我帮你整理一份 Spring Boot 项目里常见需要配置化的参数清单(比如数据库、缓存、文件上传、消息队列、限流阈值等),方便你以后做项目时参考?

Prompt:对象没有被 Spring 管理和注入成功

2025-08-19 18:11:35.489 ERROR 21720 — [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke “com.example.pojo.AliyunOSSProperties.getEndpoint()” because “this.aliyunOSSProperties” is null] with root cause

java.lang.NullPointerException: Cannot invoke “com.example.pojo.AliyunOSSProperties.getEndpoint()” because “this.aliyunOSSProperties” is null

Response:

这个报错很典型:

1
2
java.lang.NullPointerException: Cannot invoke "com.example.pojo.AliyunOSSProperties.getEndpoint()" 
because "this.aliyunOSSProperties" is null

意思是:你的 AliyunOSSProperties 对象没有被 Spring 管理和注入成功,所以在用它时是 null


🔎 常见原因 & 解决方法

1. 忘了加 @Component / @ConfigurationProperties

如果你是这样写的配置类:

1
2
3
4
5
6
7
8
public class AliyunOSSProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
private String folder;
// getter/setter
}

那么它只是一个普通的 POJO,Spring 并不会自动管理。
✅ 正确写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
private String folder;
// getter/setter
}

2. 忘了在使用的地方加 @Autowired

假设你在工具类里写了:

1
2
3
4
5
6
7
public class OssUtil {
private AliyunOSSProperties aliyunOSSProperties;

public String upload(MultipartFile file) {
String endpoint = aliyunOSSProperties.getEndpoint(); // 报 NPE
}
}

因为 AliyunOSSProperties 没有被注入。
✅ 正确写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class OssUtil {

@Autowired
private AliyunOSSProperties aliyunOSSProperties;

public String upload(MultipartFile file) {
String endpoint = aliyunOSSProperties.getEndpoint();
// ...
}
}

3. 工具类自己 new 出来的(没走 Spring 管理)

如果你写了:

1
2
OssUtil ossUtil = new OssUtil();
ossUtil.upload(file);

这样 AliyunOSSProperties 不会注入,仍然是 null
✅ 正确写法:让 Spring 容器管理

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class OssController {

@Autowired
private OssUtil ossUtil;

@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
return ossUtil.upload(file);
}
}

📝 总结排查步骤

  1. AliyunOSSProperties 类是否加了 @Component + @ConfigurationProperties(prefix="aliyun.oss")
  2. application.yml 中是否有对应的配置项(路径和前缀要对)。
  3. OssUtil 是否由 Spring 管理(加了 @Component),并用 @Autowired 注入 AliyunOSSProperties
  4. Controller 是否通过 @Autowired 注入 OssUtil,而不是 new 出来。

要不要我帮你给出一个 完整可运行的 Spring Boot 阿里云 OSS 上传示例(包括配置类、工具类、Controller)?这样你可以直接对比代码找问题。

Prompt:List和interger[]区别

Response:

好 👌,我们来对比一下 List<Integer>Integer[] 的区别:


1️⃣ 类型本质

  • List<Integer>
    是一个 接口(常见实现:ArrayList<Integer>LinkedList<Integer>)。属于 Java 集合框架,是一个动态数组/链表。
  • Integer[]
    是一个 数组,Java 的基本容器类型,长度固定。

2️⃣ 长度变化

  • **List<Integer>**:长度可变,添加、删除元素非常方便。
    1
    2
    3
    4
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.remove(0); // 删除第1个元素
  • **Integer[]**:长度固定,初始化后不能改变长度。
    1
    2
    3
    4
    Integer[] arr = new Integer[2];
    arr[0] = 1;
    arr[1] = 2;
    // arr[2] = 3; // 报错:数组越界

3️⃣ 存储方式

  • 数组 (Integer[])
    存储在连续的内存空间,访问效率高(O(1) 下标访问)。
  • 集合 (List<Integer>)
    底层通常也是数组(比如 ArrayList),但会自动扩容(大约 1.5 倍扩容),灵活性更强。

4️⃣ 功能差异

  • 数组:只提供最基本的存储、索引访问。
  • List:提供了丰富的 API,如 add/remove/contains/sort/stream 等。
    1
    2
    3
    list.contains(2);   // 是否包含元素 2
    list.sort(null); // 排序
    list.stream().map(x -> x*2).toList(); // Stream操作

5️⃣ 泛型支持

  • **List<Integer>**:支持泛型,能避免类型转换。
  • **Integer[]**:数组也能定义泛型类型,但不如集合灵活。

⚠️ 注意:数组是协变的,泛型是不可协变的

1
2
3
4
5
Integer[] arr = new Integer[10]; 
Number[] numArr = arr; // ✅ 允许,数组协变

List<Integer> list = new ArrayList<>();
// List<Number> numList = list; // ❌ 不允许,泛型不可协变

6️⃣ 性能对比

  • 数组:操作更快,占用内存更小(因为少了对象封装和额外元数据)。
  • List:灵活,API 丰富,但性能稍差。

7️⃣ 转换

  • 数组转 List
    1
    2
    Integer[] arr = {1, 2, 3};
    List<Integer> list = Arrays.asList(arr);
    ⚠️ 注意:Arrays.asList() 返回的是固定长度的 List,不能增删,只能修改。
  • List 转数组
    1
    2
    3
    List<Integer> list = new ArrayList<>();
    list.add(1); list.add(2);
    Integer[] arr = list.toArray(new Integer[0]);

一句话总结

  • Integer[]:长度固定,性能更高,功能少。
  • List<Integer>:长度可变,功能丰富,性能略低。

要不要我帮你画一个 对比表格(List vs 数组,从长度、性能、功能、适用场景几个维度对比)?这样查起来更直观。

Prompt:自定义结果集resultmap

Response:

好问题 👍,你说的 自定义结果集 ResultMapMyBatis 里一个很核心的功能,用于 把 SQL 查询结果映射到 Java 对象


🔑 为什么要用 ResultMap?

  • 默认情况下,MyBatis 会按照 列名 = 属性名 自动映射。
  • 如果数据库字段名和 Java 属性名不一致,或者需要多表关联查询结果封装,就需要 自定义 ResultMap

📘 基本示例

1. 数据库表

1
2
3
4
5
CREATE TABLE user (
id INT PRIMARY KEY,
user_name VARCHAR(50),
age INT
);

2. 实体类

1
2
3
4
5
6
public class User {
private Integer id;
private String username; // 注意:与 user_name 不一致
private Integer age;
// getter/setter
}

3. Mapper XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<mapper namespace="com.example.mapper.UserMapper">

<!-- 自定义结果映射 -->
<resultMap id="UserResultMap" type="com.example.pojo.User">
<id property="id" column="id"/>
<result property="username" column="user_name"/>
<result property="age" column="age"/>
</resultMap>

<!-- 查询语句引用 ResultMap -->
<select id="selectAll" resultMap="UserResultMap">
SELECT id, user_name, age FROM user
</select>
</mapper>

4. Mapper 接口

1
2
3
public interface UserMapper {
List<User> selectAll();
}

✅ 这样,user_name 字段就能正确映射到 username 属性。


📘 多表关联(嵌套映射)

假设一个用户有一个部门:

表结构

1
2
3
4
5
6
CREATE TABLE dept (
id INT PRIMARY KEY,
dept_name VARCHAR(50)
);

ALTER TABLE user ADD dept_id INT;

实体类

1
2
3
4
5
6
7
8
9
10
11
public class Dept {
private Integer id;
private String deptName;
}

public class User {
private Integer id;
private String username;
private Integer age;
private Dept dept; // 关联部门
}

ResultMap 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<resultMap id="UserDeptResultMap" type="com.example.pojo.User">
<id property="id" column="id"/>
<result property="username" column="user_name"/>
<result property="age" column="age"/>
<!-- 一对一关联 -->
<association property="dept" javaType="com.example.pojo.Dept">
<id property="id" column="dept_id"/>
<result property="deptName" column="dept_name"/>
</association>
</resultMap>

<select id="selectUserDept" resultMap="UserDeptResultMap">
SELECT u.id, u.user_name, u.age, d.id as dept_id, d.dept_name
FROM user u
LEFT JOIN dept d ON u.dept_id = d.id
</select>

✅ 这样,查询结果会自动封装到 User 对象里的 Dept 属性。


📘 常见标签说明

  • <id>:主键映射。
  • <result>:普通字段映射。
  • <association>:一对一(Java 属性是一个对象)。
  • <collection>:一对多(Java 属性是一个集合)。

📝 总结

  • 字段名和属性名一致 → 不需要自定义 ResultMap
  • 字段名和属性名不一致 / 多表关联 → 自定义 ResultMap
  • 一对一 / 一对多关系 → 用 <association><collection>

要不要我帮你整理一个 ResultMap 的完整示例(同时包含一对一、一对多场景),这样你就能直接套用到实际项目?

Prompt:多表关联使用ResultMap有什么注意点

Response:

这个问题问得非常关键 👍,在 MyBatis 多表关联里用 ResultMap 时,有几个常见的“坑”和注意点,帮你总结一下:


🔑 注意点 1:别名(alias)要写对

  • 多表查询时,字段名可能重复,比如 idname
  • 必须在 SQL 里起别名,否则会映射错误。
1
2
3
SELECT u.id AS user_id, u.user_name, d.id AS dept_id, d.dept_name
FROM user u
LEFT JOIN dept d ON u.dept_id = d.id;

然后在 ResultMap 里用这些别名:

1
2
3
4
5
<id property="id" column="user_id"/>
<association property="dept" javaType="Dept">
<id property="id" column="dept_id"/>
<result property="deptName" column="dept_name"/>
</association>

🔑 注意点 2:一对一用 <association>,一对多用 <collection>

  • 一对一:比如 UserDept(一个用户一个部门)
    1
    2
    3
    4
    <association property="dept" javaType="Dept">
    <id property="id" column="dept_id"/>
    <result property="deptName" column="dept_name"/>
    </association>
  • 一对多:比如 DeptUser(一个部门多个用户)
    1
    2
    3
    4
    <collection property="users" ofType="User">
    <id property="id" column="user_id"/>
    <result property="username" column="user_name"/>
    </collection>

⚠️ 一对多时,结果集会有重复数据(因为 SQL JOIN)。MyBatis 会自动去重,但前提是 id 映射要正确(必须 <id> 标注主键)。


🔑 注意点 3:<id> 必须写对

  • ResultMap<id> 元素非常重要,用来标识主键。
  • 一对多映射时,MyBatis 依赖 <id> 来判断是否是同一个对象,否则可能会创建重复对象。

🔑 注意点 4:N+1 问题

  • ResultMap 里的 <association><collection> 支持 两种方式
    1. 嵌套结果(Nested ResultMap) → 一个 SQL 搞定(推荐)。
    2. 嵌套查询(Nested Select) → 先查主表,再去子查询(容易出现 N+1 查询问题,性能差)。

示例(嵌套查询❌,不推荐大规模使用):

1
<association property="dept" select="selectDeptById" column="dept_id"/>

这种会导致 MyBatis 循环去查 dept 表,很慢。
✅ 推荐:在 SQL 里 JOIN 好,用 Nested ResultMap


🔑 注意点 5:懒加载(可选)

  • 如果使用嵌套查询 <association select="">,可以开启懒加载:
1
2
3
4
mybatis:
configuration:
lazy-loading-enabled: true
aggressive-lazy-loading: false

这样只有在真正访问 dept 时,才会去查数据库。


🔑 注意点 6:对象层级 & JavaType / ofType 要写对

  • **javaType**:单对象属性的类型(对应 <association>)。
  • **ofType**:集合里每个元素的类型(对应 <collection>)。

比如:

1
<association property="dept" javaType="com.example.pojo.Dept">
1
<collection property="users" ofType="com.example.pojo.User">

📌 总结

在多表关联时使用 ResultMap 要注意:

  1. 字段重名要用别名,避免映射混乱。
  2. **一对一用 <association>,一对多用 <collection>**。
  3. 主键 <id> 必须写清楚,否则 MyBatis 无法去重。
  4. 优先用嵌套结果(JOIN + ResultMap),避免 N+1 查询。
  5. 合理使用懒加载,在需要的时候才查询子表。
  6. javaType / ofType 必须正确,否则映射失败。

要不要我帮你画一张 多表关联 ResultMap 的流程图(从 SQL 查询结果到 Java 对象组装的过程),这样更直观?

Prompt:if (!CollectionUtils.isEmpty(exprList))和if (exprList!=null)有什么区别

2/2

Response:


1️⃣ if (exprList != null)

  • 仅仅判断 引用是否为空
  • 不能判断集合里是否有元素。
  • 如果集合是 空集合new ArrayList<>()),判断结果依然是 true

示例:

1
2
3
4
List<String> exprList = new ArrayList<>();
if (exprList != null) {
System.out.println("进入判断"); // ✅ 会执行
}

2️⃣ if (!CollectionUtils.isEmpty(exprList))

(这里的 CollectionUtils 指的是 Spring 的工具类,而不是 Apache Commons 的 CollectionUtils,两者功能类似)

源码逻辑:

1
2
3
public static boolean isEmpty(Collection<?> collection) {
return (collection == null || collection.isEmpty());
}

等价于:

1
2
3
if (exprList != null && !exprList.isEmpty()) {
// 既不为 null,也至少有一个元素
}

所以:

  • null → 返回 true
  • 空集合 → 返回 true
  • 非空集合 → 返回 false

3️⃣ 区别总结

写法 判空范围 典型场景
exprList != null 只判断是否为 null 想确认对象是否存在
!CollectionUtils.isEmpty(exprList) 同时判断 不为 null 且有元素 遍历集合前的推荐写法

4️⃣ 示例对比

1
2
3
4
5
6
7
8
9
10
11
List<Integer> list1 = null;
List<Integer> list2 = new ArrayList<>();
List<Integer> list3 = Arrays.asList(1, 2, 3);

System.out.println(list1 != null); // false
System.out.println(list2 != null); // true
System.out.println(list3 != null); // true

System.out.println(CollectionUtils.isEmpty(list1)); // true
System.out.println(CollectionUtils.isEmpty(list2)); // true
System.out.println(CollectionUtils.isEmpty(list3)); // false

一句话总结

  • exprList != null 👉 只保证集合对象存在。
  • !CollectionUtils.isEmpty(exprList) 👉 保证集合对象存在且有内容。

要不要我顺便帮你整理一份 集合判空的常见写法对比(null 检查 / size 检查 / CollectionUtils / Optional)?这样以后写代码可以快速选择最佳写法。

Prompt:set标签是什么

Response:

你问的 <set> 标签,通常是指 MyBatis 动态 SQL 里的 <set> 标签 😊。


🔑 <set> 的作用

主要用于 UPDATE 语句,自动拼接 SET 子句,并且帮你去掉多余的逗号 ,


📘 基本用法

SQL 示例(没用 <set>):

1
2
3
4
5
6
7
8
<update id="updateUser" parameterType="User">
UPDATE user
SET
username = #{username},
age = #{age},
email = #{email}
WHERE id = #{id}
</update>

如果某个字段为 null,你想跳过更新,就得写很多 <if> 判断,并且要小心逗号问题。


<set> 改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<update id="updateUser" parameterType="User">
UPDATE user
<set>
<if test="username != null">
username = #{username},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="email != null">
email = #{email},
</if>
</set>
WHERE id = #{id}
</update>

🔹 好处:

  1. 只更新非空字段(动态拼接)。
  2. 自动去掉最后一个逗号,避免 SQL 错误。

📘 生成 SQL 示例

假设传入的 User 对象:

1
2
3
User user = new User();
user.setId(1);
user.setUsername("Tom");

最终生成的 SQL:

1
2
3
UPDATE user 
SET username = 'Tom'
WHERE id = 1;

如果 ageemail 不为 null,则会自动拼上:

1
2
3
UPDATE user 
SET username = 'Tom', age = 20, email = 'xxx@xx.com'
WHERE id = 1;

🔑 总结

  • <set> 专门用于 UPDATE 语句
  • 常和 <if> 搭配,构造 动态更新字段
  • 能自动处理多余的逗号问题。

要不要我再给你一个 <trim><set><where> 综合对比的例子,这样你能系统掌握 MyBatis 动态 SQL 标签的区别?

Prompt:springboot项目怎么进行全局异常处理

Response:

好问题 👍 在 Spring Boot 项目里,全局异常处理通常通过 @ControllerAdvice + @ExceptionHandler 来实现。这样可以把所有异常集中处理,避免在每个 Controller 里都写 try-catch。


📘 基本实现步骤

1. 定义统一返回对象(可选)

一般我们会定义一个统一的响应类,用于返回错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Result<T> {
private Integer code; // 状态码
private String message; // 错误信息
private T data; // 返回数据

public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}

public static <T> Result<T> failure(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}

// getter / setter
}

2. 定义全局异常处理类

使用 @ControllerAdvice 注解,表示这是一个全局的异常处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice // 相当于 @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {

// 处理所有 Exception
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
e.printStackTrace(); // 方便调试,也可以写入日志
return Result.failure(500, "系统内部错误: " + e.getMessage());
}

// 处理空指针异常
@ExceptionHandler(NullPointerException.class)
public Result<String> handleNullPointer(NullPointerException e) {
return Result.failure(501, "空指针异常: " + e.getMessage());
}

// 处理自定义业务异常
@ExceptionHandler(BizException.class)
public Result<String> handleBizException(BizException e) {
return Result.failure(400, e.getMessage());
}
}

3. 自定义业务异常类(可选)

你可以自定义业务异常来更灵活地处理:

1
2
3
4
5
6
7
8
9
10
11
12
public class BizException extends RuntimeException {
private Integer code;

public BizException(Integer code, String message) {
super(message);
this.code = code;
}

public Integer getCode() {
return code;
}
}

抛出异常时直接写:

1
2
3
if (user == null) {
throw new BizException(404, "用户不存在");
}

📘 运行效果

比如调用接口时抛出 NullPointerException
返回 JSON:

1
2
3
4
5
{
"code": 501,
"message": "空指针异常: Cannot invoke ... because xxx is null",
"data": null
}

🔑 总结

  • @ControllerAdvice 定义全局异常处理器。
  • @ExceptionHandler 指定要捕获的异常类型。
  • 配合统一响应类 Result<T> 返回友好的 JSON 给前端。
  • 可以针对不同异常类型(系统异常、业务异常、校验异常等)分别处理。

要不要我帮你写一个 带参数校验(@Valid + MethodArgumentNotValidException)的全局异常处理版本?这样连参数校验错误都能统一处理。

Prompt:@MapKey是什么

Response:

好问题 👍,@MapKeyMyBatis 注解开发里比较特殊的一个注解,用来把查询结果 封装成 Map 类型,并指定结果 Mapkey


📘 基本概念

  • 默认情况:MyBatis 查询返回多条数据时,常用 List<T>
  • 特殊需求:有时候我们希望查询结果直接放到 Map<K, V> 中,其中:
    • K = 由某个字段值决定
    • V = 实体对象

这时候就可以用 **@MapKey**。


📘 使用方式

1. 在 Mapper 接口方法上使用

1
2
3
@MapKey("id")  // 指定结果 Map 的 key 为实体类的 id 属性
@Select("SELECT * FROM user")
Map<Integer, User> selectAllUserAsMap();

📌 说明:

  • SQL 查询返回多条 User 数据。
  • 最终结果是一个 Map<Integer, User>,其中:
    • key = Userid 字段值
    • value = User 对象本身

2. 返回结果示例

假设数据表 user 里有:

id name age
1 Tom 20
2 Alice 25

调用 selectAllUserAsMap() 得到:

1
2
3
4
{
1=User{id=1, name='Tom', age=20},
2=User{id=2, name='Alice', age=25}
}

3. 注意点

  1. **必须返回 Map<K, V>**,否则 @MapKey 无意义。
    1
    Map<Integer, User> selectAllUserAsMap();
  2. @MapKey 指定的 key 必须是返回对象的属性,否则报错。
  3. 如果 SQL 结果中 key 字段重复,后面的结果会覆盖前面的。
  4. 常用场景:
    • 需要通过主键快速查找对象(避免遍历 List)。
    • 构造类似字典结构的数据。

一句话总结
@MapKey 是 MyBatis 注解,用来指定查询结果 Map 的 key,返回结果可以直接是 Map<字段值, 实体对象>


要不要我给你再举一个 多表关联查询 + @MapKey 的例子?这样能直观理解它在复杂场景下的优势。

Prompt:

// 统计各个职位的员工人数
List<Map<String, Object>> list = empMapper.countEmpJobData();

Response:

你这个代码片段:

1
2
// 统计各个职位的员工人数
List<Map<String, Object>> list = empMapper.countEmpJobData();

意思是:通过 empMapper 调用 countEmpJobData() 方法,返回一个 List<Map<String, Object>>,里面存放每个职位的统计结果。


📘 常见 Mapper 写法

1. 使用注解方式

1
2
@Select("SELECT job, COUNT(*) AS num FROM emp GROUP BY job")
List<Map<String, Object>> countEmpJobData();

返回结果:

1
2
3
4
5
[
{ "job": "CLERK", "num": 4 },
{ "job": "MANAGER", "num": 3 },
{ "job": "ANALYST", "num": 2 }
]

2. 使用 XML 方式

EmpMapper.xml

1
2
3
4
5
<select id="countEmpJobData" resultType="map">
SELECT job, COUNT(*) AS num
FROM emp
GROUP BY job
</select>

对应接口:

1
List<Map<String, Object>> countEmpJobData();

📘 注意点

  1. resultType="map"
    • 表示返回结果每一行映射为 Map<String, Object>,列名作为 key,值作为 value
    • 如果返回多行,就自然是 List<Map<>>
  2. 列别名
    • 推荐在 SQL 中给聚合结果起别名,比如 COUNT(*) AS num,避免前端取值时列名不直观。
  3. 封装成对象(可选)
    如果你不想用 Map,也可以建一个 VO 类:
    1
    2
    3
    4
    5
    public class JobCountDTO {
    private String job;
    private Integer num;
    // getter/setter
    }
    然后写:
    1
    2
    3
    4
    5
    <select id="countEmpJobData" resultType="com.example.dto.JobCountDTO">
    SELECT job, COUNT(*) AS num
    FROM emp
    GROUP BY job
    </select>
    返回结果就变成:
    1
    List<JobCountDTO> list = empMapper.countEmpJobData();
    ✅ 更安全、可读性更强。

要不要我帮你把这个结果再写成一个 前端可直接用的 ECharts 柱状图/饼图接口?这样你统计的职位人数能直接展示出来。

Prompt:

得到这个结果后怎么组装
[
{ “job”: “CLERK”, “num”: 4 },
{ “job”: “MANAGER”, “num”: 3 },
{ “job”: “ANALYST”, “num”: 2 }
]

Response:

👌 明白了,你拿到的是一个 List<Map<String,Object>> 结果:

1
2
3
4
5
[
{ "job": "CLERK", "num": 4 },
{ "job": "MANAGER", "num": 3 },
{ "job": "ANALYST", "num": 2 }
]

常见需求就是 把它组装成前端能用的结构,例如:

  • ECharts 图表的数据(x 轴 / y 轴数组)
  • Map<String, Integer> 形式(职位 → 人数)
  • 自定义 DTO/VO 对象

📘 方法一:组装成 ECharts 需要的格式

比如前端需要:

1
2
3
4
{
"jobs": ["CLERK", "MANAGER", "ANALYST"],
"nums": [4, 3, 2]
}

后端处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<Map<String, Object>> list = empMapper.countEmpJobData();

List<String> jobs = new ArrayList<>();
List<Integer> nums = new ArrayList<>();

for (Map<String, Object> row : list) {
jobs.add((String) row.get("job"));
nums.add(((Number) row.get("num")).intValue()); // 防止 Long 转 int 出错
}

Map<String, Object> result = new HashMap<>();
result.put("jobs", jobs);
result.put("nums", nums);

// 返回给前端
return result;

📘 方法二:转成 Map<String, Integer>

1
2
3
4
Map<String, Integer> jobCountMap = new HashMap<>();
for (Map<String, Object> row : list) {
jobCountMap.put((String) row.get("job"), ((Number) row.get("num")).intValue());
}

结果:

1
2
3
4
5
{
"CLERK": 4,
"MANAGER": 3,
"ANALYST": 2
}

📘 方法三:封装成对象列表

定义 DTO:

1
2
3
4
5
public class JobCountDTO {
private String job;
private Integer num;
// getter/setter
}

转换:

1
2
3
4
5
6
7
List<JobCountDTO> dtoList = new ArrayList<>();
for (Map<String, Object> row : list) {
JobCountDTO dto = new JobCountDTO();
dto.setJob((String) row.get("job"));
dto.setNum(((Number) row.get("num")).intValue());
dtoList.add(dto);
}

结果:

1
2
3
4
5
[
{ "job": "CLERK", "num": 4 },
{ "job": "MANAGER", "num": 3 },
{ "job": "ANALYST", "num": 2 }
]

(这个和原始结果一样,但类型安全,前端更容易取值)


✅ 总结

  • ECharts → 组装成 { jobs: [], nums: [] }
  • 字典映射 → 转 Map<String, Integer>
  • 业务对象 → 转 DTO 列表

要不要我帮你直接写一个 Spring Boot Controller 接口,返回 ECharts 能直接用的 JSON 格式?

Prompt:

这种方法呢:List jobList = list.stream().map(dataMap -> dataMap.get(“pos”)).toList();

1/2

Response:

对,这个写法也是 Java Stream 的方式来组装结果 👍。

比如你现在的查询结果:

1
List<Map<String, Object>> list = empMapper.countEmpJobData();

想要提取某一列(比如职位 job),就可以用 stream + map

1
2
3
List<Object> jobList = list.stream()
.map(dataMap -> dataMap.get("job"))
.toList();

输出结果就是:

1
["CLERK", "MANAGER", "ANALYST"]

📘 几点说明

  1. 字段名要一致
    你写的是 "pos",但 SQL 里是 "job",需要保持一致,否则取出来是 null
    ✅ 正确:
    1
    .map(dataMap -> dataMap.get("job"))
  2. 类型安全
    dataMap.get("job") 返回的是 Object,所以 jobListList<Object>
    如果你知道是 String,可以强转:
    1
    2
    3
    List<String> jobList = list.stream()
    .map(dataMap -> (String) dataMap.get("job"))
    .toList();
    同样,人数可以写:
    1
    2
    3
    List<Integer> numList = list.stream()
    .map(dataMap -> ((Number) dataMap.get("num")).intValue())
    .toList();
  3. 结果组合(ECharts 常用格式)
    1
    2
    3
    4
    Map<String, Object> result = new HashMap<>();
    result.put("jobs", jobList);
    result.put("nums", numList);
    return result;
    返回给前端就是:
    1
    2
    3
    4
    {
    "jobs": ["CLERK", "MANAGER", "ANALYST"],
    "nums": [4, 3, 2]
    }

一句话总结
你用的 stream().map(...).toList() 写法很简洁,适合从 List<Map> 中提取某一列数据。但要注意列名是否一致,以及做好强转。

要不要我帮你写一个 完整的 Stream 版本,直接从 list 转成 { jobs:[], nums:[] } 的结构?

Prompt:

这里使用到什么java语法

Response:

你刚才提到的写法:

1
2
3
List<Object> jobList = list.stream()
.map(dataMap -> dataMap.get("job"))
.toList();

涉及到几个 Java 语法/特性,我帮你梳理一下:


1. 泛型(Generics)

1
List<Object> jobList
  • List<Object> 表示 jobList 是一个 List,里面存放的是 Object 类型。
  • 泛型用于保证类型安全,避免 ClassCastException

2. 集合(Collections)

1
List<Map<String, Object>> list
  • List 是有序集合接口
  • Map<String, Object> 是键值对集合,键是 String,值是 Object
  • 用于存储查询结果,每条记录是一个 Map。

3. Stream API

1
list.stream()
  • stream() 方法把 List 转换成 Stream 流,可以进行链式操作
  • Stream 是 Java 8 引入的函数式操作集合的方式。

4. Lambda 表达式

1
dataMap -> dataMap.get("job")
  • Lambda 表达式是 Java 8 新特性,用于简化匿名函数
  • 左边 dataMap 是参数,右边 dataMap.get("job") 是返回值
  • 等价于:
1
2
3
4
5
new Function<Map<String,Object>, Object>() {
public Object apply(Map<String,Object> dataMap) {
return dataMap.get("job");
}
}

5. 方法引用(可选)

  • 这里用的是 Lambda,如果用方法引用也可以写成:
1
.map(Map::values) // 不完全相同,但类似
  • Lambda 和方法引用都是函数式接口实现方式。

6. 链式调用

1
list.stream().map(...).toList();
  • stream()map(...)toList()
  • 链式调用(Fluent API)是 Java 8 的风格,提升可读性。

7. 类型转换(Casting)

如果你想直接得到 List<String>

1
2
3
List<String> jobList = list.stream()
.map(dataMap -> (String) dataMap.get("job"))
.toList();
  • (String) 是强制类型转换,把 Object 转成 String
  • 避免 List<Object> 带来的类型不安全。

✅ 总结

这一行代码综合了以下 Java 特性:

  1. 泛型
  2. 集合 ListMap
  3. Java 8 Stream API
  4. Lambda 表达式
  5. 链式调用(Fluent API)
  6. 可选的类型转换

如果你想,我可以画一张 数据流动图,帮你可视化 List<Map>StreammapList 的整个过程,让理解更直观。

你希望我画吗?

Prompt:MyBatis中case流程控制函数

2/2

Response:

MyBatis 中,SQL 层的 流程控制 主要依赖 数据库的 SQL 函数,而 MyBatis 本身提供了 <choose>, <when>, <otherwise> 标签来实现条件逻辑。在数据库 SQL 层,你提到的 CASE 函数也很常用。下面我帮你梳理一下:


1️⃣ SQL 的 CASE 表达式

CASE 是标准 SQL 的条件表达式,用来在查询时进行条件判断:

1
2
3
4
5
6
7
8
9
SELECT 
emp_name,
salary,
CASE
WHEN salary >= 10000 THEN '高'
WHEN salary >= 5000 THEN '中'
ELSE '低'
END AS salary_level
FROM employee;
  • CASE WHEN ... THEN ... ELSE ... END
  • 可以用在 SELECTORDER BYWHERE 等地方
  • 返回一个值,等价于 if…else if…else

2️⃣ MyBatis 中 XML 映射

MyBatis 支持直接写 SQL,所以 CASE 可以原封不动用:

1
2
3
4
5
6
7
8
9
10
11
<select id="selectEmployeeSalaryLevel" resultType="Map">
SELECT
emp_name,
salary,
CASE
WHEN salary >= 10000 THEN '高'
WHEN salary >= 5000 THEN '中'
ELSE '低'
END AS salary_level
FROM employee
</select>
  • 查询结果可以映射到 Map、DTO 或 ResultMap
  • 适合在 SQL 层做简单条件判断

3️⃣ MyBatis XML 自身的流程控制标签

MyBatis 也提供了动态 SQL 标签来实现流程控制(更多是构造 SQL 条件,而不是返回值判断):

<if> 标签

1
2
3
4
5
6
7
<select id="findEmployee" resultType="Employee">
SELECT * FROM employee
WHERE 1=1
<if test="salary != null">
AND salary >= #{salary}
</if>
</select>

<choose> / <when> / <otherwise>

等同于 SQL 的 if-elseif-else:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findEmployeeBySalaryLevel" resultType="Employee">
SELECT * FROM employee
WHERE
<choose>
<when test="level == '高'">
salary >= 10000
</when>
<when test="level == '中'">
salary >= 5000 AND salary < 10000
</when>
<otherwise>
salary < 5000
</otherwise>
</choose>
</select>
  • <choose> 只能有一个 <when> 被执行
  • <otherwise> 类似 else

4️⃣ 总结

功能 用法 场景
SQL CASE CASE WHEN ... THEN ... END 在查询结果列中做条件判断,返回值不同
MyBatis <if> <if test="condition"> ... </if> 动态构造 SQL 的条件
MyBatis <choose> <choose><when>...</when><otherwise>...</otherwise></choose> SQL 层的 if-elseif-else 构造
MyBatis <foreach> 遍历集合生成 SQL IN 查询或批量插入

如果你愿意,我可以帮你写一个 结合 CASE 和 MyBatis ResultMap 的示例,比如根据工资返回“高/中/低”,直接映射到 DTO 属性里,方便在 Java 层使用。

你希望我写吗?

Prompt:

解释这两种:
select
(case job when 1 then ‘班主任’
when 2 then ‘讲师’
when 3 then ‘学工主管’
when 4 then ‘教研主管’
when 5 then ‘咨询师’
else ‘其他’ end) pos,
count(*) total
from emp group by job
order by total

select
if(gender = 1, ‘男’, ‘女’) as name,
count(*) as value
from emp group by gender ;

Response:

你给出的这两条 SQL 都是 条件判断函数/表达式,但是使用的是 不同的 SQL 实现方式,下面我详细解释:


1️⃣ CASE WHEN 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT
(CASE job
WHEN 1 THEN '班主任'
WHEN 2 THEN '讲师'
WHEN 3 THEN '学工主管'
WHEN 4 THEN '教研主管'
WHEN 5 THEN '咨询师'
ELSE '其他'
END) AS pos,
COUNT(*) AS total
FROM emp
GROUP BY job
ORDER BY total;

解析

  1. CASE 表达式
    • CASE job WHEN 1 THEN '班主任' ... ELSE '其他' END
    • 根据 job 的值返回对应字符串
    • 等价于 Java 中的 switch(job) → 返回对应字符串
  2. GROUP BY
    • job 分组统计每个岗位的人数
  3. COUNT(*)
    • 统计每个岗位的人数
  4. ORDER BY total
    • 按人数升序排序

返回示例

pos total
咨询师 5
班主任 10
讲师 12

2️⃣ IF 示例(MySQL 特有)

1
2
3
4
5
SELECT
IF(gender = 1, '男', '女') AS name,
COUNT(*) AS value
FROM emp
GROUP BY gender;

解析

  1. IF(expr, true_value, false_value)
    • IF(gender = 1, '男', '女')
    • 如果 gender = 1 返回 '男',否则返回 '女'
    • 等价于 Java 中的三元运算符:gender == 1 ? "男" : "女"
  2. GROUP BY gender
    • gender 分组统计男女数量
  3. COUNT(*)
    • 统计每组人数

返回示例

name value
15
20

3️⃣ 区别

特性 CASE IF
语法标准 SQL 标准 MySQL 特有
可用场景 可处理多分支、多条件 适合二分支判断
表达形式 CASE ... WHEN ... THEN ... ELSE ... END IF(expr, true_value, false_value)
可移植性 高(多数数据库支持) 低(仅 MySQL 支持)

总结:

  • 多条件判断 → 用 CASE 更规范
  • 二分支判断 → 用 IF 更简洁(MySQL)
头像头像
John Doe
打赏作者
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
avatar
Catalog
  1. Spring 事务管理
    1. Prompt:Spring事务管理
    2. Response:
    3. 1. 什么是事务
    4. 2. Spring 事务管理的方式
    5. 3. 核心接口
    6. 4. @Transactional 注解的常用属性
    7. 5. Spring 声明式事务示例
    8. 6. Spring 事务注意点
    9. 7. 典型应用场景
    10. Prompt:事务的传播行为控制的是什么
    11. Response:
    12. 常见传播行为解析
      1. 1. REQUIRED(默认)
      2. 2. REQUIRES_NEW
      3. 3. NESTED
      4. 4. MANDATORY
      5. 5. SUPPORTS
      6. 6. NOT_SUPPORTED
      7. 7. NEVER
    13. 举个例子
    14. Prompt:springboot项目中实现文件上传方法
    15. Response:
    16. 1. 基础依赖
    17. 2. application.yml 配置
    18. 3. Controller 代码示例
    19. 4. 前端调用方式
    20. 5. 进阶:多文件上传
    21. Prompt:参数配置化是什么
    22. Response:
    23. 🔑 为什么要做参数配置化
    24. ⚙️ 在 Spring Boot 中的典型用法
      1. 1. 配置文件 application.yml / application.properties
      2. 2. @Value 注解
      3. 3. 动态配置中心(进阶)
    25. 📌 举个例子
      1. 🔖 总结一句话
    26. Prompt:对象没有被 Spring 管理和注入成功
    27. Response:
    28. 🔎 常见原因 & 解决方法
      1. 1. 忘了加 @Component / @ConfigurationProperties
      2. 2. 忘了在使用的地方加 @Autowired
      3. 3. 工具类自己 new 出来的(没走 Spring 管理)
    29. 📝 总结排查步骤
    30. Prompt:List和interger[]区别
    31. Response:
    32. 1️⃣ 类型本质
    33. 2️⃣ 长度变化
    34. 3️⃣ 存储方式
    35. 4️⃣ 功能差异
    36. 5️⃣ 泛型支持
    37. 6️⃣ 性能对比
    38. 7️⃣ 转换
    39. Prompt:自定义结果集resultmap
    40. Response:
    41. 🔑 为什么要用 ResultMap?
    42. 📘 基本示例
      1. 1. 数据库表
      2. 2. 实体类
      3. 3. Mapper XML
      4. 4. Mapper 接口
    43. 📘 多表关联(嵌套映射)
      1. 表结构
      2. 实体类
      3. ResultMap 配置
    44. 📘 常见标签说明
    45. 📝 总结
    46. Prompt:多表关联使用ResultMap有什么注意点
    47. Response:
    48. 🔑 注意点 1:别名(alias)要写对
    49. 🔑 注意点 2:一对一用 <association>,一对多用 <collection>
    50. 🔑 注意点 3:<id> 必须写对
    51. 🔑 注意点 4:N+1 问题
    52. 🔑 注意点 5:懒加载(可选)
    53. 🔑 注意点 6:对象层级 & JavaType / ofType 要写对
    54. 📌 总结
    55. Prompt:if (!CollectionUtils.isEmpty(exprList))和if (exprList!=null)有什么区别
    56. Response:
    57. 1️⃣ if (exprList != null)
    58. 2️⃣ if (!CollectionUtils.isEmpty(exprList))
    59. 3️⃣ 区别总结
    60. 4️⃣ 示例对比
    61. Prompt:set标签是什么
    62. Response:
    63. 🔑 <set> 的作用
    64. 📘 基本用法
      1. SQL 示例(没用 <set>):
      2. 用 <set> 改进:
    65. 📘 生成 SQL 示例
    66. 🔑 总结
    67. Prompt:springboot项目怎么进行全局异常处理
    68. Response:
    69. 📘 基本实现步骤
      1. 1. 定义统一返回对象(可选)
      2. 2. 定义全局异常处理类
      3. 3. 自定义业务异常类(可选)
    70. 📘 运行效果
    71. 🔑 总结
    72. Prompt:@MapKey是什么
    73. Response:
    74. 📘 基本概念
    75. 📘 使用方式
      1. 1. 在 Mapper 接口方法上使用
      2. 2. 返回结果示例
      3. 3. 注意点
    76. Prompt:
    77. Response:
    78. 📘 常见 Mapper 写法
      1. 1. 使用注解方式
      2. 2. 使用 XML 方式
    79. 📘 注意点
    80. Prompt:
    81. Response:
    82. 📘 方法一:组装成 ECharts 需要的格式
    83. 📘 方法二:转成 Map<String, Integer>
    84. 📘 方法三:封装成对象列表
    85. ✅ 总结
    86. Prompt:
    87. Response:
    88. 📘 几点说明
    89. Prompt:
    90. Response:
    91. 1. 泛型(Generics)
    92. 2. 集合(Collections)
    93. 3. Stream API
    94. 4. Lambda 表达式
    95. 5. 方法引用(可选)
    96. 6. 链式调用
    97. 7. 类型转换(Casting)
      1. ✅ 总结
    98. Prompt:MyBatis中case流程控制函数
    99. Response:
    100. 1️⃣ SQL 的 CASE 表达式
    101. 2️⃣ MyBatis 中 XML 映射
    102. 3️⃣ MyBatis XML 自身的流程控制标签
      1. <if> 标签
      2. <choose> / <when> / <otherwise>
    103. 4️⃣ 总结
    104. Prompt:
    105. Response:
    106. 1️⃣ CASE WHEN 示例
      1. 解析
      2. 返回示例
    107. 2️⃣ IF 示例(MySQL 特有)
      1. 解析
      2. 返回示例
    108. 3️⃣ 区别
Recent Post
vuewatch
vueapi对比
bean作用域
springAOP
引用到评论