Cách lưu enums trong cơ sở dữ liệu


123

Cách tốt nhất để lưu enums vào cơ sở dữ liệu là gì?

Tôi biết Java cung cấp name()valueOf()các phương thức để chuyển đổi các giá trị enum thành một Chuỗi và ngược lại. Nhưng có bất kỳ tùy chọn (linh hoạt) nào khác để lưu trữ các giá trị này không?

Có cách nào thông minh để biến enum thành các số duy nhất ( ordinal()không an toàn khi sử dụng) không?

Cập nhật:

Cảm ơn vì tất cả các câu trả lời tuyệt vời và nhanh chóng! Nó đã được như tôi nghi ngờ.

Tuy nhiên, một lưu ý đối với 'bộ công cụ'; Đó là một cách. Vấn đề là tôi sẽ phải thêm các phương thức giống nhau vào từng loại Enum mà tôi tạo. Đó là rất nhiều mã trùng lặp và hiện tại, Java không hỗ trợ bất kỳ giải pháp nào cho việc này (một enum Java không thể mở rộng các lớp khác).


2
Tại sao ordinal () không an toàn để sử dụng?
Michael Myers

Loại cơ sở dữ liệu? MySQL có một kiểu enum, nhưng tôi không nghĩ đó là ANSI SQL chuẩn.
Sherm Pendley 23/10/08

6
Bởi vì bất kỳ phép bổ sung liệt kê nào sau đó phải được đặt ở cuối. Dễ dàng cho một nhà phát triển không nghi ngờ mess up và nguyên nhân này tàn phá
oxbow_lakes

1
Tôi hiểu rồi. Đoán rằng đó là một điều tốt khi tôi không xử lý cơ sở dữ liệu nhiều, vì có lẽ tôi sẽ không nghĩ đến điều đó cho đến khi quá muộn.
Michael Myers

Câu trả lời:


165

Chúng tôi không bao giờ lưu trữ các bảng liệt kê dưới dạng giá trị số thứ tự nữa; nó làm cho việc gỡ lỗi và hỗ trợ trở nên quá khó khăn. Chúng tôi lưu trữ giá trị liệt kê thực tế được chuyển đổi thành chuỗi:

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

Suit theSuit = Suit.Heart;

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

và sau đó đọc lại với:

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

Vấn đề trước đây là do Enterprise Manager nhìn chằm chằm và cố gắng giải mã:

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

câu thơ

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

cái sau dễ dàng hơn nhiều. Yêu cầu trước đây là truy cập mã nguồn và tìm các giá trị số đã được gán cho các thành viên điều tra.

Có, nó chiếm nhiều dung lượng hơn, nhưng tên thành viên điều tra ngắn, và ổ cứng rẻ, và nó đáng để giúp đỡ khi bạn gặp sự cố.

Ngoài ra, nếu bạn sử dụng các giá trị số, bạn bị ràng buộc với chúng. Bạn không thể chèn hoặc sắp xếp lại các thành viên một cách độc đáo mà không cần phải ép các giá trị số cũ. Ví dụ: thay đổi kiểu liệt kê Suit thành:

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

sẽ phải trở thành:

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

để duy trì các giá trị số kế thừa được lưu trữ trong cơ sở dữ liệu.

Cách sắp xếp chúng trong cơ sở dữ liệu

Câu hỏi xuất hiện: giả sử tôi muốn sắp xếp các giá trị. Một số người có thể muốn sắp xếp chúng theo giá trị thứ tự của enum. Tất nhiên, việc sắp xếp các thẻ theo giá trị số của phép liệt kê là vô nghĩa:

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

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

Đó không phải là thứ tự chúng tôi muốn - chúng tôi muốn chúng theo thứ tự liệt kê:

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

Công việc tương tự được yêu cầu nếu bạn lưu các giá trị số nguyên nếu bạn lưu các chuỗi:

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

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

Nhưng đó không phải là thứ tự chúng tôi muốn - chúng tôi muốn chúng theo thứ tự liệt kê:

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

Ý kiến ​​của tôi là loại xếp hạng này thuộc về giao diện người dùng. Nếu bạn đang phân loại các mục dựa trên giá trị liệt kê của chúng: bạn đang làm sai.

Nhưng nếu bạn thực sự muốn làm điều đó, tôi sẽ tạo một Suitsbảng thứ nguyên:

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

