MyBatis-Plus公共字段自动填充无效排查

与世无争的帅哥 提交于 2019-12-18 11:53:45

目录

前言

MetaObjectHandler配置

entity字段注解

复现

原因

如何解决

但有疑问


前言

      公司系统,mybatis-plus(v3.1.0)配置了MetaObjectHandler公共字段自动填充,一直以来都使用良好。昨天发现了个BUG——某些service方法更新时,不会自动填充edit_time,遂排查。

先说问题说在:XXService.update(Wrapper updateWrapper)方法。


MetaObjectHandler配置

public class MetaObjectHandlerConfig implements MetaObjectHandler {

    // mybatis-plus公共字段自动填充,https://baomidou.oschina.io/mybatis-plus-doc/#/auto-fill
    @Override
    public void insertFill(MetaObject metaObject) {
//        System.out.println("插入方法实体填充");
//        System.out.println(DateUtils.toDateText(new Date(),DateUtils.DEFAULT_DATE_TIME_FORMAT));
        //System.out.println("公共填充方法:" + metaObject.findProperty("creator", true));
        //System.out.println(metaObject.getValue("creator"));
        Date date = new Date();
        setInsertFieldValByName("editor",metaObject.getValue("creator"),metaObject);
        setInsertFieldValByName("createTime", date, metaObject);
        setUpdateFieldValByName("editTime", date, metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
//        System.out.println("更新方法实体填充");
        Date date = new Date();
        setUpdateFieldValByName("editTime", date, metaObject);
    }
}

插入时设置editor为creator,createTime,editTime为当前时间;更新时设置editTime字段值为当前时间。


entity字段注解

@Data
public class BaseEntity implements Serializable {
    /**
     * 创建时间
     */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Date createTime;
    /**
     * 修改时间
     */
    @TableField(value = "edit_time", fill = FieldFill.INSERT_UPDATE)
    private Date editTime;
    /**
     * 创建人
     */
    @TableField(fill = FieldFill.INSERT)
    private String creator;
    /**
     * 修改人
     */
    private String editor;
    /**
     * 版本号
     */
    @Version
    private Long version;
    /**
     * 是否软删
     */
    @TableLogic
    private Boolean isDeleted;

}

editTime设置自动填充策略为:插入和更新填充字段


复现

@RunWith(SpringRunner.class)
@SpringBootTest
public class AutoFillTest {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(AutoFillTest.class);
    
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private IUserService userService;
    
    @Test
    public void test() throws InterruptedException {

        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(User::getId,1L);
        wrapper.set(User::getAge,12);

        //不会更新editTime
        userService.update(wrapper);

        //能更新
//        userService.update(new User(),wrapper);

        LOGGER.info("query user:{}",userMapper.selectById(1L));
    }
}

userService.update(wrapper);不会更新edit_time;而用userService.update(new User(),wrapper);则能更新。很奇怪。

追踪源码到com.baomidou.mybatisplus.core.MybatisDefaultParameterHandler#populateKeys方法 ->

代码165行发现:使用userService.update(wrapper);这种方法,tableInfo为null,没有拿到表相关字段信息,直接return,也就没有执行我们自定义的MetaObjectHandler配置。反之则能更新。


原因

看源码发现com.baomidou.mybatisplus.core.conditions.AbstractWrapper#entity,泛型T是 数据库表映射实体类,你不传则赋值null,并调用重载update方法(com.baomidou.mybatisplus.extension.service.IService#update(T, com.baomidou.mybatisplus.core.conditions.Wrapper<T>)) 。参考 ==>

 


如何解决

这就简单啦 -->

1.使用update的重载方法:userService.update(new User(),wrapper);

2.使用LambdaQueryWrapper<User> wrapper= new LambdaQueryWrapper<>(new User());

3.LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate(new User());

问题解决~


但有疑问

如上图136行:它实际上还是调用了update包含两个参数的重载方法,只不过entity赋值为null。回到128行代码,两个入参都是泛型T。

那么疑问来了:为什么136行代码entity参数不反射Wrapper的泛型参数T,实例化对象,再传入给entity参数呢?

......

......

结果是不行的,参考大佬博客怎样获取List的泛型参数,如List<String>?


    "看了一些别人的说法,说是带泛型参数的变量要么是成员变量,要么是方法参数才能获取泛型参数类型,因为java在类的方面只提供了一个getGenericSuperClass()的方法,只能获取到父类的泛型参数类型。在成员变量和方法上还有getGenericType()和getGenericParameterTypes()方法。这是泛型的类型擦除导致的,具体我还没有探究。
    但这不是纯粹的泛型,因为要明确给出类型才能获取。如定义方法getName(List<String> lists),使用反射可以很容易得到String类型,但如果把方法改为getName(List <T> lists),无论你使用什么方法都不可能得到运行时给出的T的类型,只能得到字符串“T”。有人说有个什么框架(忘了)采用了这样一个方法:定义一个内部类,把T传给该类的成员变量,然后使用匿名内部类得到该成员变量,我试了,绕了一大圈,最后得到的还是“T”,要想得到运行时传递的泛型参数类型,就必须在方法中明确声明泛型类型。
    那么如果真的想使用纯粹的泛型参数(确实有这个需求,比如设计一些工具方法时)怎么办呢,其实有一种方法,不需要反射(如果用反射,代码还要多几行),就是加一个Class的参数,如getName(Class clazz, List<T> lists),这样调用方法时给出clazz即可,而clazz就是T的Class。如果你想说这代码不够优雅,那我也没办法了。"

好吧,看来泛型和反射方面的知识我还有待补充啊。

完~

 

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!