mybatis拦截器ParameterHandler详解

目录

需求

实现

使用ParameterHandler拦截器

开启拦截器

spring方式

springboot方式

拦截器实现

方法解读

@Intercepts

@Signature

[public void setProperties(Properties properties)](#public void setProperties(Properties properties))


需求

客户要求入库的用户信息表中敏感信息,例如用户名和证件号等需要SM2国密加密后再入库。

实现

使用mybatis拦截器对入库的敏感信息进行拦截并加密,对查询的敏感信息进行解密。mybatis的拦截器执行的时间节点如下所示。

MyBatis执行流程:

↓

配置文件加载 (Configuration)

↓

创建 SqlSessionFactory

↓

获取 SqlSession

↓

获取 Mapper 代理对象

↓

执行数据库操作

├── Executor 拦截器

├── ParameterHandler 拦截器

├── StatementHandler 拦截器

└── ResultSetHandler 拦截器

使用ParameterHandler拦截器

ParameterHandler是参数拦截器,主要用于拦截参数设置的过程,即拦截ParameterHandler.setParameters方法。这允许我们在执行SQL之前对参数进行修改或记录

开启拦截器

spring方式

XML 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置插件(拦截器) -->
    <plugins>
        <plugin interceptor="cfca.hke.privatization.dao.Intercepts.SubscriberTableParameterHandlerInterceptor"/>
    </plugins>
</configuration>

springboot方式

java 复制代码
@Configuration
public class MyBatisConfig {
    
    @Bean
    public DesensitizeInterceptor desensitizeInterceptor() {
        return new SubscriberTableParameterHandlerInterceptor();
    }
}

拦截器实现

代码如下

java 复制代码
@Component
@Intercepts({
        @Signature(
                type = ParameterHandler.class,
                method = "setParameters",
                args = {PreparedStatement.class}
        )
})
public class SubscriberTableParameterHandlerInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取 parameterObject 对象
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        Object parameterObject = parameterHandler.getParameterObject();

        // 执行拦截逻辑
        processDesensitize(parameterObject);
        return invocation.proceed();
    }

    /**
     * 加密处理
     */
    private void processDesensitize(Object parameter) throws Exception {
        if (parameter == null) return;

        // 处理不同类型的参数
        if (parameter instanceof Map) {
            // Map参数(如@Param注解)
            processMapParameter((Map<?, ?>) parameter);
        } else if (parameter instanceof Collection) {
            // 集合参数
            for (Object item : (Collection<?>) parameter) {
                processObject(item);
            }
        } else if (parameter.getClass().isArray()) {
            // 数组参数
            for (Object item : (Object[]) parameter) {
                processObject(item);
            }
        } else {
            // 单个对象参数
            processObject(parameter);
        }
    }

    /**
     * 处理Map参数
     */
    private void processMapParameter(Map<?, ?> parameterMap) throws Exception {
        for (Map.Entry<?, ?> entry : parameterMap.entrySet()) {
            Object value = entry.getValue();
            if (value != null) {
                processObject(value);
            }
        }
    }

    /**
     * 处理单个对象
     */
    private void processObject(Object obj) throws Exception {
        if (obj == null) return;
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.getName().equals("subscriberName")) {
                field.setAccessible(true);
                Object fieldValue = field.get(obj);
                if (fieldValue instanceof String originalName) {
                    String EncName = getEncName(originalName);
                    // 设置新值
                    field.set(obj, EncName);
                }
            }
        }
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 从配置读取属性
    }

    /**
     * 获取加密机字符串
     *
     * @param name 用户名
     * @return String 加密后的值,带 Encrypted: 前缀
     */
    private String getEncName(String name) throws HKEException {
        String encName = HKECipher.encryptAndDecryptBySysKey(name);
        if (StringUtil.isEmpty(encName)) {
            throw new HKEException(ExceptionConstant.ERROR_CODE_862267);
        }
        String res = BaseConstant.ENCRYPTED_PREFIX + encName;
        if (res.length() > AdvancedValueConstant.DATABASE_LIMIT_SUBSCRIBER_NAME_NO) {
            throw new HKEException(ExceptionConstant.ERROR_CODE_862266);
        }
        return res;
    }
}

方法解读

@Intercepts

  • 标记类为 MyBatis 拦截器,服务启动的时候加载并生成拦截目标对应的代理类

  • 包含一个或多个 @Signature 注解

@Signature

java 复制代码
@Signature(
    type = ParameterHandler.class,              // 拦截的目标类
    method = "setParameters",                   // 拦截的方法名
    args = {PreparedStatement.class}            // 方法的参数类型
)

public void setProperties(Properties properties)

该方法可以从配置拦截器的地方读入指定的参数,以便更加灵活的控制拦截的逻辑,例如通过该方法读取配置中指定的表名,来实现不同的拦截器只对指定的表进行拦截操作等。

======================

喜欢求个关注,回归持续更新

======================

相关推荐
_Evan_Yao6 小时前
Redis 缓存穿透、击穿、雪崩,一次讲清!
java·spring·mybatis
梓豪csd6 小时前
消息订阅中心:谁该收到哪条通知
java·开发语言·人工智能·低代码·自动化·1024程序员节
秋饼6 小时前
Spring AI 2.1 Message Parts 深度实战:推理签名回放与跨轮记忆持久化的源码级落地
java·ai·技术分享·后端开发
thefool1122667 小时前
TCP 1
java·1024程序员节
不脱发的程序猿8 小时前
纳指ETF量化策略
数据库·人工智能·1024程序员节
程序员塔塔喵9 小时前
【MySQL篇|第一篇】索引失效的情况
数据库·mysql·索引失效
paopaokaka_luck9 小时前
宠物门店经营管理系统(销售与库存联动、进货自动记账、 退换与退款关联、员工个人业绩、ECharts 经营分析、滞销识别与客户分层、宠物档案年龄计算)
前端·javascript·spring boot·mysql·echarts·mybatis·1024程序员节
北京青蓝智慧马老师9 小时前
比外部黑客更难防的,是那个有权限的自己人(3 个脱敏复盘)
大数据·数据库·人工智能
金銀銅鐵9 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(下)
java·后端·ai编程
AI人工智能+电脑小能手9 小时前
一次请求打几个下游,这个Java并行任务工具类6个方法按序聚合
java·线程池·并发编程·1024程序员节·批量处理·并行任务