一、问题背景。
在常见的后台管理系统中,每张表的公共字段通常有create_time, update_time等,我们在使用MyBatis操作这些表进行增删改查的过程中,每次都学要对这些字段进赋值。
而针对于这些字段,我们的赋值方式为:
1). 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。
2). 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。
目前,在我们的项目中处理这些字段都是在每一个业务方法中进行赋值操作,如下:
/**
* 新增员工
*
* @param employeeDTO
*/
public void save(EmployeeDTO employeeDTO) {
//.......................
//设置当前记录的创建时间和修改时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//设置当前记录创建人id和修改人id
employee.setCreateUser(BaseContext.getCurrentId());//目前写个假数据,后期修改
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.insert(employee);
}
如果都按照上述的操作方式来处理这些公共字段, 需要在每一个业务方法中进行操作, 编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?
**答案是可以的,我们使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。
二、实现思路
在实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:
序号 | 字段名 | 含义 | 数据类型 | 操作类型 |
1 | create_time | 创建时间 | datetime | insert |
2 | create_user | 创建人id | bigint | insert |
3 | update_time | 修改时间 | datetime | insert、update |
4 | update_user | 修改人id | bigint | insert、update |
实现步骤:
(1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法
(2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值
(3). 在 Mapper 的方法上加入 AutoFill 注解
技术点:枚举、注解、AOP、反射
2.1 自定义注解注解@AutoFill
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
@Target(Element.METHOD) // 中该注解作用域
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
// 数据库操作类型: UPDATE, INSERT
OperationType value();
}
2.2 定义枚举
/**
* 数据库操作类型
*/
public enum OperationType {
UPDATE, // 更新操作
INSERT // 插入操作
}
2.3 定义切面类
@Aspect
@Component
@Slf4j
public class AutoFillAspect{
/**
* 切入点
*/
@PointCut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.nuc.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知
*/
@Before("autoFillPointCut()")
public void autoFill(JointPoint jointPoint) {
log.info("快开始进行公共字段填充....");
}
}
2.4完善切面类中autoFill方法
@Before
public void autoFill(JointPoint jointPoint) {
// 获取被迁至方法拦截的书库操作类型
MethodSignature methodSignature = (MethodSignature) jointPoint.getSignature(); // 方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class) // 获得方法上的注解对象。
OperationType operationType = autoFill.value();
// 获得被当前方法拦截的方法参数--实体对象
Object[] args= jointPoint.getArgs();
if(args == null || args.length == 0) {
return;
}
Object entity = arg[0];
// 准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
if(operationType == OperationType.INSERT){
try{
Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.5在Mapper接口的方法上加入 AutoFill 注解
@Mapper
public interface CategoryMapper {
/**
* 插入数据
* @param category
*/
@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
" VALUES" +
" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
@AutoFill(value = OperationType.INSERT)
void insert(Category category);
/**
* 根据id修改分类
* @param category
*/
@AutoFill(value = OperationType.UPDATE)
void update(Category category);
}