2016-01-11 2 views
2

В настоящее время я использую стандартный способ сопоставления перечислений с Hibernate, например.JPA/Hibernate - persist enums как таблица констант

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

public enum State{ 
    NEW,OLD; 
} 

Теперь требования изменились, и я должны создать таблицу State, который содержит все допустимые значения моего перечисления в качестве констант String. Таким образом, Job должен ссылаться на таблицу State. Мне не нужно переносить устаревшие данные.

  1. Какие у меня есть варианты сопоставить это с JPA/Hibernate?
  2. Возможно ли, чтобы Hibernate создал таблицу Stateсо значениями (1 -> «новый», 2 -> «старый»). во время генерации DDL?
+0

Тот факт, что вы не знаете, как это сделать, можно рассматривать как ключ к ошибкам дизайна. Если для каждого значения перечисления есть некоторый код, он должен быть перечислением и не должен иметь таблицу в базе данных (из-за проблем с синхронизацией). Если для каждого значения перечисления не существует кода, он должен быть сущностью, а не перечислением. Имея это как таблицу И перечисление кажется странным. Не могли бы вы рассказать нам больше о том, зачем вам это нужно? –

+2

Второй намек: не позволяйте Hibernate генерировать DDL. (IMHO) –

+0

ему не нужно быть перечислением, но есть существующий код, использующий его. Это общее требование относительно всех перечислений в этом существующем проекте. –

ответ

1

в конечном итоге с раствором, который генерирует 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() порядка неявно предполагается.

-1

ИМХО это не имеет большого смысла. Значения enum являются статическими и постоянными, а значения в таблице SQL являются динамическими. Что делать, если БД не содержит точно значений (не более, не меньше) для такого перечисления?

EDIT: если вы вынуждены его реализовать, может быть что-то вроде этой работы?

public enum State{ 
    int primaryKey; 
    NEW(0),OLD(1); 
    public State(int pk) { 
     primarykey = pk; 
    } 
} 

А затем присоединиться по первичному ключу ....

+0

, без сомнения, это нехорошее решение. Но я должен это реализовать. –

+0

на самом деле, ваше предложенное предложение неверно. «Недействительный модификатор для конструктора перечисления, разрешено только личное». - и с помощью частного конструктора hibernate жалуется «Нет конструктора по умолчанию для объекта», –

Смежные вопросы