Hibernate ColumnTransformer写入时无法传递实体属性问题求助
解决@ColumnTransformer写入时无法传递其他字段的问题
你遇到的问题根源是@ColumnTransformer的write表达式中,?仅能指代当前字段(此处为firstName)的值,Hibernate不会自动将实体的其他属性(如CREATION_DATE)注入到该表达式中,因此写入操作无法传递该依赖参数。以下是几种可行的解决方案:
方案一:自定义Hibernate用户类型(推荐)
通过实现自定义用户类型,可在加密/解密时访问整个实体的状态,直接获取creationDate的值。这种方式完全符合JPA/Hibernate的设计规范,能自动处理读写全流程的加密解密逻辑。
实现步骤:
- 创建实现
org.hibernate.usertype.UserType的自定义类型类,在nullSafeGet(读取解密)和nullSafeSet(写入加密)方法中处理逻辑:
public class EncryptedFirstNameType implements UserType { @Override public int[] sqlTypes() { return new int[]{Types.VARCHAR}; } @Override public Class<String> returnedClass() { return String.class; } @Override public boolean equals(Object x, Object y) throws HibernateException { return Objects.equals(x, y); } @Override public int hashCode(Object x) throws HibernateException { return x != null ? x.hashCode() : 0; } @Override public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException { String encryptedValue = rs.getString(names[0]); Long creationDate = ((CustomerLedger) owner).getCreationDate(); if (encryptedValue != null && creationDate != null) { // 调用数据库解密函数 try (CallableStatement cs = session.getJdbcConnectionAccess().obtainConnection().prepareCall("{call LOYLTY_DECRYPT(?, ?)}")) { cs.setString(1, encryptedValue); cs.setLong(2, creationDate); cs.execute(); return cs.getString(1); } catch (Exception e) { throw new HibernateException("解密失败", e); } } return encryptedValue; } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { String plainText = (String) value; CustomerLedger entity = (CustomerLedger) session.getEntityPersister(null, value).getEntity(session, value); Long creationDate = entity.getCreationDate(); if (plainText != null && creationDate != null) { // 调用数据库加密函数 try (CallableStatement cs = session.getJdbcConnectionAccess().obtainConnection().prepareCall("{call LOYLTY_ENCRYPT(?, ?)}")) { cs.setString(1, plainText); cs.setLong(2, creationDate); cs.execute(); st.setString(index, cs.getString(1)); } catch (Exception e) { throw new HibernateException("加密失败", e); } } else { st.setNull(index, Types.VARCHAR); } } // 实现其他必要接口方法 @Override public Object deepCopy(Object value) throws HibernateException { return value; } @Override public boolean isMutable() { return false; } @Override public Serializable disassemble(Object value) throws HibernateException { return (Serializable) value; } @Override public Object assemble(Serializable cached, Object owner) throws HibernateException { return cached; } @Override public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; } }
- 修改实体类字段注解,替换
@ColumnTransformer为自定义类型:
@Column(name = "FIRST_NAME") @Attribute(keyword = "CUSTOMERLEDGER_FIRSTNAME", resolvedKeyword = "firstName", displayName = "First Name", length = 100) @Type(type = "com.yourpackage.EncryptedFirstNameType") private String firstName; @Column(name = "CREATION_DATE") @Attribute(keyword = "CUSTOMERLEDGER_CREATIONDATE", resolvedKeyword = "creationDate", uiList = false, length = 20) private Long creationDate;
方案二:使用实体生命周期回调
若加密逻辑可在Java端实现,或能通过JDBC直接调用数据库加密函数,可借助@PrePersist和@PreUpdate注解,在保存/更新前手动完成加密:
@Entity public class CustomerLedger { // 字段定义... @PrePersist @PreUpdate public void encryptFirstName() { if (firstName != null && creationDate != null) { // 调用数据库加密函数获取加密后的值 String encrypted = getEntityManager().createNativeQuery("SELECT LOYLTY_ENCRYPT(?, ?)") .setParameter(1, firstName) .setParameter(2, creationDate) .getSingleResult(); this.firstName = encrypted; } } // 需注入EntityManager或通过其他方式获取数据库连接 }
方案三:使用原生SQL插入/更新
针对简单场景,可直接编写原生SQL语句,手动将CREATION_DATE作为参数传入加密函数:
// 插入示例 String sql = "INSERT INTO CUSTOMER_LEDGER (FIRST_NAME, CREATION_DATE) VALUES (LOYLTY_ENCRYPT(?, ?), ?)"; entityManager.createNativeQuery(sql) .setParameter(1, customer.getFirstName()) .setParameter(2, customer.getCreationDate()) .setParameter(3, customer.getCreationDate()) .executeUpdate();
这种方式直接但需手动维护SQL,适合小规模场景。
内容的提问来源于stack exchange,提问作者Muddassir Rahman
相关产品推荐
相关产品推荐