Bằng cách này, khi bạn muốn thay đổi quân bài của mình để sử dụng Thứ tự Bộ bài Mới Kissing Kings, bạn có thể thay đổi nó cho mục đích hiển thị mà không cần vứt bỏ tất cả dữ liệu của mình:

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

Bây giờ chúng tôi đang tách một chi tiết lập trình nội bộ (tên liệt kê, giá trị liệt kê) với cài đặt hiển thị dành cho người dùng:

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

23
toString thường được ghi đè để cung cấp giá trị hiển thị. name () là một lựa chọn tốt hơn vì nó là theo định nghĩa các đối tác của valueOf ()
ddimitrov

9
Tôi hoàn toàn không đồng ý với điều này, nếu sự kiên trì enum là bắt buộc thì không nên tồn tại những cái tên. Khi đọc ngược lại nó thậm chí còn đơn giản hơn với giá trị thay vì tên có thể chỉ cần đánh máy là SomeEnum enum1 = (SomeEnum) 2;
mamu

3
mamu: Điều gì sẽ xảy ra khi các số tương đương thay đổi?
Ian Boyd

2
Tôi sẽ không khuyến khích bất cứ ai sử dụng phương pháp này. Ràng buộc bản thân vào biểu diễn chuỗi sẽ hạn chế tính linh hoạt và tái cấu trúc của mã. Tốt hơn bạn nên sử dụng id duy nhất. Việc lưu trữ chuỗi cũng gây lãng phí không gian lưu trữ.
Tautvydas

2
@LuisGouveia Tôi đồng ý với bạn rằng thời gian có thể tăng gấp đôi. Gây ra một truy vấn mà 12.37 msthay vào đó phải thực hiện 12.3702 ms. Đó là những gì tôi có nghĩa là "trong tiếng ồn" . Bạn chạy lại truy vấn và phải mất 13.29 ms, hoặc 11.36 ms. Nói cách khác, tính ngẫu nhiên của bộ lập lịch luồng sẽ phá hủy đáng kể bất kỳ tối ưu hóa vi mô nào mà bạn có về mặt lý thuyết mà không ai có thể nhìn thấy được theo bất kỳ cách nào.
Ian Boyd

42

Trừ khi bạn có lý do hiệu suất cụ thể để tránh nó, tôi khuyên bạn nên sử dụng một bảng riêng biệt cho việc liệt kê. Sử dụng tính toàn vẹn của khóa ngoại trừ khi việc tra cứu thêm thực sự giết chết bạn.

Bàn phù hợp:

suit_id suit_name
1       Clubs
2       Hearts
3       Spades
4       Diamonds

Bàn chơi

player_name suit_id
Ian Boyd           4
Shelby Lake        2
  1. Nếu bạn đã từng cấu trúc lại bảng liệt kê của mình thành các lớp có hành vi (chẳng hạn như ưu tiên), thì cơ sở dữ liệu của bạn đã lập mô hình chính xác
  2. DBA của bạn rất vui vì lược đồ của bạn được chuẩn hóa (lưu trữ một số nguyên duy nhất cho mỗi trình phát, thay vì toàn bộ chuỗi, có thể có hoặc không có lỗi chính tả).
  3. Giá trị cơ sở dữ liệu của bạn ( suit_id) độc lập với giá trị liệt kê của bạn, điều này cũng giúp bạn làm việc trên dữ liệu từ các ngôn ngữ khác.

14
Mặc dù tôi đồng ý rằng thật tuyệt khi nó được chuẩn hóa và bị ràng buộc trong DB, nhưng điều này khiến các bản cập nhật ở hai nơi thêm một giá trị mới (mã và db), điều này có thể gây ra nhiều chi phí hơn. Ngoài ra, lỗi chính tả sẽ không tồn tại nếu tất cả các cập nhật được thực hiện theo chương trình từ tên Enum.
Jason

3
Tôi đồng ý với nhận xét trên. Một cơ chế thực thi thay thế ở cấp cơ sở dữ liệu sẽ là viết một trình kích hoạt ràng buộc, cơ chế này sẽ từ chối các chèn hoặc cập nhật cố gắng sử dụng một giá trị không hợp lệ.
Steve Perkins

1
Tại sao tôi muốn khai báo cùng một thông tin ở hai nơi? Cả trong CODE public enum foo {bar}CREATE TABLE foo (name varchar);điều đó có thể dễ dàng bị mất đồng bộ.
ebyrob

