Jumlah yang Berterusan dalam JPA

Java Teratas

Saya baru sahaja mengumumkan kursus Learn Spring yang baru , yang berfokus pada asas-asas Spring 5 dan Spring Boot 2:

>> SEMAK KURSUS

1. Pengenalan

Dalam JPA versi 2.0 dan ke bawah, tidak ada cara yang mudah untuk memetakan nilai Enum ke lajur pangkalan data. Setiap pilihan mempunyai batasan dan kekurangannya. Masalah-masalah ini dapat dielakkan dengan menggunakan JPA 2.1. ciri-ciri.

Dalam tutorial ini, kita akan melihat pelbagai kemungkinan yang kita miliki untuk mengekalkan jumlah dalam pangkalan data menggunakan JPA. Kami juga akan menerangkan kelebihan dan kekurangan mereka serta memberikan contoh kod mudah.

2. Menggunakan Anotasi @Enumerated

Pilihan yang paling biasa untuk memetakan nilai enum ke dan dari perwakilan pangkalan data di JPA sebelum 2.1. adalah menggunakan anotasi @Enumerated . Dengan cara ini, kita dapat mengarahkan penyedia JPA untuk menukar enum ke nilai ordinal atau Stringnya .

Kami akan meneroka kedua-dua pilihan di bahagian ini.

Tetapi pertama, mari buat @Entity mudah yang akan kami gunakan sepanjang tutorial ini:

@Entity public class Article { @Id private int id; private String title; // standard constructors, getters and setters }

2.1. Pemetaan Nilai Biasa

Sekiranya kita meletakkan anotasi @Enumerated (EnumType.ORDINAL) di medan enum, JPA akan menggunakan nilai Enum.ordinal () ketika meneruskan entiti tertentu dalam pangkalan data.

Mari memperkenalkan enum pertama:

public enum Status { OPEN, REVIEW, APPROVED, REJECTED; }

Seterusnya, mari kita tambahkan ke kelas Artikel dan beri keterangan dengan @Enumerated (EnumType.ORDINAL) :

@Entity public class Article { @Id private int id; private String title; @Enumerated(EnumType.ORDINAL) private Status status; }

Sekarang, semasa meneruskan entiti Artikel :

Article article = new Article(); article.setId(1); article.setTitle("ordinal title"); article.setStatus(Status.OPEN); 

JPA akan mencetuskan pernyataan SQL berikut:

insert into Article (status, title, id) values (?, ?, ?) binding parameter [1] as [INTEGER] - [0] binding parameter [2] as [VARCHAR] - [ordinal title] binding parameter [3] as [INTEGER] - [1]

Masalah dengan pemetaan seperti ini timbul apabila kita perlu mengubah suai enum kita. Sekiranya kita menambah nilai baru di tengah atau menyusun semula pesanan enum, kita akan memecahkan model data yang ada .

Masalah seperti ini mungkin sukar untuk ditangani, dan juga bermasalah untuk diperbaiki, kerana kita harus mengemas kini semua catatan pangkalan data.

2.2. Pemetaan Nilai Rentetan

Secara analogi, JPA akan menggunakan nilai Enum.name () ketika menyimpan entiti jika kita memberi anotasi medan enum dengan @Enumerated (EnumType.STRING) .

Mari buat enum kedua:

public enum Type { INTERNAL, EXTERNAL; }

Dan mari kita tambahkan ke kelas Artikel kami dan beri catatan dengan @Enumerated (EnumType.STRING) :

@Entity public class Article { @Id private int id; private String title; @Enumerated(EnumType.ORDINAL) private Status status; @Enumerated(EnumType.STRING) private Type type; }

Sekarang, semasa meneruskan entiti Artikel :

Article article = new Article(); article.setId(2); article.setTitle("string title"); article.setType(Type.EXTERNAL);

JPA akan melaksanakan penyataan SQL berikut:

insert into Article (status, title, type, id) values (?, ?, ?, ?) binding parameter [1] as [INTEGER] - [null] binding parameter [2] as [VARCHAR] - [string title] binding parameter [3] as [VARCHAR] - [EXTERNAL] binding parameter [4] as [INTEGER] - [2]

Dengan @Enumerated (EnumType.STRING) , kita dapat menambahkan nilai enum baru atau menukar pesanan enum kita dengan selamat. Walau bagaimanapun, menamakan semula nilai enum masih akan memecah data pangkalan data.

Selain itu, walaupun perwakilan data ini jauh lebih mudah dibaca berbanding dengan pilihan @Enumerated (EnumType.ORDINAL) , ia juga memakan lebih banyak ruang daripada yang diperlukan. Ini mungkin menjadi masalah penting ketika kita perlu menangani jumlah data yang tinggi.

3. menggunakan @PostLoad dan @PrePersist Anotasi

Pilihan lain yang harus kita hadapi dengan jumlah yang berterusan dalam pangkalan data adalah dengan menggunakan kaedah panggilan balik JPA standard. Kami dapat memetakan enum kami berulang-alik dalam acara @PostLoad dan @PrePersist .

Ideanya adalah mempunyai dua atribut dalam entiti. Yang pertama dipetakan ke nilai pangkalan data, dan yang kedua adalah medan @Transient yang menyimpan nilai enum sebenar. Atribut sementara kemudian digunakan oleh kod logik perniagaan.

Untuk lebih memahami konsepnya, mari buat enum baru dan gunakan nilai intnya dalam logik pemetaan:

public enum Priority { LOW(100), MEDIUM(200), HIGH(300); private int priority; private Priority(int priority) { this.priority = priority; } public int getPriority() { return priority; } public static Priority of(int priority) { return Stream.of(Priority.values()) .filter(p -> p.getPriority() == priority) .findFirst() .orElseThrow(IllegalArgumentException::new); } }

Kami juga telah menambahkan kaedah Priority.of () untuk memudahkan mendapatkan instance Priority berdasarkan nilai intinya .

Sekarang, untuk menggunakannya di kelas Artikel , kita perlu menambahkan dua atribut dan melaksanakan kaedah panggilan balik:

@Entity public class Article { @Id private int id; private String title; @Enumerated(EnumType.ORDINAL) private Status status; @Enumerated(EnumType.STRING) private Type type; @Basic private int priorityValue; @Transient private Priority priority; @PostLoad void fillTransient() { if (priorityValue > 0) { this.priority = Priority.of(priorityValue); } } @PrePersist void fillPersistent() { if (priority != null) { this.priorityValue = priority.getPriority(); } } }

Sekarang, semasa meneruskan entiti Artikel :

Article article = new Article(); article.setId(3); article.setTitle("callback title"); article.setPriority(Priority.HIGH);

JPA akan mencetuskan pertanyaan SQL berikut:

insert into Article (priorityValue, status, title, type, id) values (?, ?, ?, ?, ?) binding parameter [1] as [INTEGER] - [300] binding parameter [2] as [INTEGER] - [null] binding parameter [3] as [VARCHAR] - [callback title] binding parameter [4] as [VARCHAR] - [null] binding parameter [5] as [INTEGER] - [3]

Walaupun pilihan ini memberi kita lebih banyak fleksibiliti dalam memilih perwakilan nilai pangkalan data dibandingkan dengan penyelesaian yang dijelaskan sebelumnya, itu tidak ideal. Rasanya tidak wajar mempunyai dua atribut yang mewakili satu enum dalam entiti. Selain itu, jika kita menggunakan pemetaan jenis ini, kita tidak dapat menggunakan nilai enum dalam pertanyaan JPQL.

4. Using JPA 2.1 @Converter Annotation

To overcome the limitations of the solutions shown above, JPA 2.1 release introduced a new standardized API that can be used to convert an entity attribute to a database value and vice versa. All we need to do is to create a new class that implements javax.persistence.AttributeConverter and annotate it with @Converter.

Let's see a practical example. But first, as usual, we'll create a new enum:

public enum Category { SPORT("S"), MUSIC("M"), TECHNOLOGY("T"); private String code; private Category(String code) { this.code = code; } public String getCode() { return code; } }

We also need to add it to the Article class:

@Entity public class Article { @Id private int id; private String title; @Enumerated(EnumType.ORDINAL) private Status status; @Enumerated(EnumType.STRING) private Type type; @Basic private int priorityValue; @Transient private Priority priority; private Category category; }

Now, let's create a new CategoryConverter:

@Converter(autoApply = true) public class CategoryConverter implements AttributeConverter { @Override public String convertToDatabaseColumn(Category category) { if (category == null) { return null; } return category.getCode(); } @Override public Category convertToEntityAttribute(String code) { if (code == null) { return null; } return Stream.of(Category.values()) .filter(c -> c.getCode().equals(code)) .findFirst() .orElseThrow(IllegalArgumentException::new); } }

We've set the @Converter‘s value of autoApply to true so that JPA will automatically apply the conversion logic to all mapped attributes of a Category type. Otherwise, we'd have to put the @Converter annotation directly on the entity's field.

Let's now persist an Article entity:

Article article = new Article(); article.setId(4); article.setTitle("converted title"); article.setCategory(Category.MUSIC);

Then JPA will execute the following SQL statement:

insert into Article (category, priorityValue, status, title, type, id) values (?, ?, ?, ?, ?, ?) Converted value on binding : MUSIC -> M binding parameter [1] as [VARCHAR] - [M] binding parameter [2] as [INTEGER] - [0] binding parameter [3] as [INTEGER] - [null] binding parameter [4] as [VARCHAR] - [converted title] binding parameter [5] as [VARCHAR] - [null] binding parameter [6] as [INTEGER] - [4]

As we can see, we can simply set our own rules of converting enums to a corresponding database value if we use the AttributeConverter interface. Moreover, we can safely add new enum values or change the existing ones without breaking the already persisted data.

The overall solution is simple to implement and addresses all the drawbacks of the options presented in the earlier sections.

5. Using Enums in JPQL

Let's now see how easy it is to use enums in the JPQL queries.

To find all Article entities with Category.SPORT category, we need to execute the following statement:

String jpql = "select a from Article a where a.category = com.baeldung.jpa.enums.Category.SPORT"; List articles = em.createQuery(jpql, Article.class).getResultList();

It's important to note, that in this case, we need to use a fully qualified enum name.

Of course, we're not limited to static queries. It's perfectly legal to use the named parameters:

String jpql = "select a from Article a where a.category = :category"; TypedQuery query = em.createQuery(jpql, Article.class); query.setParameter("category", Category.TECHNOLOGY); List articles = query.getResultList();

The above example presents a very convenient way to form dynamic queries.

Additionally, we don't need to use fully qualified names.

6. Conclusion

Dalam tutorial ini, kami telah membahas pelbagai cara untuk mengekalkan nilai enum dalam pangkalan data. Kami telah memberikan pilihan yang kami ada ketika menggunakan JPA dalam versi 2.0 dan yang lebih rendah, serta API baru yang tersedia dalam JPA 2.1 dan yang lebih tinggi.

Perlu diingat bahawa ini bukan satu-satunya kemungkinan untuk menangani enum di JPA. Beberapa pangkalan data, seperti PostgreSQL, menyediakan jenis lajur khusus untuk menyimpan nilai enum. Walau bagaimanapun, penyelesaian seperti itu berada di luar ruang lingkup artikel ini.

Sebagai peraturan ibu jari, kita harus sentiasa menggunakan AttributeConverter muka dan @Converter anotasi jika kita menggunakan JPA 2.1 atau kemudian.

Seperti biasa, semua contoh kod boleh didapati di repositori GitHub kami.

Bahagian bawah Java

Saya baru sahaja mengumumkan kursus Learn Spring yang baru , yang berfokus pada asas-asas Spring 5 dan Spring Boot 2:

>> SEMAK KURSUS