MyBatis参数加解密
一、概述
在MyBatis中通过拦截器实现SQL参数加密/结果集解密是数据安全的常见场景,核心是拦截参数处理环节(加密入参)和结果集处理环节(解密出参)。
适配Spring Boot3 + MyBatis环境。
核心思路
| 拦截点 | 作用 | 拦截接口 | 拦截方法 |
|---|---|---|---|
| 参数加密 | 执行SQL前加密敏感参数 | ParameterHandler | setParameters(PreparedStatement) |
| 结果集解密 | 查询后解密敏感字段 | ResultSetHandler | handleResultSets(Statement) |
二、实现步骤
2.1 定义加密注解
通过自定义注解标记实体类中需要加密/解密的字段,让拦截器识别目标字段:
import java.lang.annotation.*;
/**
* 敏感字段加解密注解
*/
@Target(ElementType.FIELD) // 作用于字段
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Documented
public @interface EncryptField {
// 可扩展:指定加密算法类型,默认 AES
String algorithm() default "AES";
}
import java.lang.annotation.*;
/**
* 标记需要解密的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
// 可扩展:指定加密算法类型,默认AES
String algorithm() default "AES";
}
2.2 加密工具类
封装通用的加密/解密方法(实际项目建议使用更安全的算法,如RSA+AES混合):
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
/**
* 加解密工具类(AES示例)
*/
public class EncryptUtils {
// 密钥(生产环境需从配置中心/环境变量读取,避免硬编码)
private static final String AES_KEY = "1234567890123456"; //AES密钥长度必须16/24/32位
private static final String AES_ALGORITHM = "AES";
/**
* AES加密
*/
public static String aesEncrypt(String content) {
if (content == null || content.isEmpty()) {
return content;
}
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(AES_KEY.getBytes());
keyGenerator.init(128, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, AES_ALGORITHM);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encryptedBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Base64.encodeBase64String(encryptedBytes);
} catch (Exception e) {
throw new RuntimeException("AES加密失败", e);
}
}
/**
* AES解密
*/
public static String aesDecrypt(String encryptedContent) {
if (encryptedContent == null || encryptedContent.isEmpty()) {
return encryptedContent;
}
try {
byte[] encryptedBytes = Base64.decodeBase64(encryptedContent);
KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(AES_KEY.getBytes());
keyGenerator.init(128, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, AES_ALGORITHM);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES解密失败", e);
}
}
}
2.3 加密拦截器
拦截ParameterHandler的setParameters方法,对标记@EncryptField的字段加密:
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Properties;
/**
* 参数加密拦截器:拦截入参,对@EncryptField字段加密
*/
@Intercepts({
@Signature(
type = ParameterHandler.class,
method = "setParameters",
args = {PreparedStatement.class})
})
public class EncryptParameterInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 1. 获取ParameterHandler
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 2. 通过反射获取参数对象
MetaObject metaObject = SystemMetaObject.forObject(parameterHandler);
Object parameterObject = metaObject.getValue("parameterObject");
if (parameterObject == null) {
return invocation.proceed();
}
// 3. 遍历参数对象的字段,对@EncryptField字段加密
encryptField(parameterObject);
// 4. 执行原方法
return invocation.proceed();
}
/**
* 加密对象中标记@EncryptField的字段
*/
private void encryptField(Object obj) throws IllegalAccessException {
if (obj == null) {
return;
}
Class<?> clazz = obj.getClass();
// 处理基本类型包装类/字符串(若参数是单个值)
if (obj instanceof String) {
return; // 单个字符串参数需特殊处理,此处示例针对实体类
}
// 遍历所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 检查是否有@EncryptField注解
if (field.isAnnotationPresent(EncryptField.class)) {
field.setAccessible(true);
Object value = field.get(obj);
if (value instanceof String) {
// 加密字段值
String encryptedValue = EncryptUtils.aesEncrypt((String) value);
field.set(obj, encryptedValue);
}
}
}
}
@Override
public Object plugin(Object target) {
// 仅对ParameterHandler类型创建代理
if (target instanceof ParameterHandler) {
// 生成代理对象
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
// 可配置拦截器参数,此处暂不处理
}
}
2.4 解密拦截器
拦截ResultSetHandler的handleResultSets方法,对标记@DecryptField的字段解密:
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;
/**
* 结果集解密拦截器:拦截出参,对@DecryptField字段解密
*/
@Intercepts({
@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}
)
})
public class DecryptResultSetInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 1. 执行原方法,获取结果集
Object result = invocation.proceed();
if (result == null) {
return null;
}
// 2. 对结果集解密(支持单对象/列表)
if (result instanceof List) {
List<?> list = (List<?>) result;
for (Object obj : list) {
decryptField(obj);
}
} else {
decryptField(result);
}
return result;
}
/**
* 解密对象中标记@DecryptField的字段
*/
private void decryptField(Object obj) throws IllegalAccessException {
if (obj == null) {
return;
}
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.isAnnotationPresent(DecryptField.class)) {
EncryptField annotation = field.getAnnotation(EncryptField.class);
Object value = field.get(obj);
if (value instanceof String) {
// 解密字段值
String decryptedValue = EncryptUtils.aesDecrypt((String) value);
field.set(obj, decryptedValue);
}
}
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可配置拦截器参数
}
}
2.5 注册拦截器
Spring Boot 3 配置拦截器
将拦截器注册到MyBatis的SqlSessionFactory中:
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@MapperScan("com.yourpackage.mapper") // 替换Mapper包路径
public class MyBatisConfig {
/**
* 注册参数加密拦截器
*/
@Bean
public EncryptParameterInterceptor encryptParameterInterceptor() {
return new EncryptParameterInterceptor();
}
/**
* 注册结果集解密拦截器
*/
@Bean
public DecryptResultSetInterceptor decryptResultSetInterceptor() {
return new DecryptResultSetInterceptor();
}
/**
* 配置SqlSessionFactory,添加拦截器
*/
@Bean
public SqlSessionFactory sqlSessionFactory(
DataSource dataSource,
EncryptParameterInterceptor encryptInterceptor,
DecryptResultSetInterceptor decryptInterceptor
) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
// 加载Mapper.xml(可选,注解版Mapper可省略)
sessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml")
);
// 核心:注册拦截器
sessionFactoryBean.setPlugins(encryptInterceptor, decryptInterceptor);
// 可选:MyBatis全局配置
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true); // 下划线转驼峰
sessionFactoryBean.setConfiguration(configuration);
return sessionFactoryBean.getObject();
}
}
2.6 实战使用
在实体类中标记需要加密/解密的字段:
public class User {
private Long id;
private String userName;
// 加密存储:入参加密,数据库存密文
@EncryptField
@DecryptField // 查询时解密
private String phone;
// 加密存储:入参加密,数据库存密文
@EncryptField
@DecryptField
private String idCard;
// getter/setter 省略
}
三、关键注意事项
3.1 性能优化
- 避免递归处理大量嵌套对象,可限制递归深度;
- 拦截器仅对标记注解的字段处理,减少反射开销;
- 加密算法选择:
AES性能优于RSA,建议敏感字段用AES加密,密钥用RSA加密存储。
3.2 特殊场景处理
- 批量操作:拦截器已兼容
List类型结果集,批量插入/查询无需额外处理; - 参数为Map:需修改
encryptField方法,支持遍历Map的key-value(判断value是否为标记注解的实体); - 空值处理:加密工具类已兼容空值,避免空指针。
3.3 安全增强
- 加密密钥不要硬编码,建议从
application.yml读取(可结合Spring Cloud Config/阿里云KMS); - 生产环境禁用ECB模式,改用CBC/GCM模式(需添加初始化向量IV);
- 对加密后的字段建立索引时,需用密文查询(拦截器会自动加密入参,无需手动处理)。
3.4 调试技巧
- 在拦截器的
intercept方法中加日志/断点,确认参数是否加密、结果集是否解密; - 开启
MyBatis SQL日志,查看执行的SQL参数是否为密文。
四、扩展场景
4.1 多算法支持
在注解中指定加密算法(如@EncryptField(algorithm="AES"));
封装支持多种算法的加密/解密方法,统一入口,根据算法标识分发逻辑:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
/**
* 多算法加密工具类
*/
public class MultiAlgEncryptUtils {
// 算法密钥配置(实际项目建议从配置中心读取,此处为示例)
private static final Map<String, String> ALG_KEY_MAP = new HashMap<>();
static {
ALG_KEY_MAP.put("AES", "1234567890123456"); // AES-128密钥(16位)
ALG_KEY_MAP.put("SM4", "8765432109876543"); // SM4密钥(16位)
ALG_KEY_MAP.put("RSA", "MIGfMA0GCSqGSIb3DQEBBQUAA4GNADCBiQKBgQC..."); // RSA公钥(示例)
}
// 加密函数映射(算法标识 -> 加密逻辑)
private static final Map<String, BiFunction<String, String, String>> ENCRYPT_FUNC = new HashMap<>();
// 解密函数映射(算法标识 -> 解密逻辑)
private static final Map<String, BiFunction<String, String, String>> DECRYPT_FUNC = new HashMap<>();
static {
// 注册AES算法
ENCRYPT_FUNC.put("AES", MultiAlgEncryptUtils::aesEncrypt);
DECRYPT_FUNC.put("AES", MultiAlgEncryptUtils::aesDecrypt);
// 注册SM4算法
ENCRYPT_FUNC.put("SM4", MultiAlgEncryptUtils::sm4Encrypt);
DECRYPT_FUNC.put("SM4", MultiAlgEncryptUtils::sm4Decrypt);
// 注册RSA算法(示例,实际需处理密钥对)
ENCRYPT_FUNC.put("RSA", MultiAlgEncryptUtils::rsaEncrypt);
DECRYPT_FUNC.put("RSA", MultiAlgEncryptUtils::rsaDecrypt);
}
/**
* 统一加密入口
*
* @param content 待加密内容
* @param algorithm 加密算法
* @return 加密后内容(Base64编码)
*/
public static String encrypt(String content, String algorithm) {
if (content == null || content.isEmpty()) return content;
// 校验算法是否支持
if (!ENCRYPT_FUNC.containsKey(algorithm)) {
throw new UnsupportedOperationException("不支持的加密算法:" + algorithm);
}
// 获取对应算法密钥(可通过config参数扩展密钥选择)
String key = ALG_KEY_MAP.get(algorithm);
// 执行加密
return ENCRYPT_FUNC.get(algorithm).apply(content, key);
}
/**
* 统一解密入口
*
* @param content 待解密内容(Base64编码)
* @param algorithm 解密算法
* @return 解密后内容
*/
public static String decrypt(String content, String algorithm) {
if (content == null || content.isEmpty()) return content;
if (!DECRYPT_FUNC.containsKey(algorithm)) {
throw new UnsupportedOperationException("不支持的解密算法:" + algorithm);
}
String key = ALG_KEY_MAP.get(algorithm);
return DECRYPT_FUNC.get(algorithm).apply(content, key);
}
// ------------------------------ 各算法具体实现 ------------------------------
/**
* AES加密(ECB模式,PKCS5Padding填充)
*/
private static String aesEncrypt(String content, String key) {
try {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("AES加密失败", e);
}
}
/**
* AES解密
*/
private static String aesDecrypt(String content, String key) {
try {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(content));
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES解密失败", e);
}
}
/**
* SM4加密(ECB模式,PKCS5Padding填充)
*/
private static String sm4Encrypt(String content, String key) {
try {
// 实际项目需引入SM4算法依赖(如BouncyCastle)
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "SM4");
Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("SM4加密失败", e);
}
}
/**
* SM4解密
*/
private static String sm4Decrypt(String content, String key) {
try {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "SM4");
Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(content));
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("SM4解密失败", e);
}
}
/**
* RSA加密(示例,实际需使用公钥加密,私钥解密)
*/
private static String rsaEncrypt(String content, String publicKey) {
// 实际实现需解析公钥、处理分块加密(RSA加密长度有限制)
try {
// 简化示例:直接返回Base64编码的内容(实际需替换为真实RSA加密逻辑)
return Base64.getEncoder().encodeToString((content + "_RSA_ENC").getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("RSA加密失败", e);
}
}
/**
* RSA解密
*/
private static String rsaDecrypt(String content, String privateKey) {
try {
// 简化示例:直接解码并去除加密标识
String decrypted = new String(Base64.getDecoder().decode(content), StandardCharsets.UTF_8);
return decrypted.replace("_RSA_ENC", "");
} catch (Exception e) {
throw new RuntimeException("RSA解密失败", e);
}
}
}
改造参数加密拦截器
/**
* 递归加密字段,根据注解指定的算法选择加密方式
*/
private void encryptField(Object obj) throws IllegalAccessException {
if (obj == null) return;
Class<?> clazz = obj.getClass();
// 跳过基础类型和字符串
if (clazz.isPrimitive() || clazz == String.class || obj instanceof Number) {
return;
}
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
// 仅处理标记@EncryptField的字段
if (field.isAnnotationPresent(EncryptField.class)) {
EncryptField annotation = field.getAnnotation(EncryptField.class);
String algorithm = annotation.algorithm(); // 获取注解指定的算法
Object value = field.get(obj);
if (value != null && value instanceof String) {
// 调用多算法工具类加密
String encryptedValue = MultiAlgEncryptUtils.encrypt((String) value, algorithm);
field.set(obj, encryptedValue);
}
}
// 递归处理嵌套对象
encryptField(field.get(obj));
}
}
4.2 拦截Map参数
@Target(value = {ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) // 作用于字段
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Documented
public @interface EncryptField {
// 可扩展:指定加密算法类型,默认 AES
String algorithm() default "AES";
// 需要加密的Map字段名数组
String[] fields() default "";
}
package com.example.demo.test3;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.Properties;
/**
* 参数加密拦截器:拦截入参,对@EncryptField字段加密
*/
@Intercepts({
@Signature(
type = ParameterHandler.class,
method = "setParameters",
args = {PreparedStatement.class})
})
public class EncryptParameterInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(parameterHandler);
// 1. 获取核心参数:MappedStatement(包含Mapper方法信息)、参数对象
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement");
Object parameterObject = metaObject.getValue("parameterObject");
if (parameterObject == null) {
return invocation.proceed();
}
// 处理「单个字符串参数」(核心新增逻辑)
if (parameterObject instanceof String) {
encryptStringField(parameterObject, mappedStatement, metaObject);
}
// 处理「Map类型参数」
else if (parameterObject instanceof Map) {
encryptMapField((Map<Object, Object>) parameterObject, mappedStatement);
} else {
// 处理「实体类字段加密」
encryptEntityField(parameterObject);
}
return invocation.proceed();
}
/**
* 获取Mapper方法(从MappedStatement中解析)
*/
private Method getMapperMethod(MappedStatement mappedStatement) throws ClassNotFoundException {
String mappedStatementId = mappedStatement.getId();
// 拆分:com.yourpackage.mapper.UserMapper.selectByPhone -> 类名 + 方法名
String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf("."));
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
Class<?> mapperClass = Class.forName(className);
// 遍历方法,匹配方法名(简化:假设无重载;有重载需匹配参数类型)
for (Method method : mapperClass.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
return method;
}
}
throw new RuntimeException("未找到Mapper方法:" + mappedStatementId);
}
/**
* 检查Mapper方法的参数是否标记了@EncryptField
*/
private EncryptField getEncryptParamAnnotation(Method mapperMethod) {
Parameter[] parameters = mapperMethod.getParameters();
for (Parameter param : parameters) {
if (param.isAnnotationPresent(EncryptField.class)) {
return param.getAnnotation(EncryptField.class);
}
}
return null;
}
private void encryptStringField(Object parameterObject, MappedStatement mappedStatement,
MetaObject metaObject) throws Throwable {
// 获取Mapper方法的参数注解(判断是否需要加密)
Method mapperMethod = getMapperMethod(mappedStatement);
EncryptField encryptField = getEncryptParamAnnotation(mapperMethod);
if (encryptField != null) {
// 执行加密:根据注解指定的算法
String algorithm = encryptField.algorithm();
String encryptedValue = MultiAlgEncryptUtils.encrypt((String) parameterObject, algorithm);
// 替换参数值为加密后的密文
metaObject.setValue("parameterObject", encryptedValue);
}
}
/**
* 加密Map中的指定字段
* 规则:1. 优先从Mapper方法参数注解获取需要加密的字段和算法;
* 2. 兜底按字段名约定(如encrypt_前缀)
*/
private void encryptMapField(Map<Object, Object> map, MappedStatement mappedStatement)
throws Throwable {
if (map.isEmpty()) {
return;
}
// 关键修复:将不可变的 Map<?,?> 转为可操作的 Map<Object, Object>
// MyBatis中传入的Map本质都是<Object, Object>类型,此转换安全
// 步骤1:从Mapper方法注解获取需要加密的Map字段配置(推荐方式)
Method mapperMethod = getMapperMethod(mappedStatement);
EncryptField encryptMapParam = mapperMethod.getAnnotation(EncryptField.class);
if (encryptMapParam != null) {
// 获取注解中指定的加密字段和算法
String[] encryptFields = encryptMapParam.fields();
String algorithm = encryptMapParam.algorithm();
for (String field : encryptFields) {
Object value = map.get(field);
if (value instanceof String) {
String encryptedValue = MultiAlgEncryptUtils.encrypt((String) value, algorithm);
map.put(field, encryptedValue); // 用转换后的map执行put,解决泛型问题
}
}
return;
}
// 步骤2:兜底规则(无注解时):加密以encrypt_为前缀的字段,默认AES算法
for (Map.Entry<?, ?> entry : map.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
if (key instanceof String && value instanceof String) {
String fieldName = (String) key;
if (fieldName.startsWith("encrypt_")) {
String encryptedValue = MultiAlgEncryptUtils.encrypt((String) value, "AES");
map.put(key, encryptedValue); // 用转换后的map执行put,解决泛型问题
}
}
}
}
/**
* 加密对象中标记@EncryptField的字段
*/
private void encryptEntityField(Object obj) throws IllegalAccessException {
if (obj == null) return;
Class<?> clazz = obj.getClass();
// 跳过基础类型和字符串(已单独处理)
if (clazz.isPrimitive() || clazz == String.class || obj instanceof Number) {
return;
}
// 遍历实体类字段,加密标记@EncryptField的字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(EncryptField.class)) {
field.setAccessible(true);
Object value = field.get(obj);
if (value instanceof String) {
EncryptField annotation = field.getAnnotation(EncryptField.class);
String encryptedValue = MultiAlgEncryptUtils.encrypt((String) value, annotation.algorithm());
field.set(obj, encryptedValue);
}
// 递归处理嵌套实体
encryptEntityField(field.get(obj));
}
}
}
@Override
public Object plugin(Object target) {
// 仅对ParameterHandler类型创建代理
if (target instanceof ParameterHandler) {
// 生成代理对象
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
// 可配置拦截器参数,此处暂不处理
}
}
Map参数:
@Mapper
public interface UserMapper {
@Insert("INSERT INTO user (username, phone, id_card) VALUES (#{userName}, #{phone}, #{idCard})")
int insert(User user);
@Select("SELECT id, username, phone, id_card as idCard FROM user WHERE id = #{id}")
User selectById(Long id);
@Select("SELECT id, username, phone, id_card as idCard FROM user WHERE id_card = #{idCard}")
List<User> selectByIdCard(@EncryptField String idCard);
@Insert("INSERT INTO user (username, phone, id_card) VALUES (#{userName}, #{phone}, #{idCard})")
@EncryptField(fields = {"idCard", "phone"})
int insertMap(Map<String, String> user);
}
4.3 其他
- 动态加密字段:通过配置文件指定需要加密的字段,避免硬编码注解;
- 脱敏+加密:解密后对敏感字段脱敏(如手机号显示为
138****1234)。
通过以上实现,即可在MyBatis中全自动完成SQL参数加密和结果集解密,无需在业务代码中手动处理加密逻辑,保证数据安全的同时降低耦合。

浙公网安备 33010602011771号