Memakai parameter konfigurasi untuk finders di simple-jpa


Finders pada plugin simple-jpa dapat menerima parameter berupa sebuah Map untuk mengatur query.  Map adalah struktur data yang berisi asosiasi antara key dan value.  Pada bahasa pemograman Groovy, sebuah Map didefinisikan seperti [key1:value1, key2:value2].  Sebagai perbandingan, di bahasa pemograman Java, sebuah Map didefinisikan seperti Map m1 = new HashMap(); m1.put(key1, value1); m1.put(key2, value2);.  Pada bahasa pemograman PHP, yang paling mendekati Map adalah associative array, seperti array(key1 => value1, key2 => value2);.

Berikut ini adalah key yang dapat diberikan pada finders di simple-jpa (versi 0.2):

  • page :  nilainya berupa sebuah angka yang mewakili halaman, dimulai dari 1.  Bila nilai pageSize tidak diberikan, maka dianggap setiap halaman terdiri atas 10 object.
  • pageSize : menentukan jumlah object per halaman yang akan dipakai oleh page.
  • orderBy : menentukan kolom yang dijadikan sebagai acuan dalam pengurutan.
  • orderDirection : nilainya harus berupa 'asc' untuk pengurutan menaik (A-Z) dan 'desc' untuk pengurutan menurun (Z-A).
  • notSoftDeleted :  bila bernilai true, maka tidak akan mengembalikan object yang sudah di-soft delete walaupun object tersebut masih tersimpan di database.

Untuk melihat bantuan lebih lanjut, gunakan perintah berikut ini:

griffon plugin-info simple-jpa

Sebagai latihan, kita akan mencoba membuat sebuah form pengisian data untuk domain class Stok.  Class tersebut memiliki sebuah atribut yaitu kodeBarang yang harus di-isi secara otomatis berdasarkan format tertentu, dalam hal ini adalah BRYYYYMMDDXXXX dimana nilai YYYY adalah tahun, MM adalah bulan, DD adalah tanggal, dan XXXX adalah nomor berurut.

Seperti biasa, langkah pertama setelah membuat sebuah proyek Griffon baru adalah men-install plugin simple-jpa.  Caranya adalah dengan memberikan perintah berikut ini:

griffon install-plugin simple-jpa 0.2

Saat ini versi terbaru adalah versi 0.2.   Setelah itu, berikan perintah untuk menyiapkan database.  Jangan lupa bila kamu telah memiliki schema database (juga user) yang dibuat sebelumnya, kamu perlu menambahkan parameter --skip-database=true pada perintah dibawah ini:

griffon create-simple-jpa --user=latihan --password=12345 --database=latihan --rootPassword=password

Setelah itu, kita akan membuat domain class dengan memberikan perintah berikut ini:

griffon create-domain-class Stok

Sekarang, kita akan mengubah file Stok.groovy yang terletak di package domain sehingga isinya terlihat seperti berikut ini:

package domain

import ...

@DomainModel @Entity
@TupleConstructor @ToString
class Stok {

  @Size(min=14, max=14)
  @Column(unique = true)
  String kodeBarang

  @Size(min=3, max=100)
  String nama

  @Min(0l)
  Integer jumlah = 0
}

Sebuah domain class untuk simple-jpa wajib memakai annotation @DomainModel.  Karena domain class juga merupakan sebuah entity JPA, maka ia juga wajib memakai annotation @Entity dari JPA.  Sementara itu, @TupleConstructor dan @ToString adalah annotation bawaan Groovy yang tidak wajib dipakai, hanya membantu menghasilkan constructor dan method toString() secara otomatis untuk mempermudah pemakaian.

Pada atribut yang ada, kita memakai annotation bawaan Hibernate Validator (JSR 303) untuk melakukan validasi, yaitu @Size dan @Min.  Hibernate Validator juga memungkinkan kita untuk membuat annotation validasi baru sesuai kebutuhan.  Dokumentasi untuk Hibernate Validator dapat dibaca di http://docs.jboss.org/hibernate/validator/4.3/reference/en-US/html_single/.

Kita juga memakai annotation bawaan JPA untuk mendefinisikan bahwa sebuah kolom bersifat unik, yaitu annotation @Column.  Domain model ini bila dijalankan akan disimpan ke dalam bentuk tabel dengan struktur seperti yang  terlihat pada gambar berikut ini:

Tabel yang dibuat dari domain class

Tabel yang dibuat dari domain class

Bila tidak ingin mengatur struktur tabel yang dibuat, kamu dapat mengatur nama field melalui @Column.  Informasi mengenai annotation yang ada dapat dibaca di dokumentasi Hibernate berikut ini http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/.

Langkah berikutnya, kita perlu menghasilkan sebuah MVCGroup berdasarkan domain class yang telah dibuat.  Caranya adalah dengan memberikan perintah berikut ini:

