Was ist der beste Weg, um Aufzählungen in einer Datenbank zu speichern?
Ich weiß, dass Java bietet name()
und valueOf()
Methoden, um Aufzählungswerte in einen String und zurück zu konvertieren. Aber gibt es andere (flexible) Optionen zum Speichern diese Werte?
Gibt es eine clevere Möglichkeit, eindeutige Zahlen zu erstellen (ordinal()
ist nicht sicher zu verwenden)?
pdate:
Vielen Dank für alle tollen und schnellen Antworten! Es war wie ich vermutet habe.
Allerdings ein Hinweis zu "Toolkit"; Das ist eine Möglichkeit. Das Problem ist, dass ich jedem von mir erstellten Aufzählungstyp die gleichen Methoden hinzufügen müsste. Das ist viel duplizierter Code und im Moment unterstützt Java keine Lösungen dafür (a Java enum kann andere Klassen nicht erweitern)).
Wir speichern niemals Aufzählungen mehr als numerische Ordnungswerte; Das macht das Debuggen und den Support viel zu schwierig. Wir speichern den tatsächlichen Aufzählungswert, der in einen String konvertiert wurde:
public enum Suit { Spade, Heart, Diamond, Club }
Suit theSuit = Suit.Heart;
szQuery = "INSERT INTO Customers (Name, Suit) " +
"VALUES ('Ian Boyd', %s)".format(theSuit.name());
und dann lesen Sie zurück mit:
Suit theSuit = Suit.valueOf(reader["Suit"]);
Das Problem bestand in der Vergangenheit darin, Enterprise Manager anzustarren und zu entschlüsseln:
Name Suit
================== ==========
Shelby Jackson 2
Ian Boyd 1
verse
Name Suit
================== ==========
Shelby Jackson Diamond
Ian Boyd Heart
letzteres ist viel einfacher. Ersteres erforderte das Auffinden des Quellcodes und der numerischen Werte, die den Aufzählungsmitgliedern zugewiesen wurden.
Ja, es nimmt mehr Platz in Anspruch, aber die Namen der Aufzählungsmitglieder sind kurz und die Festplatten sind billig, und es lohnt sich viel mehr, zu helfen, wenn Sie ein Problem haben.
Wenn Sie numerische Werte verwenden, sind Sie außerdem an diese gebunden. Sie können die Elemente nicht ordnungsgemäß einfügen oder neu anordnen, ohne die alten numerischen Werte erzwingen zu müssen. Beispiel: Ändern der Suit-Aufzählung in:
public enum Suit { Unknown, Heart, Club, Diamond, Spade }
müsste werden:
public enum Suit {
Unknown = 4,
Heart = 1,
Club = 3,
Diamond = 2,
Spade = 0 }
um die in der Datenbank gespeicherten alten numerischen Werte beizubehalten.
Die Frage kommt auf: Nehmen wir an, ich wollte die Werte bestellen. Einige Leute möchten sie möglicherweise nach dem Ordnungswert der Aufzählung sortieren. Natürlich ist es bedeutungslos, die Karten nach dem Zahlenwert der Aufzählung zu ordnen:
SELECT Suit FROM Cards
ORDER BY SuitID; --where SuitID is integer value(4,1,3,2,0)
Suit
------
Spade
Heart
Diamond
Club
Unknown
Das ist nicht die Reihenfolge, die wir wollen - wir wollen sie in Aufzählungsreihenfolge:
SELECT Suit FROM Cards
ORDER BY CASE SuitID OF
WHEN 4 THEN 0 --Unknown first
WHEN 1 THEN 1 --Heart
WHEN 3 THEN 2 --Club
WHEN 2 THEN 3 --Diamond
WHEN 0 THEN 4 --Spade
ELSE 999 END
Dieselbe Arbeit, die erforderlich ist, wenn Sie Ganzzahlwerte speichern, ist erforderlich, wenn Sie Zeichenfolgen speichern:
SELECT Suit FROM Cards
ORDER BY Suit; --where Suit is an enum name
Suit
-------
Club
Diamond
Heart
Spade
Unknown
Aber das ist nicht die Reihenfolge, die wir wollen - wir wollen sie in Aufzählungsreihenfolge:
SELECT Suit FROM Cards
ORDER BY CASE Suit OF
WHEN 'Unknown' THEN 0
WHEN 'Heart' THEN 1
WHEN 'Club' THEN 2
WHEN 'Diamond' THEN 3
WHEN 'Space' THEN 4
ELSE 999 END
Meiner Meinung nach gehört ein solches Ranking in die Benutzeroberfläche. Wenn Sie Elemente nach ihrem Aufzählungswert sortieren: Sie machen etwas falsch.
Aber wenn Sie das wirklich tun wollten, würde ich eine Suits
-Dimensionstabelle erstellen:
| Suit | SuitID | Rank | Color |
|------------|--------------|---------------|--------|
| Unknown | 4 | 0 | NULL |
| Heart | 1 | 1 | Red |
| Club | 3 | 2 | Black |
| Diamond | 2 | 3 | Red |
| Spade | 0 | 4 | Black |
Auf diese Weise können Sie Ihre Karten für die Verwendung von Kissing Kings New Deck Order zu Anzeigezwecken ändern, ohne wirf alle deine Daten weg:
| Suit | SuitID | Rank | Color | CardOrder |
|------------|--------------|---------------|--------|-----------|
| Unknown | 4 | 0 | NULL | NULL |
| Spade | 0 | 1 | Black | 1 |
| Diamond | 2 | 2 | Red | 1 |
| Club | 3 | 3 | Black | -1 |
| Heart | 1 | 4 | Red | -1 |
Jetzt trennen wir ein internes Programmierdetail (Aufzählungsname, Aufzählungswert) von einer Anzeigeeinstellung, die für Benutzer bestimmt ist:
SELECT Cards.Suit
FROM Cards
INNER JOIN Suits ON Cards.Suit = Suits.Suit
ORDER BY Suits.Rank,
Card.Rank*Suits.CardOrder
Ich würde empfehlen, für die Aufzählung eine separate Tabelle zu verwenden, es sei denn, Sie haben bestimmte Leistungsgründe, um dies zu vermeiden. Verwenden Sie die Integrität von Fremdschlüsseln, es sei denn, die zusätzliche Suche bringt Sie wirklich um.
suit_id suit_name
1 Clubs
2 Hearts
3 Spades
4 Diamonds
player_name suit_id
Ian Boyd 4
Shelby Lake 2
suit_id
) sind unabhängig von Ihrem Aufzählungswert, wodurch Sie auch die Daten aus anderen Sprachen bearbeiten können.Ich würde argumentieren, dass der einzige sichere Mechanismus hier die Verwendung des Werts String name()
ist. Beim Schreiben in die Datenbank können Sie könnte einen Sproc zum Einfügen des Werts und beim Lesen eine View verwenden. Wenn sich die Aufzählungen ändern, gibt es auf diese Weise eine Indirektionsebene im Sproc/in der Ansicht, um die Daten als Aufzählungswert darstellen zu können, ohne dies der Datenbank "aufzuerlegen".
Wie Sie sagen, ist Ordinal ein bisschen riskant. Betrachten Sie zum Beispiel:
public enum Boolean {
TRUE, FALSE
}
public class BooleanTest {
@Test
public void testEnum() {
assertEquals(0, Boolean.TRUE.ordinal());
assertEquals(1, Boolean.FALSE.ordinal());
}
}
Wenn Sie dies als Ordnungszahl gespeichert haben, haben Sie möglicherweise folgende Zeilen:
> SELECT STATEMENT, TRUTH FROM CALL_MY_BLUFF
"Alice is a boy" 1
"Graham is a boy" 0
Aber was passiert, wenn Sie Boolean aktualisiert haben?
public enum Boolean {
TRUE, FILE_NOT_FOUND, FALSE
}
Dies bedeutet, dass all Ihre Lügen als "Datei nicht gefunden" fehlinterpretiert werden.
Verwenden Sie einfach eine Zeichenfolgendarstellung
Bei einer großen Datenbank möchte ich die Vorteile der numerischen Darstellung hinsichtlich Größe und Geschwindigkeit nur ungern verlieren. Am Ende habe ich oft eine Datenbanktabelle, die die Aufzählung darstellt.
Sie können die Datenbankkonsistenz erzwingen, indem Sie einen Fremdschlüssel deklarieren. In einigen Fällen ist es jedoch besser, diesen nicht als Fremdschlüsseleinschränkung zu deklarieren, da jede Transaktion mit Kosten verbunden ist. Sie können die Konsistenz sicherstellen, indem Sie in regelmäßigen Abständen eine Überprüfung Ihrer Wahl durchführen:
SELECT reftable.* FROM reftable
LEFT JOIN enumtable ON reftable.enum_ref_id = enumtable.enum_id
WHERE enumtable.enum_id IS NULL;
Die andere Hälfte dieser Lösung besteht darin, einen Testcode zu schreiben, der prüft, ob Java enum und die Datenbank-Enum-Tabelle den gleichen Inhalt haben. Dies ist eine Übung für den Leser.
Wir speichern nur den Namen der Aufzählung selbst - er ist besser lesbar.
Wir haben herumgespielt, um bestimmte Werte für Aufzählungen zu speichern, bei denen es eine begrenzte Menge von Werten gibt, z. B. diese Aufzählung, die eine begrenzte Menge von Status hat, für die wir ein Zeichen verwenden (aussagekräftiger als ein numerischer Wert):
public enum EmailStatus {
EMAIL_NEW('N'), EMAIL_SENT('S'), EMAIL_FAILED('F'), EMAIL_SKIPPED('K'), UNDEFINED('-');
private char dbChar = '-';
EmailStatus(char statusChar) {
this.dbChar = statusChar;
}
public char statusChar() {
return dbChar;
}
public static EmailStatus getFromStatusChar(char statusChar) {
switch (statusChar) {
case 'N':
return EMAIL_NEW;
case 'S':
return EMAIL_SENT;
case 'F':
return EMAIL_FAILED;
case 'K':
return EMAIL_SKIPPED;
default:
return UNDEFINED;
}
}
}
und wenn Sie viele Werte haben, müssen Sie eine Map in Ihrer Enumeration haben, um diese getFromXYZ-Methode klein zu halten.
Wenn Sie Aufzählungen als Zeichenfolgen in der Datenbank speichern, können Sie Dienstprogrammmethoden erstellen, um eine Aufzählung zu (de) serialisieren:
public static String getSerializedForm(Enum<?> enumVal) {
String name = enumVal.name();
// possibly quote value?
return name;
}
public static <E extends Enum<E>> E deserialize(Class<E> enumType, String dbVal) {
// possibly handle unknown values, below throws IllegalArgEx
return Enum.valueOf(enumType, dbVal.trim());
}
// Sample use:
String dbVal = getSerializedForm(Suit.SPADE);
// save dbVal to db in larger insert/update ...
Suit suit = deserialize(Suit.class, dbVal);
Ich habe das gleiche Problem festgestellt, bei dem es mein Ziel ist, den Wert der Aufzählungszeichenfolge anstelle des Ordnungswerts in der Datenbank zu belassen.
Um dieses Problem zu lösen, habe ich @Enumerated(EnumType.STRING)
verwendet und mein Ziel wurde gelöst.
Zum Beispiel haben Sie eine Enum
Klasse:
public enum FurthitMethod {
Apple,
Orange,
Lemon
}
Definieren Sie in der Entitätsklasse @Enumerated(EnumType.STRING)
:
@Enumerated(EnumType.STRING)
@Column(name = "Fruits")
public FurthitMethod getFuritMethod() {
return fruitMethod;
}
public void setFruitMethod(FurthitMethod authenticationMethod) {
this.fruitMethod= fruitMethod;
}
Während Sie versuchen, Ihren Wert auf Database zu setzen, wird der String-Wert in Database als "Apple
", "ORANGE
" oder "LEMON
" gespeichert.
All meine Erfahrungen haben mir gezeigt, dass es am sichersten ist, Aufzählungen irgendwo zu speichern, indem man einen zusätzlichen Codewert oder eine ID verwendet (eine Art Weiterentwicklung von @jeebee answer). Dies könnte ein schönes Beispiel für eine Idee sein:
enum Race {
HUMAN ("human"),
ELF ("elf"),
DWARF ("dwarf");
private final String code;
private Race(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
Jetzt können Sie mit jeder Persistenz fortfahren, die auf Ihre Enum-Konstanten anhand ihres Codes verweist. Auch wenn Sie sich entscheiden, einige der Konstantennamen zu ändern, können Sie den Codewert (z. B. DWARF("dwarf")
bis GNOME("dwarf")
) immer speichern.
Ok, tauchen Sie mit dieser Vorstellung etwas tiefer ein. Im Folgenden finden Sie eine Dienstprogrammmethode, mit der Sie einen beliebigen Aufzählungswert ermitteln können. Zunächst können Sie jedoch unseren Ansatz erweitern.
interface CodeValue {
String getCode();
}
Und lassen Sie es unsere Enumeration implementieren:
enum Race implement CodeValue {...}
Dies ist die Zeit für die magische Suchmethode:
static <T extends Enum & CodeValue> T resolveByCode(Class<T> enumClass, String code) {
T[] enumConstants = enumClass.getEnumConstants();
for (T entry : enumConstants) {
if (entry.getCode().equals(code)) return entry;
}
// In case we failed to find it, return null.
// I'd recommend you make some log record here to get notified about wrong logic, perhaps.
return null;
}
Und benutze es wie einen Zauber: Race race = resolveByCode(Race.class, "elf")
Mehrere Werte mit der Beziehung OR für ein, Aufzählungsfeld. Das Konzept für .NET mit dem Speichern von Aufzählungstypen in einer Datenbank wie einem Byte oder einem Int und dem Verwenden von FlagsAttribute in Ihrem Code.
http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx