Numaralandırmaları veritabanına kaydetmenin yolları


123

Numaralandırmaları bir veritabanına kaydetmenin en iyi yolu nedir?

Java sağlar biliyorum name()ve valueOf()bir String ve arkasına enum değerlerini dönüştürmek için yöntemler. Ancak bu değerleri saklamak için başka (esnek) seçenekler var mı?

Numaralandırmaları benzersiz sayılara dönüştürmenin akıllı bir yolu var mı ( ordinal()kullanımı güvenli değil)?

Güncelleme:

Tüm harika ve hızlı cevaplar için teşekkürler! Tahmin ettiğim gibiydi.

Ancak 'araç setine' bir not; Bu bir yol. Sorun, oluşturduğum her Enum türüne aynı yöntemleri eklemem gerekmesidir. Bu, çok sayıda yinelenen koddur ve şu anda Java bunun için herhangi bir çözümü desteklememektedir (Java enum diğer sınıfları genişletemez).


2
Ordinal () neden güvenli değil?
Michael Myers

Ne tür bir veritabanı? MySQL'in bir enum türü vardır, ancak bunun standart ANSI SQL olduğunu düşünmüyorum.
Sherm Pendley

6
Çünkü herhangi bir numaralayıcı eklemenin sonuna konması gerekir.
Şüphelenmeyen

1
Anlıyorum. Veritabanlarıyla fazla ilgilenmemem iyi bir şey çünkü muhtemelen çok geç olana kadar bunu düşünmezdim.
Michael Myers

Yanıtlar:


165

Biz asla artık sayısal sıralı değerler olarak numaralandırma saklamak; hata ayıklamayı ve desteği çok zor hale getirir. Dizeye dönüştürülen gerçek numaralandırma değerini saklarız:

public enum Suit { Spade, Heart, Diamond, Club }

Suit theSuit = Suit.Heart;

szQuery = "INSERT INTO Customers (Name, Suit) " +
          "VALUES ('Ian Boyd', %s)".format(theSuit.name());

ve sonra şununla tekrar okuyun:

Suit theSuit = Suit.valueOf(reader["Suit"]);

Sorun geçmişte Enterprise Manager'a bakıp deşifre etmeye çalışmaktı:

Name                Suit
==================  ==========
Shelby Jackson      2
Ian Boyd            1

ayetler

Name                Suit
==================  ==========
Shelby Jackson      Diamond
Ian Boyd            Heart

ikincisi çok daha kolay. İlki, kaynak koduna girmeyi ve numaralandırma üyelerine atanan sayısal değerleri bulmayı gerektiriyordu.

Evet, daha fazla yer kaplıyor, ancak numaralandırma üye adları kısadır ve sabit diskler ucuzdur ve bir sorun yaşadığınızda yardım etmeye değer.

Ek olarak, sayısal değerler kullanırsanız, bunlara bağlanırsınız. Eski sayısal değerleri zorlamak zorunda kalmadan üyeleri güzel bir şekilde ekleyemez veya yeniden düzenleyemezsiniz. Örneğin, Suit numaralandırmasını şu şekilde değiştirmek:

public enum Suit { Unknown, Heart, Club, Diamond, Spade }

olması gerekirdi:

public enum Suit { 
      Unknown = 4,
      Heart = 1,
      Club = 3,
      Diamond = 2,
      Spade = 0 }

veritabanında depolanan eski sayısal değerleri korumak için.

Bunları veritabanında nasıl sıralayabilirim?

Soru ortaya çıkıyor: Diyelim ki değerleri sipariş etmek istedim. Bazı insanlar bunları numaralamanın sıra değerine göre sıralamak isteyebilir. Elbette, kartları numaralandırmanın sayısal değerine göre sıralamak anlamsızdır:

SELECT Suit FROM Cards
ORDER BY SuitID; --where SuitID is integer value(4,1,3,2,0)

Suit
------
Spade
Heart
Diamond
Club
Unknown

İstediğimiz sıra bu değil - onları numaralandırma sırasına göre istiyoruz:

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

Tamsayı değerlerini kaydederseniz gerekli olan işin aynısı, dizeleri kaydederseniz de gereklidir:

SELECT Suit FROM Cards
ORDER BY Suit; --where Suit is an enum name

Suit
-------
Club
Diamond
Heart
Spade
Unknown

Ama istediğimiz sıra bu değil - onları numaralandırma sırasına göre istiyoruz:

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

