Springboot中全局myBaits插件配置
MybatisUpdateInterceptor](file://E:\SupplyChain\mdgylxt_sourcing\md-gyl-sourcing-start\src\main\java\com\mdgyl\hussar\sourcing\config\MybatisUpdateInterceptor.java#L24-L174) 是一个 MyBatis 拦截器,用于在数据库操作前后
一、mybatis拦截器配置
类基本信息
类名: DataSourceConfig
包路径: com.mdgyl.hussar.sourcing.config
注解: 使用了 @Configuration,表明这是一个Spring配置类
主要功能
1. 数据源配置
通过 @Resource 注入了外部配置的 DataSource 数据源
在 SqlSessionFactoryBeanCustomizer 中将数据源设置到 customizeFactoryBean 上
2. 自定义拦截器配置
注入了两个自定义拦截器:
MybatisUpdateInterceptor: 更新操作拦截器
MybatisQueryInterceptor: 查询操作拦截器
将这两个拦截器注册到 SqlSessionFactoryBean 中,形成拦截器链
3. MyBatis定制化配置
实现了 SqlSessionFactoryBeanCustomizer 接口
通过 customize 方法对 SqlSessionFactoryBean 进行定制化配置
这是官方推荐的MyBatis配置方式
package com.mdgyl.hussar.sourcing.config;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SqlSessionFactoryBeanCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* description: mybatis拦截器配置
* author: wuyc
* createTime: 2023-12-02 14:13:54
*/
@Configuration
public class DataSourceConfig {
// 注入数据源
@Resource
private DataSource dataSource;
// 注入自定义参数拦截器
@Resource
private MybatisUpdateInterceptor updateInterceptor;
@Resource
private MybatisQueryInterceptor queryInterceptor;
// 官方推荐的实现方式
@Bean
SqlSessionFactoryBeanCustomizer sqlSessionFactoryBeanCustomizer() {
return new SqlSessionFactoryBeanCustomizer() {
@Override
public void customize(SqlSessionFactoryBean customizeFactoryBean) {
// 设置数据源
customizeFactoryBean.setDataSource(dataSource);
// 设置自定义拦截器(可设置多个)
Interceptor[] plugins = {queryInterceptor, updateInterceptor};
customizeFactoryBean.setPlugins(plugins);
}
};
}
}
二、 MybatisUpdateInterceptor 类介绍
[MybatisUpdateInterceptor](file://E:\SupplyChain\mdgylxt_sourcing\md-gyl-sourcing-start\src\main\java\com\mdgyl\hussar\sourcing\config\MybatisUpdateInterceptor.java#L24-L174) 是一个 MyBatis 拦截器,用于在数据库操作前后自动填充通用字段。
基本信息
- 类名: [MybatisUpdateInterceptor](file://E:\SupplyChain\mdgylxt_sourcing\md-gyl-sourcing-start\src\main\java\com\mdgyl\hussar\sourcing\config\MybatisUpdateInterceptor.java#L24-L174)
- 注解:
- `@Component`: 标识为 Spring 组件
- `@Intercepts`: 定义拦截点,拦截 `Executor` 的 [update]方法
- `@Slf4j`: 提供日志记录功能
主要功能
1. 拦截数据库操作
- 拦截 MyBatis 的 [update](操作(包括 INSERT 和 UPDATE)
- 通过 `@Intercepts` 注解配置拦截点
2. 自动填充字段
针对不同的数据库操作自动填充相应字段:
INSERT 操作**:
- `CREATE_TIME`: 创建时间
- `CREATE_USER_ID`: 创建用户ID
- `CREATE_USER_NAME`: 创建用户名
- `UPDATE_TIME`: 更新时间
- `UPDATE_USER_ID`: 更新用户ID
- `UPDATE_USER_NAME`: 更新用户名
- [TENANT_ID](file://E:\SupplyChain\mdgylxt_sourcing\md-gyl-sourcing-api\src\main\java\com\mdgyl\hussar\sourcing\inquiry\service\impl\InquiryApiServiceImpl.java#L36-L36): 租户ID
- `DELETE_FLAG`: 删除标识(默认正常状态)*UPDATE 操作**:
- `UPDATE_TIME`: 更新时间
- `UPDATE_USER_ID`: 更新用户ID
- `UPDATE_USER_NAME`: 更新用户名
技术特点
- 使用反射机制动态设置对象属性
- 通过 `BaseSecurityUtil` 获取当前登录用户信息
- 支持对 `MapperMethod.ParamMap` 类型参数的处理
- 具备字段存在性检查,避免对不存在的字段进行操作
- 异常处理完善,出现错误时记录日志但不影响主流程
package com.mdgyl.hussar.sourcing.config;
import com.jxdinfo.hussar.common.security.BaseSecurityUtil;
import com.jxdinfo.hussar.common.security.SecurityUser;
import com.mdgyl.common.constants.DatabaseConstants;
import com.mdgyl.common.enums.DeleteStatusEnum;
import com.mdgyl.common.util.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Properties;
/**
* description: mybatis全局拦截器
* author: wuyc
* createTime: 2023-12-02 14:03:19
*/
@Slf4j
@Component
@Intercepts({
@Signature(
type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class}
)
})
public class MybatisUpdateInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// 获取Mybatis的当前操作方法名称
String sqlCommandType = mappedStatement.getSqlCommandType().name();
if (!DatabaseConstants.SQL_COMMAND_TYPE_LIST.contains(sqlCommandType)) {
return invocation.proceed();
}
if (invocation.getArgs().length < 2) {
return invocation.proceed();
}
// 获取Mybatis插入或更新时传入的参数对象
Object paramEntity = invocation.getArgs()[1];
this.interceptInsertOrUpdateMethod(sqlCommandType, paramEntity);
return invocation.proceed();
}
/**
* 插入或更新时的参数拦截方法
*
* @param methodName Mybatis方法名称
* @param paramEntity 参数实体类
*/
private void interceptInsertOrUpdateMethod(String methodName, Object paramEntity) {
SecurityUser securityUser = getSecurityUser();
LocalDateTime systemTime = LocalDateTime.now();
// 若Mybatis的当前方法为INSERT
if (DatabaseConstants.INSERT.equals(methodName)) {
fillInsertMethodProperties(paramEntity, securityUser, systemTime);
return;
}
// 若Mybatis的当前方法为UPDATE
if (DatabaseConstants.UPDATE.equals(methodName)) {
fillUpdateMethodProperties(paramEntity, securityUser, systemTime);
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
// -------------------------------------- private method start, 私有方法放公有方法下面 --------------------------------------------
/**
* 新增数据时填充创建人、创建时间
*
* @param paramEntity 新增数据参数对象
* @param securityUser 用户信息
* @param systemTime 当前系统时间
*/
private void fillInsertMethodProperties(Object paramEntity, SecurityUser securityUser, LocalDateTime systemTime) {
fillProperties(paramEntity, DatabaseConstants.CREATE_TIME, systemTime);
fillProperties(paramEntity, DatabaseConstants.CREATE_USER_ID, securityUser.getUserId());
fillProperties(paramEntity, DatabaseConstants.CREATE_USER_NAME, securityUser.getUserName());
fillProperties(paramEntity, DatabaseConstants.UPDATE_TIME, systemTime);
fillProperties(paramEntity, DatabaseConstants.UPDATE_USER_ID, securityUser.getUserId());
fillProperties(paramEntity, DatabaseConstants.UPDATE_USER_NAME, securityUser.getUserName());
fillProperties(paramEntity, DatabaseConstants.TENANT_ID, securityUser.getTenantId());
fillProperties(paramEntity, DatabaseConstants.DELETE_FLAG, DeleteStatusEnum.NORMAL.getCode());
}
/**
* 编辑数据时填充创建人、创建时间
*
* @param paramEntity 新增数据参数对象
* @param securityUser 登录用户信息
* @param systemTime 当前系统时间
*/
private void fillUpdateMethodProperties(Object paramEntity, SecurityUser securityUser, LocalDateTime systemTime) {
if (!(paramEntity instanceof MapperMethod.ParamMap)) {
return;
}
MapperMethod.ParamMap<?> paramMap = (MapperMethod.ParamMap<?>) paramEntity;
Object updateEntity = paramMap.containsKey(DatabaseConstants.ET) ? paramMap.get(DatabaseConstants.ET) : paramMap.get(DatabaseConstants.PARAM1);
if (Objects.isNull(updateEntity)) {
return;
}
fillProperties(updateEntity, DatabaseConstants.UPDATE_TIME, systemTime);
fillProperties(updateEntity, DatabaseConstants.UPDATE_USER_ID, securityUser.getUserId());
fillProperties(updateEntity, DatabaseConstants.UPDATE_USER_NAME, securityUser.getUserName());
}
private Object getValue(Object updateEntity, String fieldName) {
Field updateUserIdField;
try {
updateUserIdField = updateEntity.getClass().getDeclaredField(fieldName);
updateUserIdField.setAccessible(true);
return updateUserIdField.get(updateEntity);
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error("获取对象值失败: " + fieldName, e);
}
return null;
}
/**
* 校验字段在数据是否存在,存在的话设置默认值
*
* @param paramEntity 操作对象
* @param propertiesKey 数据库的key
* @param propertiesValue 默认赋的值
*/
private void fillProperties(Object paramEntity, String propertiesKey, Object propertiesValue) {
if (!ReflectionUtils.existsField(paramEntity, propertiesKey)) {
return;
}
Object value = getValue(paramEntity, propertiesKey);
if (value != null) {
return;
}
try {
ReflectionUtils.invokeSetterMethod(paramEntity, propertiesKey, propertiesValue);
} catch (Exception e) {
log.error("MybatisParamInterceptor fillProperties error,propertiesKey: {}, propertiesValue: {}, failReason: {}",
propertiesKey, propertiesValue, e.getMessage());
}
}
/**
* 获取系统登录用户信息
*
* @return SecurityUser
*/
private SecurityUser getSecurityUser() {
SecurityUser securityUser = BaseSecurityUtil.getUser();
return Objects.isNull(securityUser) ? new SecurityUser() : securityUser;
}
}
三 MybatisQueryInterceptor 类介绍
MybatisQueryInterceptor 是一个 MyBatis 查询拦截器,主要用于自动添加逻辑删除条件到 SELECT 查询语句中。
基本信息
类名: MybatisQueryInterceptor
注解:
@Component: 标识为 Spring 组件
@Intercepts: 定义拦截点,拦截 StatementHandler 的 prepare 方法
@Slf4j: 提供日志记录功能
主要功能
1. 拦截查询语句
拦截 MyBatis 的 SQL 查询语句执行过程
只处理以 SELECT 开头的 SQL 语句
2. 自动添加逻辑删除条件
针对以 t_ 开头的业务表(忽略大小写)
自动在查询语句中添加 delete_flag = 0 条件
避免查询到已被逻辑删除的数据
核心处理逻辑
SQL 语句识别:
通过 StatementHandler 获取原始 SQL 语句
判断是否为 SELECT 语句
表名识别:
解析 SQL 语句,提取 FROM 子句后的表名
只处理表名以 t_ 开头的业务表
条件添加策略:
如果 SQL 中没有 WHERE 子句,则添加 WHERE delete_flag = 0
如果已有 WHERE 子句但不含 delete_flag 条件,则添加 AND delete_flag = 0
如果已包含 delete_flag 条件,则不做处理
特殊处理包含 LIMIT 子句的情况
核心方法
intercept: 拦截入口方法,实现 SQL 语句的解析和修改
plugin: 包装目标对象,返回代理对象
setProperties: 设置插件属性(当前未实现具体功能)
技术特点
使用正则表达式分割 SQL 语句来识别不同部分
通过反射工具 ReflectionUtils 修改 BoundSql 中的 SQL 语句
支持多种 SQL 结构的处理(带/不带 WHERE、LIMIT 等)
具备完善的日志记录功能,便于调试和监控
package com.mdgyl.hussar.sourcing.config;
import com.mdgyl.common.util.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.util.Properties;
@Slf4j
@Component
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MybatisQueryInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取原始的 SQL 语句
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
String originalSql = statementHandler.getBoundSql().getSql();
if (StringUtils.isEmpty(originalSql) ||
!originalSql.trim().toLowerCase().startsWith("select")) {
return invocation.proceed();
}
log.info("拦截器拼接前的sql是: {}", originalSql);
// 表名前缀为 t_开头的是业务的表 忽略大小写
String[] split = originalSql.split("(?i)from");
String[] tableNameArr = split[1].split("(?i)where");
if (!tableNameArr[0].trim().startsWith("t_")) {
return invocation.proceed();
}
// 修改 SQL,添加逻辑删除字段的条件
String[] sqlStrArr = originalSql.split("(?i)where");
StringBuilder stringBuilder = new StringBuilder(originalSql);
// 判断数据库是否有删除标识字段,如果有的话,就拼接
if (sqlStrArr.length <= 1) {
if (originalSql.toLowerCase().contains("limit")) {
stringBuilder = new StringBuilder(originalSql.replaceFirst("(?i)limit", "WHERE delete_flag = 0 limit "));
} else {
stringBuilder = new StringBuilder(originalSql + " WHERE delete_flag = 0");
}
} else if (!sqlStrArr[1].contains("delete_flag")) {
stringBuilder = new StringBuilder(originalSql.replaceFirst("(?i)where", "WHERE delete_flag = 0 AND "));
}
log.info("拦截器拼接后的sql是: {}", stringBuilder);
String modifiedSql = stringBuilder.toString();
try {
ReflectionUtils.setFieldValue(statementHandler.getBoundSql(), "sql", modifiedSql);
} catch (Exception e) {
log.error("MybatisQueryInterceptor invokeSetterMethod error,modifiedSql: {},failReason: {}", modifiedSql, e.getMessage());
}
// 执行拦截的方法
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 这里可以用来设置插件的属性
}
}
更多推荐



所有评论(0)