You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 01:42:21