深度分析MybatisPlus查询结果映射失败@TableField失效解决办法
目录
- 问题场景
- 源码分析
- 解决办法
- 参考
问题场景
Springboot使用MyBATisPlus框架,数据库中SQL能查到值,但是代码中查不到,出现All elements are null等问题。以下MybatisPlus简称MP
表结构
CREATE TABLE `camel` ( `pwd` varchar(255), `age` int(11) ); INSERT INTO `camel` VALUES ('100', 0); INSERT INTO `camel` VALUES ('200', 1); INSERT INTO `camel` VALUES ('300', 2); INSERT INTO `camel` VALUES ('400', NULL);
Java实体类
@TableName(value = "camel") @Data public class Camel { @TableField("pwd") private String pass_word; @TableField("age") private Integer age; }
SpringBoot测试类
@SpringBootTest class DemoApplicationTests { @Autowired CamelMapper camelMapper; @Test void testTableField() { // 构造查询条件 pwd > 100 的 LambdaQueryWrapper Lamb编程daQueryWrapper<Camel> lambdaQueryWrapper = Wrappers.lambdaQuery(Camel.class) .gt(Camel::getPass_word, "100"); List<Camel> list = camelMapper.selectList(lambdaQueryWrapper); list.forEach(System.out::println); assert list.get(0).getPass_word()!=null; } }
执行结果
代码执行结果与预期不符,数据库中能查询到pwd,代码中查到的pass_word为NULL
先说原因
pass_word字段不满足命名规范,导致MP框架无法通过反射将查询字段赋值给实体类属性。
思考
数据从表中到实体类,可以理解为有两个阶段,首先是通过查询sql得到字段值,然后ORM框架将查询到的结果集通过setter的方式赋值给接收对象。
@TableField注解,用于使用MP动态生成SQL或用resultType接受自定义SQL结果集时,解决数据库字段名与实体类的属性名不一致时的问题,先前认为使用该注解就能结果上述问题。查看源码中MP给出的注释,如下
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) public @i编程nterface TableField { /** * 数据库字段值 * <p> * 不需要配置该值的情况: python * <li> 当 {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} 为 true 时, * (mp下默认是true,mybatis默认是false), 数据库字段值.replace("_","").toUpperCase() == 实体属性名.toUpperCase() </li> * <li> 当 {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} 为 false 时, * 数据库字段值.toUpperCase() == 实体属性名.toUpperCase() </li> */ String value() default "";
当使用@TableField注解,执行sql如下:
select 注解值 as 实体类属性名 from table;
@TableField注解是通过别名实现的,其作用是当resultType为实体类时,自动为查询结果设置别名,是发生在查询阶段,也因此将控制台打印的sql拿到数据库中能正常执行,而本次问题是发生在用实体类接收查询结果阶段。
源码分析
容器启动后,MP会缓存Mapper实体类的setter与getter方法集合,这里使用了Lombok自动生成setter与getter
开始执行查询方法后,首先预编译PrepareStatement
当配置了Mybatis或MP的日志实现,控制台会打印预编译sql与参数,就是在这里实现的
预编译参数赋值,这里就是查询条件中的100
查询SQL执行完成,处理查询结果的映射关系
根据是否打开驼峰自动转换开关,处理属性名,注意这个开关在Mybatis中是默认关闭,而在MP中是默认打开的!所以在这里的pass_word被替换成了password
之后从容器启动时MP缓存的实体类的setter与getter方法集合中,查找实体类属性的setter方法,这里可以找到age,但是pass_word因被驼峰命名替换成password,而实际上实体类中的属性名是pass_word,导致无法找到password
用实体类中映射成功的字段接收查询结果,这里就只有age,没有password了
通过反射执行上面找到的属性的setter方法
只有符合命名规范的字段才会被赋值成功
最终接收查询结果的集合size = 3,但集合中只有两个元素,是因为SQL查出了3条记录,其中pwd=400的记录,因password字段赋值失败为NULL,且age也为NULL,该对象的所有属性都为NULL,所以集合中就存了一个NULL对象,如果集合中所有元素都是NULL,就会size != 0,且All elements are null
总结
MP通过反射的方式,使用属性对应setter方法为属性赋值,将查询结果映射到实体类,当属性命名不规范,且开启了驼峰命名开关,就会无法找到对应setter方法,导致属性无法赋值成功。
解决办法
方法一:手动添加setter方法
private void setPassword(String pass_word) { this.pass_word = pass_word; }
方法二:若使用Lombok,规范属性命名(可以保留命名不规范的属性,避免影响项目原功能),本质与方法一相同
private String password;
方法三:在配置文件中关闭驼峰自动映射
my编程客栈batis-plus.configuration.map-underscore-to-camel-case=false
方法四:自定义Mapper方法,不使用实体类作为resultType,在XML中使用自定义resultMap,指定查询结果字段与实体类属性对应关系(没有使用resultType,@TableField注解不会生效)
<!--property="pass_word"是实体类属性名,最终执行的setter方法是setPass_word--> <!--column="pwd"是查询结果字段名--> <resultMap id="myResultMap" type="com.example.demo.generator.domain.Camel"> <result property="pass_word" column="pwd"/> <result property="age" column="age"/> </resultMap> <javascriptselect id="myList" resultMap="myResultMap"> select pwd, age from camel </select>
参考
https://blog.csdn.net/HongYu012/article/details/123301153
到此这篇关于MybatisPlus查询结果映射失败@TableField失效解决办法【源码解析】的文章就介绍到这了,更多相关mybatisplus @tablefield失效内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论