Способы сохранения перечислений в базе данных
Каков наилучший способ сохранить перечисления в базе данных?
Я знаю, что Java обеспечивает name()
а также valueOf()
методы для преобразования значений перечисления в строку и обратно. Но есть ли другие (гибкие) варианты для хранения этих значений?
Есть ли умный способ сделать перечисления в уникальные числа (ordinal()
не безопасно использовать)?
Обновить:
Спасибо за все классные и быстрые ответы! Это было, как я подозревал.
Однако примечание к "инструментарию"; Это один из способов. Проблема в том, что мне придется добавлять одинаковые методы к каждому типу Enum, который я создаю. Это много дублированного кода, и на данный момент Java не поддерживает никаких решений для этого (перечисление Java не может расширять другие классы).
10 ответов
Мы больше не храним перечисления как числовые порядковые значения; это делает отладку и поддержку слишком сложной. Мы сохраняем фактическое значение перечисления, преобразованное в строку:
public enum Suit { Spade, Heart, Diamond, Club }
Suit theSuit = Suit.Heart;
szQuery = "INSERT INTO Customers (Name, Suit) " +
"VALUES ('Ian Boyd', %s)".format(theSuit.name());
и затем прочитайте обратно с:
Suit theSuit = Suit.valueOf(reader["Suit"]);
В прошлом проблема была в том, чтобы посмотреть на Enterprise Manager и попытаться расшифровать:
Name Suit
================== ==========
Shelby Jackson 2
Ian Boyd 1
вирши
Name Suit
================== ==========
Shelby Jackson Diamond
Ian Boyd Heart
последнее намного проще. Первый требовал получить исходный код и найти числовые значения, которые были назначены элементам перечисления.
Да, это занимает больше места, но имена членов перечисления короткие, а жесткие диски дешевы, и гораздо важнее помочь, если у вас возникнут проблемы.
Кроме того, если вы используете числовые значения, вы привязаны к ним. Вы не можете красиво вставить или переставить элементы, не форсируя старые числовые значения. Например, изменив перечисление Suit на:
public enum Suit { Unknown, Heart, Club, Diamond, Spade }
должен был бы стать:
public enum Suit {
Unknown = 4,
Heart = 1,
Club = 3,
Diamond = 2,
Spade = 0 }
чтобы поддерживать устаревшие числовые значения, хранящиеся в базе данных.
Как отсортировать их в базе данных
Возникает вопрос: допустим, я хотел заказать значения. Некоторые люди могут захотеть отсортировать их по порядковому значению перечисления. Конечно, упорядочивать карточки по числовому значению перечисления бессмысленно:
SELECT Suit FROM Cards
ORDER BY SuitID; --where SuitID is integer value(4,1,3,2,0)
Suit
------
Spade
Heart
Diamond
Club
Unknown
Это не тот порядок, который мы хотим - мы хотим, чтобы они были в порядке перечисления:
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
Та же самая работа, которая требуется, если вы сохраняете целочисленные значения, требуется, если вы сохраняете строки:
SELECT Suit FROM Cards
ORDER BY Suit; --where Suit is an enum name
Suit
-------
Club
Diamond
Heart
Spade
Unknown
Но это не тот порядок, который мы хотим - мы хотим, чтобы они были в порядке перечисления:
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
Мое мнение, что этот вид рейтинга принадлежит пользовательскому интерфейсу. Если вы сортируете элементы по значению перечисления: вы делаете что-то не так.
Но если бы вы действительно хотели это сделать, я бы создал 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 |
Таким образом, когда вы захотите поменять свои карты, чтобы использовать новый порядокколод Kissing Kings, вы можете изменить его для отображения, не выбрасывая все свои данные:
| 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 |
Теперь мы разделяем внутреннюю деталь программирования (имя перечисления, значение перечисления) с настройкой отображения, предназначенной для пользователей:
SELECT Cards.Suit
FROM Cards
INNER JOIN Suits ON Cards.Suit = Suits.Suit
ORDER BY Suits.Rank,
Card.Rank*Suits.CardOrder
Если у вас нет особых причин для повышения производительности, я бы рекомендовал использовать для перечисления отдельную таблицу. Используйте целостность внешнего ключа, если дополнительный поиск действительно не убивает вас.
Костюмы столовые:
suit_id suit_name
1 Clubs
2 Hearts
3 Spades
4 Diamonds
Стол игроков
player_name suit_id
Ian Boyd 4
Shelby Lake 2
- Если вы когда-либо реорганизовали свое перечисление в классы с поведением (например, приоритетом), ваша база данных уже смоделирует его правильно
- Ваш администратор базы данных счастлив, потому что ваша схема нормализована (хранение одного целого числа на игрока, а не всей строки, которая может иметь или не иметь опечатки).
- Ваши значения базы данных (
suit_id
) не зависят от значения перечисления, что также помогает вам работать с данными на других языках.
Я столкнулся с той же проблемой, когда моя цель - сохранить значение Enum String в базе данных вместо обычного значения.
Чтобы преодолеть эту проблему, я использовал @Enumerated(EnumType.STRING)
и моя цель была решена.
Например, у вас есть Enum
Учебный класс:
public enum FurthitMethod {
Apple,
Orange,
Lemon
}
В классе сущности определите @Enumerated(EnumType.STRING)
:
@Enumerated(EnumType.STRING)
@Column(name = "Fruits")
public FurthitMethod getFuritMethod() {
return fruitMethod;
}
public void setFruitMethod(FurthitMethod authenticationMethod) {
this.fruitMethod= fruitMethod;
}
Пока вы пытаетесь установить значение базы данных, строковое значение будет сохранено в базе данных как "APPLE
","ORANGE
" или же "LEMON
".
Как вы говорите, порядковый номер немного рискован. Рассмотрим для примера:
public enum Boolean {
TRUE, FALSE
}
public class BooleanTest {
@Test
public void testEnum() {
assertEquals(0, Boolean.TRUE.ordinal());
assertEquals(1, Boolean.FALSE.ordinal());
}
}
Если вы сохранили это как порядковые номера, у вас могут быть строки вроде:
> SELECT STATEMENT, TRUTH FROM CALL_MY_BLUFF
"Alice is a boy" 1
"Graham is a boy" 0
Но что произойдет, если вы обновите Boolean?
public enum Boolean {
TRUE, FILE_NOT_FOUND, FALSE
}
Это означает, что вся ваша ложь будет неверно истолкована как "файл не найден"
Лучше просто использовать строковое представление
Я бы сказал, что единственным безопасным механизмом здесь является использование строки name()
значение. При записи в БД вы можете использовать sproc для вставки значения, а при чтении использовать View. Таким образом, если перечисления изменяются, в sproc/view есть уровень косвенности, чтобы можно было представить данные в виде значения перечисления, не "накладывая" это на DB.
Для большой базы данных я не хочу терять преимущества размера и скорости числового представления. Я часто заканчиваю с таблицей базы данных, представляющей Enum.
Вы можете обеспечить согласованность базы данных, объявив внешний ключ - хотя в некоторых случаях может быть лучше не объявлять это как ограничение внешнего ключа, что накладывает расходы на каждую транзакцию. Вы можете обеспечить согласованность, периодически делая проверки, по вашему выбору, с помощью:
SELECT reftable.* FROM reftable
LEFT JOIN enumtable ON reftable.enum_ref_id = enumtable.enum_id
WHERE enumtable.enum_id IS NULL;
Другая половина этого решения заключается в написании некоторого тестового кода, который проверяет, что перечисление Java и таблица перечисления базы данных имеют одинаковое содержимое. Это оставлено в качестве упражнения для читателя.
Мы просто храним само имя enum - оно более читаемо.
Мы возились с сохранением определенных значений для перечислений, где существует ограниченный набор значений, например, это перечисление, которое имеет ограниченный набор состояний, которые мы используем для представления символа (более значимого, чем числовое значение):
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;
}
}
}
и когда у вас много значений, вам нужно иметь Map внутри вашего перечисления, чтобы этот метод getFromXYZ был маленьким.
Весь мой опыт подсказывает мне, что самый безопасный способ сохранения перечислений в любом месте - это использовать дополнительное значение кода или id (своего рода эволюция ответа @jeebee). Это может быть хорошим примером идеи:
enum Race {
HUMAN ("human"),
ELF ("elf"),
DWARF ("dwarf");
private final String code;
private Race(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
Теперь вы можете с любой настойчивостью ссылаться на ваши константы перечисления по его коду. Даже если вы решите изменить некоторые имена констант, вы всегда можете сохранить значение кода (например, DWARF("dwarf")
в GNOME("dwarf")
)
Хорошо, нырни еще глубже с этой концепцией. Вот некоторый служебный метод, который помогает вам найти любое значение перечисления, но сначала давайте расширим наш подход.
interface CodeValue {
String getCode();
}
И пусть наш enum реализует это:
enum Race implement CodeValue {...}
Это время для волшебного метода поиска:
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;
}
И используйте это как очарование: Race race = resolveByCode(Race.class, "elf")
Если вы сохраняете перечисления в виде строк в базе данных, вы можете создать служебные методы для (де) сериализации любого перечисления:
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);
Несколько значений с отношением ИЛИ для одного, перечисляемого поля. Концепция для.NET с хранением перечислимых типов в базе данных, таких как байты или целые числа, и использованием FlagsAttribute в вашем коде.
http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx
Вы можете использовать дополнительное значение в константе перечисления, которое может выжить как при изменении имени, так и при использовании перечислений:
public enum MyEnum {
MyFirstValue(10),
MyFirstAndAHalfValue(15),
MySecondValue(20);
public int getId() {
return id;
}
public static MyEnum of(int id) {
for (MyEnum e : values()) {
if (id == e.id) {
return e;
}
}
return null;
}
MyEnum(int id) {
this.id = id;
}
private final int id;
}
Чтобы получить идентификатор из перечисления:
int id = MyFirstValue.getId();
Чтобы получить перечисление из идентификатора:
MyEnum e = MyEnum.of(id);
Я предлагаю использовать значения без смысла, чтобы избежать путаницы, если имена перечислений необходимо изменить.
В приведенном выше примере я использовал вариант "базовой нумерации строк", оставляя пробелы, поэтому числа, скорее всего, останутся в том же порядке, что и перечисления.
Эта версия быстрее, чем использование вторичной таблицы, но делает систему более зависимой от кода и знания исходного кода.
Чтобы исправить это, вы также можете настроить таблицу с идентификаторами перечислений в базе данных. Или пойти другим путем и выбрать идентификаторы перечислений из таблицы по мере добавления в нее строк.
Примечание: всегда проверяйте, что вы не разрабатываете что-то, что следует хранить в таблице базы данных и поддерживать как обычный объект. Если вы можете представить, что вам нужно добавить новые константы в перечисление на этом этапе, когда вы его настраиваете, это признак того, что вам может быть лучше вместо этого создать обычный объект и таблицу.