Bence bu tür bir sıralama kullanıcı arayüzüne aittir. Öğeleri numaralandırma değerlerine göre sıralıyorsanız: yanlış bir şey yapıyorsunuz demektir.

Ama bunu gerçekten yapmak istiyorsanız, bir Suitsboyut tablosu oluşturardım :

| Suit       | SuitID       | Rank          | Color  |
|------------|--------------|---------------|--------|
| Unknown    | 4            | 0             | NULL   |
| Heart      | 1            | 1             | Red    |
| Club       | 3            | 2             | Black  |
| Diamond    | 2            | 3             | Red    |
| Spade      | 0            | 4             | Black  |

Bu şekilde, Kissing Kings Yeni Deste Sırasını kullanmak için kartlarınızı değiştirmek istediğinizde, tüm verilerinizi atmadan görüntüleme amaçlı olarak değiştirebilirsiniz:

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

Şimdi dahili bir programlama ayrıntısını (numaralandırma adı, numaralandırma değeri) kullanıcılar için tasarlanmış bir görüntüleme ayarıyla ayırıyoruz:

SELECT Cards.Suit 
FROM Cards
   INNER JOIN Suits ON Cards.Suit = Suits.Suit
ORDER BY Suits.Rank, 
   Card.Rank*Suits.CardOrder

23
toString genellikle görüntü değeri sağlamak için geçersiz kılınır. name (), tanım gereği valueOf ()
ifadesinin

9
Buna kesinlikle katılmıyorum, eğer enum kalıcılığı gerekiyorsa isimler kalıcı olmamalıdır. geriye okunduğunda, isim yerine değer ile daha da basittir, sadece SomeEnum enum1 = (SomeEnum) 2;
mamu

3
mamu: Sayısal eşdeğerler değiştiğinde ne olur?
Ian Boyd 04

2
Bu yaklaşımı kullanan herkesi caydırırdım. Kendinizi dizgi temsiline bağlamak kod esnekliğini ve yeniden düzenlemeyi sınırlar. Benzersiz kimlikler kullanmalısınız. Ayrıca dizeleri depolamak, depolama alanını boşa harcar.
Tautvydas

2
@LuisGouveia Zamanın ikiye katlanabileceği konusunda sana katılıyorum. 12.37 msBunun yerine alacak bir sorguya neden olmak 12.3702 ms. "Gürültü içinde" ile bunu kastediyorum . Sorguyu tekrar çalıştırırsanız 13.29 ms, veya alır 11.36 ms. Başka bir deyişle, iş parçacığı zamanlayıcısının rastgeleliği, teorik olarak sahip olduğunuz ve hiç kimse tarafından hiçbir şekilde görülemeyen herhangi bir mikro optimizasyonu büyük ölçüde batıracaktır.
Ian Boyd

42

Bundan kaçınmak için belirli performans nedenleriniz olmadıkça, numaralandırma için ayrı bir tablo kullanmanızı tavsiye ederim. Ekstra arama sizi gerçekten öldürmedikçe yabancı anahtar bütünlüğünü kullanın.

Takım elbise tablosu:

suit_id suit_name
1       Clubs
2       Hearts
3       Spades
4       Diamonds

Oyuncular tablosu

player_name suit_id
Ian Boyd           4
Shelby Lake        2
  1. Numaralandırmanızı davranışa sahip sınıflar (öncelik gibi) olacak şekilde yeniden düzenlerseniz, veritabanınız zaten doğru şekilde modelliyor
  2. DBA'nız mutludur çünkü şemanız normalleştirilmiştir (yazım hataları olan veya olmayan tüm bir dize yerine oyuncu başına tek bir tamsayı depolar).
  3. Veritabanı değerleriniz ( suit_id), numaralandırma değerinizden bağımsızdır, bu da diğer dillerdeki veriler üzerinde çalışmanıza yardımcı olur.

14
Normalize edilmesinin ve DB'de kısıtlanmasının güzel olduğunu kabul etsem de, bu, iki yerde güncellemelerin yeni bir değer (kod ve db) eklemesine neden oluyor ve bu da daha fazla ek yüke neden olabilir. Ayrıca, tüm güncellemeler Programlı olarak Enum adından yapılırsa yazım hataları olmamalıdır.
Jason

3
Yukarıdaki yoruma katılıyorum. Veritabanı düzeyinde alternatif bir uygulama mekanizması, geçersiz bir değer kullanmaya çalışan ekleme veya güncellemeleri reddeden bir kısıtlama tetikleyicisi yazmak olabilir.
Steve Perkins

1
Neden aynı bilgiyi iki yerde beyan etmek isteyeyim? Hem CODE'de public enum foo {bar}hem CREATE TABLE foo (name varchar);de bu kolayca senkronize edilmeyebilir.
ebyrob

Kabul edilen yanıtı yüz değerinde alırsak, yani sıralama adları yalnızca manuel araştırmalar için kullanılırsa, o zaman bu yanıt gerçekten en iyi seçenektir. Ayrıca, numaralandırma sırasını veya değerleri veya adları değiştirmeye devam ederseniz, her zaman bu ekstra tabloyu sürdürmekten çok daha fazla sorun yaşarsınız. Özellikle, hata ayıklama ve destek için yalnızca ihtiyacınız olduğunda (ve yalnızca geçici olarak oluşturmayı seçebildiğinizde).
afk5min

5

Buradaki tek güvenli mekanizmanın String name()değerini kullanmak olduğunu iddia ediyorum . DB yazarken, sen olabilir değerini eklemek için bir sproc kullanıp okurken, bir Görünüm kullanın. Bu şekilde, numaralandırmalar değişirse, verileri DB'ye "empoze etmeden" verileri sıralama değeri olarak sunabilmek için sproc / view'da bir dolaylılık seviyesi vardır.


1
Çözümünüzün hibrit bir yaklaşımını ve @ Ian Boyd'un çözümünü büyük bir başarıyla kullanıyorum. Bahşiş için teşekkürler!
technomalogical

5

Dediğiniz gibi, ordinal biraz riskli. Örneğin düşünün:

public enum Boolean {
    TRUE, FALSE
}

public class BooleanTest {
    @Test
    public void testEnum() {
        assertEquals(0, Boolean.TRUE.ordinal());
        assertEquals(1, Boolean.FALSE.ordinal());
    }
}

Bunu sıra sayıları olarak kaydettiyseniz, aşağıdaki gibi satırlarınız olabilir:

> SELECT STATEMENT, TRUTH FROM CALL_MY_BLUFF

"Alice is a boy"      1
"Graham is a boy"     0

Peki Boolean'ı güncellediyseniz ne olur?

public enum Boolean {
    TRUE, FILE_NOT_FOUND, FALSE
}

Bu, tüm yalanlarınızın 'dosya bulunamadı' olarak yanlış yorumlanacağı anlamına gelir.

Sadece bir dize gösterimi kullanmak daha iyidir


4

Büyük bir veritabanı için sayısal gösterimin boyut ve hız avantajlarını kaybetme konusunda isteksizim. Sıklıkla Enum'u temsil eden bir veritabanı tablosu elde ederim.

Bir yabancı anahtar bildirerek veritabanı tutarlılığını zorunlu kılabilirsiniz - ancak bazı durumlarda bunu her işlem için bir maliyet oluşturan bir yabancı anahtar kısıtlaması olarak belirtmemek daha iyi olabilir. Aşağıdakilerle seçtiğiniz zamanlarda periyodik olarak bir kontrol yaparak tutarlılığı sağlayabilirsiniz:

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

Bu çözümün diğer yarısı, Java enum ve veritabanı enum tablosunun aynı içeriğe sahip olup olmadığını kontrol eden bazı test kodları yazmaktır. Bu, okuyucu için bir alıştırma olarak kaldı.


1
Ortalama numaralandırma adı uzunluğunun 7 karakter olduğunu varsayalım. Sizin enumIDdört bayttır, bu nedenle adları kullanarak satır başına fazladan üç baytınız olur. 3 bayt x 1 milyon satır 3MB'dir.
Ian Boyd

@IanBoyd: Ama enumIdkesinlikle iki bayta sığar (Java'da daha uzun numaralandırmalar mümkün değildir) ve çoğu tek bir bayta (bazı DB'yi destekler) sığar. Tasarruf edilen alan önemsizdir, ancak daha hızlı karşılaştırma ve sabit uzunluk yardımcı olacaktır.
maaartinus

3

Sadece enum adının kendisini saklıyoruz - daha okunabilir.

Sınırlı bir değer kümesinin olduğu numaralandırmalar için belirli değerleri depolamakla uğraştık, örneğin, temsil etmek için bir karakter kullandığımız sınırlı bir durum kümesine sahip bu enum (sayısal bir değerden daha anlamlı):

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

ve çok fazla değeriniz olduğunda, getFromXYZ yöntemini küçük tutmak için numaranızın içinde bir Harita olması gerekir.


