java - JPA/Hibernate - 将枚举保留为常量表

标签 java hibernate jpa enums

目前,我正在使用标准方法通过 Hibernate 映射枚举,例如

@Entity
public class Job {
    @Enumerated(EnumType.STRING)
    protected State state;
}

public enum State{
    NEW,OLD;
}

现在需求发生了变化,我必须创建一个表State,其中包含我的枚举的所有有效值作为字符串常量。因此,Job 必须引用State 表。我不必迁移旧数据。

  1. 我必须使用哪些选项来将其映射到 JPA/Hibernate?
  2. 是否可以让 Hibernate 创建带有值State表(1->"new",2->“旧”)。 在 DDL 生成期间?

最佳答案

最终得到了一个解决方案,它生成包括枚举常量和外键约束的 DDL。

例如

@Entity
public enum MyEnum{
    @EnumValue
    private String name;
    @Id
    private int id;
}
@Entity
public class MyEntity {
    @EnumReference
    protected MyEnum myEnum;
}

使用以下MetadataContributor就足够了(/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataContributor):

public class EnumConstantsMetadataContributor implements MetadataContributor {
    private final static Logger LOG = LoggerFactory.getLogger(EnumConstantsMetadataContributor.class);

    private final static List<String> ENABLED_ON = Arrays.asList("validate", "update", "create", "create-drop");
    private final static Integer DEFAULT_VARCHAR_SIZE = 255;
    private final static Identifier DEFAULT_COLUMN_NAME = Identifier.toIdentifier("enum_constant", false);

    @Override
    public void contribute(InFlightMetadataCollector metadataCollector, IndexView jandexIndex) {
        if (shouldRun(metadataCollector)) {
            addEnumsAsTableConstantsAndFkConstraint(metadataCollector);
        }
    }

    private boolean shouldRun(InFlightMetadataCollector metadataCollector) {
        StandardServiceRegistry serviceRegistry = metadataCollector.getMetadataBuildingOptions().getServiceRegistry();
        ConfigurationService config = serviceRegistry.getService(ConfigurationService.class);
        String setting = config.getSetting(AvailableSettings.HBM2DDL_AUTO, String.class, null);
        return (setting != null || ENABLED_ON.contains(setting));
    }

    private void addEnumsAsTableConstantsAndFkConstraint(InFlightMetadataCollector metadataCollector) {
        for (PersistentClass persistentClass : metadataCollector.getEntityBindings()) {
            Class<?> plainJavaClass = persistentClass.getMappedClass();
            if (Enum.class.isAssignableFrom((plainJavaClass))) {
                createEnumInsertsAndDbColumns(persistentClass, plainJavaClass, metadataCollector);
            }
            tryAddFkConstraint(persistentClass, metadataCollector);
        }
    }

    private void tryAddFkConstraint(PersistentClass persistentClass, InFlightMetadataCollector metadataCollector) {
        Consumer<Field> createEnumFkConstraintForField = field -> {
            String fieldName = field.getName();
            PersistentClass targetPersistentClass = metadataCollector.getEntityBinding(field.getType().getCanonicalName());
            if (targetPersistentClass == null) {
                LOG.error("Target (enum) class must be an @Entity: {}", field.getType().getCanonicalName());
                System.exit(1);
            }
            Property enumReferenceAnnotatedProperty = persistentClass.getProperty(fieldName);
            persistentClass.getTable().createForeignKey(null,
                    Arrays.asList(enumReferenceAnnotatedProperty.getColumnIterator().next()),
                    targetPersistentClass.getEntityName());
        };

        Field[] declaredFields = persistentClass.getMappedClass().getDeclaredFields();
        of(declaredFields).filter(field -> field.isAnnotationPresent(EnumReference.class)).forEach(
                createEnumFkConstraintForField);
    }

