mybatis MetaObjectHandler重复字段自动填充与线程变量ThreadLocal的巧妙联合使用方法

发布时间 2023-04-11 16:12:05作者: 青竹玉简

1.重复字段自动填充
在对数据库crud时,相信很多人都写过这样的代码,在多个service中反复的对诸如createTime,updateTime,createUser,updateUser这样的字段进行反复的set操作。很显然这样的代码时相当烦杂的,那么有没有一种方法能够让程序全局对这样的字段进行自动的set操作呢?从而让我们更加关注业务的底层逻辑,答案是肯定的。

mybatisPlus提供了这样一个接口metaObjectHandler,使用起来非常简单,首先在实体类需要自动填充的属性上加上@TableField注解,括号里面指定填充策略,是要在插入时填充还是在更新时填充?如下:

@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

@TableField(fill = FieldFill.INSERT)
private Long createUser;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
其次,我们只需要继承该接口,重写两个方法,这里看名字应该就能明白,insertFill意味着插入填充,代表着往数据库插入数据时该方法触发,updateFill则是更新数据触发,这里的metaObject时数据源的意思,我们只需要借助它的setValue方法就能能够设置需要自动填充的数据,数据参数为典型的键值对形式,借此我们可以省去项目中的所有对重复字段的set操作!示例如下:

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser",BaseContext.getCurrentId());

}

@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
}
那么还有一个问题,有些数据在该类中拿不到怎么办?比如这里的currenetId,以为着当前登录用户的id,因为当有人对数据库中的某些数据进行修改或创建时,我们毫无疑问需要记录下这个操作人是谁?而这个数据很显然是存储在前端的session中的,我们这个类不像spring mvc的controlelr层可以自动注入session对象,我们要如何拿到这个数据呢?这里就引出了下面的问题,线程变量的使用。

2.线程变量ThreadLocal的使用
在使用该变量之前,我们需要先明确一个事情,那就是在spring mvc中,由前端发送一个ajax请求到服务端,服务端的filter进行拦截,拦截通过后抵达相应的controller,controller调用service,service操作mybatis持久化到数据库的过程中,他们是由一个线程来完成!!意思就是说前端的每一个http请求,他在filter - controller - 数据库这个过程中,他们拥有一个相同的ThreadLocal !而ThreadLocal的作用是存放变量,这也就意味着我们可以借助threadLocal在这个数据链路上访问相同的资源!!那么上面的问题要解决就很简单了,我们只需要在filter中借助servlet对象拿到相应的数据存入ThreadLocal中,那么在我们继承metaObjectHandler的类中由于处在同一条执行线路上,它就可以从ThreadLocal中取出这个数据,进而填入数据库。具体代码流程如下:

1.封装一个util:(这里我已Long型数据为例,实际这里是一个泛型T,意味着你可以放入任何类型的数据)

/**
* 基于threadLocal封装工具类,用于保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

public static void setCurrentId(Long id){
threadLocal.set(id);
}

public static Long getCurrentId(){
return threadLocal.get();
}


}
2.在filter中往ThreadLocal存入数据

if (request.getSession().getAttribute("employee") != null) {
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);

filterChain.doFilter(servletRequest, servletResponse);
return;
}
3.就是上面在MyMetaObjectHandler中直接取值完成自动填充。

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {

metaObject.setValue("createUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser",BaseContext.getCurrentId());

}

@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
}
至此,全流程结束