Enumset in Der Datenbank

Julian | Nov 16, 2024 min read

Motivation

Auch in der heutigen Zeit, in der Speicher vergleichsweise günstig ist, kann es trotzdem sinnvoll sein, eben diesen nicht zu verschwenden. Wir haben innerhalb unseres Projekts nach einer vergleichsweise günstigen Alternative gesucht, einem Datensatz zusätzliche Informationen mitzugeben, welche wir innerhalb des Frontends Anzeigen, sprich wir müssen diese nicht in der Datenbank für Menschen leserlich ablegen. Da ein Datensatz allerdings mehrere zusätzlich Informationen haben kann, ist einfaches Enum hier nicht das Mittel der Wahl. Somit sind wir auf das EnumSet gestoßen und wie wir das Ganze umgesetzt haben und wie du es auch umsetzen kannst, erfährst du im nachfolgenden Beitrag.

Exkurs Enum

Enums (Abkürzung für “Enumerationen”) sind eine spezielle Klasse in Programmiersprachen wie Java, die eine Gruppe von konstanten Werten definiert. Enums helfen dabei, eine Sammlung verwandter Konstanten wie z.B. Tage der Woche, Farben oder Statuswerte sicherer und lesbarer zu verwalten. Sie sorgen für typsichere Konstruktionen und verbessern die Codequalität durch die Einschränkung auf vordefinierte Werte.

Beispiel in Java:

public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

In diesem Fall repräsentiert das Enum Day die sieben Tage der Woche. Enums verbessern die Lesbarkeit und Wartbarkeit des Codes, indem sie eine saubere und typsichere Möglichkeit bieten, mit vordefinierten konstanten Werten zu arbeiten.

Noch etwas Theorie

In der Beispiel Implementierung benutzen wir ein Byte als Grundlage für unser EnumSet. Du kannst das Ganze auch Short oder Integer umsetzen, dies hängt davon ab, wie viele Ausprägungen des Enums du benötigst. Da ein Byte maximal 8 Ausprägungen haben kann.

Um uns das Ganze noch etwas besser Vorstellen zu können, versuche ich dies einmal in folgender Skizze darzustellen:

Bild das ein EnumSet verdeutlicht

Die Implementierung

Um ein EnumSet als BitSet mit einem JPA Converter in einer JPA Entity zu speichern, kannst du den Ansatz aus dem genannten Beispiel verwenden. Der Prozess umfasst die Erstellung eines AttributeConverter sowie die Umwandlung des EnumSet in ein numerisches Format. Hier ist ein vollständiges Beispiel, das zeigt, wie das geht:

Schritt 1: Enum definieren

public enum DayOfWeek {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

Schritt 2: EnumSetConverter erstellen

Du erstellst den Converter, der zwischen EnumSet und einer numerischen Darstellung konvertiert:

import javax.persistence.AttributeConverter;
import java.util.BitSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public abstract class EnumSetConverter<E extends Enum<E>, N extends Number> implements AttributeConverter<Set<E>, N> {
    private final E[] values;

    public EnumSetConverter(Class<E> enumType) {
        values = enumType.getEnumConstants();
    }

    @Override
    public Set<E> convertToEntityAttribute(N dbData) {
        if (dbData == null) {
            return null;
        }
        BitSet bitSet = map(dbData);
        return bitSet.stream().mapToObj(i -> values[i]).collect(Collectors.toSet());
    }

    @Override
    public N convertToDatabaseColumn(Set<E> attribute) {
        if (attribute == null) {
            return null;
        }
        BitSet bitSet = attribute.stream().collect(Collectors.toCollection(
                () -> {
                    BitSet bs = new BitSet();
                    attribute.forEach(e -> bs.set(e.ordinal()));
                    return bs;
                }));
        return map(bitSet);
    }

    protected abstract N map(BitSet bitSet);

    protected abstract BitSet map(N value);
}

Schritt 3: ToByteConverter implementieren

Da unser Enum nur sieben Werte hat, reicht ein Byte als numerischer Typ:

import java.time.DayOfWeek;
import java.util.BitSet;

public class DayOfWeekConverter extends EnumSetConverter<DayOfWeek, Byte> {
    public DayOfWeekConverter() {
        super(DayOfWeek.class);
    }

    @Override
    protected Byte map(BitSet bitSet) {
        return (byte) bitSet.toLongArray()[0];
    }

    @Override
    protected BitSet map(Byte value) {
        return BitSet.valueOf(new long[]{value});
    }
}

Schritt 4: JPA Entity definieren

Definiere die JPA Entity und verwende den Converter:

import javax.persistence.*;
import java.util.EnumSet;
import java.util.Set;

@Entity
public class Alarm {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Convert(converter = DayOfWeekConverter.class)
    private Set<DayOfWeek> recurringDays = EnumSet.noneOf(DayOfWeek.class);

    // Standard-Konstruktor
    public Alarm() {
    }

    // Getter und Setter
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Set<DayOfWeek> getRecurringDaysDays() {
        return recurringDays;
    }

    public void setRecurringDaysDays(Set<DayOfWeek> days) {
        this.recurringDays = days;
    }
}

Erklärung:

  • EnumSetConverter: Eine abstrakte Klasse, die den allgemeinen Konvertierungsvorgang bereitstellt.
  • DayOfWeekConverter: Eine spezifische Implementierung des Konverters für das DayOfWeek-Enum, die ein BitSet in ein Byte umwandelt und umgekehrt.
  • @Convert: Auf der JPA Entity wird der Konverter angewendet, um die EnumSet in der Datenbank als Byte zu speichern.

Mit diesem Setup kannst du EnumSet als BitSet in der Datenbank speichern, was sowohl Speicherplatz spart als auch die Effizienz verbessert.