griffon generate-all Stok

Struktur proyek akan terlihat menjadi seperti pada gambar berikut ini:

Struktur Proyek

Struktur Proyek

Secara garis besar, panduan yang dapat kamu pakai selama membuat kode program adalah seperti berikut ini:

  1. Selama berada dalam satu MVCGroup, kamu dapat memanggil salah satu dari view , controller atau view model dengan memakai variabel view, controller, dan model.  Sebagai contoh, kamu bisa mengakses nilai namaBarang dari model di view dengan menggunakan perintah model.namaBarang.
  2. Kamu juga bisa memanggil view, view model atau controller milik MVCGroup lainnya melalui variabel app dengan syarat sebelumnya MVCGroup tersebut telah dibuat.
  3. Usahakan view hanya berisi kode program yang berhubungan dengan tampilan.
  4. Usahakan controller berisi kode program yang berhubungan dengan business logic.
  5. Usahakan controller tidak mengakses view secara langsung; melainkan melalui view model sebagai perantara.  Dengan demikian, perubahan pada view akan mengubah view model yang nantinya akan dipakai oleh controller (view –> view model –> controller).  Begitu juga sebaliknya, controller akan mengubah view model yang selanjutnya menyebabkan nilai pada view berubah (view <– view model <– controller).

Tujuan dari pemisahan antara view, controller, dan view model adalah untuk meningkatkan reusability / mengurangi coupling.  Selain itu, bila diterapkan dengan baik, juga dapat mempermudah pengujian karena pengujian otomatis dapat langsung diterapkan ke controller tanpa harus ada view.

Berdasarkan panduan di atas, maka kamu harus meletakkan kode program yang menghasilkan format kode barang di StokController.groovy, seperti berikut ini:

def buatKodeBarang = {
  String tanggal = DateTime.now().toString("yyyyMMdd")
  Stok stok = findAllStok([orderBy: 'kodeBarang', orderDirection: 'desc', page: 1, pageSize: 1])[0]
  Integer nomorBerikut = stok ? stok.kodeBarang[10..13].toInteger() + 1 : 1
  model.kodeBarang = String.format("BR%s%04d", tanggal, nomorBerikut)
}

Pada baris pertama, kita memakai Joda Time untuk menformat tanggal (class bawaan Java untuk memanipulasi tanggal masih sangat terbatas; masih harus menunggu Java 8 untuk penanganan tanggal yang lebih baik!).

Pada baris kedua, kita memanggil dynamic finder milik simple-jpa untuk mencari seluruh Stok yang ada.  Kita memberikan parameter konfigurasi dalam bentuk Map untuk mengatur query lebih lanjut.  Dalam hal ini, kita meminta agar pengurutan dilakukan terhadap atribut kodeBarang secara menurun (Z-A).  Dengan demikian, nilai kodeBarang yang paling ‘besar’ akan berada di urutan paling awal.  Karena kita hanya membutuhkan nilai paling awal tersebut, parameter page dan pageSize di-isi dengan 1 sehingga query hanya mengembalikan 1 baris saja.  findAllStok akan mengembalikan sebuah List; di Groovy, kita bisa mengakses List sama seperti array sehingga untuk mengambil elemen pertama dari sebuah List, kita bisa memberikan perintah seperti list[0].

Pada baris ketiga, kita memakai operator ternary ekspresi ? jika_true : jika_false.  Bila tidak ada Stok yang ditemukan, maka nomorBerikutnya adalah 1.  Tetapi bila ada Stok yang ditemukan, maka konversikan 4 digit terakhir menjadi angka dan tambahkan dengan 1.  Di Groovy, String dapat diakses seperti array melalui range.

Ingat bahwa sebaiknya controller dan view harus berkomunikasi melalui model.  Oleh sebab itu, closure buatKodeBarang tidak akan mengembalikan nilai ke view, melainkan mengubah nilai kodeBarang milik model.   MVC yang di-generate oleh simple-jpa secara otomatis akan melakukan binding dari JTextField di view ke model.  Dengan demikian, bila kamu mengubah nilai model.kodeBarang dari controller, secara otomatis nilai JTextField di view akan ikut berubah.

Berikutnya, kita perlu menentukan kapan closure buatKodeBarang akan dipanggil.  Pada saat sebuah form ditampilkan, sebaiknya kode barang sudah terisi secara otomatis.  Setiap kali sebuah form ditampilkan, closure listAll akan dipanggil.  Oleh sebab itu, kamu perlu  menambahkan baris berikut ini di closure listAll:

def listAll = {
   ...
   buatKodeBarang()
}

Selain itu, setiap kali proses simpan/update selesai dilakukan, maka sebaiknya kode barang yang baru akan dihasilkan.  Kamu  akan menambahkan baris berikut ini di bagian paling terakhir dari closure save:

