Подтвердить что ты не робот

Способы сохранения перечислений в базе данных

Мне интересно, какие наилучшие способы сохранить перечисления в базе данных.

Я знаю, что есть методы name() и valueOf(), чтобы превратить его в строку. Но есть ли другие (гибкие) опции для хранения этих значений?

Есть ли разумный способ сделать их уникальными числами (ordinal() небезопасно использовать)?

Любые комментарии и предложения будут полезны:)

Update:

Спасибо за все замечательные и быстрые ответы! Это было, как я подозревал.

Однако примечание к "toolkit"; Это один из способов. Проблема в том, что мне пришлось бы добавлять те же методы с каждым типом enum, который я создаю. Это много дублированный код, и на данный момент Java не поддерживает никаких решений для этого (вы не можете позволить enum расширять другие классы).

Однако, спасибо за все ответы!

4b9b3361

Ответ 1

Мы больше не храним перечисления как числовые порядковые значения; это затрудняет отладку и поддержку. Мы сохраняем фактическое значение перечисления, преобразованное в строку:

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 }

чтобы сохранить устаревшие числовые значения, хранящиеся в базе данных.

Ответ 2

Если у вас нет особых причин, чтобы избежать этого, я бы рекомендовал использовать отдельную таблицу для перечисления. Используйте целостность внешнего ключа, если только дополнительный поиск не убивает вас.

Таблица костюмов:

suit_id suit_name
1       Clubs
2       Hearts
3       Spades
4       Diamonds

Таблица игроков

player_name suit_id
Ian Boyd           4
Shelby Lake        2
  • Если вы когда-либо реорганизовали свое перечисление как классы с поведением (например, приоритет), ваша база данных уже правильно его моделирует.
  • Ваш администратор базы данных счастлив, потому что ваша схема нормализована (сохраняя одно целое число на игрока, а не целую строку, которая может иметь или не иметь опечатки).
  • Значения вашей базы данных (suit_id) не зависят от вашего значения перечисления, что также помогает вам работать с данными с других языков.

Ответ 3

Я бы сказал, что единственным безопасным механизмом здесь является использование значения String name(). При записи в БД вы можете использовать sproc для вставки значения, а при чтении используйте View. Таким образом, если изменения перечислены, в sproc/view имеется уровень косвенности, чтобы иметь возможность представить данные как значение перечисления без "наложения" этого на БД.

Ответ 4

Как вы говорите, порядковый номер немного рискован. Рассмотрим, например:

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
}

Это означает, что вся ваша ложь станет неверно истолкована как "файл не найден"

Лучше просто использовать строковое представление

Ответ 5

Мы просто сохраняем имя перечисления - это более читаемо.

Мы столкнулись с сохранением определенных значений для перечислений, где имеется ограниченный набор значений, например, это перечисление, которое имеет ограниченный набор статусов, которые мы используем для представления char (более значимого, чем числовое значение)

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;
        }
    }
}

и когда у вас много значений, вам нужно иметь карту внутри вашего перечисления, чтобы этот метод getFromXYZ был небольшим.

Ответ 6

Для большой базы данных я неохотно теряю преимущества цифрового числа и скорости. Я часто получаю таблицу базы данных, представляющую Enum.

Вы можете обеспечить согласованность базы данных, объявив внешний ключ - хотя в некоторых случаях было бы лучше не объявлять это как ограничение внешнего ключа, что накладывает затраты на каждую транзакцию. Вы можете обеспечить согласованность, периодически выполняя проверку, в любое время по вашему выбору, с помощью

SELECT reftable.* FROM reftable
  LEFT JOIN enumtable ON reftable.enum_ref_id = enumtable.enum_id
WHERE enumtable.enum_id IS NULL;

Другая половина этого решения - написать некоторый тестовый код, который проверяет, что перечисление Java и таблица перечисления базы данных имеют одинаковое содержимое. Это оставило упражнение для читателя.

Ответ 7

Если вы сохраняете перечисления как строки в базе данных, вы можете создавать методы утилиты для (де) сериализации любого перечисления:

   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);

Ответ 8

Я столкнулся с той же проблемой, когда моя цель - сохранить значение Enum String в базе данных вместо значения Ordinal.

Чтобы решить эту проблему, я использовал @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;
}

Пока вы пытаетесь установить значение в базу данных, значение String будет сохраняться в базе данных как "APPLE", "ORANGE" или "LEMON".

Ответ 10

Весь мой опыт подсказывает мне, что безопасный способ сохранения перечислений в любом месте - использовать дополнительное значение кода или идентификатор (какая-то эволюция ответа @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 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")