MyBatis中可通过自定义TypeHandler实现敏感字段的自动加解密:在setNonNullParameter中加密写入,在getNullableResult中解密读取,需统一算法密钥、避免硬编码密钥、使用线程安全Cipher并预留足够字段长度。

在 MyBatis 中,通过自定义 TypeHandler 可以在数据从 Java 对象映射到 JDBC 参数(写入数据库),以及从数据库结果集映射回 Java 对象(读取数据)时,自动完成加解密转换。核心思路是:**把加密逻辑封装在 setNonNullParameter 中,解密逻辑封装在 getNullableResult 中,并确保加解密使用同一套密钥与算法**。
1. 实现自定义 TypeHandler 处理加解密
继承 BaseTypeHandler<String>(或其他目标类型),重写四个关键方法。以对敏感字段(如手机号、身份证号)做 AES 加密存储为例:
- setNonNullParameter:Java 字符串 → 加密后字节数组 → 存入 PreparedStatement(通常转为 Base64 或十六进制字符串存入 VARCHAR)
- getNullableResult(三个重载):从 ResultSet 中取出加密字符串 → 解码(Base64)→ AES 解密 → 返回明文 String
- 注意处理 null 值,避免 NPE;加解密异常建议包装为
RuntimeException,便于定位问题
示例代码片段:
public class AesEncryptTypeHandler extends BaseTypeHandler<String> {
private final Cipher encryptCipher;
private final Cipher decryptCipher;
public AesEncryptTypeHandler() throws Exception {
SecretKeySpec key = new SecretKeySpec("your-16-byte-key".getBytes(), "AES");
this.encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
this.decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
decryptCipher.init(Cipher.DECRYPT_MODE, key);
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
if (parameter == null) {
ps.setNull(i, Types.VARCHAR);
} else {
byte[] encrypted = encryptCipher.doFinal(parameter.getBytes(StandardCharsets.UTF_8));
ps.setString(i, Base64.getEncoder().encodeToString(encrypted));
}
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String encryptedStr = rs.getString(columnName);
return decrypt(encryptedStr);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String encryptedStr = rs.getString(columnIndex);
return decrypt(encryptedStr);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String encryptedStr = cs.getString(columnIndex);
return decrypt(encryptedStr);
}
private String decrypt(String encryptedStr) {
if (encryptedStr == null) return null;
try {
byte[] decoded = Base64.getDecoder().decode(encryptedStr);
byte[] decrypted = decryptCipher.doFinal(decoded);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("Decrypt failed", e);
}
}
}
2. 在 Mapper XML 中注册并使用 TypeHandler
在 MyBatis 的 <resultMap> 或 SQL 语句中,通过 typeHandler 属性显式指定处理器:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 用于查询映射:
<result column="phone_enc" property="phone" typeHandler="com.example.AesEncryptTypeHandler"/> - 用于插入/更新参数:
<insert ...><bind name="encryptedPhone" value="@com.example.AesEncryptTypeHandler@encrypt(phone)" />(不推荐)——更推荐直接在参数对象字段上用注解或全局配置 - 更简洁方式:在 MyBatis 配置文件中全局注册,让所有匹配字段自动生效
3. 全局注册 TypeHandler(推荐)
在 mybatis-config.xml 或 Spring Boot 的 application.yml 中配置,避免每个字段重复声明:
<typeHandlers>
<typeHandler handler="com.example.AesEncryptTypeHandler"
javaType="java.lang.String"
jdbcType="VARCHAR"/>
</typeHandlers>
或 Spring Boot 方式(application.yml):
mybatis: type-handlers-package: com.example.handler
再配合 @MappedTypes(String.class) 和 @MappedJdbcTypes(JdbcType.VARCHAR) 注解在 TypeHandler 类上,即可实现自动绑定。
4. 注意事项与最佳实践
- 密钥管理不能硬编码,应从配置中心或环境变量加载;生产环境建议使用 KMS 或 HSM
- AES-ECB 不安全,优先选用 AES-GCM 或 AES-CBC + 随机 IV(需额外字段存 IV)
- 加解密耗时较高,避免在高频查询字段上滥用;可考虑只对极敏感字段启用
- TypeHandler 是无状态的,但 Cipher 实例不是线程安全的,务必为每次加解密新建或使用 ThreadLocal 缓存
- 数据库字段长度要预留足够空间(如 AES-128 加密后 Base64 编码约增长 33%)

















