Memakai @OrderColumn Di Hibernate JPA


Anggap saja saya membuat dua buah class, Faktur dan ItemFaktur.   Sebuah Faktur dapat memilih lebih dari satu ItemFaktur, dengan demikian hubungan mereka adalah one-to-many.   Saya menginginkan mereka mereka memiliki hubungan bidirectional: sebuah Faktur bisa mengetahui apa saja ItemFaktur-nya; sebaliknya sebuah ItemFaktur bisa mengetahui siapa Faktur ‘pemilik’-nya. Berikut adalah contoh implementasi yang saya buat (memakai Griffon dan simple-jpa):

package domain

import ...

@DomainModel @Entity @Canonical(excludes='itemFakturList')
class Faktur {

    @NotBlank
    String nomor

    @NotEmpty @OneToMany(cascade=CascadeType.ALL, mappedBy="faktur")
    List<ItemFaktur> itemFakturList = []

}

@DomainModel @Entity @Canonical
class ItemFaktur {

    @NotBlank
    String namaBarang

    @NotNull
    Integer jumlah

    @NotNull @ManyToOne
    Faktur faktur

}

Pada sebuah faktur, umumnya akan terdapat item faktur yang memiliki nomor urut.   Akan tetapi pada implementasi di atas, saya bisa mendapatkan hasil berurut hanya karena kebetulan generated id yang dihasilkan untuk ItemFaktur berurut.   Seandainya saya tiba-tiba menyisipkan sebuah ItemFaktur diantara ItemFaktur yang sudah ada, maka urutan akan menjadi salah! Untuk membuktikannya, saya akan membuat test case seperti ini di simple-jpa:

public void testFaktur() {
   Faktur faktur = new Faktur("FAKTUR1")
   ItemFaktur item1 = new ItemFaktur("BARANG1", 10, faktur)
   ItemFaktur item2 = new ItemFaktur("BARANG2", 10, faktur)
   ItemFaktur item3 = new ItemFaktur("BARANG3", 10, faktur)
   faktur.itemFakturList.addAll([item1, item2, item3])
   controller.persist(faktur)

   //
   // Hasil berurut sesuai dengan yang diharapkan
   //
   controller.destroyEntityManager()
   Faktur hasil1 = controller.findFakturByNomor("FAKTUR1")[0]
   assertEquals(3, hasil1.itemFakturList.size())
   assertEquals(item1, hasil1.itemFakturList[0])
   assertEquals(item2, hasil1.itemFakturList[1])
   assertEquals(item3, hasil1.itemFakturList[2])

   //
   // Menyisipkan item4 pada baris kedua
   //
   ItemFaktur item4 = new ItemFaktur("BARANG4", 10, hasil1)
   hasil1.itemFakturList.add(1, item4)
   controller.merge(hasil1)

   //
   // Urutan tidak lagi sesuai dengan harapan
   //
   controller.destroyEntityManager()
   Faktur hasil2 = controller.findFakturByNomor("FAKTUR1")[0]
   assertEquals(4, hasil2.itemFakturList.size())
   assertEquals(item1, hasil2.itemFakturList[0])
   assertEquals(item4, hasil2.itemFakturList[1])
   assertEquals(item2, hasil2.itemFakturList[2])
   assertEquals(item3, hasil2.itemFakturList[3])

   //
   // Seharusnya:       Yang Diperoleh:
   //
   // "BARANG1" 10      "BARANG1" 10
   // "BARANG4" 10      "BARANG2" 10
   // "BARANG2" 10      "BARANG3" 10
   // "BARANG3" 10      "BARANG4" 10
   //
}

Bila test case di atas dijalankan, saya akan memperoleh kesalahan seperti berikut ini:

junit.framework.AssertionFailedError: junit.framework.AssertionFailedError:
expected:<domain.ItemFaktur(BARANG4, 10, domain.Faktur(FAKTUR1))>
but was:<domain.ItemFaktur(BARANG2, 10, domain.Faktur(FAKTUR1))>

