我忏悔我有罪,下次再也不偷懒了T_T
书接上文,我们已经新建了一个项目并写了俩简单的接口
一、恢复凶案现场
查询都已经正常返回结果,说明项目配置、Mapper映射以及基础查询流程都没有问题
但是,奇怪的事情出现了,,
我尝试为User表添加一个插入接口
我的xml:

我的Mapper:

我的service:

我的controller:(为了方便在浏览器中调用接口,这里统一都用@RequestMapping)
看起来一片祥和,没啥问题,但是当我真正调用这个接口的时候,纷争开始了。。。
我们正常插入一条信息

使用navicat看一下

user表中正确插入了这条信息,欸,我们不是写了一个selectAll接口嘛,我们调这个接口来看一下

什么?!(教主同款哨音)
再看日志,
???,把张三塞哪去了?
二、真凶居然是它
我们先冷静一下,插入接口执行成功,数据库中确实插入了我们想要的数据,但是查询接口报错,问题不是SQL写错了,也不是数据库没数据,那就是MyBatis在把结果封装成对象这一步出现了问题
那么问题来了,MyBatis是怎么把查询结果封装成对象的?
回到User实体类,我们是这样写的
java
@Data
public class User {
private Integer id;
private String userName;
private Integer age;
private Integer deptId;
// 为了 insert 方便,偷了个懒
public User(String userName, Integer age, Integer deptId) {
this.userName = userName;
this.age = age;
this.deptId = deptId;
}
}
当初的想法很简单,插入的时候直接new User(name, age, deptId),简单高效呀
没想到,正是这里爆雷了
由于User中存在带参构造方法,我们在XML中又使用了resultType,那么MyBatis会尝试使用构造方法进行映射
于是他开始这么干:
java
new User( ? , ? , ? )
然后把查询结果按顺序往里塞
java
id -> 第一个参数
user_name -> 第二个参数
age -> 第三个参数
很明显,这里传参的时候我没给id,第一个参数是userName,把这玩意往integer里面塞,不报错就怪了
三、如何死而复生?
坑已经出现了,该怎么填一下呢?
大致两种方法
3.1 缝缝补补又一年
很简单,MyBatis想用构造方法映射结果集,不让它用!!
我们补充一个无参的构造方法,让MyBatis老老实实走Setter注入,问题解决
补充一下这样做的原理:MyBatis在映射查询结果时,有一套固定的对象创建优先级策略
优先会尝试使用构造方法注入,实体类中存在可用的带参构造方法,我们在XML中使用的也是resultType,MyBatis会猜测构造方法参数,并与查询结果进行匹配。这也就是我们暴雷的那种方法,缺点很明显,这种匹配时隐式的,一旦顺序或者类型对不上,直接报错
找不到合适的构造方法,才会退回Setter注入,而Setter注入的前提是,类中必须存在一个无参构造方法
因此我们补无参构造,本质是在引导MyBatis走正确的路
3.2 引入新伙伴
既然User你完成不好这个操作,那我再定义一个类来帮你,顺便把你俩职责划分清晰一点
我们新定义一个类,UserDTO,专门用来接受参数
java
public class User {
private Integer id;
private String name;
private Integer age;
private Integer deptId;
public User() {
}
// getter / setter
}
它只关心select和resultMap,不关心插入时哪些字段必填
对应的,Mapper层也修改一下
java
int insert(UserInsertDTO dto);
此时MyBatis的角色很纯粹:负责从DTO里取值,不再尝试"构造对象"
这样我们就不需要"凑一个User对象",而是直接创建一个待插入的用户
3.3 终极解决方案
前面的坑,本质原因其实一句话概括就是:MyBatis在使用resultType时,会自作聪明地推断如何创建对象
那我们不如直接告诉MyBatis,Java中的类对应数据表的那个字段
这种方式在表结构复杂、字段名和属性名对应不上、实体类有多个构造方法等场景下基本上是必选项
我们可以在xml文件中添加这样一段
xml
<resultMap id="UserResultMap" type="com.example.demo2.entity.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="dept_id" property="deptId"/>
</resultMap>
使用resultMap
xml
<select id="selectAll" resultMap="UserResultMap">
select id, name, age, dept_id from user
</select>
这样MyBatis在处理查询结果的时候就完全不会再猜了
好啦,今天的坑就踩到这里吧