公共字段填充
每次添加数据时都要设置创建时间,创建人,修改时间,修改人等字段,实在是太繁琐了。那我能不能让他们添加这些字段的信息呢?
问题分析
创建时间,创建人,修改时间,修改人等字段都属于公共字段,能否将这些公共字段在某个地方统一处理,来简化开发呢?
如果都按照上面的方法进行操作的话,那我们就需要在每个业务方法中进行操作,这样会不会显得我们的代码过于冗余、繁琐。
这个时候我们就可以使用Mybatis Plus提供的公共字段自动填充功能
基本功能实现
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。
在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:
字段名 | 赋值时机 | 说明 |
---|---|---|
createTime | 插入(INSERT) | 当前时间 |
updateTime | 插入(INSERT) , 更新(UPDATE) | 当前时间 |
createUser | 插入(INSERT) | 当前登录用户ID |
updateUser | 插入(INSERT) , 更新(UPDATE) | 当前登录用户ID |
接下来我们就开始编写代码。
首先第一步就是在实体类的属性上加入 @TableField 注解,指定自动填充的策略。
注 :
fill : 字段自动填充策略
添加好注解后,我们就需要按照框架要求编写元数据对象处理器这样的一个类,在此类中统一为公共字段赋值,注意!此类需要实现MetaObjectHandler接口。
/**
* 自定义元数据对象处理器
*
* @author shenyi
*/
@Component
@Slf4j
public class MyMetaObjectHandle implements MetaObjectHandler {
/**
* 插入操作自动填充
*
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充 [insert]...");
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("createUser", new Long(1));
metaObject.setValue("updateUser", new Long(1));
}
/**
* 更新操作自动填充
*
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充 [update]...");
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", new Long(1));
}
}
功能完善
细心的朋友已经发现,我们上面只填充了时间,并没有填充创建人和更新人,现在我们就来完善一下。
本来我想的是,我登录的时候是将用户id存入了HttpSession中,现在我从HttpSession中获取不就行了?
但是 ,MyMetaObjectHandler类中是不能直接获得HttpSession对象的,所以我们需要通过其他方式来获取登录用户id。
这里我们使用JAVA 给我们提供的一个类,ThreadLocal。
在了解 ThreadLocal 之前我们先了解当我们在增加/修改员工信息时, 我目前项目业务的执行流程是什么样子的,如下图:
客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
- LoginCheckFilter (登录过滤器) 的doFilter方法
- Controller的方法
- MyMetaObjectHandler的insertFill/updateFill方法
介绍
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问当前线程对应的值。
常用方法
public void set(T value) | 设置当前线程的线程局部变量的值 |
---|---|
public T get() | 返回当前线程所对应的线程局部变量的值 |
public void remove() | 删除当前线程所对应的线程局部变量的值 |
思路分析
- 我们可以先在LoginCheckFilter(登录过滤器) 的doFilter方法中获取当前登录用户id。并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id)。
- 在MyMetaObjectHandler的insertFill/updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
- 如果在后续的操作中, 我们需要在Controller / Service中要使用当前登录用户的ID, 可以直接从ThreadLocal直接获取。
代码实现
首先为了使用方便,我们可以编写一个基于ThreadLocal封装的工具类,主要get 和 set 方法。
/**
* @description: 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
* @author: Jie
* @date: 2022/8/12 14:18
**/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* @description: 设置值
* @author: Jie
* @date: 2022/8/12 14:18
* @param: [id] 线程id
**/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* @description: 获取值
* @author: Jie
* @date: 2022/8/12 14:18
**/
public static Long getCurrentId(){
return threadLocal.get();
}
}
然后在LoginCheckFilter(登录过滤器)的doFilter方法中调用BaseContext来设置当前登录用户的id。
接下来我们就可以 在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id。