Pengguna tentu akan terkejut bila melihat urutan item di Faktur menjadi tidak sesuai seperti saat dimasukkan. Lalu, bagaimana solusinya?

Beruntungnya, pada JPA 2.0 terdapat @OrderColumn dan @OrderBy.   Annotation @OrderColumn dipakai bila kita menginginkan pengurutan secara otomatis sesuai dengan Collection yang dipakai.   Kolom di tabel yang dipakai untuk menyimpan pengurutan tidak di-ekspos ke aplikasi melainkan dikelola oleh JPA provider secara otomatis sesuai dengan urutan di List.   Sebaliknya, bila kolom tersebut akan di-isi oleh aplikasi secara manual, maka gunakan @OrderBy.

Saya segera mengubah definisi class saya menjadi seperti berikut ini:

...
@DomainModel @Entity @Canonical(excludes='itemFakturList')
class Faktur {

    @NotBlank
    String nomor

    @NotEmpty @OneToMany(cascade=CascadeType.ALL, mappedBy="faktur") @OrderColumn
    List<ItemFaktur> itemFakturList = []

}
...

Seharusnya ini mengatasi masalah, tapi tampaknya saya tidak selalu beruntung!   Saya malah memperoleh kesalahan seperti berikut ini:

org.hibernate.HibernateException: null index column 
for collection: domain.Faktur.itemFakturList

simple-jpa saat ini memakai Hibernate sebagai JPA provider, lebih tepatnya org.hibernate:hibernate-entitymanager:4.1.9.Final.   Ternyata, versi Hibernate yang dipakai tidak mendukung penggunaan @OrderColumn bersamaan dengan mappedBy.   Saya diwajibkan untuk mengisi nilai urut ini secara manual.   Mengisi nilai urut untuk setiap ItemFaktur setiap kali menyimpan, men-update, atau men-delete bukanlah hal yang sulit, tapi hal itu seharusnya adalah sesuatu yang transparan dan otomatis.   Bila harus membuat kode program baru yang serupa setiap kali menjumpai List yang harus berurut di database, saya khawatir kemungkinkan ada sesuatu yang terlupakan dan timbulnya bug menjadi semakin besar.   Jadi ini bukan persoalan malas dan tergantung pada framework, tapi ini adalah urusan menjaga kualitas😉

Kabar gembiranya adalah pada versi 4.2.0.Final, Hibernate sudah mendukung penggunaan @OrderColumn bersamaan dengan mappedBy.   Informasi ini saya peroleh dari https://hibernate.atlassian.net/browse/HHH-5732.   Saya segera mencoba Hibernate 4.2.0.Final.

Bagaimana caranya memakai versi Hibernate yang berbeda dari simple-jpa?   Saya membuka file griffon-app\conf\BuildConfig.groovy, kemudian mengubah beberapa bagian sehingga terlihat seperti berikut ini:

...
griffon.project.dependency.resolution = {
    ...
    repositories {
        griffonHome()

        // uncomment the below to enable remote dependency resolution
        // from public Maven repositories
        mavenLocal()
        mavenCentral()
        ...
    }
    dependencies {
        // specify dependencies here under either 'build', 'compile', 'runtime' or 'test' scopes eg.
        compile'org.hibernate:hibernate-entitymanager:4.2.0.Final'
        ...
    }
}
...

Kemudian, saya membersihkan proyek dengan:

griffon clean

Setelah ini, saya kembali menjalankan test case di atas dengan perintah:

griffon test-app FakturTest.testFaktur

Akhirnya test case berhasil dilalui dengan baik tanpa ada kesalahan lagi.   Hibernate secara otomatis memberikan urutan di tabel seperti yang terlihat pada gambar berikut ini:

Nomor urut untuk List yang diisi secara otomatis

Nomor urut untuk List yang diisi secara otomatis

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: