Map enum w JPA ze stałymi wartościami?

Szukam różnych sposobów mapowania enum za pomocą JPA. Szczególnie chcę ustawić wartość całkowitą każdego wpisu enum i zapisać tylko wartość całkowitą.

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

  public enum Right {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      Right(int value) { this.value = value; }

      public int getValue() { return value; }
  };

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;

  // the enum to map : 
  private Right right;
}

Prostym rozwiązaniem jest użycie Enumerated adnotation z EnumType."ORDINAL": {]}

@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;

Ale w tym przypadku JPA mapuje indeks enum (0,1,2), a nie żądaną wartość (100,200,300).

Te dwa rozwiązania, które znalazłem, nie wydają się proste...

Pierwsze Rozwiązanie

A solution, proposed w tym miejscu używa @PrePersist i @PostLoad, aby przekonwertować enum na inne pole i oznaczyć pole enum jako przejściowe:

@Basic
private int intValueForAnEnum;

@PrePersist
void populateDBFields() {
  intValueForAnEnum = right.getValue();
}

@PostLoad
void populateTransientFields() {
  right = Right.valueOf(intValueForAnEnum);
}

Drugie Rozwiązanie

Drugie rozwiązanie zaproponowane tutaj zaproponowało ogólny obiekt konwersji, ale nadal wydaje się być ciężkie i zorientowane na hibernację (@Type nie wydaje się istnieć w Java EE):

@Type(
    type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
    parameters = {
            @Parameter(
                name  = "enumClass",                      
                value = "Authority$Right"),
            @Parameter(
                name  = "identifierMethod",
                value = "toInt"),
            @Parameter(
                name  = "valueOfMethod",
                value = "fromInt")
            }
)

Czy są jakieś inne rozwiązania ?

Mam kilka pomysłów na myśli, ale nie wiem, czy istnieją w JPA:

  • użyj metody setter i getter prawego członka klasy Authority podczas ładowania i zapisywania obiektu Authority
  • W przeciwieństwie do JPA, enum nie jest w stanie przekształcić Enum W int, a int w enum.]}
  • ponieważ używam Springa, czy jest jakiś sposób, aby powiedzieć JPA, aby używał określonego konwertera (RightEditor) ?
Author: Arjan Tijms, 2010-05-02

7 answers

Dla wersji wcześniejszych niż JPA 2.1, JPA zapewnia tylko dwa sposoby radzenia sobie z enumami, przez ich name lub przez ich ordinal. A standardowe JPA nie obsługuje niestandardowych typów. Więc:

  • Jeśli chcesz wykonywać niestandardowe konwersje typów, musisz użyć rozszerzenia provider (z Hibernate UserType, EclipseLink Converter, itd.). (drugie rozwiązanie). ~ or ~
  • będziesz musiał użyć sztuczki @PrePersist i @PostLoad (pierwsze rozwiązanie). ~ or ~
  • Annotate getter and setter taking and zwracanie wartości int ~ lub ~
  • użyj atrybutu integer na poziomie encji i wykonaj tłumaczenie w getterach i seterach.

Zilustruję najnowszą opcję (Jest to podstawowa implementacja, dostosuj ją zgodnie z wymaganiami):

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

    public enum Right {
        READ(100), WRITE(200), EDITOR (300);

        private int value;

        Right(int value) { this.value = value; }    

        public int getValue() { return value; }

        public static Right parse(int id) {
            Right right = null; // Default
            for (Right item : Right.values()) {
                if (item.getValue()==id) {
                    right = item;
                    break;
                }
            }
            return right;
        }

    };

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "AUTHORITY_ID")
    private Long id;

    @Column(name = "RIGHT_ID")
    private int rightId;

    public Right getRight () {
        return Right.parse(this.rightId);
    }

    public void setRight(Right right) {
        this.rightId = right.getValue();
    }

}
 153
Author: Pascal Thivent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-12 21:48:35

Jest to teraz możliwe dzięki JPA 2.1:

@Column(name = "RIGHT")
@Enumerated(EnumType.STRING)
private Right right;

Dalsze szczegóły:

 57
Author: Dave Jarvis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-12 21:45:17

Najlepszym rozwiązaniem byłoby mapowanie unikalnego identyfikatora do każdego typu enum, unikając w ten sposób pułapek ORDINAL i STRING. Zobacz ten post , w którym opisano 5 sposobów mapowania enum.

Wzięte z linku powyżej:

1&2. Using @Enumerated

Istnieją obecnie 2 sposoby mapowania enum w encjach JPA za pomocą adnotacji @ Enumerated. Niestety oba EnumType.STRING i EnumType.ORDINAL mają swoje ograniczenia.

