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

基于MySQL与Oracle,如何用Hibernate @ColumnTransformer实现分库加解密?

适配MySQL/Oracle的Hibernate加密解密方案(动态切换数据库函数)

方案一:扩展Hibernate方言,统一加密解密函数名

核心思路是给不同数据库的方言注册同名自定义函数,映射到各自原生的加密解密逻辑,让实体类的@ColumnTransformer可以用统一调用,无需关心底层数据库类型。

1. 自定义MySQL方言

public class CustomMySQLDialect extends MySQL8Dialect {
    public CustomMySQLDialect() {
        super();
        // 注册加密函数,映射到MySQL原生AES_ENCRYPT
        registerFunction("encrypt", new StandardSQLFunction("AES_ENCRYPT", StandardBasicTypes.BINARY));
        // 注册解密函数,映射到MySQL原生AES_DECRYPT
        registerFunction("decrypt", new StandardSQLFunction("AES_DECRYPT", StandardBasicTypes.STRING));
    }
}

2. 自定义Oracle方言

Oracle需依赖DBMS_CRYPTO包,提前给数据库用户授权:GRANT EXECUTE ON SYS.DBMS_CRYPTO TO your_user;

public class CustomOracleDialect extends OracleDialect {
    public CustomOracleDialect() {
        super();
        // 注册加密函数,封装Oracle的AES256加密逻辑
        registerFunction("encrypt", new StandardSQLFunction("DBMS_CRYPTO.ENCRYPT", StandardBasicTypes.RAW) {
            @Override
            public String render(Type firstArgType, List<?> arguments, SessionFactoryImplementor factory) {
                // 常量2对应DBMS_CRYPTO.AES256_ENCRYPT,统一用UTF-8字符集转换
                return "DBMS_CRYPTO.ENCRYPT(UTL_I18N.STRING_TO_RAW(" + arguments.get(0) + ", 'AL32UTF8'), 2, UTL_I18N.STRING_TO_RAW(" + arguments.get(1) + ", 'AL32UTF8'))";
            }
        });

        // 注册解密函数,封装Oracle的AES256解密逻辑
        registerFunction("decrypt", new StandardSQLFunction("DBMS_CRYPTO.DECRYPT", StandardBasicTypes.STRING) {
            @Override
            public String render(Type firstArgType, List<?> arguments, SessionFactoryImplementor factory) {
                return "UTL_I18N.RAW_TO_CHAR(DBMS_CRYPTO.DECRYPT(" + arguments.get(0) + ", 2, UTL_I18N.STRING_TO_RAW(" + arguments.get(1) + ", 'AL32UTF8')), 'AL32UTF8')";
            }
        });
    }
}

3. 实体类中使用统一函数

@Entity
@Table(name = "user")
public class User {
    @Id
    private Long id;

    // 写入调用encrypt,读取调用decrypt,密钥通过会话参数传递
    @ColumnTransformer(
        write = "encrypt(?, :encryptKey)",
        read = "decrypt(encrypted_data, :encryptKey)"
    )
    // MySQL用VARBINARY,Oracle用RAW(256),根据加密长度调整
    @Column(name = "encrypted_data", columnDefinition = "VARBINARY(256)")
    private String sensitiveData;

    // getter/setter省略
}

4. 配置动态切换方言

通过配置文件根据数据库类型指定对应方言:

spring:
  jpa:
    properties:
      hibernate:
        dialect: ${db.dialect}
        encrypt.key: ${ENCRYPT_KEY:your_default_key}

# 环境变量切换:MySQL填com.example.CustomMySQLDialect,Oracle填com.example.CustomOracleDialect
db:
  dialect: com.example.CustomMySQLDialect

5. 绑定加密密钥到会话

用Hibernate拦截器把密钥注入会话参数,供@ColumnTransformer使用:

public class EncryptionKeyInterceptor extends EmptyInterceptor {
    private final String encryptKey;

    public EncryptionKeyInterceptor(String encryptKey) {
        this.encryptKey = encryptKey;
    }

