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

Hibernate ColumnTransformer写入时无法传递实体属性问题求助

解决@ColumnTransformer写入时无法传递其他字段的问题

你遇到的问题根源是@ColumnTransformer的write表达式中,?仅能指代当前字段(此处为firstName)的值,Hibernate不会自动将实体的其他属性(如CREATION_DATE)注入到该表达式中,因此写入操作无法传递该依赖参数。以下是几种可行的解决方案:

方案一:自定义Hibernate用户类型(推荐)

通过实现自定义用户类型,可在加密/解密时访问整个实体的状态,直接获取creationDate的值。这种方式完全符合JPA/Hibernate的设计规范,能自动处理读写全流程的加密解密逻辑。

实现步骤:

  1. 创建实现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;
    }
}
  1. 修改实体类字段注解,替换@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 07:26:08