Nếu chúng ta lấy câu trả lời được chấp nhận theo mệnh giá, tức là các tên enum chỉ được sử dụng để điều tra thủ công, thì câu trả lời này thực sự là lựa chọn tốt nhất. Ngoài ra, nếu bạn tiếp tục thay đổi thứ tự liệt kê hoặc các giá trị hoặc tên, bạn sẽ luôn gặp nhiều vấn đề hơn là duy trì bảng bổ sung này. Đặc biệt là khi bạn chỉ cần nó (và có thể chỉ tạo tạm thời) để gỡ lỗi và hỗ trợ.
afk5phút

5

Tôi cho rằng cơ chế an toàn duy nhất ở đây là sử dụng name()giá trị Chuỗi . Khi ghi vào DB, bạn có thể sử dụng một cái mầm để chèn giá trị và khi đọc, hãy sử dụng một Dạng xem. Theo cách này, nếu các enum thay đổi, sẽ có một mức độ hướng trong khung nhìn / kiểu mầm để có thể hiển thị dữ liệu dưới dạng giá trị enum mà không "áp đặt" điều này lên DB.


1
Tôi đang sử dụng một cách tiếp cận kết hợp giữa giải pháp của bạn và giải pháp của @Ian Boyd và rất thành công. Cảm ơn vì tiền hỗ trợ!
technomalogical

5

Như bạn nói, thứ tự có một chút rủi ro. Hãy xem xét ví dụ:

public enum Boolean {
    TRUE, FALSE
}

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

Nếu bạn lưu trữ dữ liệu này dưới dạng thứ tự, bạn có thể có các hàng như:

> SELECT STATEMENT, TRUTH FROM CALL_MY_BLUFF

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

Nhưng điều gì sẽ xảy ra nếu bạn cập nhật Boolean?

public enum Boolean {
    TRUE, FILE_NOT_FOUND, FALSE
}

Điều này có nghĩa là tất cả những lời nói dối của bạn sẽ bị hiểu sai thành 'không tìm thấy tệp'

Tốt hơn là chỉ sử dụng biểu diễn chuỗi


4

Đối với một cơ sở dữ liệu lớn, tôi không muốn đánh mất lợi thế về kích thước và tốc độ của biểu diễn số. Tôi thường kết thúc với một bảng cơ sở dữ liệu đại diện cho Enum.

Bạn có thể thực thi tính nhất quán của cơ sở dữ liệu bằng cách khai báo khóa ngoại - mặc dù trong một số trường hợp, tốt hơn là không khai báo đó là ràng buộc khóa ngoại, điều này dẫn đến chi phí cho mọi giao dịch. Bạn có thể đảm bảo tính nhất quán bằng cách kiểm tra định kỳ, vào thời điểm bạn chọn, với:

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

Nửa còn lại của giải pháp này là viết một số mã kiểm tra để kiểm tra xem Java enum và bảng enum cơ sở dữ liệu có cùng nội dung hay không. Điều đó còn lại như một bài tập cho người đọc.


1
Giả sử độ dài trung bình của tên liệt kê là 7 ký tự. Của bạn enumIDlà bốn byte, vì vậy bạn có thêm ba byte mỗi hàng bằng cách sử dụng tên. 3 byte x 1 triệu hàng là 3MB.
Ian Boyd

@IanBoyd: Nhưng enumIdchắc chắn phù hợp với hai byte ( enum dài hơn là không thể trong Java) và hầu hết chúng phù hợp với một byte duy nhất (một số DB hỗ trợ). Không gian tiết kiệm được là không đáng kể, nhưng so sánh nhanh hơn và chiều dài cố định sẽ giúp ích.
maaartinus

3

Chúng tôi chỉ lưu trữ tên enum - nó dễ đọc hơn.

Chúng tôi đã làm lộn xộn với việc lưu trữ các giá trị cụ thể cho các enum trong đó có một tập hợp giá trị giới hạn, ví dụ: enum này có một tập hợp các trạng thái giới hạn mà chúng tôi sử dụng một ký tự để đại diện (có ý nghĩa hơn một giá trị số):

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

và khi bạn có nhiều giá trị, bạn cần có Bản đồ bên trong enum của mình để giữ cho phương thức getFromXYZ đó nhỏ.