Jeśli używasz EnumType.Zmiana nazwy jednego z typów enum spowoduje, że wartość enum nie będzie zsynchronizowana z wartościami zapisanymi w bazie danych. Jeśli używasz EnumType.Uporządkowanie następnie usunięcie lub zmiana kolejności typów w enum spowoduje mapowanie wartości zapisanych w bazie danych do niewłaściwych typów enum.

Obie te opcje są delikatne. Jeśli enum zostanie zmodyfikowane bez przeprowadzenia migracji bazy danych, możesz zagrozić integralności danych.

3. Cykl życia Callbacks

Możliwym rozwiązaniem byłoby użycie adnotacji w cyklu życia JPA call back, @PrePersist i @PostLoad. Wydaje się to dość brzydkie, ponieważ będziesz miał teraz dwie zmienne w swojej jednostce. Jeden mapuje wartość przechowywaną w bazie danych, a drugi-rzeczywisty enum.

4. Mapowanie unikalnych identyfikatorów dla każdego typu enum

Preferowanym rozwiązaniem jest mapowanie enum do stałej wartości, lub ID, zdefiniowanej w enum. Mapowanie do predefiniowanej, stałej wartości sprawia, że kod jest bardziej solidny. Jakakolwiek zmiana kolejności typów enum lub refaktoryzacja nazw nie spowoduje żadnych negatywnych skutków.

5. Korzystanie z Java Ee7 @Convert

Jeśli używasz JPA 2.1, możesz użyć nowej adnotacji @ Convert. Wymaga to utworzenia klasy konwertera, z adnotacją @Converter, wewnątrz której można określić, jakie wartości są zapisywane do bazy danych dla każdego typu enum. W Twoim enum będziesz wtedy adnotował swoje enum za pomocą @ Convert.

Moje preferencje: (Liczba 4)

Powodem, dla którego wolę definiować moje ID w enum jako przeciwne używaniu konwertera, jest dobra enkapsulacja. Tylko Typ enum powinien wiedzieć o swoim ID i tylko encja powinna wiedzieć o tym, jak mapuje enum do bazy danych.

Zobacz oryginalny post dla przykładu kodu.

 15
Author: Chris Ritchie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-26 11:32:02

Problem polega na tym, że myślę, że JPA nigdy nie było inicjowane z myślą o tym, że możemy mieć już złożony, istniejący schemat.

Myślę, że wynikają z tego dwa główne niedociągnięcia, specyficzne dla Enum:

  1. ograniczenie używania name () I ordinal (). Dlaczego po prostu nie oznaczyć gettera za pomocą @ Id, tak jak to robimy z @ Entity?
  2. Enum mają zazwyczaj reprezentację w bazie danych, aby umożliwić skojarzenie z wszelkiego rodzaju metadanymi, w tym z odpowiednim Nazwa, Nazwa opisowa, może coś z lokalizacją itp. Potrzebujemy łatwego w użyciu Enum w połączeniu z elastycznością jednostki.

Pomóż mojej sprawie i zagłosuj na JPA_SPEC-47

Czy nie byłoby to bardziej eleganckie niż użycie konwertera @do rozwiązania problemu?

// Note: this code won't work!!
// it is just a sample of how I *would* want it to work!
@Enumerated
public enum Language {
  ENGLISH_US("en-US"),
  ENGLISH_BRITISH("en-BR"),
  FRENCH("fr"),
  FRENCH_CANADIAN("fr-CA");
  @ID
  private String code;
  @Column(name="DESCRIPTION")
  private String description;

  Language(String code) {
    this.code = code;
  }

  public String getCode() {
    return code;
  }

  public String getDescription() {
    return description;
  }
}
 10
Author: JoD.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-26 01:32:30

Z JPA 2.1 możesz użyć AttributeConverter .

Utwórz klasę wyliczoną w ten sposób:

public enum NodeType {

    ROOT("root-node"),
    BRANCH("branch-node"),
    LEAF("leaf-node");

    private final String code;

    private NodeType(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

I utwórz konwerter w ten sposób:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class NodeTypeConverter implements AttributeConverter<NodeType, String> {

    @Override
    public String convertToDatabaseColumn(NodeType nodeType) {
        return nodeType.getCode();
    }

    @Override
    public NodeType convertToEntityAttribute(String dbData) {
        for (NodeType nodeType : values()) {
            if (nodeType.getCode().equals(dbData)) {
                return nodeType;
            }
        }

        throw new IllegalArgumentException("Unknown database value:" + dbData);
    }
}

Na encji wystarczy:

@Column(name = "node_type_code")

Masz szczęście z @Converter(autoApply = true) może się różnić w zależności od kontenera, ale przetestowany do pracy na Wildfly 8.1.0. Jeśli to nie zadziała, możesz dodać @Convert(converter = NodeTypeConverter.class) do kolumny entity class.

 5
Author: Pool,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-11 15:32:56

Prawdopodobnie blisko spokrewniony kod Pascala

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

    public enum Right {
        READ(100), WRITE(200), EDITOR(300);

        private Integer value;

        private Right(Integer value) {
            this.value = value;
        }

        // Reverse lookup Right for getting a Key from it's values
        private static final Map<Integer, Right> lookup = new HashMap<Integer, Right>();
        static {
            for (Right item : Right.values())
                lookup.put(item.getValue(), item);
        }

        public Integer getValue() {
            return value;
        }

        public static Right getKey(Integer value) {
            return lookup.get(value);
        }

    };

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "AUTHORITY_ID")
    private Long id;

