Quel est le meilleur moyen de sauvegarder des enums dans une base de données?
Je sais que Java fournit les méthodes name()
et valueOf()
] pour convertir les valeurs enum en chaîne et inversement. Mais existe-t-il d'autres options (flexibles) à stocker ces valeurs?
Existe-t-il un moyen intelligent de transformer des énums en nombres uniques (ordinal()
n'est pas sûr d'utiliser)?
Mise à jour:
Merci pour toutes les réponses géniales et rapides! C'était comme je le soupçonnais.
Cependant, une note à la "boîte à outils"; C'est une façon. Le problème est que je devrais ajouter les mêmes méthodes à chaque type Enum que je crée. C’est beaucoup de code dupliqué et, pour le moment, Java ne prend en charge aucune solution à ce problème (a Java enum ne peut pas étendre d’autres classes).).
Nous ne stockons plus jamais les énumérations sous forme de valeurs ordinales numériques; cela rend le débogage et le support beaucoup trop difficiles. Nous stockons la valeur d'énumération réelle convertie en chaîne:
public enum Suit { Spade, Heart, Diamond, Club }
Suit theSuit = Suit.Heart;
szQuery = "INSERT INTO Customers (Name, Suit) " +
"VALUES ('Ian Boyd', %s)".format(theSuit.name());
et ensuite relire avec:
Suit theSuit = Suit.valueOf(reader["Suit"]);
Dans le passé, le problème concernait Enterprise Manager et tentait de déchiffrer:
Name Suit
================== ==========
Shelby Jackson 2
Ian Boyd 1
vers
Name Suit
================== ==========
Shelby Jackson Diamond
Ian Boyd Heart
ce dernier est beaucoup plus facile. Le premier nécessitait de connaître le code source et de trouver les valeurs numériques attribuées aux membres du dénombrement.
Oui, cela prend plus de place, mais les noms des membres de l'énumération sont courts, les disques durs sont bon marché et il est beaucoup plus utile de vous aider lorsque vous rencontrez un problème.
De plus, si vous utilisez des valeurs numériques, vous y êtes lié. Vous ne pouvez pas insérer ou réorganiser les membres sans forcer les anciennes valeurs numériques. Par exemple, en modifiant l'énumération Suit en:
public enum Suit { Unknown, Heart, Club, Diamond, Spade }
devrait devenir:
public enum Suit {
Unknown = 4,
Heart = 1,
Club = 3,
Diamond = 2,
Spade = 0 }
afin de conserver les anciennes valeurs numériques stockées dans la base de données.
La question se pose: disons que je voulais ordonner les valeurs. Certaines personnes peuvent vouloir les trier selon la valeur ordinale de l'énum. Bien sûr, classer les cartes en fonction de la valeur numérique de l'énumération n'a pas de sens:
SELECT Suit FROM Cards
ORDER BY SuitID; --where SuitID is integer value(4,1,3,2,0)
Suit
------
Spade
Heart
Diamond
Club
Unknown
Ce n'est pas l'ordre que nous voulons - nous les voulons par ordre de recensement:
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
Le même travail requis si vous enregistrez des valeurs entières est requis si vous enregistrez des chaînes:
SELECT Suit FROM Cards
ORDER BY Suit; --where Suit is an enum name
Suit
-------
Club
Diamond
Heart
Spade
Unknown
Mais ce n'est pas l'ordre que nous voulons - nous les voulons dans l'ordre de l'énumération:
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
Mon opinion est que ce genre de classement appartient à l'interface utilisateur. Si vous triez les éléments en fonction de leur valeur d'énumération: vous faites quelque chose de mal.
Mais si vous vouliez vraiment faire cela, je créerais une table de dimension Suits
:
| Suit | SuitID | Rank | Color |
|------------|--------------|---------------|--------|
| Unknown | 4 | 0 | NULL |
| Heart | 1 | 1 | Red |
| Club | 3 | 2 | Black |
| Diamond | 2 | 3 | Red |
| Spade | 0 | 4 | Black |
De cette façon, lorsque vous souhaitez modifier vos cartes à utiliser Embrasser des rois Nouvel ordre de deck vous pouvez le modifier à des fins d'affichage sans jeter toutes vos données:
| 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 |
Nous séparons maintenant un détail de programmation interne (nom d’énumération, valeur d’énumération) avec un paramètre d’affichage destiné aux utilisateurs:
SELECT Cards.Suit
FROM Cards
INNER JOIN Suits ON Cards.Suit = Suits.Suit
ORDER BY Suits.Rank,
Card.Rank*Suits.CardOrder
À moins que vous ne l'ayez évité pour des raisons de performances spécifiques, nous vous recommandons d'utiliser un tableau séparé pour l'énumération. Utilisez l'intégrité de la clé étrangère à moins que la recherche supplémentaire ne vous tue vraiment.
suit_id suit_name
1 Clubs
2 Hearts
3 Spades
4 Diamonds
player_name suit_id
Ian Boyd 4
Shelby Lake 2
suit_id
) sont indépendants de votre valeur d'énumération, ce qui vous permet également de travailler sur les données d'autres langues.Je dirais que le seul mécanisme sûr consiste à utiliser la valeur String name()
. Lors de l'écriture dans la base de données, vous pourriez utiliser un sproc pour insérer la valeur et, lors de la lecture, utiliser une vue. De cette manière, si les enums changent, il y a un niveau d'indirection dans le sproc/vue pour pouvoir présenter les données en tant que valeur enum sans "imposer" cela au DB.
Comme vous le dites, l'ordinal est un peu risqué. Considérons par exemple:
public enum Boolean {
TRUE, FALSE
}
public class BooleanTest {
@Test
public void testEnum() {
assertEquals(0, Boolean.TRUE.ordinal());
assertEquals(1, Boolean.FALSE.ordinal());
}
}
Si vous avez stocké cela en tant que ordinaux, vous pourriez avoir des lignes comme:
> SELECT STATEMENT, TRUTH FROM CALL_MY_BLUFF
"Alice is a boy" 1
"Graham is a boy" 0
Mais que se passe-t-il si vous mettez à jour Boolean?
public enum Boolean {
TRUE, FILE_NOT_FOUND, FALSE
}
Cela signifie que tous vos mensonges seront mal interprétés en tant que "fichier introuvable"
Mieux vaut simplement utiliser une représentation de chaîne
Pour une base de données volumineuse, je suis réticent à perdre les avantages de la représentation numérique en termes de taille et de rapidité. Je me retrouve souvent avec une table de base de données représentant l'énum.
Vous pouvez imposer la cohérence de la base de données en déclarant une clé étrangère - même si dans certains cas, il peut être préférable de ne pas la déclarer comme contrainte de clé étrangère, ce qui impose un coût pour chaque transaction. Vous pouvez assurer la cohérence en effectuant une vérification périodique, aux moments de votre choix, avec:
SELECT reftable.* FROM reftable
LEFT JOIN enumtable ON reftable.enum_ref_id = enumtable.enum_id
WHERE enumtable.enum_id IS NULL;
L'autre moitié de cette solution consiste à écrire du code de test qui vérifie que Java enum et la table enum de base de données ont le même contenu. Ceci est laissé comme exercice pour le lecteur.
Nous stockons simplement le nom de l'énum lui-même - il est plus lisible.
Nous nous sommes trompés en stockant des valeurs spécifiques pour des énumérations dans lesquelles il existe un ensemble limité de valeurs, par exemple, cette énumération qui possède un ensemble limité de statuts que nous utilisons un caractère pour représenter (plus significatif qu'une valeur numérique):
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;
}
}
}
et lorsque vous avez beaucoup de valeurs, vous devez avoir une carte dans votre enum pour que cette méthode getFromXYZ reste petite.
Si vous enregistrez des énumérations sous forme de chaînes dans la base de données, vous pouvez créer des méthodes utilitaires pour (dé) sérialiser toute énumération:
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);
J'ai rencontré le même problème où mon objectif est de conserver la valeur Enum String dans la base de données au lieu de la valeur Ordinal.
Pour résoudre ce problème, j'ai utilisé @Enumerated(EnumType.STRING)
et mon objectif a été résolu.
Par exemple, vous avez une classe Enum
:
public enum FurthitMethod {
Apple,
Orange,
Lemon
}
Dans la classe d'entité, définissez @Enumerated(EnumType.STRING)
:
@Enumerated(EnumType.STRING)
@Column(name = "Fruits")
public FurthitMethod getFuritMethod() {
return fruitMethod;
}
public void setFruitMethod(FurthitMethod authenticationMethod) {
this.fruitMethod= fruitMethod;
}
Lorsque vous essayez de définir votre valeur sur Base de données, la valeur de Chaîne sera conservée dans Base de données en tant que "Apple
", "ORANGE
" ou "LEMON
".
Toute mon expérience me dit que le moyen le plus sûr de persister enums n'importe où est d'utiliser une valeur de code ou un identifiant supplémentaire (une sorte d'évolution de la réponse @jeebee). Cela pourrait être un bel exemple d’idée:
enum Race {
HUMAN ("human"),
ELF ("elf"),
DWARF ("dwarf");
private final String code;
private Race(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
Maintenant, vous pouvez utiliser n'importe quelle persistance pour référencer vos constantes enum par son code. Même si vous décidez de modifier certains noms de constantes, vous pouvez toujours enregistrer la valeur du code (par exemple, DWARF("dwarf")
à GNOME("dwarf")
).
Ok, plongez un peu plus avec cette conception. Voici une méthode utilitaire, qui vous aide à trouver une valeur enum, mais nous allons d’abord étendre notre approche.
interface CodeValue {
String getCode();
}
Et laissez notre enum l'implémenter:
enum Race implement CodeValue {...}
C'est l'heure de la méthode de recherche magique:
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;
}
Et utilisez-le comme un charme: Race race = resolveByCode(Race.class, "elf")
Valeurs multiples avec OR relation pour un, champ enum. Le concept .NET avec stockage des types enum dans la base de données, comme un octet ou un entier, et utilisation de FlagsAttribute dans votre code.
http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx