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 小时前
Java企业级实战:对接天远名下车辆数量查询API构建自动化风控中台
java·大数据·开发语言·自动化
AC赳赳老秦2 小时前
Python 爬虫进阶:DeepSeek 优化反爬策略与动态数据解析逻辑
开发语言·hadoop·spring boot·爬虫·python·postgresql·deepseek
zgl_200537792 小时前
ZGLanguage 解析SQL数据血缘 之 标识提取SQL语句中的目标表
java·大数据·数据库·数据仓库·hadoop·sql·源代码管理
liwulin05062 小时前
【JAVA】创建一个不需要依赖的websocket服务器接收音频文件
java·服务器·websocket
莳花微语2 小时前
记录一次OGG进程abended,报错OGG-01431、OGG-01003、OGG-01151、OGG-01296问题的处理
数据库·sql·mysql
钦拆大仁2 小时前
统一数据返回格式和统一异常处理
java
czlczl200209252 小时前
OAuth 2.0 解析:后端开发者视角的原理与流程讲解
java·spring boot·后端
尋有緣2 小时前
力扣1355-活动参与者
大数据·数据库·leetcode·oracle·数据库开发
颜淡慕潇2 小时前
Spring Boot 3.3.x、3.4.x、3.5.x 深度对比与演进分析
java·后端·架构
g***55752 小时前
Java高级开发进阶教程之系列
java·开发语言