Hibernate 6 Envers:如何自定义@ElementCollection(@CompositeType)的审计表?
我正在将项目从Spring Boot 2.7.x迁移至3.0.x,其中一个实体包含复合类型的集合,核心代码如下:
实体与复合类型定义
@Entity @Audited public class Specialist { @Id @GeneratedValue private Long id; @ElementCollection @CompositeType(QualificationConverter.class) private Set<Qualification> qualifications; // ... 其他属性与方法 } public class Qualification implements Serializable { private LocalDate date = LocalDate.now(); private String customData; // ... getter、setter及builder方法 } public class Expert extends Qualification { /* ... */ } public class Professional extends Qualification { /* ... */ }
复合类型转换器
public class QualificationConverter implements CompositeUserType<Qualification> { @Override public Object getPropertyValue(Qualification qualification, int i) throws HibernateException { return switch (i) { case 0 -> qualification.getCustomData(); case 1 -> qualification.getDate(); case 2 -> qualification.getClass().getName(); default -> null; }; } @Override public Qualification instantiate(ValueAccess valueAccess, SessionFactoryImplementor sessionFactoryImplementor) { String type = valueAccess.getValue(2, String.class); try { QualificationBuilder builder = (QualificationBuilder) Class.forName(type).getMethod("builder").invoke(null); return builder.customData(valueAccess.getValue(0, String.class)) .date(valueAccess.getValue(1, LocalDate.class)) .build(); } catch (Exception ex) { throw new HibernateException(ex); } } @Override public Class<?> embeddable() { return QualificationMapper.class; } @Override public Class<Qualification> returnedClass() { return Qualification.class; } // ... 其他必要方法实现 public static class QualificationMapper { String customData; LocalDate date; String type; } }
当前问题
启动应用后,Envers生成的审计映射仅包含SETORDINAL列,而非预期的customData、date、type列:
<hibernate-mapping xmlns="http://www.hibernate.org/xsd/orm/hbm"> <class table="specialist_qualifications_AUD" entity-name="specialist_qualifications_AUD"> <composite-id name="originalId"> <key-many-to-one class="org.hibernate.envers.DefaultRevisionEntity" name="REV"> <column name="REV"/> </key-many-to-one> <key-property name="REVTYPE" type="org.hibernate.envers.internal.entities.RevisionTypeType"/> <key-property name="Specialist_id"> <column name="specialist_id"/> <type name="long"> <!-- 类型参数省略 --> </type> </key-property> <key-property name="SETORDINAL" type="integer"> <column name="SETORDINAL"/> </key-property> </composite-id> </class> </hibernate-mapping>
此前使用Spring Boot 2.7.x及旧版@Type(Def)注解时,该功能正常工作。
解决方案
1. 实现AuditCompositeUserType接口
Hibernate 6中,Envers对复合用户类型的审计逻辑有变更,仅实现CompositeUserType不再足够,需要实现**org.hibernate.envers.AuditCompositeUserType**(它是CompositeUserType的子接口),明确提供审计时的属性值获取逻辑:
public class QualificationConverter implements AuditCompositeUserType<Qualification> { // 保留原有CompositeUserType的所有实现方法 // 新增AuditCompositeUserType要求的方法 @Override public Object getAuditValue(Qualification qualification, String propertyName) { return switch (propertyName) { case "customData" -> qualification.getCustomData(); case "date" -> qualification.getDate(); case "type" -> qualification.getClass().getName(); default -> null; }; } }
2. 确保映射类属性与审计列匹配
确认QualificationMapper的属性名(customData、date、type)和类型与数据库列完全对应,Envers依赖这个类生成审计表的列结构。
3. 验证Envers基础配置
检查spring.jpa.properties.org.hibernate.envers.audit_table_suffix配置是否正确(默认值_AUD与当前场景匹配),避免其他自定义配置干扰复合类型的审计逻辑。
4. 兼容回退方案(可选)
如果上述方法未生效,可以尝试使用Hibernate 6兼容旧版注解的方式,替换@CompositeType为@Type并配合@TypeDef:
@Entity @Audited @TypeDef(name = "qualificationType", typeClass = QualificationConverter.class) public class Specialist { // ... @ElementCollection @Type(type = "qualificationType") private Set<Qualification> qualifications; // ... }
原理说明
Spring Boot 3.0默认集成Hibernate 6,Envers在该版本中对复合用户类型的审计要求更明确:旧版仅通过CompositeUserType就能自动识别并生成对应审计列的逻辑已被调整,必须通过AuditCompositeUserType接口明确告知Envers如何提取复合类型的属性值用于审计,才能生成包含复合类型属性列的审计表。
内容的提问来源于stack exchange,提问作者F. Labusch

