Memakai @Embeddable Di JPA


Pada rancangan di post sebelumnya (Apa Bedanya Merancang Sebuah Class Dan Sebuah Tabel?), saya menunjukkan bahwa merancang class adalah proses yang berbeda dengan merancang tabel.   Tapi pada akhirnya, class memiliki data yang perlu disimpan ke tabel.  Misalnya, bagaimana menyimpan class Diskon?   Setiap objek Faktur bisa memiliki satu object Diskon atau tidak sama sekali.   Begitu juga dengan objek ItemFaktur.  Bila masing-masing objek disimpan pada tabel sendiri (terdapat 3 tabel),  setiap kali membaca objek Faktur atau ItemFaktur, perlu dilakukan join ke tabel yang menyimpan data Diskon.  Ini bukan solusi yang baik dilihat dari segi database relasional.  Apakah ada solusi lain?

Pada OOP, dapat dijumpai dua jenis object, yaitu entity object dan value object.

Sebuah entity object, setelah disimpan ke dalam tabel, dapat dicari berdasarkan id.  Objek tersebut juga dapat di-edit (bersifat mutable).  Pada kasus ini, seluruh objek Faktur adalah entity object.  Pada JPA, definisi class untuk entity object perlu memiliki annotation @Entity.

Sebuah value object tidak memiliki pengenal (id), dengan demikian mereka biasanya tidak perlu di-cari.  Nilai dari value object biasanya tidak dapat diubah (bersifat immutable) sehingga untuk mengedit nilainya dilakukan dengan membuat objek baru.  Value object biasanya mewakili sebuah nilai, sebagai contoh, seluruh instance dari class String, BigDecimal, DateTime dan sebagainya adalah sebuah value object.   Begitu juga dengan class Diskon yang saya buat sebelumnya.   Instance dari class Diskon selalu menjadi bagian dari objek lain. Pada JPA, definisi class untuk value object perlu memiliki annotation @Embeddable.

Sama seperti value object lainnya, sebuah object Diskon sebaiknya disimpan sebagai bagian dari entity object yang memilikinya. Dengan demikian, walaupun terdapat dua class, Faktur dan Diskon, hanya satu tabel yang dibutuhkan karena nilai atribut untuk objek Diskon akan disimpan pada tabel untuk objek Faktur yang memilikinya.  Begitu juga dengan relasi antara ItemFaktur dan Diskon, nilai atribut untuk objek Diskon akan disimpan pada tabel untuk objek ItemFaktur yang memilikinya.

Spesifikasi JPA tidak mendukung inheritance pada class yang diberi annotation @Embeddable (ada juga implementasi JPA yang mendukung seperti EclipseLink).  Oleh sebab itu,  saya perlu merancang ulang representasi diskon menjadi seperti berikut ini:

Rancangan Class Diagram Dengan Embeddable Class

Rancangan Class Diagram Dengan Embeddable Class

Pada diagram di atas, saya juga mengganti asosiasi biasa dengan yang lebih spesifik yaitu komposisi.  Komposisi menunjukkan bahwa objek Diskon adalah bagian dari objek Faktur.  Bila objek Faktur dihapus, maka objek Diskon miliknya juga wajib dihapus karena sebuah Diskon tidak bisa berdiri sendiri.

Berikut ini adalah contoh implementasi class Faktur:

import ...

@DomainModel @Entity @Canonical
class Faktur {

    @NotBlank @Size(min=3, max=50)
    String nomor

    @NotNull @Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    LocalDate tanggal

    @Size(min=2, max=200)
    String keterangan

    @Embedded
    Diskon diskon

    @ElementCollection @OrderColumn(name="nomorUrut")
    List<ItemFaktur> itemList = []

    BigDecimal total() {
        BigDecimal total = itemList.sum { ItemFaktur it -> it.total() }
        diskon ? diskon.hasilDiskon(total): total
    }

    ...
}

Class ItemFaktur juga memiliki Diskon, seperti yang terlihat pada kode program berikut ini:

import ...

@Embeddable @Canonical
class ItemFaktur {

    @NotNull @ManyToOne
    Barang barang

    @NotNull @Min(0l)
    Integer jumlah

    @NotNull @Min(0l)
    BigDecimal harga

    @Embedded
    Diskon diskon

    @NotNull @ManyToOne
    Faktur faktur

    BigDecimal total() {
        (diskon ? diskon.hasilDiskon(harga): harga) * jumlah
    }
}

Class Diskon sendiri akan terlihat seperti berikut ini:

import ...

@Embeddable @Canonical
class Diskon {

    @Min(0l)
    BigDecimal persen = 0;

    @Min(0l)
    BigDecimal potonganLangsung = 0;

    BigDecimal hasilDiskon(BigDecimal nilai) {
        nilai - ((persen/100) * nilai) - potonganLangsung
    }

}

Berikut ini adalah contoh penggunaan class Diskon:

Faktur faktur1 = new Faktur()
faktur1.itemList << new ItemFaktur(diskon: new Diskon(persen: 27), jumlah: 80, harga: 133400)
faktur1.itemList << new ItemFaktur(diskon: new Diskon(persen: 27), jumlah: 50, harga: 98000)
faktur1.itemList << new ItemFaktur(diskon: new Diskon(persen: 27), jumlah: 10, harga: 133400)
faktur1.diskon = new Diskon(potonganLangsung: 218)
assertEquals(12341162, faktur1.total())

Dengan rancangan seperti ini, perubahan mekanisme perhitungan diskon hanya perlu dilakukan pada class Diskon.  Walaupun Diskon adalah sebuah class terpisah, nilai atribut-nya tidak tersimpan di sebuah tabel tersendiri, melainkan tergabung bersama dengan tabel faktur atau tabel faktur_itemlist, seperti yang terlihat pada gambar berikut ini:

Struktur Tabel Yang Dihasilkan

Struktur Tabel Yang Dihasilkan

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: