Penggunaan Enum di DB & JPA

Enum adalah kumpulan nilai konstan pada pemrograman. Sifatnya tetap dan hanya bisa digunakan menggunakan nilai yang tersedia di sana. Biasanya digunakan pada kolom Kategori Produk, Status Pesanan, Metode Pembayaran, dan sejenisnya. Misalnya pada Metode Pembayaran mempunyai enum CASH, TRANSFER, QRIS, dan lainnya. Pilihan nilainya hanya itu saja, jika ada nilai selain itu maka akan ditolak. Ada beberapa cara untuk handle ini📋.
DB Enum Type
Pada beberapa DB ada pilihan tipe enum. Kita bisa bikin tipe data enum sendiri. Misalnya kita membuat enum Metode Pembayaran yang berisi CASH, TRANSFER, dan QRIS menggunakan PostgreSql.
CREATE TYPE PaymentMethod AS ENUM ('CASH', 'TRANSFER', 'QRIS')
;Setelah tipe enum di atas terbuat, maka kita bisa apply ini ke sebuah kolom.
ALTER TABLE payments
ADD payment_method TYPE PaymentMethod
;Kelebihannya adalah ini jadi constraint yang kuat di DB💪. Begitu ada yang masukin data dengan value selain yang di atas maka akan digagalkan oleh DB. Kekurangannya, ini susah dimaintain. Setiap ada penambahan pilihan metode pembayaran yang baru atau ada perubahan nama pada enum yang sudah ada maka kita perlu alter table. Ini bisa memicu locking.
Enum Annotation
Selain pada DB, kita juga bisa menggunakan anotasi @Enumerated dari JPA di Entity class. Value yang akan disimpan ke DB nanti ada 2 pilihan di sini. Pertama menggunakan Ordinal, yaitu angka index sesuai urutan enum value. Pilihan kedua menggunakan String, yaitu sesuai nama konstan yang ditulis. Gw sendiri lebih suka pakai String karena lebih aman. Jika menggunakan Ordinal, lalu suatu saat ada yang mengubah urutan value dari enum, maka bisa menyebabkan bugs🪲. Misalnya urutan awalnya CASH, TRANSFER, dan QRIS. Lalu ada yang ngubah jadi TRANSFER, CASH, dan QRIS. Value Ordinal TRANSFER dan CASH akan tertukar.
@Getter
@RequiredArgsConstructor
public enum PaymentMethod{
CASH,
TRANSFER,
QRIS,
;
}@Getter
@Setter
@EqualsAndHashCode(of = "id")
@Entity
public static class Payment{
@Id
@Column(nullable = false, length = 50)
private String id;
@Column(nullable = false, length = 50)
private String name;
@Enumerated(EnumType.STRING)
PaymentMethod paymentMethod;
}Kelebihannya, ini lebih cepat karena tipe yang disimpan hanya varchar biasa di DB. Kalau ada penambahan atau perubahan tinggal lakukan di sisi code, ga perlu otak-atik DB. Kekurangannya, ga ada constraint DB di sini. Validasinya hanya berlaku di sisi code, sehingga jika ada yang mengubah data langsung ke DB yang ga sesuai nilai enum, maka bisa-bisa aja. Misalnya ada yang menginput EWALLET di DB. Meskipun pilihan EWALLET ga ada di pilihan enum, ini tetap bisa dilakukan dan dapat menyebabkan bugs. Selain itu misalkan mau rename metode, dari CASH mau diubah jadi TUNAI, maka kita perlu update seluruh data yang sudah ada dengan value CASH menjadi TUNAI😱. Ini ga efektif.
Domain Table
Alternatifnya adalah menggunakan Domain Table. Kita perlu bikin table baru untuk metode pembayaran.
| id (PK) | name |
|---|---|
| 1 | CASH |
| 2 | TRANSFER |
| 3 | QRIS |
Lalu pada tabel pembayaran kita tinggal relasikan domain table di atas menggunakan id.
| id (PK) | paid_date | method (FK) | total_amount | payer |
|---|---|---|---|---|
| asdfgh | 2020-10-01 | 1 | 1000 | bahlil |
Kelebihannya, di sini ada constraint Foreign Key di DB. Kolom method yang ada di tabel payments hanya bisa diisi nilainya sesuai id dari Domain Table payment_methods. Jika diisi berbeda maka akan error. Maintenance lebih mudah, tiap ada penambahan atau perubahan nama enum bisa dilakukan di Domain Table. Tinggal insert atau update aja di sana. Kalau misalkan mau rename CASH jadi TUNAI, tinggal ubah satu kolom di Domain Table saja. Semua data terkait di table payments ga perlu disentuh karena hanya berelasi dengan id. Ga perlu alter table sehingga eksekusinya lebih cepat dan aman tanpa locking. Ukuran kolomnya juga lebih hemat karena kita bisa menggunakan tipe data smallint pada kolom method. Kekurangannya adalah kita perlu join ke Domain Table untuk menampilkan nama lengkap metode pembayarannya🐌.
Enumerated Value
Di Java terbaru ada anotasi baru, yaitu @EnumeratedValue pada enum. Jadi kita bisa menggabungkan pendekatan Enum Annotation dan Domain Table. Kita tetap menggunakan Domain Table seperti di atas. Lalu pada Entity kita bisa menggunakan enum yang di dalamnya terdapat anotasi ini pada salah satu field yang dijadikan id.
@Getter
@RequiredArgsConstructor
public enum PaymentMethod{
CASH((short) 1),
TRANSFER((short) 2),
QRIS((short) 3),
;
@EnumeratedValue
private final short id;
}Di class Entity tinggal gunakan enum tersebut, semuanya udah otomatis dipasangkan oleh JPA saat CRUD.
@Getter
@Setter
@EqualsAndHashCode(of = "id")
@Entity
public static class Payment{
@Id
@Column(nullable = false, length = 50)
private String id;
@Column(nullable = false, length = 50)
private String name;
PaymentMethod paymentMethod;
}Kelebihannya adalah ini gabungan 2 pendekatan sebelumnya. Tiap ada perubahan, tinggal ubah enum di code dan Domain Table tanpa locking dan tanpa update seluruh data terkait sehingga lebih cepat. Di sini ada constraint Foreign Key sehingga value yang dimasukkan ga bisa asal-asalan baik dari sisi code maupun dari sisi DB. Ukuran kolomnya juga lebih hemat karena kita bisa menggunakan tipe data short pada code dan smallint pada DB dengan asumsi pilihan enum ga akan melebihi kapasitas tipe data tersebut. Biasanya juga jarang banget ada enum value yang pilihannya banyak banget. Di sini ga perlu join saat menampilkan data karena akan diterjemahkan oleh JPA sesuai id & objek enum secara otomatis😎. Kekurangannya adalah kita perlu pastikan enum di code dan Domain Table harus sama. Jika ada perbedaan maka bisa menyebabkan bugs. Jadi saat ada penambahan atau perubahan, pastikan dilakukan di code dan di DB.
Verdict
Gw sendiri selama ini menggunakan pendekatan Domain Table karena cukup strict, maintenance mudah, dan codebase gw masih legacy. Untuk codebase yang lebih modern menurut gw lebih baik gunakan pendekatan Enumerated Value. Constraint jadi berlaku di code dan di DB. Sebenarnya ada juga pilihan lain yang lebih advanced menggunakan Custom Converter, tapi menurut gw ini terlalu overkill untuk enum. Ini cocoknya untuk tipe data custom pada kasus yang unik. Sedangkan untuk enum udah ada anotasi khusus kasus ini seperti @Enumerated atau @EnumeratedValue. Mending gunakan ini aja.
