Enums mit ID

Ausgangslage

In einem Migrationsprojekt ging es darum, Datentypen aus einer bestehenden Datenbanktabelle als Enums zu übernehmen, da man auf Grund der CI/CD-Pipeline eine Änderung in der Anwendung wesentlich schneller hätte produktiv bringen können, als eine Änderung in der Datenbank.

Scheint auf dem ersten Blick nicht so schwierig aus. Entweder nehme ich die Ordinalnummer oder eben den Namen des Enums.

Doch jetzt kommts …

Der jeweilige „Datensatz“ sollte noch als aktiv und inaktiv markiert werden können und eine Umbenennung des Enums musste auch berücksichtigt werden. Ebenso eine Löschung eines nicht mehr benötigten Enums.

Somit habe ich mich am Ende für folgendeLösung entschieden, bei der in der Datenbank in den entsprechenden Tabellen eine eigene ID des Enums hinterlegt wird. Diese wird beim Lesen mit Hilfe eines Converters in das richtige Enum umgewandelt.
Da bekanntermaßen ein Enum nicht erweitert werden kann, musste man das Ganze mit einem Interface implementieren.

Hier die notwendigen Code-Snipplets, falls jemand ebenfalls in die Verlegenheit kommen sollte, eine solche Lösung umsetzen zu müssen.

Umsetzung

Die Umsetzung erfolgte mit Javax Persistence.

  1. Definition eines Interfaces, das später von den entsprechenden Enums implementiert wird, wobei E für den gewünschten Typen der ID steht. Selbstverständlich kann hier auch ein fester Typ angegeben werden.
public interface EnumWithId<E> {
  public E getId();
}

2. Implementierung des AttributeConverters, in dem die Konvertierung des Enums in die jeweilige Richtung vorgenommen wird

public abstract class EnumWithIdConverter<T extends Enum<T> & EnumWithId<E>, E> implements AttributeConverter<T, E> {
  private final Class<T> clazz;
 
  public EnumWithIdConverter(Class<T> clazz) {
    this.clazz = clazz;
  }
 
  @Override
  public E convertToDatabaseColumn(T attribute) {
    return attribute != null ? attribute.getId() : null;
  }
 
  @Override
  public T convertToEntityAttribute(E attribute) {
    if (attribute != null) {
      T[] enums = clazz.getEnumConstants();
 
      for (T e : enums) {
        if (e.getId().equals(attribute)) {
          return e;
        }
      }
 
      throw new IllegalArgumentException("invalid enum: " + attribute);
    }
    return null;
  }
}

3. Implementierung des Enums und festlegen des Converters

public enum Status implements EnumWithId<Integer> {
  OPEN(1, true),
  PENDING(2, false),
  IN_PROGRESS(3, true),
  CLOSED(4, true);

  private Integer id;
  private boolean active;
  
  @javax.persistence.Converter
  public static class Converter extends EnumWithIdConverter<Status, Integer> {
    public Converter() {
      super(Status.class);
    }
  }
}

4. In der Entität, in der auf dieses Enum referenziert wird, kann der Converter dann einfach per Annotation gesetzt werden

@Entity
@Table(name = "ORDER")
public class Order {
  ...
  @Convert(converter = Status.Converter.class)
  private Status status;
}