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)

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

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

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

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

相关推荐
小手cool9 分钟前
在保持数组中对应元素(包括负数和正数)各自组内顺序不变的情况下,交换数组中对应的负数和正数元素
java
笨手笨脚の14 分钟前
深入理解 Java 虚拟机-04 垃圾收集器
java·jvm·垃圾收集器·垃圾回收
skywalker_1120 分钟前
Java中异常
java·开发语言·异常
没有天赋那就反复26 分钟前
JAVA 静态方法
java·开发语言
代码丰42 分钟前
SpringAI+RAG向量库+知识图谱+多模型路由+Docker打造SmartHR智能招聘助手
人工智能·spring·知识图谱
山茶花.44 分钟前
SQL注入总结
数据库·sql·oracle
Java天梯之路1 小时前
Spring Boot 钩子全集实战(七):BeanFactoryPostProcessor详解
java·spring boot·后端
wr2005141 小时前
第二次作业,渗透
java·后端·spring
m0_736919101 小时前
超越Python:下一步该学什么编程语言?
jvm·数据库·python
m0_748229992 小时前
ThinkPHP快速入门:从零到实战
c语言·开发语言·数据库·学习