Nếu bạn không muốn duy trì câu lệnh switch và có thể đảm bảo rằng dbChar là duy nhất, bạn có thể sử dụng một cái gì đó như: public static EmailStatus getFromStatusChar (char statusChar) {return Arrays.stream (EmailStatus.values ​​()) .filter (e -> e.statusChar () == statusChar) .findFirst () .orElse (UNDEFINED); }
Kuchi

2

Nếu lưu enum dưới dạng chuỗi trong cơ sở dữ liệu, bạn có thể tạo các phương thức tiện ích để (de) tuần tự hóa bất kỳ enum nào:

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

Rất vui khi sử dụng nó với một giá trị enum mặc định để bật lại trong deserialize. Ví dụ: bắt IllegalArgEx và trả về Suit.None.
Jason

2

Tất cả kinh nghiệm của tôi cho tôi biết rằng cách an toàn nhất để tồn tại enums ở bất cứ đâu là sử dụng giá trị hoặc id mã bổ sung (một số kiểu phát triển của câu trả lời @jeebee). Đây có thể là một ví dụ hay về một ý tưởng:

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

    private final String code;

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

    public String getCode() {
        return code;
    }
}

Bây giờ bạn có thể sử dụng bất kỳ sự kiên trì nào tham chiếu đến hằng số enum của bạn bằng mã của nó. Ngay cả khi bạn quyết định thay đổi một số tên không đổi, bạn luôn có thể lưu giá trị mã (ví dụ: DWARF("dwarf")thành GNOME("dwarf"))

Được rồi, hãy tìm hiểu sâu hơn về quan niệm này. Đây là một số phương pháp tiện ích, giúp bạn tìm thấy bất kỳ giá trị enum nào, nhưng trước tiên hãy mở rộng cách tiếp cận của chúng tôi.

interface CodeValue {
    String getCode();
}

Và hãy để enum của chúng tôi thực hiện nó:

enum Race implement CodeValue {...}

Đây là lúc cho phương pháp tìm kiếm kỳ diệu:

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

Và sử dụng nó như một sự quyến rũ: Race race = resolveByCode(Race.class, "elf")


2

Tôi đã gặp phải vấn đề tương tự, trong đó mục tiêu của tôi là duy trì giá trị Enum String vào cơ sở dữ liệu thay vì giá trị Ordinal.

Để giải quyết vấn đề này, tôi đã sử dụng @Enumerated(EnumType.STRING) và mục tiêu của tôi đã được giải quyết.

Ví dụ, bạn có một EnumLớp:

public enum FurthitMethod {

    Apple,
    Orange,
    Lemon
}

Trong lớp thực thể, hãy xác định @Enumerated(EnumType.STRING):

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

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

Trong khi bạn cố gắng đặt giá trị của mình thành Cơ sở dữ liệu, giá trị Chuỗi sẽ được duy trì trong Cơ sở dữ liệu dưới dạng " APPLE", " ORANGE" hoặc " LEMON".



0

Bạn có thể sử dụng một giá trị bổ sung trong hằng số enum có thể tồn tại cả khi thay đổi tên và sử dụng các enum:

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

Để lấy id từ enum:

int id = MyFirstValue.getId();

Để lấy enum từ một id:

MyEnum e = MyEnum.of(id);

Tôi đề nghị sử dụng các giá trị không có nghĩa để tránh nhầm lẫn nếu tên enum phải được thay đổi.

Trong ví dụ trên, tôi đã sử dụng một số biến thể của "Đánh số hàng cơ bản" để lại dấu cách để các số có thể sẽ giữ nguyên thứ tự như các ô.

Phiên bản này nhanh hơn so với việc sử dụng bảng phụ, nhưng nó khiến hệ thống phụ thuộc nhiều hơn vào mã và kiến ​​thức mã nguồn.

Để khắc phục điều đó, bạn cũng có thể thiết lập một bảng với các id enum trong cơ sở dữ liệu. Hoặc đi theo cách khác và chọn id cho enums từ một bảng khi bạn thêm hàng vào nó.

Sidenote : Luôn xác minh rằng bạn không thiết kế thứ gì đó nên được lưu trữ trong bảng cơ sở dữ liệu và được duy trì như một đối tượng thông thường. Nếu bạn có thể tưởng tượng rằng bạn phải thêm hằng số mới vào enum tại thời điểm này, khi bạn đang thiết lập nó, đó là một dấu hiệu bạn có thể tốt hơn nên tạo một đối tượng thông thường và một bảng thay thế.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.