部门管理、员工管理
部门管理、员工管理
John Doe查询时间为空原因
Link: https://chatgpt.com/c/689d978c-767c-832c-a6c1-a949a75d8d70
Prompt:为什么查询出来时间为空:
@Mapper
public interface DeptMapper {
@Select("select id, name, create_time, update_time from dept order by update_time desc")
List<Dept> selectAll();
}
Response:
你这个问题大概率是 MyBatis 没有把数据库的时间字段正确映射到 Dept 对象的 createTime / updateTime 属性,导致查询结果里这两个字段是 null。
常见原因有几个:
1. 字段名与属性名不匹配(驼峰问题)
你在 SQL 里写的是
1 | create_time, update_time |
但在 Java 类 Dept 里很可能是:
1 | private LocalDateTime createTime; |
默认情况下,MyBatis 不会自动把 create_time → createTime 映射,除非在配置里启用了 mapUnderscoreToCamelCase:
1 | mybatis: |
或者用别名让 SQL 字段名和属性名一致:
1 |
2. Dept 类字段类型不匹配
如果数据库是 DATETIME 或 TIMESTAMP 类型,Java 中用:
1 | private LocalDateTime updateTime; |
或
1 | private Date updateTime; |
要确保 MyBatis 能够自动转换,否则也可能映射失败。
3. SQL 确实查出来就是 NULL
如果数据库里的 create_time / update_time 本身就是 NULL,那自然取出来就是空。
可以先直接在数据库里执行:
1 | select id, name, create_time, update_time from dept order by update_time desc; |
看看结果是不是本来就是 null。
✅ 建议先这样改,排查字段映射问题:
1 |
|
然后在 application.yml 里加:
1 | mybatis: |
我可以帮你写个完整的 Dept 类 + Mapper 配置,保证时间字段能查出来,你要我直接帮你配好吗?
Prompt:mybatis默认数据封装的规则
Response:
MyBatis 默认的数据封装(结果映射)规则,其实就是查询结果列名 → Java 对象属性名的匹配规则。
如果没有额外配置,它的行为是比较“严格”的:
1. 默认匹配规则(不开启驼峰映射)
- MyBatis 默认是 列名和 Java 属性名必须一模一样 才能自动映射。
- 不会自动把
create_time转成createTime。 - 如果不一致,就需要:
- 在 SQL 里用别名:
1
SELECT create_time AS createTime FROM dept
- 或者用
<resultMap>手动映射。
- 在 SQL 里用别名:
2. 开启驼峰命名映射(mapUnderscoreToCamelCase)
- 如果在配置中加上:或
1
2
3mybatis:
configuration:
map-underscore-to-camel-case: true1
configuration.setMapUnderscoreToCamelCase(true);
- MyBatis 会自动把以下规则应用:
create_time→createTimeuser_id→userIdorder_no→orderNo
3. 类型转换
- MyBatis 会根据 JDBC 类型 → Java 类型 的默认映射规则自动转换,比如:
DATETIME→java.util.Date/java.time.LocalDateTimeINT→IntegerVARCHAR→String
- 如果类型不兼容,需要注册
TypeHandler。
4. 优先级
MyBatis 封装时的列名匹配优先级:
- 先找完全相同的列名与属性名。
- 如果开启了驼峰命名,则会做下划线到驼峰的转换。
- 如果还找不到,就不会赋值(该字段会是
null)。
💡 你当前问题的原因:
MyBatis 默认没开 mapUnderscoreToCamelCase,所以 create_time 没能自动映射到 createTime,导致 Java 对象时间字段为 null。
如果你愿意,我可以帮你画一张 MyBatis 默认封装规则的流程图,这样你一眼就能看懂它是怎么匹配字段的。
Prompt:
nginx的反向代理
Response:
Nginx 的反向代理(Reverse Proxy)是它最常用、最核心的功能之一,核心作用就是 客户端不直接访问后端服务器,而是访问 Nginx,由 Nginx 转发请求给后端。
我帮你分成几个层次讲,这样你记得更牢:
1. 什么是反向代理
- 正向代理:代理客户端去访问外部服务器,外部服务器不知道真实客户端是谁。
举例:VPN、科学上网工具。 - 反向代理:代理服务器去访问内部服务器,客户端不知道真实后端是谁。
举例:Nginx 作为反向代理,把www.example.com的请求转发到内部192.168.0.10:8080。
示意图:
1 | [Client] → [Nginx] → [App Server] |
2. 反向代理的作用
- 隐藏后端真实地址(安全)
- 负载均衡(可以把请求分发到多个后端)
- 缓存静态资源(减少后端压力)
- SSL 终结(Nginx 处理 HTTPS,后端走 HTTP)
- 跨域处理(前端直接请求 Nginx,由 Nginx 转发到后端)
3. 基本配置示例
假设你的后端服务在 http://127.0.0.1:8080,我们让 Nginx 反向代理 /api 请求到它:
1 | server { |
proxy_pass:指定后端服务地址proxy_set_header:传递真实客户端 IP 等信息给后端
4. 注意事项
proxy_pass结尾斜杠规则:/api/→http://127.0.0.1:8080/会把/api/xxx变成/xxx/api/→http://127.0.0.1:8080/api/会保留/api前缀
- 如果是 HTTPS 后端,可以用
proxy_pass https://...,但要注意ssl_certificate配置。 - 如果要跨域,需要加:
1
2
3add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Headers Content-Type;
5. 典型反向代理架构
1 | [用户浏览器] |
Nginx 根据 URL 路径或域名,把请求分发到不同后端。
如果你需要,我可以帮你画一个 反向代理工作流程的图解,可视化地展示 Nginx 收到请求、匹配规则、转发到后端的全过程,这样记忆会更牢。
Prompt:springboot项目中controller中的方法获取查询参数的方法
2/2
Response:
在 Spring Boot 的 Controller 方法中获取查询参数(Query Parameter)主要有几种常用方式,我帮你按“简单 → 灵活”顺序整理一下:
1. 直接用 @RequestParam(最常用)
适合直接绑定单个参数:
1 |
|
- URL 示例:
GET /user?name=Tom&age=20 required = false表示参数可选defaultValue设置默认值
2. 用 @PathVariable(路径参数)
适合参数是 URL 路径的一部分:
1 |
|
- URL 示例:
GET /user/1001
3. 用 Java Bean(批量绑定)
如果参数很多,可以直接绑定到一个对象:
1 | public class UserQuery { |
- URL 示例:
GET /user?name=Tom&age=20 - Spring 会自动按字段名匹配并赋值
4. 用 HttpServletRequest 直接取
适合要手动取各种原始数据:
1 |
|
5. 用 @RequestParam Map(动态参数)
适合参数不确定的情况:
1 |
|
- URL 示例:
GET /user?name=Tom&age=20&gender=male - 输出:
{name=Tom, age=20, gender=male}
✅ 总结:
- 参数确定 →
@RequestParam - 参数在 URL 路径中 →
@PathVariable - 参数多且结构固定 → Java Bean
- 参数不确定 →
Map - 需要底层控制 →
HttpServletRequest
我可以帮你画一张 Spring Boot Controller 参数绑定速查表,这样你开发时能秒查该用哪种方式。
2/2
Prompt:如何接收json格式的请求参数
Response:
在 Spring Boot 中,如果你要接收 JSON 格式 的请求参数,核心就是用 @RequestBody 把请求体里的 JSON 反序列化成 Java 对象。
我给你分几种常见场景讲:
1. 接收 JSON → Java 对象
1 |
|
User 类:
1 | public class User { |
- 请求示例(POST):
1 | { |
- Spring Boot 会用 Jackson 自动反序列化 JSON →
User。
2. 接收 JSON → Map
适合字段不固定的情况:
1 |
|
- 请求:
1 | { |
- 输出:
{name=Tom, age=20, gender=male}
3. 接收 JSON 数组
1 |
|
- 请求:
1 | [ |
4. 接收 JSON + 其他参数(混合)
如果既有 JSON,又有 URL 参数:
1 |
|
- URL:
POST /user?token=abc123 - Body:
1 | {"name": "Tom", "age": 20} |
5. 注意事项
- Content-Type 必须是
application/json
如果前端是axios:1
2
3axios.post('/user', data, {
headers: { 'Content-Type': 'application/json' }
}) @RequestBody不能和GET一起用(GET 没有请求体)- Spring Boot 默认用 Jackson,也可以换成 Fastjson、Gson
如果你愿意,我可以帮你画一张 Spring Boot 接收 JSON 参数的流程图,把 @RequestBody 反序列化到 Java 对象的过程直观画出来。这样你能更清楚 Spring 是怎么处理的。
Prompt:逻辑外键和物理外键有什么区别
Response:
逻辑外键和物理外键的区别,其实就是 “有没有在数据库层面真正建立外键约束” 的问题。
我给你分成几个维度来对比,这样你一看就明白:
1. 概念区别
| 对比项 | 物理外键(Physical Foreign Key) | 逻辑外键(Logical Foreign Key) |
|---|---|---|
| 定义位置 | 在数据库表结构中定义 FOREIGN KEY 约束 |
在业务代码或 ORM 映射中定义关系,不在数据库层创建外键 |
| 数据库约束 | 数据库强制保证参照完整性(插入、更新、删除都会检查) | 数据库层不检查完整性,由业务代码保证 |
| 约束方式 | 通过 SQL 语法 FOREIGN KEY 明确绑定两表 |
只是用相同的字段值关联,没有数据库约束 |
| 删除/更新行为 | 可设置 ON DELETE CASCADE 等自动联动 |
需要业务代码自己处理联动逻辑 |
2. 例子
物理外键
1 | CREATE TABLE orders ( |
- 数据库会保证
orders.user_id必须在users.id中存在。 - 如果删除用户,数据库会阻止删除(或按设置级联删除)。
逻辑外键
1 | CREATE TABLE orders ( |
- 数据库不检查
user_id是否存在于users表。 - 业务代码中:
1
2
3
4User user = userMapper.selectById(order.getUserId());
if (user == null) {
throw new RuntimeException("用户不存在");
} - 检查关系完全靠业务逻辑。
3. 优缺点对比
| 物理外键 | 逻辑外键 | |
|---|---|---|
| 优点 | 数据完整性由数据库自动保证;减少脏数据 | 灵活性高;跨库可用;性能好(省去约束检查) |
| 缺点 | 限制灵活性(跨库困难、批量导入慢);可能影响性能 | 需要业务严格控制;容易产生脏数据 |
| 适用场景 | 数据安全性要求高、库不大 | 高并发、大型分布式系统、微服务、跨库 |
4. 实际开发经验
- 中小项目/单库:建议用物理外键,数据库帮你保证数据干净。
- 大型系统/微服务/分布式:常用逻辑外键,因为外键跨库难实现,也避免性能瓶颈。
如果你需要,我可以帮你画一张 逻辑外键 vs 物理外键 决策流程图,让你在不同场景下快速判断该用哪种外键。
Prompt:pagehelper实现分页
Response:
在 Spring Boot + MyBatis 项目里,PageHelper 是最常用的分页插件。它的原理是 拦截 SQL,在执行前自动拼接 limit offset 分页语句,并返回带分页信息的结果。
我帮你分步骤说明,并给出示例代码:
1. 引入依赖
Maven:
1 | <dependency> |
2. 配置(可选)
一般不用额外配置,PageHelper 默认就能用。
如果要定制,可以在 application.yml 里加:
1 | pagehelper: |
3. 使用方式
(1)Mapper 方法
1 |
|
(2)Service 中调用分页
1 |
|
(3)Controller
1 |
|
4. 返回结果示例
假设请求 GET /dept/list?pageNum=1&pageSize=5,返回 JSON 类似:
1 | { |
5. 注意事项
PageHelper.startPage必须紧跟查询方法,中间不能再有其他 SQL。- 返回结果最好用
PageInfo包装,能拿到总数、页码等信息。 - 如果你只想要
List而不需要额外信息,也可以直接返回List。
要不要我帮你整理一张 PageHelper 使用速查表(含常见坑点),你平时分页时能直接对照用?
Prompt:它不是有一个com.github.pagehelper.Page吗
Response:
对的 👍,PageHelper 里除了常用的 PageInfo<T>,还提供了一个 com.github.pagehelper.Page<T> 类型。很多人第一次用会有点疑惑。
我帮你梳理一下区别和用法:
1. Page<T> 是什么?
Page<T>实际上是ArrayList<T>的子类,在内部额外包含了分页信息(总数、页号、页大小等)。- 当你调用
PageHelper.startPage()以后,再执行的List<T>查询,如果 Mapper 方法返回的就是List<T>,那么这个List实际上就是一个Page<T>对象。
1 | PageHelper.startPage(1, 5); |
2. Page<T> vs PageInfo<T>
| 特性 | Page<T> |
PageInfo<T> |
|---|---|---|
| 本质 | ArrayList<T> 子类 |
普通的 VO 封装类 |
| 分页信息 | 内部直接包含总数、页码、页大小等 | 包装了 Page<T>,提供更多分页属性 |
| 使用场景 | 只需要简单分页数据时 | API/前端需要更完整分页信息时 |
| 返回给前端 | 不推荐(不直观,字段较少) | 推荐,结构更清晰 |
3. 示例代码
使用 Page<T>
1 |
|
返回 JSON 大概是:
1 | { |
使用 PageInfo<T>(推荐)
1 |
|
返回 JSON 会更完整,带有导航页等信息。
4. 总结
Page<T>= 带分页信息的List<T>PageInfo<T>= 对Page<T>的再包装,字段更全,更适合对外返回
👉 在 内部逻辑里用 Page<T> 就够了,但对 前端接口返回建议用 PageInfo<T>,结构更清晰,兼容性也好。
要不要我帮你画一张 Page → PageInfo 的关系图,这样你一眼就能看懂它们之间的继承和包装关系?
Prompt:pagehelper有什么使用注意事项
Response:
好问题 👍,PageHelper 在 Spring Boot + MyBatis 项目里虽然很方便,但也有不少“坑点”,我帮你总结几个常见注意事项,这样你用的时候少踩坑:
✅ PageHelper 使用注意事项
1. PageHelper.startPage() 必须紧跟查询
- 原理:PageHelper 是基于 MyBatis 的拦截器,拦截下一条 SQL。
- 如果你在
startPage()和查询之间执行了别的 SQL,就会分页错乱。
1 | // 正确 ✅ |
2. 只能分页一条查询
一次 startPage() 只能影响 一条查询,执行完之后就失效。
如果你有多个查询,需要每次都重新调用 startPage()。
3. 返回值类型
List<T>查询结果在底层会被转换成Page<T>(继承ArrayList)。- 如果需要分页信息,应该用
PageInfo<T>包装。1
2
3PageHelper.startPage(pageNum, pageSize);
List<User> list = userMapper.selectAll();
PageInfo<User> pageInfo = new PageInfo<>(list);
4. count 查询性能
- PageHelper 默认会自动生成
count语句(select count(*) ...)。 - 如果你的 SQL 很复杂(多表 join、子查询),count 可能非常慢。
- 解决办法:
- 在
PageHelper.startPage()后调用setCount(false)禁用 count - 或者单独写一条优化过的
countSQL
- 在
5. 合理化参数(reasonable)
在配置中可以启用:
1 | pagehelper: |
作用:
- 页码 <= 0 → 自动查第一页
- 页码 > 最大页码 → 自动查最后一页
6. 和 RowBounds 冲突
PageHelper 内部其实是替代了 MyBatis 的 RowBounds 分页。
如果你自己在 Mapper 方法里又传了 RowBounds,可能冲突。
7. 不要和数据流式处理混用
分页结果是基于 List,如果你在同一查询中尝试用 MyBatis 的 ResultHandler 流式处理,会失效。
8. 分页顺序问题
PageHelper.startPage() 是在 SQL 执行前生效的,
所以如果你想分页+排序,排序一定要写在 SQL 里,或者用 PageHelper 的 orderBy:
1 | PageHelper.startPage(1, 10, "create_time desc"); |
9. 分布式场景
PageHelper 是基于数据库分页的,如果你是分库分表环境,PageHelper 只会分页单个库的结果,不能自动做跨库分页。
⚡ 总结
- 位置:
startPage()必须紧跟查询 - 结果:结果是
Page<T>,通常用PageInfo<T>包装 - 性能:注意
count语句性能问题 - 配置:合理化参数、排序等要正确设置
- 限制:不支持跨库分页、不能和流式处理混用
要不要我帮你整理一份 “PageHelper 常见坑与最佳实践清单”,让你在写分页时直接对照避免错误?
Prompt:基于xml映射mapper的注意点有什么:
Response:
你这个 XML 写法整体没问题 👍,但基于 MyBatis XML 映射 mapper 时,确实有一些容易踩坑的注意点,我帮你分层梳理一下:
✅ 基于 XML 的 Mapper 注意点
1. 命名空间要和接口一致
1 | <mapper namespace="com.example.mapper.EmpMapper"> |
- 这个
namespace必须和对应的EmpMapper接口的全限定名一致,否则 MyBatis 找不到对应关系。
2. SQL 条件的参数绑定
你写了:
1 | where e.name like concat('%',#{name},'%') |
- 如果某个参数可能是 null,SQL 会出错或查不到数据。
👉 建议配合<if>动态 SQL:
1 | <select id="list" resultType="com.example.pojo.Emp"> |
3. 字段和实体属性的对应
- 你写了
d.name deptName,要确保Emp类里有字段private String deptName;,并且有 getter/setter。 - 如果
Emp里用的是驼峰命名,比如deptName,那要么:- 启用
mapUnderscoreToCamelCase=true(全局配置) - 要么在 SQL 里显式写别名:
d.name AS deptName
- 启用
4. resultType vs resultMap
- 你现在用的是:这要求列名和
1
resultType="com.example.pojo.Emp"
Emp属性名一一对应。 - 如果列名和属性名不一致(例如
entry_date↔entryDate),建议用resultMap:1
2
3
4
5
6
7
8
9<resultMap id="empResultMap" type="com.example.pojo.Emp">
<id column="id" property="id"/>
<result column="entry_date" property="entryDate"/>
<result column="deptName" property="deptName"/>
</resultMap>
<select id="list" resultMap="empResultMap">
select e.*, d.name deptName from emp e
left join dept d on e.dept_id = d.id
</select>
5. SQL 关键字大小写和别名
- MyBatis 对大小写不敏感,但 SQL 最好保持统一风格。
AS可以省略,建议写全:d.name AS deptName。
6. 传参方式
Mapper 接口要和 XML 参数对应:
1 | List<Emp> list( String name, |
- 如果方法参数是多个,必须用
@Param标记,不然 XML 里#{name}会报错。 - 如果方法参数是对象,比如
EmpQuery,则可以直接用#{name}访问对象属性。
7. SQL 拼接 vs 预编译
#{}会安全地绑定参数(预编译,防 SQL 注入 ✅)。${}是字符串拼接(可能被注入 ❌)。
一般查询条件要用#{},只有动态表名/排序字段才会用${}。
⚡ 总结
基于 XML 写 mapper 时要注意:
namespace要和接口一致- 动态条件要用
<if>+<where> - 列名 ↔ 属性名一致,否则用
AS或resultMap - 多参数时必须加
@Param #{}安全,${}谨慎
要不要我帮你写一个 改进后的完整 EmpMapper.xml(含动态 SQL 和 resultMap),你可以直接拿去用?
Prompt:concat(‘%’,#{name},’%’)是什么
Response:
好问题 👍
concat('%', #{name}, '%') 是 SQL 里的字符串拼接函数,作用是:
👉 给传入的参数 name 前后加上 %,形成一个模糊查询的匹配模式。
1. 分解说明
假设你写的 SQL 是:
1 | select * from emp where e.name like concat('%', #{name}, '%') |
#{name}是 MyBatis 占位符,会被替换成预编译参数(防 SQL 注入)。concat('%', #{name}, '%')表示把三部分拼接起来:'%'name的值'%'
2. 示例
假设你传的参数是:
1 | name = "Tom" |
SQL 执行时等价于:
1 | where e.name like '%Tom%' |
这就能匹配 Tom、Tommy、Little Tom 等等。
3. 和直接写 '%${name}%' 的区别
很多人一开始可能写:
1 | where e.name like '%${name}%' |
${}是字符串拼接,不安全,有 SQL 注入风险。#{}是预编译参数绑定,安全。- 所以正确用法是
concat('%', #{name}, '%')。
4. 小扩展
如果数据库是 MySQL,拼接也可以写成:
1 | where e.name like CONCAT('%', #{name}, '%') |
或者用 MyBatis 的写法:
1 | <if test="name != null and name != ''"> |
✅ 总结:concat('%', #{name}, '%') 的作用是 安全地拼接模糊查询参数,最终生成 %xxx% 用于 LIKE 查询。
要不要我帮你整理一份 MyBatis 模糊查询的常见写法对比(安全 vs 不安全),这样你写 SQL 时能直接套用?
Prompt:@datetimeformat注解
Response:
@DateTimeFormat 是 Spring 提供的一个注解,主要用来将请求参数中的日期/时间字符串转换成 Java 中的日期对象。常用于 Controller 方法的参数绑定。
1. 基本用法
1 |
|
- 请求:
1
GET /test?date=2025-08-18
- 自动绑定:
"2025-08-18"→LocalDate(2025-08-18)
2. 常见场景
(1)查询区间
1 |
|
请求:
1 | GET /emp?begin=2025-01-01&end=2025-12-31 |
(2)绑定到实体对象
1 | public class EmpQuery { |
1 |
|
请求:
1 | GET /emp?entryDate=2025-08-18 |
3. 注意事项
@DateTimeFormat只对请求参数有效(比如 QueryString、表单提交)。- 对
@RequestBodyJSON 无效,因为 JSON 反序列化用的是 Jackson。 - JSON 要用
@JsonFormat。1
2
private LocalDateTime createTime;
- 对
pattern必须和传入的日期字符串格式匹配,否则会报 400 错误。- 常用格式:
"yyyy-MM-dd"→LocalDate"yyyy-MM-dd HH:mm:ss"→LocalDateTime"HH:mm:ss"→LocalTime
4. 对比 @JsonFormat
@DateTimeFormat:作用在请求参数绑定(URL、表单)@JsonFormat:作用在JSON 序列化/反序列化(RequestBody、ResponseBody)
✅ 总结:@DateTimeFormat 用于接收请求参数中的日期字符串,并自动转成 LocalDate / LocalDateTime / Date。如果是 JSON 数据,需要用 @JsonFormat。
要不要我帮你整理一份 @DateTimeFormat 和 @JsonFormat 的对比表,这样你能快速知道什么时候用哪个?
Prompt:动态sql
Response:
在 MyBatis 中,动态 SQL 是一大特色。它能让 SQL 根据条件拼接生成,避免写很多 if-else 判断,常用于查询条件不固定的场景(比如多条件查询)。
1. 动态 SQL 标签
MyBatis 提供了一些 XML 标签,用来拼接 SQL:
✅ <if>
条件判断,常用于可选查询条件。
1 | <select id="list" resultType="com.example.pojo.Emp"> |
✅ <where>
自动处理 SQL 条件开头的 AND/OR。
1 | <select id="list" resultType="com.example.pojo.Emp"> |
👉 如果没有条件,<where> 不会生成 where。
👉 如果有条件,会自动去掉第一个 and。
✅ <choose> / <when> / <otherwise>
类似 Java 的 switch。
1 | <select id="list" resultType="com.example.pojo.Emp"> |
✅ <trim>
自定义去掉多余的 and/or,比 <where> 灵活。
1 | <trim prefix="where" prefixOverrides="and|or"> |
✅ <set>
用于 update,自动去掉多余的 ,。
1 | <update id="update"> |
✅ <foreach>
用于遍历集合,常见于 in 查询或批量插入。
1 | <select id="listByIds" resultType="com.example.pojo.Emp"> |
2. 动态 SQL 示例 —— 多条件分页查询
1 | <select id="list" resultType="com.example.pojo.Emp"> |
3. 注意事项
<if>、<choose>里写的是 OGNL 表达式,要保证参数名和 Java 代码一致。- 如果有多个查询条件,推荐用
<where>或<trim>来自动去掉多余的and。 in查询要用<foreach>,不能直接拼接${},否则有 SQL 注入风险。
✅ 总结:
MyBatis 动态 SQL 的核心是 <if>、<where>、<choose>、<trim>、<set>、<foreach>。合理使用这些标签,可以让 SQL 既灵活又安全。
要不要我帮你画一张 动态 SQL 标签用法速查表(每个标签对应场景 + 示例),方便你查阅?
Prompt:如何获取生成的主键
Response:
在 MyBatis / MyBatis-Plus / Spring Boot + MyBatis 项目里,插入数据时往往需要获取数据库生成的主键(自增 id),常见做法有以下几种:
1. MyBatis 注解方式
在 @Insert 中指定 @Options:
1 |
|
useGeneratedKeys = true:启用 JDBC 的返回主键功能keyProperty = "id":指定把生成的主键赋值到实体类的id字段
调用时:
1 | Emp emp = new Emp(); |
2. MyBatis XML 方式
1 | <insert id="insert" useGeneratedKeys="true" keyProperty="id"> |
和注解方式一样,插入后会把主键回填到 Emp.id。
3. MyBatis + <selectKey>
适用于 不支持 JDBC 自动返回主键 的数据库(如 Oracle)。
1 | <insert id="insert" parameterType="com.example.pojo.Emp"> |
order="BEFORE":先执行selectKey,再执行insert(Oracle 这种场景)。order="AFTER":先插入,再获取主键(MySQL 支持)。
4. MyBatis-Plus
如果你用的是 MyBatis-Plus,主键策略配置好后,会自动回填。
(1)实体类配置
1 |
|
(2)使用 Mapper 插入
1 | empMapper.insert(emp); |
MyBatis-Plus 会自动回填主键。
5. 注意事项
- 数据库必须支持自增主键(MySQL 的
AUTO_INCREMENT,PostgreSQL 的SERIAL,Oracle 需要序列)。 keyProperty要和 Java 实体类的字段名一致,否则无法回填。- 如果主键不是自增(比如 UUID),需要在 Java 层生成后手动传入。
要不要我帮你整理一份 「MyBatis 获取主键」对比表(注解、XML、selectKey、MyBatis-Plus)」,方便你面试和开发时记忆?
Prompt:这是什么错误
JDBC Connection [HikariProxyConnection@99682068 wrapping com.mysql.cj.jdbc.ConnectionImpl@f6984cb] will not be managed by Spring
==> Preparing: insert into emp(username, password, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76b99f67]
2025-08-19 00:45:18.741 ERROR 46284 — [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘entry_date’ in ‘class com.example.pojo.Emp’] with root cause
org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘entry_date’ in ‘class com.example.pojo.Emp’
at org.
Response:
你这个报错其实很典型,我帮你拆解一下:
错误信息核心
1 | There is no getter for property named 'entry_date' in 'class com.example.pojo.Emp' |
意思是:MyBatis 在执行 SQL 时,发现你 SQL 里写了 #{entry_date},但是在 Emp 实体类里没有找到对应的 getter 方法。
具体原因
- 你的 SQL 是这样的:
1
2insert into emp(username, password, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time)
values(#{username}, #{password}, #{name}, #{gender}, #{phone}, #{job}, #{salary}, #{image}, #{entry_date}, #{deptId}, #{createTime}, #{updateTime}) - 实体类
Emp里字段名很可能是 **entryDate**(驼峰),而不是 **entry_date**(下划线)。
所以 MyBatis 找不到 getEntry_date() 这个方法,自然报错。
解决方案 ✅
- 保持 SQL 占位符与 Java 字段名一致(推荐)注意这里要改成
1
values(#{username}, #{password}, #{name}, #{gender}, #{phone}, #{job}, #{salary}, #{image}, #{entryDate}, #{deptId}, #{createTime}, #{updateTime})
#{entryDate},因为 Java 里是驼峰entryDate。
- 或者修改实体类字段名,保持和表一致但这样不符合 Java 命名规范,不推荐。
1
private LocalDate entry_date;
- 使用 MyBatis 驼峰映射(推荐做法)
在application.yml里启用:这样 MyBatis 会自动把1
2
3mybatis:
configuration:
map-underscore-to-camel-case: trueentry_date↔entryDate映射,但注意 SQL 中#{}里的参数名还是要写成 Java 字段名(即#{entryDate})。
关于前面那句提示
1 | JDBC Connection [...] will not be managed by Spring |
这只是 日志提示,不是导致报错的原因。它表示这次获取的连接没有事务控制,Spring 不会自动管理提交/回滚。但真正的错误是上面的 属性名不匹配。
👉 总结:
把 SQL 里的 #{entry_date} 改成 #{entryDate} 就能解决问题。
要不要我帮你写一个 实体类 Emp 的正确字段定义 + 插入 SQL 示例,让你对照一下避免后面再踩坑?
Prompt:
JDBC Connection [HikariProxyConnection@1158882433 wrapping com.mysql.cj.jdbc.ConnectionImpl@25e086de] will not be managed by Spring
==> Preparing: insert into emp(username, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: linpingzhi(String), 林平之(String), 1(Integer), 18809091234(String), 1(Integer), 8000(Integer), https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-03-07-37-38222.jpg(String), 2022-09-18(LocalDate), 1(Integer), 2025-08-19T00:49:47.628760100(LocalDateTime), 2025-08-19T00:49:47.628760100(LocalDateTime)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@21b28c64]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@ddfb3bb] was not registered for synchronization because synchronization is not active
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@ddfb3bb]
2025-08-19 00:49:48.030 ERROR 42900 — [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter ‘exprList’ not found. Available parameters are [arg0, collection, list]] with root cause
org.apache.ibatis.binding.BindingException: Parameter ‘exprList’ not found. Available parameters are [arg0, collection, list]
2/2
Response:
从日志来看,你这次插入操作本身是 成功的:
1 | ==> Preparing: insert into emp(...) |
说明数据已经正确写入数据库。真正的异常出现在 后续 SQL 执行,错误是:
1 | org.apache.ibatis.binding.BindingException: Parameter 'exprList' not found. |
错误根源解析
MyBatis 在执行某个语句时需要用到 exprList 参数,但你的方法签名中并没有提供这个名字的参数。
Available parameters are [arg0, collection, list]
表明 MyBatis 能识别到的参数名只有arg0、collection、list,这通常发生在 你传递了一个List或Collection类型参数 的场景。- 典型情况就是在写 批量操作 / in 查询 时,使用了类似:但是方法签名可能是:
1
2
3<foreach collection="exprList" item="expr" separator="," open="(" close=")">
#{expr}
</foreach>这时 MyBatis 默认把这个1
int batchDelete(List<Integer> ids);
List命名为list或collection,并没有exprList这个名字,所以报错。
解决方案
要么修改 SQL,要么修改方法签名,让两边保持一致:
- 方式一:改 XML
1
2
3<foreach collection="list" item="expr" separator="," open="(" close=")">
#{expr}
</foreach> - 方式二:改 Mapper 方法参数并加注解这样 XML 里
1
int batchDelete( List<Integer> exprList);
collection="exprList"就能对应上。
关于日志中的提示
1 | JDBC Connection [...] will not be managed by Spring |
这些只是 事务未开启 的提示,不是导致报错的直接原因。表示你这段操作没有开启 Spring 声明式事务(@Transactional),所以 MyBatis 的 SqlSession 是非事务性的。
只要确认你的场景确实不需要事务,这个提示可以忽略;如果是多步写操作需要原子性,就要在 Service 方法上加上 @Transactional。
✅ 总结:
- 插入语句是成功的,报错来源于另一个 SQL(涉及
exprList的动态 SQL 语句)。 - 修改 XML 的
foreach collection与 Mapper 方法参数一致即可解决。 will not be managed by Spring只是事务管理提示,不是异常根源。
2/2
Prompt:方法参数名和 XML 中的 collection 不一致
void insertBatch(List
insert into emp_expr(emp_id, begin, end, company, job) values
(#{empExpr.empId}, #{empExpr.begin}, #{empExpr.end}, #{empExpr.company}, #{empExpr.job})
Response:
你这个写法报的错正是因为 方法参数名和 XML 中的 collection 不一致。
在 MyBatis 里,方法参数如果是 List 或 Collection,默认可用的名字只有:
listcollectionarg0(按位置参数编号)
所以你在 XML 写 collection="exprList",MyBatis 找不到,就抛了:
1 | Parameter 'exprList' not found. Available parameters are [arg0, collection, list] |
✅ 解决方案
方案一:加 @Param 注解(推荐)
1 | void insertBatch( List<EmpExpr> exprList); |
XML 不变:
1 | <foreach collection="exprList" item="empExpr" separator=","> |
这样 MyBatis 就能识别到 exprList 参数了。
方案二:直接用默认的名字 list
保持 Mapper 方法:
1 | void insertBatch(List<EmpExpr> exprList); |
XML 改为:
1 | <foreach collection="list" item="empExpr" separator=","> |
方案三:如果只有一个 List 参数,直接用 collection
1 | <foreach collection="collection" item="empExpr" separator=","> |
⚠️ 补充注意事项
- 批量插入性能优化
- 如果数据量很大,建议在 MyBatis 的
mybatis-config.xml里开启:1
2
3<settings>
<setting name="defaultExecutorType" value="BATCH"/>
</settings> - 或者在 Service 层用
SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);。
- 如果数据量很大,建议在 MyBatis 的
- 数据库驱动支持
批量插入时 MySQL 驱动必须是rewriteBatchedStatements=true才能真正合并成批量 SQL。
例如在application.yml:1
2
3spring:
datasource:
url: jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true