    @Override
    public void beforeTransactionCompletion(Transaction tx) {
        tx.getSession().setParameter("encryptKey", encryptKey);
    }
}

Spring配置中注册拦截器:

@Configuration
public class HibernateConfig {
    @Value("${spring.jpa.properties.hibernate.encrypt.key}")
    private String encryptKey;

    @Bean
    public HibernatePropertiesCustomizer hibernatePropertiesCustomizer() {
        return props -> props.put("hibernate.session_factory.interceptor", new EncryptionKeyInterceptor(encryptKey));
    }
}

方案二:自定义Hibernate类型封装加密解密逻辑

如果觉得@ColumnTransformer不够灵活,可自定义Hibernate类型,把加密解密逻辑完全封装,实体类只需标注@Type即可。

1. 自定义加密字符串类型

public class EncryptedStringType extends AbstractSingleColumnStandardBasicType<String> {
    private static final String ENCRYPT_KEY_PROPERTY = "hibernate.encrypt.key";

    public EncryptedStringType() {
        super(VarcharTypeDescriptor.INSTANCE, StringTypeDescriptor.INSTANCE);
    }

    @Override
    public String getName() {
        return "encrypted_string";
    }

    @Override
    public String[] getRegistrationKeys() {
        return new String[]{getName(), String.class.getName()};
    }

    // 读取结果集时解密
    @Override
    public String nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException {
        Dialect dialect = session.getJdbcServices().getDialect();
        byte[] encrypted = rs.getBytes(names[0]);
        if (encrypted == null) return null;

        String key = getEncryptKey(session.getFactory());
        if (dialect instanceof MySQLDialect) {
            return CipherUtils.decryptAES(encrypted, key);
        } else if (dialect instanceof OracleDialect) {
            return CipherUtils.decryptAESOracle(encrypted, key);
        }
        return new String(encrypted);
    }

    // 写入数据库时加密
    @Override
    public void nullSafeSet(PreparedStatement st, String value, int index, SharedSessionContractImplementor session) throws SQLException {
        if (value == null) {
            st.setNull(index, getSqlType());
            return;
        }

        Dialect dialect = session.getJdbcServices().getDialect();
        String key = getEncryptKey(session.getFactory());
        byte[] encrypted;

        if (dialect instanceof MySQLDialect) {
            encrypted = CipherUtils.encryptAES(value, key);
        } else if (dialect instanceof OracleDialect) {
            encrypted = CipherUtils.encryptAESOracle(value, key);
        } else {
            encrypted = value.getBytes(StandardCharsets.UTF_8);
        }
        st.setBytes(index, encrypted);
    }

    // 从Hibernate配置中获取密钥
    private String getEncryptKey(SessionFactoryImplementor factory) {
        return (String) factory.getProperties().get(ENCRYPT_KEY_PROPERTY);
    }
}

2. 注册自定义类型

spring:
  jpa:
    properties:
      hibernate:
        encrypt.key: ${ENCRYPT_KEY:your_default_key}
        type:
          registration:
            key: encrypted_string
            class: com.example.EncryptedStringType

3. 实体类中使用

@Entity
@Table(name = "user")
public class User {
    @Id
    private Long id;

    @Type(type = "encrypted_string")
    @Column(name = "encrypted_data", columnDefinition = "VARBINARY(256)")
    private String sensitiveData;

    // getter/setter省略
}

注意事项

  • 数据库列类型:MySQL用VARBINARY/BLOB,Oracle用RAW/BLOB,根据加密后的数据长度调整。
  • 密钥管理:禁止硬编码密钥,建议用环境变量、配置中心或专业密钥管理服务存储。
  • Oracle权限:必须给数据库用户授予DBMS_CRYPTO执行权限,否则会抛出权限不足异常。
  • 字符集一致性:加密解密统一使用UTF-8字符集,避免乱码问题。

内容的提问来源于stack exchange,提问作者Adithya R

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 15:25:44