    private void createEnumInsertsAndDbColumns(PersistentClass persistentClass, Class<?> clazz,
            InFlightMetadataCollector metadata) {
        String tableName = persistentClass.getTable().getName();
        Enum<?>[] enumJavaConstants = clazz.asSubclass(Enum.class).getEnumConstants();

        ArrayList<String> insertCommandAccumulator = new ArrayList<String>(enumJavaConstants.length);

        Optional<Field> enumValueAnnotatedField = of(enumJavaConstants.getClass().getComponentType().getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(EnumValue.class)).map(fieldWithEnumValue -> {
                    fieldWithEnumValue.setAccessible(true);
                    return fieldWithEnumValue;
                }).findAny(); // just none or one is supported

        if (enumValueAnnotatedField.isPresent()) {
            setMinimalFieldLengthOfExitingColumn(enumValueAnnotatedField.get(), enumJavaConstants, persistentClass);
        }

        for (int i = 0; i < enumJavaConstants.length; i++) {
            Enum<?> it = enumJavaConstants[i];
            String constantEnumValue = enumValueAnnotatedField.map(v -> getInstanceValueOfEnumValueAnnotation(it, v))
                    .orElse(it.name());
            if (!enumValueAnnotatedField.isPresent()) {
                insertAdditionalColumn(persistentClass, metadata.getDatabase(), enumJavaConstants);
            }
            insertCommandAccumulator.add(createInsert(tableName, i, constantEnumValue));
        }

        InitCommand initCommand = new InitCommand(insertCommandAccumulator.toArray(new String[0]));
        persistentClass.getTable().addInitCommand(initCommand);
    }

    private void setMinimalFieldLengthOfExitingColumn(Field field, Enum<?>[] enumJavaConstants,
            PersistentClass persistentClass) {
        Property property = persistentClass.getProperty(field.getName());
        Column column = persistentClass.getTable().getColumn(Identifier.toIdentifier(property.getName()));
        Integer maxLengthOfEnums = maxLengthOfEnums(enumJavaConstants,
                e -> getInstanceValueOfEnumValueAnnotation(e, field));
        column.setLength(maxLengthOfEnums);
    }

    private String getInstanceValueOfEnumValueAnnotation(Enum<?> myEnum, Field enumValueAnnotatedField) {
        try {
            return enumValueAnnotatedField.get(myEnum).toString();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
            return null;
        }
    }

    private static Integer maxLengthOfEnums(Enum<?>[] enums, Function<Enum<?>, String> enumConstantValueExtractor) {
        return of(enums).map(it -> enumConstantValueExtractor.apply(it).length()).reduce(Math::max)
                .orElse(DEFAULT_VARCHAR_SIZE);
    };

    private void insertAdditionalColumn(PersistentClass persistentClass, Database database, Enum<?>[] enumJavaConstants) {
        Integer maxEnumStringLength = maxLengthOfEnums(enumJavaConstants, c -> c.name());
        Column column = new Column(DEFAULT_COLUMN_NAME.render(database.getDialect()));
        String typeName = database.getDialect().getTypeName(Types.VARCHAR, maxEnumStringLength, 0, 0);
        column.setSqlType(typeName);
        persistentClass.getTable().addColumn(column);
    }

    private String createInsert(String tableName, int position, String dbEnumValue) {
        return ("insert into " + tableName + " values(" + position + ",\'" + dbEnumValue + "\')");
    }
}

适用于 MySQL 5.7 和 Hibernate 5。

JPA 查询 MyEnum 是不可能的,并且 @Enumerated(EnumType.ORDINAL)getEnumConstants() 顺序之间的一致性是隐式的假设。

关于java - JPA/Hibernate - 将枚举保留为常量表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34718942/

相关文章:

java - 使用 Restful 服务下载 zip 文件

java - 我应该在构造函数中使用 getter 和 setter 吗?

java - 为什么 HibernateTemplate.merge() 会毫无异常地完成但不保留数据?

java - 两个具有相同自动生成 ID 的表

java - Hibernate 省略 JsonManagedReference 来持久化

java - 继承类中的相等和哈希码

java - ClosesableHttpClient 关闭最优性

javascript - 如何在具有返回值的 Vaadin 中添加 JavaScript 函数?

java - 在 MySQL 中处理 null

java - 当事务更新数千个实体时如何避免 StaleObjectStateException?