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
        LambdaQueryWrapper<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框架无法通过反射将查询字段赋值给实体类属性。

思考

@TableField注解的value属性,常是用于解决数据库字段名与实体类的属性名不一致时的问题,先前认为使用该注解就能结果上述问题。查看源码中MP给出的注释,如下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableField {

    /**
     * 数据库字段值
     * <p>
     * 不需要配置该值的情况:
     * <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
prepareStatement
查询SQL执行完成,处理查询结果的映射关系
在这里插入图片描述
根据是否打开驼峰自动转换开关,处理属性名,注意这个开关在Mybatis中是默认关闭,而在MP中是默认打开的!所以在这里的pass_word被替换成了password
在这里插入图片描述
之后从容器启动时MP缓存的实体类的setter与getter方法集合中,查找实体类属性的setter方法,这里可以找到age,但是pass_word因被替换成password,导致无法找到对应的setter方法
在这里插入图片描述
接下用实体类接收字段查询结果不为NULL,且符合命名规范的属性
在这里插入图片描述
通过反射执行上面找到的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;

方法三:在配置文件中关闭驼峰自动映射

mybatis-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>
    
    <select id="myList" resultMap="myResultMap">
        select pwd, age from camel
    </select>  

参考

https://blog.csdn.net/HongYu012/article/details/123301153

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值