文章目录
1、问题:
2.常规冗余不利于维护操作:
而针对于这些字段,我们的赋值方式为:
1). 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户姓名。
2). 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户姓名。
目前,在我们的项目中处理这些字段都是在每一个业务方法中进行赋值操作,如下:
如果都按照上述的操作方式来处理这些公共字段, 需要在每一个业务方法中进行操作, 编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?
答案是可以的,我们使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。
3.实现思路:
在实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:
- 自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法
- 自定义切面类AutoFillAspect,统一拦截加入了AutoFill 注解的方法,通过反射为公共字段赋值
- 在 Mapper 的方法上加入 AutoFill 注解
2、步骤:搭建一个简单的SpringBoot+MyPlus项目:
表结构:
CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL COMMENT '学生名称',
`age` int(10) DEFAULT NULL COMMENT '学生年龄',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`create_name` varchar(255) DEFAULT NULL COMMENT '创建人姓名',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_name` varchar(255) DEFAULT NULL COMMENT '修改人姓名',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
1. 依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.js</groupId>
<artifactId>AopMybatisTest</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<log4j.version>1.2.17</log4j.version>
<druid.version>1.2.8</druid.version>
<mybatisplus.version>3.4.2</mybatisplus.version>
</properties>
<dependencies>
<!--web启动依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 配置连接信息:
#配置端口
server:
port: 80
spring:
#配置数据源
datasource:
#配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
#配置连接数据库的信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
username: root
password: root
#MyBatis-Plus相关配置
mybatis-plus:
configuration:
#配置日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3. 创建三层架构:
略~
4. 代码开发:
4.1 定义数据库操作类型枚举
package com.js.Enum;
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
4.2、自定义注解 AutoFill
package com.js.annotation;
import com.js.Enum.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();
}
4.3、自定义切面,实现公共字段自动填充处理逻辑
package com.js.constant;
/**
* @ClassName AutoFillConstant
* @Author lenovo
* @Date 2024-04-02 23:00
* @Version 1.0
**/
public class AutoFillConstant {
public static final String SET_CREATE_TIME = "setCreateTime";
public static final String SET_CREATE_NAME = "setCreateName";
public static final String SET_UPDATE_TIME = "setUpdateTime";
public static final String SET_UPDATE_NAME = "setUpdateName";
}
package com.js.aspect;
import com.js.Enum.OperationType;
import com.js.annotation.AutoFill;
import com.js.constant.AutoFillConstant;
import com.sun.prism.impl.BaseContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect//声明这个类是一个切面类
@Component//他也是个Bean 也需要交个Spring进行管理
@Slf4j
public class AutoFillAspect {
/**
* 切入点:对那些类,那些方法进行拦截
* 在切入点表达式里:如果想要缩小Spring的扫描范围,可以使用多切入点表达式连接 连接符 &&进行条件组合
* “ * ”:返回值是所有的
* “ com.js.mapper.*.*(..) ”:拦截这个mapper文件夹下面的所有类.方法(参数类型)
* “@annotation(com.js.annotation.AutoFill)” :拦截com.js.mapper中方法加了这个注解的方法
* 。
*/
@Pointcut("execution(* com.js.mapper.*.*(..)) && @annotation(com.js.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段的赋值
* “joinPoint” :连接点,知道那个被拦截到的方法具体的参数是啥样的
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");
/**
* Java里如果要调用一个方法,有两种方式
* 方式1:对象名.方法名(实参)
* 方式2:反射调用方法
* 先获取目标类的字节码
* 在获取想要调用的方法
* 最后反射执行这个方法
*/
//1.获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型:新增,还是修改
//2.获取所有添加注解并且被拦截的方法所有的参数--实体对象
/**
* 注意:
* 如果想实现自动填充的话,一定要保证方法中参数的实体对象 Name Name,放在第一个位置,接下来我们要获取的话,
* 就获取第一个就行,不管之后有多少参数,我们就获取第一个就行,这就是我们的约定
*/
// 为了提高程序的健壮性,加一个判断:如果方法没有实参,就什么都不做
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
Object entity = args[0];
//3.准备赋值的数据
LocalDateTime now = LocalDateTime.now();
//---创建人信息:一般从ThreadLocal中获取,这里就给个默认值
String currentId = "张三";//新增
String updateId = "丰奶大硕";//修改
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if(operationType == OperationType.INSERT){//新增操作
//为4个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateName = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_NAME, String.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateName.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateName = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_NAME, String.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateName.invoke(entity,updateId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.4 mapper方法上面添加相应注解,和操作类型
package com.js.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.js.Enum.OperationType;
import com.js.annotation.AutoFill;
import com.js.domain.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @description: 用户mapper
* @author:js
* @date:2024/1/17 14:55
* @version:3.0
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 插入数据
*
* @param user
* @return
*/
@Insert("insert into User( name, age , create_time, update_time, create_name, update_name)" +
" VALUES" +
" (#{name}, #{age}, #{createTime}, #{updateTime}, #{createName}, #{updateName})")
@AutoFill(value = OperationType.INSERT)
int insert(User user);
/**
* 根据id修改分类
* @param User
*/
@AutoFill(value = OperationType.UPDATE)
void updated( @Param("user") User user);
}
4.5 mapper.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.js.mapper.UserMapper">
<update id="updated">
update user
set name=#{user.name},
age=#{user.age},
update_time=#{user.updateTime},
update_name=#{user.updateName}
where
id=#{user.id}
</update>
</mapper>