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)

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

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

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

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

相关推荐
爬山算法1 分钟前
Hibernate(89)如何在压力测试中使用Hibernate?
java·压力测试·hibernate
暮色妖娆丶23 分钟前
SpringBoot 启动流程源码分析 ~ 它其实不复杂
spring boot·后端·spring
消失的旧时光-194323 分钟前
第十四课:Redis 在后端到底扮演什么角色?——缓存模型全景图
java·redis·缓存
BD_Marathon24 分钟前
设计模式——依赖倒转原则
java·开发语言·设计模式
BD_Marathon28 分钟前
设计模式——里氏替换原则
java·设计模式·里氏替换原则
Coder_Boy_31 分钟前
Deeplearning4j+ Spring Boot 电商用户复购预测案例中相关概念
java·人工智能·spring boot·后端·spring
css趣多多33 分钟前
add组件增删改的表单处理
java·服务器·前端
雨中飘荡的记忆35 分钟前
Spring Batch实战
java·spring
Java后端的Ai之路42 分钟前
【Spring全家桶】-一文弄懂Spring Cloud Gateway
java·后端·spring cloud·gateway
devmoon43 分钟前
在 Polkadot Runtime 中添加多个 Pallet 实例实战指南
java·开发语言·数据库·web3·区块链·波卡