    @Column(name = "RIGHT_ID")
    private Integer rightId;

    public Right getRight() {
        return Right.getKey(this.rightId);
    }

    public void setRight(Right right) {
        this.rightId = right.getValue();
    }

}
 3
Author: Rafiq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-31 08:43:52

Zrobiłbym folowing:

Zadeklaruj osobno enum, we własnym pliku:

public enum RightEnum {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      private RightEnum (int value) { this.value = value; }


      @Override
      public static Etapa valueOf(Integer value){
           for( RightEnum r : RightEnum .values() ){
              if ( r.getValue().equals(value))
                 return r;
           }
           return null;//or throw exception
     }

      public int getValue() { return value; }


}

Declare a new JPA entity named Right

@Entity
public class Right{
    @Id
    private Integer id;
    //FIElDS

    // constructor
    public Right(RightEnum rightEnum){
          this.id = rightEnum.getValue();
    }

    public Right getInstance(RightEnum rightEnum){
          return new Right(rightEnum);
    }


}

Będziesz również potrzebował konwertera do otrzymywania tych wartości (tylko JPA 2.1 i nie ma problemu, aby nie dyskutować tutaj z tymi enumami, które mają być bezpośrednio utrzymywane przy użyciu konwertera, więc będzie to droga jednokierunkowa)

import mypackage.RightEnum;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

/**
 * 
 * 
 */
@Converter(autoApply = true)
public class RightEnumConverter implements AttributeConverter<RightEnum, Integer>{

    @Override //this method shoudn´t be used, but I implemented anyway, just in case
    public Integer convertToDatabaseColumn(RightEnum attribute) {
        return attribute.getValue();
    }

    @Override
    public RightEnum convertToEntityAttribute(Integer dbData) {
        return RightEnum.valueOf(dbData);
    }

}

Podmiot władzy:

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {


  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;

  // the **Entity** to map : 
  private Right right;

  // the **Enum** to map (not to be persisted or updated) : 
  @Column(name="COLUMN1", insertable = false, updatable = false)
  @Convert(converter = RightEnumConverter.class)
  private RightEnum rightEnum;

}

W ten sposób nie można ustawić bezpośrednio na polu enum. Możesz jednak ustawić odpowiednie pole w polu autorytet za pomocą

autorithy.setRight( Right.getInstance( RightEnum.READ ) );//for example

I jeśli chcesz porównać, możesz użyć:

authority.getRight().equals( RightEnum.READ ); //for example

Co jest całkiem fajne, myślę. Nie jest to całkowicie poprawne, ponieważ konwerter nie jest przeznaczony do użycia z enumami. właściwie, dokumentacja mówi, aby nigdy nie używać go do tego celu, powinieneś zamiast tego użyć adnotacji @ Enumerated. Problem polega na tym, że istnieją tylko dwa typy enum: ORDINAL lub STRING, ale ORDINAL jest trudny i nie bezpiecznie.


Jeśli jednak cię to nie usatysfakcjonuje, możesz zrobić coś nieco prostszego i prostszego (lub nie).

Zobaczmy.

Prawa:

public enum RightEnum {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      private RightEnum (int value) { 
            try {
                  this.value= value;
                  final Field field = this.getClass().getSuperclass().getDeclaredField("ordinal");
                  field.setAccessible(true);
                  field.set(this, value);
             } catch (Exception e) {//or use more multicatch if you use JDK 1.7+
                  throw new RuntimeException(e);
            }
      }


      @Override
      public static Etapa valueOf(Integer value){
           for( RightEnum r : RightEnum .values() ){
              if ( r.getValue().equals(value))
                 return r;
           }
           return null;//or throw exception
     }

      public int getValue() { return value; }


}

I podmiot władzy

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {


  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;


  // the **Enum** to map (to be persisted or updated) : 
  @Column(name="COLUMN1")
  @Enumerated(EnumType.ORDINAL)
  private RightEnum rightEnum;

}

W tym drugim pomyśle, nie jest to idealna sytuacja, ponieważ hakujemy atrybut porządkowy, ale jest to znacznie mniejszy kod.

Myślę, że specyfikacja JPA powinna zawierać EnumType.ID gdzie pole wartości enum powinno być adnotowane jakimś @ EnumId adnotacja.

 2
Author: Carlos Cariello,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-22 12:02:59