如何将带参数的CompositeUserType迁移至Hibernate 6
Hibernate 6迁移:带参数的CompositeUserType替代方案
问题背景
在Hibernate 5中,你通过@TypeDef传递hardLevelsSize和softLevelsSize参数给实现了CompositeUserType+ParameterizedType的自定义类型,用来映射BendableScore中的两个数值数组。但Hibernate 6弃用了@TypeDef,替代的@CompositeType不支持直接传递参数,导致带动态数组长度的类型迁移遇到阻碍,同时需要兼容Hibernate 5持久化的数据。
解决方案:自定义参数注解+反射读取
由于@CompositeType不支持参数,我们可以通过自定义字段注解传递数组长度参数,再在自定义CompositeUserType中通过反射读取这些参数,实现动态适配数组长度的逻辑。
步骤1:自定义参数注解
创建包含数组长度参数的注解,用于标记实体字段:
@Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface BendableScoreConfig { int hardLevelsSize(); int softLevelsSize(); }
步骤2:实现Hibernate 6的CompositeUserType
修改原有的BendableScoreHibernateType,实现Hibernate 6的CompositeUserType接口,在类型初始化时读取自定义注解的参数:
public class BendableScoreHibernateType implements CompositeUserType<BendableScore> { private int hardLevelsSize; private int softLevelsSize; @Override public void initialize(TypeConfiguration typeConfiguration, Properties properties) { // 可在这里处理全局默认配置 } @Override public BendableScore instantiate(ValueAccess valueAccess, SessionFactoryImplementor sessionFactory) { // 从字段元数据中读取自定义注解参数 AttributeMapping attributeMapping = valueAccess.getSessionFactory() .getRuntimeMetamodels() .getMappingMetamodel() .findAttributeMappingByPath(valueAccess.getPath()); BendableScoreConfig config = attributeMapping.getAttribute().getAnnotation(BendableScoreConfig.class); if (config != null) { this.hardLevelsSize = config.hardLevelsSize(); this.softLevelsSize = config.softLevelsSize(); } else { // 提供默认值避免空指针 this.hardLevelsSize = 3; this.softLevelsSize = 2; } // 按数组长度读取列值并组装对象 BigDecimal initScore = valueAccess.getValue(0, BigDecimal.class); BigDecimal[] hardLevels = new BigDecimal[hardLevelsSize]; for (int i = 0; i < hardLevelsSize; i++) { hardLevels[i] = valueAccess.getValue(1 + i, BigDecimal.class); } BigDecimal[] softLevels = new BigDecimal[softLevelsSize]; for (int i = 0; i < softLevelsSize; i++) { softLevels[i] = valueAccess.getValue(1 + hardLevelsSize + i, BigDecimal.class); } return new BendableScore(initScore, hardLevels, softLevels); } @Override public void decompose(BendableScore value, ValueSink valueSink, SessionFactoryImplementor sessionFactory) { // 保持与Hibernate 5一致的列写入顺序,确保兼容性 valueSink.putValue(value.getInitScore()); for (BigDecimal hard : value.getHardLevels()) { valueSink.putValue(hard); } for (BigDecimal soft : value.getSoftLevels()) { valueSink.putValue(soft); } } // 实现其他必要接口方法 @Override public Class<BendableScore> getJavaType() { return BendableScore.class; } @Override public EmbeddableMapping getEmbeddableMapping(TypeConfiguration typeConfiguration) { // 若需动态列映射可在此实现,或返回null配合实体字段的@Column注解 return null; } // equals、hashCode、isMutable等方法保持原有逻辑即可 }
步骤3:调整实体映射
移除@TypeDef,在实体字段上添加@CompositeType和自定义的@BendableScoreConfig,同时用@AttributeOverrides指定列名,与Hibernate 5中的@Columns配置完全对齐:
@Entity class BendableScoreHibernateTypeTestJpaEntity { @CompositeType(BendableScoreHibernateType.class) @BendableScoreConfig(hardLevelsSize = 3, softLevelsSize = 2) @AttributeOverrides({ @AttributeOverride(name = "initScore", column = @Column(name = "initScore")), @AttributeOverride(name = "hardLevels[0]", column = @Column(name = "hard0Score")), @AttributeOverride(name = "hardLevels[1]", column = @Column(name = "hard1Score")), @AttributeOverride(name = "hardLevels[2]", column = @Column(name = "hard2Score")), @AttributeOverride(name = "softLevels[0]", column = @Column(name = "soft0Score")), @AttributeOverride(name = "softLevels[1]", column = @Column(name = "soft1Score")) }) protected BendableScore score; // 其他实体代码... }
兼容性保障
- 读写逻辑对齐:确保
decompose和instantiate方法的列顺序、数据类型与Hibernate 5实现完全一致,保证Hibernate 6能正确读取旧数据。 - 列名映射一致:通过
@AttributeOverrides严格指定列名,与Hibernate 5中@Columns的配置匹配,避免列名不匹配导致的数据读取失败。
替代方案:全局配置(适用于单一场景)
如果系统中所有BendableScore类型的数组长度固定,可直接在自定义CompositeUserType中硬编码参数值,或通过静态配置类注入,无需自定义注解,简化实现。但该方案灵活性低,仅适用于全局参数统一的场景。
内容的提问来源于stack exchange,提问作者Radovan Synek
相关产品推荐
相关产品推荐

