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

Hibernate @ColumnTransformer注解中使用密钥的实现方案咨询

可行的实现方法

因为@ColumnTransformer的属性要求是编译时常量,没法直接注入配置中的密钥,推荐两种更灵活的实现方式:

方法一:使用Spring Data JPA的AttributeConverter

这是最轻量化的方案,通过实现转换器把实体字段和数据库列的加解密逻辑封装起来,同时可以注入配置中的密钥:

  1. 实现AttributeConverter接口,封装加密解密逻辑:
@Component
public class StringEncryptConverter implements AttributeConverter<String, byte[]> {

    @Value("${database.encrypt.secret}")
    private String secret;

    @Override
    public byte[] convertToDatabaseColumn(String attribute) {
        if (attribute == null) {
            return null;
        }
        try {
            // 对密钥做SHA256哈希,和原SQL逻辑一致
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] key = digest.digest(secret.getBytes(StandardCharsets.UTF_8));
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
            return cipher.doFinal(attribute.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new RuntimeException("加密失败", e);
        }
    }

    @Override
    public String convertToEntityAttribute(byte[] dbData) {
        if (dbData == null) {
            return null;
        }
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] key = digest.digest(secret.getBytes(StandardCharsets.UTF_8));
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
            byte[] decryptedBytes = cipher.doFinal(dbData);
            // 移除末尾的CHAR(0),匹配原SQL的TRIM逻辑
            return new String(decryptedBytes, StandardCharsets.UTF_8).trim().replace("\0", "");
        } catch (Exception e) {
            throw new RuntimeException("解密失败", e);
        }
    }
}
  1. 在实体类字段上添加@Convert注解关联转换器:
@Convert(converter = StringEncryptConverter.class)
@Column(columnDefinition = "VARBINARY")
private String name;

方法二:自定义Hibernate UserType

如果需要更深度的Hibernate集成(比如支持更复杂的类型或缓存逻辑),可以自定义UserType:

  1. 实现UserType接口,封装加解密和类型映射逻辑:
@Component
public class EncryptedStringType implements UserType {

    @Value("${database.encrypt.secret}")
    private String secret;

    @Override
    public int[] sqlTypes() {
        return new int[]{Types.VARBINARY};
    }

    @Override
    public Class<String> returnedClass() {
        return String.class;
    }

    @Override
    public boolean equals(Object x, Object y) {
        return Objects.equals(x, y);
    }

    @Override
    public int hashCode(Object x) {
        return x != null ? x.hashCode() : 0;
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException {
        byte[] encryptedBytes = rs.getBytes(names[0]);
        if (encryptedBytes == null) {
            return null;
        }
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] key = digest.digest(secret.getBytes(StandardCharsets.UTF_8));
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            return new String(decryptedBytes, StandardCharsets.UTF_8).trim().replace("\0", "");
        } catch (Exception e) {
            throw new HibernateException("解密失败", e);
        }
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws SQLException {
        if (value == null) {
            st.setNull(index, Types.VARBINARY);
            return;
        }
        String strValue = (String) value;
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] key = digest.digest(secret.getBytes(StandardCharsets.UTF_8));
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
            byte[] encryptedBytes = cipher.doFinal(strValue.getBytes(StandardCharsets.UTF_8));
            st.setBytes(index, encryptedBytes);
        } catch (Exception e) {
            throw new HibernateException("加密失败", e);
        }
    }

    @Override
    public Object deepCopy(Object value) {
        return value == null ? null : new String((String) value);
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(Object value) {
        return (Serializable) deepCopy(value);
    }

    @Override
    public Object assemble(Serializable cached, Object owner) {
        return deepCopy(cached);
    }

    @Override
    public Object replace(Object original, Object target, Object owner) {
        return deepCopy(original);
    }
}
  1. 在实体类字段上用@Type注解指定自定义类型:
@Type(type = "com.yourpackage.EncryptedStringType")
@Column(columnDefinition = "VARBINARY")
private String name;

注意事项

  • 两种方案都是在Java端完成加解密,和原数据库端函数逻辑对齐(SHA256哈希密钥、AES加解密、移除末尾空字符)。
  • 如果需要在数据库端直接查询加密字段,需先在Java端对查询值加密,再传入SQL;若坚持用数据库端函数,可通过Hibernate拦截器动态拼接SQL,但实现复杂度较高,不推荐。

内容的提问来源于stack exchange,提问作者Joel Quenard Martinez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 08:17:40