之前写过
MybatisPlus 统一管理创建人、更新人、创建时间、更新时间等公共字段_明湖起风了的博客-CSDN博客
但是如果我们实际开发中用的不是 MybatisPlus 而是 Mybatis 怎么实现呐?这就用到了mybatis拦截器。
Mybatis拦截器是什么?
MyBatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用,通过织入拦截器,在不同节点修改一些执行过程中的关键属性,从而影响SQL的生成、执行和返回结果,如:来影响Mapper.xml到SQL语句的生成、执行SQL前对预编译的SQL执行参数的修改、SQL执行后返回结果到Mapper接口方法返参POJO对象的类型转换和封装等。
根据上面的对Mybatis拦截器作用的描述,可以分析其可能的用途;最常见的就是Mybatis自带的分页插件PageHelper或Rowbound参数,通过打印实际执行的SQL语句,发现我们的分页查询之前,先执行了COUNT(*)语句查询数量,然后再执行查询时修改了SQL语句即在我们写的SQL语句后拼接上了分页语句LIMIT(offset, pageSize);
此外,实际工作中,可以使用Mybatis拦截器来做一些数据过滤、数据加密脱敏、SQL执行时间性能监控和告警等;
这里读者自行了解
Mybatis——拦截器Interceptor_七海健人的博客-CSDN博客_mybatisinterceptor
实现:
pom
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mybatisInterceptor</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatisInterceptor</name>
<description>mybatisInterceptor</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.mybatisinterceptor.MybatisInterceptorApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
1. 抽取公共字段创建父类基础模型
package com.example.mybatisinterceptor.bean;
import com.example.mybatisinterceptor.MyInterface.CreateBy;
import com.example.mybatisinterceptor.MyInterface.CreateTime;
import com.example.mybatisinterceptor.MyInterface.UpdateBy;
import com.example.mybatisinterceptor.MyInterface.UpdateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 数据库模型设计时抽出所有通用字段,抽象为父类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class BaseEntity implements Serializable {
@CreateTime
//@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@UpdateTime
//@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
@CreateBy
private String creator;
@UpdateBy
private String updater;
private Boolean deleted;
}
2. java中所有业务实体继承该父类基础模型
package com.example.mybatisinterceptor.bean;
import lombok.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyTableEntity extends BaseEntity {
/**
* 用户ID
*/
private Long id;
/**
* 用户账号
*/
private String userName;
/**
* 加密后的密码
*/
private String passWord;
}
3. mapper
package com.example.mybatisinterceptor.mapper;
import com.example.mybatisinterceptor.bean.MyTableEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MyTableMapper {
public int insert(MyTableEntity myTableEntity);
public int update(MyTableEntity myTableEntity);
}
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.example.mybatisinterceptor.mapper.MyTableMapper">
<insert id="insert" parameterType="com.example.mybatisinterceptor.bean.MyTableEntity" useGeneratedKeys="true" keyProperty="id">
insert into im_base_entity (username,password,create_time,update_time,creator,updater,deleted)
values (#{userName},#{passWord},#{createTime},#{updateTime},#{creator},#{updater},0);
</insert>
<update id="update">
update im_base_entity
<set>
<if test="userName!=null">
username=#{userName},
</if>
<if test="passWord!=null">
password=#{passWord},
</if>
<if test="createTime!=null">
create_time=#{createTime},
</if>
<if test="updateTime!=null">
update_time=#{updateTime},
</if>
<if test="creator!=null">
creator=#{creator},
</if>
<if test="updater!=null">
updater=#{updater}
</if>
</set>
where id=#{id}
</update>
</mapper>
4. MyTableService
package com.example.mybatisinterceptor.service;
import com.example.mybatisinterceptor.bean.MyTableEntity;
import org.springframework.transaction.annotation.Transactional;
@Transactional(rollbackFor = Exception.class)
public interface MyTableService {
public int insert(MyTableEntity myTableEntity);
public int update(MyTableEntity myTableEntity);
}
MyTableServiceImpl
package com.example.mybatisinterceptor.service;
import com.example.mybatisinterceptor.bean.MyTableEntity;
import com.example.mybatisinterceptor.mapper.MyTableMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyTableServiceImpl implements MyTableService{
@Autowired
private MyTableMapper mapper;
@Override
public int insert(MyTableEntity myTableEntity) {
return mapper.insert(myTableEntity);
}
@Override
public int update(MyTableEntity myTableEntity) {
return mapper.update(myTableEntity);
}
}
5. controller
package com.example.mybatisinterceptor.controller;
import com.example.mybatisinterceptor.bean.MyTableEntity;
import com.example.mybatisinterceptor.mapper.MyTableMapper;
import com.example.mybatisinterceptor.service.MyTableService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class baseController {
@Autowired
private MyTableService myTableService;
@PostMapping("/insert")
public int insert (@RequestBody MyTableEntity myTableEntity){
System.out.println(myTableEntity);
return myTableService.insert(myTableEntity);
}
@PostMapping("/uptate")
public int uptate (@RequestBody MyTableEntity myTableEntity){
System.out.println(myTableEntity);
return myTableService.update(myTableEntity);
}
}
别忘了 yml 与mapper扫描
server:
port: 8088
spring:
datasource:
url: jdbc:mysql://localhost:3306/sg_security?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapper-locations: classpath:/mapper/*.xml
#开启驼峰映射
configuration:
map-underscore-to-camel-case: true
6. 核心 自定义注解
package com.example.mybatisinterceptor.MyInterface;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自动设置创建人
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CreateBy {
String value() default "";
}
package com.example.mybatisinterceptor.MyInterface;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自动设置创建时间
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CreateTime {
String value() default "";
}
package com.example.mybatisinterceptor.MyInterface;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自动设置修改人
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface UpdateBy {
String value() default "";
}
package com.example.mybatisinterceptor.MyInterface;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自动设置修改时间
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface UpdateTime {
String value() default "";
}
7. 核心 自定义过滤器
package com.example.mybatisinterceptor.interceptor;
import com.example.mybatisinterceptor.MyInterface.CreateBy;
import com.example.mybatisinterceptor.MyInterface.CreateTime;
import com.example.mybatisinterceptor.MyInterface.UpdateBy;
import com.example.mybatisinterceptor.MyInterface.UpdateTime;
import com.example.mybatisinterceptor.bean.BaseEntity;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.List;
/**
* 自定义 Mybatis 插件,自动设置 createTime 和 updatTime 的值。
* 拦截 update 操作(添加和修改)
*/
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class CustomInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(CustomInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// 获取 SQL 命令
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
//只判断新增和修改
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// 获取参数
Object parameter = invocation.getArgs()[1];
//批量操作时
if (parameter instanceof MapperMethod.ParamMap) {
MapperMethod.ParamMap map = (MapperMethod.ParamMap) parameter;
Object obj = map.get("list");
List<?> list = (List<?>) obj;
if (list != null) {
for (Object o : list) {
setParameter(o, sqlCommandType);
}
}
} else {
setParameter(parameter, sqlCommandType);
}
}
return invocation.proceed();
}
public void setParameter(Object parameter, SqlCommandType sqlCommandType) throws Throwable {
Class<?> aClass = parameter.getClass();
Field[] declaredFields;
//如果常用字段提取了公共类 BaseEntity
//判断BaseEntity是否是父类
if (BaseEntity.class.isAssignableFrom(aClass)) {
// 获取父类私有成员变量
declaredFields = aClass.getSuperclass().getDeclaredFields();
} else {
// 获取私有成员变量
declaredFields = aClass.getDeclaredFields();
}
for (Field field : declaredFields) {
if (SqlCommandType.INSERT.equals(sqlCommandType)) { // insert 语句插入 createBy
if (field.getAnnotation(CreateBy.class) != null) {
field.setAccessible(true);
// 这里实际开发中获取登录用户名,填写真实值
field.set(parameter, "fan");
}
if (field.getAnnotation(CreateTime.class) != null) { // insert 语句插入 createTime
field.setAccessible(true);
field.set(parameter, new Date());
}
}
if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
if (field.getAnnotation(UpdateTime.class) != null) { // update 语句插入 updateTime
field.setAccessible(true);
field.set(parameter, new Date());
}
if (field.getAnnotation(UpdateBy.class) != null) { // update 语句插入 updateBy
field.setAccessible(true);
// 这里实际开发中获取登录用户名,填写真实值
field.set(parameter, "fan");
}
}
}
}
}
@Intercepts 注解用于标记一个类是一个拦截器,它接收一个Signature数组参数,用于指定该拦截器要拦截哪些方法。每个Signature对象都包含三个属性来精确描述要拦截的方法:
1. type: 这个属性指定了要拦截的接口或者类的完全限定名(全类名)。MyBatis中几个常见的可拦截类型及其意义包括但不限于:
(1)Executor: MyBatis的核心执行器接口,负责查询和更新等操作的执行。常见的方法如update、query等,分别对应SQL的更新和查询操作。
(2)ParameterHandler: 参数处理器接口,负责设置预编译语句(PreparedStatement)的参数值。
(3)ResultSetHandler: 结果集处理器接口,负责处理查询结果集,转换为用户定义的对象。
(4)StatementHandler: 语句处理器接口,负责创建和执行SQL语句对象(PreparedStatement或Statement)。
2. method: 指定要拦截的接口或类中的具体方法名称。例如,在Executor类中,update方法用于执行更新操作(包括插入、更新和删除)。
3. args: 这是一个Class数组,表示要拦截方法的参数类型列表。这些类型必须与被拦截方法的实际参数类型严格匹配。例如,{MappedStatement.class, Object.class}意味着拦截的方法接受两个参数,第一个参数类型为MappedStatement(映射语句对象,包含了SQL语句和相关配置信息),第二个参数类型为Object(通常为执行SQL时需要的参数对象)。
@Intercepts注解配合Signature详细定义了拦截的范围和条件,使得开发者可以针对特定的数据库操作或处理流程插入自定义的行为。
测试:添加
结果
更新
结果
至此完毕