如何在Jooq自定义Converter中实现MySQL同表密钥列自动加解密
使用jOOQ实现同表密钥列的自动加解密(无Spring)
需求
- 基于
org.jooq.util.mysql.MySQLDSL实现指定列的加解密 - 全局配置,查询、插入、更新时自动处理加解密逻辑
- 加解密密钥来自当前表的另一列(如TESTCRYPT表的Secret列)
- 不依赖Spring框架
遇到的问题
- 自定义
Converter无法访问RecordContext,无法获取同表的密钥列值 - 无法实现全局通用配置,需手动处理每一次操作
核心对应SQL
CREATE TABLE TESTCRYPT( Id mediumint AUTO_INCREMENT, Secret int NOT NULL, encrypteddata BLOB, PRIMARY KEY(Id) ); INSERT INTO TESTCRYPT(Secret,encrypteddata) VALUE ('1234',AES_ENCRYPT('my-data','1234')); SELECT Secret ,AES_DECRYPT(encrypteddata,Secret) from TESTCRYPT t ;
解决方案
方案1:全局RecordListener拦截处理
Converter是无状态的,无法访问当前记录的其他字段,改用RecordListener在记录加载/存储时拦截处理:
1.1 自定义CryptRecordListener
import org.jooq.Record; import org.jooq.RecordContext; import org.jooq.RecordListener; import org.jooq.util.mysql.MySQLDSL; import static com.yourpackage.generated.Tables.TESTCRYPT; public class CryptRecordListener implements RecordListener { @Override public void loadEnd(RecordContext ctx) { Record record = ctx.record(); if (record == null) return; // 仅处理包含Secret和encrypteddata列的记录 if (record.field(TESTCRYPT.SECRET) != null && record.field(TESTCRYPT.ENCRYPTEDDATA) != null) { String secret = record.getValue(TESTCRYPT.SECRET).toString(); byte[] encrypted = record.getValue(TESTCRYPT.ENCRYPTEDDATA); // 调用MySQLDSL解密,覆盖原字段值 String decrypted = MySQLDSL.aesDecrypt(encrypted, secret).toString(); record.setValue(TESTCRYPT.ENCRYPTEDDATA, decrypted); } } @Override public void storeStart(RecordContext ctx) { Record record = ctx.record(); if (record == null) return; if (record.field(TESTCRYPT.SECRET) != null && record.field(TESTCRYPT.ENCRYPTEDDATA) != null) { String secret = record.getValue(TESTCRYPT.SECRET).toString(); String rawData = record.getValue(TESTCRYPT.ENCRYPTEDDATA).toString(); // 调用MySQLDSL加密,覆盖原字段值 byte[] encrypted = MySQLDSL.aesEncrypt(rawData, secret).getBytes(); record.setValue(TESTCRYPT.ENCRYPTEDDATA, encrypted); } } }
1.2 全局注册Listener
import org.jooq.Configuration; import org.jooq.impl.DefaultConfiguration; import org.jooq.impl.DefaultDSLContext; import java.sql.Connection; // 初始化JDBC连接(自行实现) Connection conn = getJdbcConnection(); // 构建全局配置并注册Listener Configuration config = new DefaultConfiguration() .set(conn) .set(new CryptRecordListener()); DSLContext dsl = new DefaultDSLContext(config);
方案2:通用RecordMapperProvider(多表适配)
如果需要适配多个表,可通过代码生成+自定义MapperProvider实现通用处理(需约定密钥列名为Secret,加密列名为encrypteddata):
2.1 代码生成配置ForcedType
在代码生成XML中指定加密列的自定义类型:
<configuration> <generator> <database> <forcedTypes> <forcedType> <userType>java.lang.String</userType> <includeExpression>encrypteddata</includeExpression> <includeTypes>BLOB</includeTypes> </forcedType> </forcedTypes> </database> </generator> </configuration>
2.2 自定义CryptRecordMapperProvider
import org.jooq.Record; import org.jooq.RecordMapper; import org.jooq.RecordMapperProvider; import org.jooq.impl.DefaultRecordMapper; import org.jooq.util.mysql.MySQLDSL; public class CryptRecordMapperProvider implements RecordMapperProvider { @Override public <R extends Record, E> RecordMapper<R, E> provide(Class<R> recordType, Class<E> type) { return record -> { // 通用匹配密钥列和加密列 if (record.field("SECRET") != null && record.field("ENCRYPTEDDATA") != null) { String secret = record.getValue("SECRET").toString(); byte[] encrypted = record.getValue("ENCRYPTEDDATA", byte[].class); String decrypted = MySQLDSL.aesDecrypt(encrypted, secret).toString(); record.setValue("ENCRYPTEDDATA", decrypted); } // 委托默认Mapper完成转换 return new DefaultRecordMapper<>(recordType, type).map(record); }; } }
2.3 注册MapperProvider到全局配置
Configuration config = new DefaultConfiguration() .set(conn) .set(new CryptRecordMapperProvider()); DSLContext dsl = new DefaultDSLContext(config);
注意事项
- 确保MySQL版本支持
AES_ENCRYPT/AES_DECRYPT函数,jOOQ版本与MySQLDSL兼容 - 密钥列与加密列的类型需匹配,如Secret为int时需转为String传入MySQLDSL
- 插入/更新时需先设置Secret列值,否则加密会失败
- 全局拦截逻辑可通过字段名/表名判断,避免处理无关记录
内容的提问来源于stack exchange,提问作者introvertkernel
相关产品推荐
相关产品推荐