def simpan = {
   ...
   buatKodeBarang()   
}

Selain itu, setiap kali proses hapus dilakukan, juga  nilai kode barang juga harus ditampilkan, oleh sebab itu, kamu perlu menambahkan baris berikut ini di bagian paling terakhir dari closure delete:

def delete = {
  ...
  buatKodeBarang()
}

Sebelum menjalankan aplikasi, buka file Application.groovy  dan ubah nilai startupGroups menjadi stok seperti yang terlihat berikut ini:

startupGroups = ['stok']

Sebuah aplikasi bisa saja terdiri atas banyak MVCGroup, tetapi hanya ada 1 yang akan dijalankan pertama kali (dan kamu perlu menentukan dalam contoh ini).

Bila kamu menjalankan aplikasi, kamu akan memperoleh tampilan seperti pada gambar berikut ini:

Contoh Tampilan Awal

Contoh Tampilan Awal

Tapi perjalanan kita belum selesai!  Kode program di controller kita terlihat dipenuhi duplikasi pemanggilan buatKodeBarang(). Bukankah setiap kali isi nilai model (yang mewakili JTextField) dikosongkan, maka nilai kode barang baru akan dihasilkan?  Tidak peduli operasi simpan maupun hapus.  Kapan isi JTextField akan dikosongkan?  Jawabannya adalah pada saat clear() di model dipanggil!  Oleh sebab itu, kamu perlu mengubah isi closure clear() file StokModel.groovy sehingga terlihat seperti berikut ini:

class StokModel {

   ...

   def controller
   ...

   def clear = {
     ...
     controller.buatKodeBarang()
   }
}

Bila kamu mendefinisikan sebuah variabel bernama controller di model,  maka kamu bisa mengakses controller dari model.  Sesuai peraturan awal kita, ini bukan sesuatu yang baik!  Tetapi dalam kasus kita, ini termasuk solusi yang optimal.

Kamu sekarang boleh menghapus buatKodebarang() di closure save() dan delete(). Untuk listAll(), pemanggilan ke buatKodeBarang() tetap dibutuhkan.  Kode program akan terlihat lebih sederhana, dan hasilnya akan tetap sama seperti sebelumnya.  Oops!  Tunggu!  Ada yang tidak beres!

Griffon mempermudah kita mengatur threading, tetapi kita tetap wajib menentukan kapan sebuah kode program dijalankan dalam UI Thread (pada Swing, ini adalah event dispatcher thread) atau tidak.  Sebagai patokan:

  1. Kode program yang menyebabkan perubahan komponen Swing harus dijalankan dalam UI Thread melalui execInsideUISync{} atau execInsideUIAsync{}.
  2. Kode program yang membutuhkan waktu lama harus dikerjakan pada thread terpisah melalui execOutsideUI{} atau execFuture{}.  Secara default, kode program di controller akan dikerjakan di thread terpisah (setara dengan execOutsideUI{}).  Bila kode program yang membutuhkan waktu lama dipaksakan bekerja di UI Thread, maka tampilan akan terlihat tidak responsive seolah-olah nge-hang.

Perubahan yang kamu lakukan pada model.clear() kini membuat closure tersebut menjadi ‘berat’.  Padahal saat ini, simpan() di controller memanggilnya seperti berikut ini:

def simpan = {
  ...
  execInsideUIASync {
     model.clear()
  }
}

Oleh sebab itu, kamu perlu mengubahnya menjadi seperti ini:

def simpan = {
  ...
  model.clear()
}

Ok, sekarang semuanya sudah bekerja sesuai harapan.

Tapi masih ada satu hal yang menganggu.  Nilai kode barang harusnya tidak boleh diubah atau di-edit!  Perubahan seperti ini kaitannya adalah dengan view (tampilan).  Oleh sebab itu, kamu perlu membuat StokView.groovy dan mencari baris berikut ini:

textField(id: 'kodeBarang' columns: 20, text: bind('kodeBarang', target: model, mutual: true), errorPath: 'kodeBarang')

Bila kamu sudah mempelajari Groovy Swing Builder sebelumnya, maka kamu pasti tahu bahwa node textField() akan membuat instance baru dari JTextField.  Bagaimana bisa?  Inilah kelebihan bahasa pemograman dinamis (kebalikannya adalah bahasa pemograman statis seperti Java dan PHP dimana segala sesuatu telah ‘ditentukan’ dengan baik).

