一、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) {
        // 这里可以用来设置插件的属性
    }

}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