Bir switch ifadesini korumak istemiyorsanız ve dbChar'ın benzersiz olmasını sağlayabiliyorsanız, şöyle bir şey kullanabilirsiniz: public static EmailStatus getFromStatusChar (char statusChar) {return Arrays.stream (EmailStatus.values ​​()) .filter (e -> e.statusChar () == statusChar) .findFirst () .orElse (UNDEFINED); }
Kuchi

2

Numaralandırmaları veritabanında dizeler olarak kaydediyorsanız, herhangi bir numaralandırmayı (de) serileştirmek için yardımcı program yöntemleri oluşturabilirsiniz:

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

Bunu, seriyi kaldırmada geri dönmek için varsayılan bir enum değeriyle kullanmak güzel. Örneğin, IllegalArgEx'i yakalayın ve Suit.None dönün.
Jason

2

Tüm deneyimlerim bana numaralandırmaları herhangi bir yerde sürdürmenin en güvenli yolunun ek kod değeri veya kimliği kullanmak olduğunu söylüyor (@jeebee yanıtının bir tür evrimi). Bu, bir fikrin güzel bir örneği olabilir:

enum Race {
    HUMAN ("human"),
    ELF ("elf"),
    DWARF ("dwarf");

    private final String code;

    private Race(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

Artık, enum sabitlerinizi koduna göre referans alan herhangi bir kalıcılıkla gidebilirsiniz. Eğer sabit isimlerden bazılarını değiştirmeye karar edeceğiz bile, her zaman kod değerini kaydedebilirsiniz (örn DWARF("dwarf")için GNOME("dwarf"))

Tamam, bu anlayışla biraz daha derine dalın. Burada, herhangi bir enum değerini bulmanıza yardımcı olan bazı yardımcı yöntemlerdir, ancak önce yaklaşımımızı genişletelim.

interface CodeValue {
    String getCode();
}

Ve numaramızın bunu uygulamasına izin verin:

enum Race implement CodeValue {...}

Bu sihirli arama yönteminin zamanıdır:

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

Ve onu bir tılsım gibi kullanın: Race race = resolveByCode(Race.class, "elf")


2

Amacımın Sıralı değer yerine Enum String değerini veritabanında kalıcı hale getirdiği aynı sorunla karşılaştım.

Bu konuyu aşmak için kullandım @Enumerated(EnumType.STRING)ve amacım çözüldü.

Örneğin, bir EnumSınıfınız var:

public enum FurthitMethod {

    Apple,
    Orange,
    Lemon
}

Varlık sınıfında @Enumerated(EnumType.STRING)şunları tanımlayın :

@Enumerated(EnumType.STRING)
@Column(name = "Fruits")
public FurthitMethod getFuritMethod() {
    return fruitMethod;
}

public void setFruitMethod(FurthitMethod authenticationMethod) {
    this.fruitMethod= fruitMethod;
}

Değerinizi Veritabanı olarak ayarlamaya çalışırken, Dize değeri Veritabanında " APPLE", " ORANGE" veya " LEMON" olarak kalacaktır .



0

Numaralandırma sabitinde, hem ad değişiklikleri hem de numaralandırmalara başvurulmasına dayanabilecek ekstra bir değer kullanabilirsiniz:

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

Numaralandırmadan kimliği almak için:

int id = MyFirstValue.getId();

Numarayı bir kimlikten almak için:

MyEnum e = MyEnum.of(id);

Enum adlarının değiştirilmesi gerekiyorsa, karışıklığı önlemek için anlamsız değerleri kullanmanızı öneririm.

Yukarıdaki örnekte, "Temel satır numaralandırması" nın bazı varyantlarını boşluk bırakarak kullandım, böylece sayılar muhtemelen numaralandırmalarla aynı sırada kalacak.

Bu sürüm ikincil bir tablo kullanmaktan daha hızlıdır, ancak sistemi koda ve kaynak kod bilgisine daha bağımlı hale getirir.

Bunu düzeltmek için, veritabanındaki enum kimlikleriyle bir tablo da oluşturabilirsiniz. Veya diğer tarafa gidin ve tabloya satır eklerken tablodan numaralandırmalar için kimlikler seçin.

Sidenote : Her zaman olsa düzenli bir nesne olarak bir veritabanı tablosunda saklanabilir ve muhafaza edilmelidir şey tasarımı değil emin olun. Bu noktada numaralandırmaya yeni sabitler eklemeniz gerektiğini hayal edebiliyorsanız, bunu kurarken, bu, bunun yerine normal bir nesne ve bir tablo oluşturmanın daha iyi olabileceğinin bir göstergesidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.