Pada node textField() terdapat atribut seperti columns dan text. Bukan kebetulan bila kamu melihat daftar method JTextField (di http://docs.oracle.com/javase/7/docs/api/javax/swing/JTextField.html), kamu akan menemukan method setColumns() dan setText().  Yup, atribut columns akan memanggil setColumns dengan melewatkan nilai 20.  Demikian juga, atribut text akan memanggil setText dengan melewatkan binding (salah! sebenarnya disini terjadi proses binding yang melibatkan addPropertyChangeListener() dan sebagainya; baca http://griffon.codehaus.org/guide/latest/guide/modelsAndBinding.html#binding untuk mengetahui lebih lanjut mengenai apa itu binding).

Method apa yang bisa dipanggil agar sebuah JTextField tidak dapat di-edit?  Jawabannya adalah setEditable(false).  Oleh sebab itu, kamu perlu mengubah definisi textField() menjadi seperti berikut ini:

textField(id: 'kodeBarang', columns: 20, text: bind('kodeBarang', target: model, mutual: true), 
    editable: false, errorPath: 'kodeBarang')

Sekarang, bila kamu menjalankan aplikasi, kamu tidak bisa mengisi kode barang lagi.

Perihal Solid Snake
I'm nothing...

4 Responses to Memakai parameter konfigurasi untuk finders di simple-jpa

  1. Komang Hendra Santosa mengatakan:

    Wah, bagus mas..dah mw jalan..Tp ada kekurangannya..dengan mengisi langsung kodeBarang fungsi untuk mendeteksi kesalahan errorLabel tidak bisa dilakukan karena data sudah terupdate duluan di tabel atau karena hasil database yg defaultnya “null” ya?

    O ya mas, untuk createDate dan modifiedDate itu bawaan langsung dari hibernate ya?settingannya apa sih?mksdnya datetimenya itu tepat inputnya walau mase utc+8. saya mau pakai untuk variabel yg pakai datetime nantinya.

    • Solid Snake mengatakan:

      Proses validasi dilakukan sebelum proses penyimpanan ke database sehingga seharusnya tidak ada masalah. Mungkin kamu bisa menunjukkan contoh kode program untuk mereproduksi kesalahan ini?

      Proses validasi dilakukan dengan memanggil method validate() milik simple-jpa untuk domain objek, misalnya validate(stok) yang akan mengembalikan nilai true atau false. Pada kode program yang dihasilkan oleh generate-all, bila validate(stok) bernilai false, maka proses penyimpanan akan diabaikan.

      createDate dan modifiedDate adalah bawaan simple-jpa, yang di-inject secara otomatis untuk setiap domain class dan di-isi secara otomatis oleh sebuah Entity Listener (lihat definisi di file griffon-app/conf/metainf/orm.xml). Coba baca https://thesolidsnake.wordpress.com/2013/04/11/menggunakan-datetime-di-simple-jpa/ untuk informasi lebih lanjut.

      Jadira User Type (custom type yang dipakai oleh Hibernate untuk menyimpan tipe data DateTime) akan mengkonversi DateTime ke dalam UTC saat disimpan di database. Begitu juga sebaliknya, saat nilai dibaca dari database, Jadira User Type akan menkonversi nilai UTC di database menjadi sesuai time zone aplikasi. Informasi lebih lanjut bisa dibaca di https://thesolidsnake.wordpress.com/2013/04/11/menggunakan-datetime-di-simple-jpa/.

  2. Komang Hendra Santosa mengatakan:

    Berikut kodenya mas:

    –PembelianController.groovy–

    def save = {
    Pembelian pembelian = new Pembelian(model.noNota, model.tanggal, model.kodeSupplier.selectedItem, model.total, model.userID.selectedItem)
    if (!validate(pembelian)) return_failed()

    if (model.id == null) {
    // Insert operation
    if (findPembelianByNoNota(pembelian.noNota)?.size() > 0) {
    model.errors[‘noNota’] = app.getMessage(“simplejpa.error.alreadyExist.message”)
    return_failed()
    }
    persist(pembelian)
    execInsideUIAsync { model.pembelianList << pembelian }
    } else {
    // Update operation
    Pembelian selectedPembelian = model.pembelianSelection.selected[0]
    selectedPembelian.noNota = model.noNota
    selectedPembelian.tanggal = model.tanggal
    selectedPembelian.kodeSupplier = model.kodeSupplier.selectedItem
    selectedPembelian.total = model.total
    selectedPembelian.userID = model.userID.selectedItem
    merge(selectedPembelian)
    }
    model.clear()
    }

    Saya pakai code default generatenya simple-jpa

    Berikut gambarnya: http://img809.imageshack.us/img809/8808/testtsf.png

    • Solid Snake mengatakan:

      Saya sudah mencobanya dan validasi berjalan seperti yang diharapkan, misalnya atribut yang @NotNull bila tidak di-isi akan memberikan pesan kesalahan begitu tombol “Save” di-klik, seperti pada gambar berikut ini:

      tampilan

      Apakah pada domain model, untuk atribut yang tidak boleh null sudah diberikan annotation @NotNull?

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: