Sample inventory application using simple-jpa 0.8

As a companion for simple-jpa 0.8, there is a demo application called simple-jpa-demo-inventory. You can find its source code in https://github.com/JockiHendry/simple-jpa-demo-inventory. To run this project, you must have Griffon 1.5 and Groovy 2.3.

This demo application will ask for username and password everytime it is launched:

Login dialog

Login dialog

This feature is enabled by adding the following line in Config.groovy:

griffon.simplejpa.auditing.loginService = 'UserService'

UserRepository creates a new user called admin if it doesn’t exist. The default pasword for admin is 12345. So you can login by entering admin as user and 12345 as password. You can change default password or creating new user later by using MVC group User. You can even change which menu is displayed for each user.

See Auditing In simple-jpa Documentation for more information about auditing in simple-jpa 0.8.

After login, main screen will be displayed. This is the startup group which is configured in Application.groovy.

The startup MVC group

The startup MVC group

simple-jpa 0.8 now uses a tabbed pane to display instances of MVCGroup. You can display more than one instances of the same MVCGroup. For example, you can open two instances of MVC group Laporan (report), each displaying different report. You easily compare them just by switching tab.

Multiple tabs in `Main` MVC group

Multiple tabs in `Main` MVC group

In simple-jpa 0.8, you can add menuItem() to glazedTable() to add popup menu for that table. For example, in this picture, I can click on Tampilkan Referensi to display related invoice in a new tab:

Popup menu in table

Popup menu in table

See View In simple-jpa Documentation for more information about presentation layer in simple-jpa 0.8.

simple-jpa-demo-inventory uses simple-escp to print invoices to dot matrix printer. All invoice layouts are saved in database. They’re handled by TemplateFakturRepository. User can change or reset existing templates by displaying MVCGroup TemplateFaktur. This will open JSON editor where user can edit JSON template used by simple-escp.

Using simple-escp to print invoice

Using simple-escp to print invoice

Edit or reset invoice layout in JSON format

Edit or reset invoice layout in JSON format

By taking the advantage of Groovy as dynamic language, simple-jpa-demo-inventory even allows user to run custom code on the fly without redeploying entire application.

Launch Groovy Console from running application

Launch Groovy Console from running application

You can find unit test cases in test/unit folder and integration test cases in test/integration. They’re used to test domain classes, services, and repositories. Beware that running integration tests will take a long time because they need to actually hit database and every test cases always repopulate tables. You’re not limited to use Microsoft Excel 97 format because now simple-jpa 0.8 can read test data in form of Office Open XML format (used by Excel 2007 and later).

See Testing In simple-jpa Documentation for more information about testing in simple-jpa 0.8.

Iklan

simple-jpa 0.8 is released

What’s new in this release?

  1. Improvements in default scaffolding’s templates.
  2. Allow nested property in finders.
  3. Allow user to specify custom generator for scaffolding by using generate-all -generator=package.custom.generator.
  4. Allow user to add scaffolding configurations in Config.groovy.
  5. Add griffon.simplejpa.scaffolding.auto to automatically run scaffolding before compiling classes.
  6. Add DDD scaffolding generator that can be selected by using generate-all -generator=simplejpa.scaffolding.generator.ddd.DDDGenerator.
  7. Add new artifact type: repository.
  8. Add create-repository command.
  9. Automatically add createdBy and modifiedBy to domain classes.
  10. Add optional login dialog that will be displayed at startup time.
  11. Fix nested property path for validation wasn’t parsed properly.
  12. Fix TagChooser hover not working properly.
  13. Change DialogUtils methods signature.
  14. Increase performance of DbUnitTestCase by caching IDataSet.
  15. Change DbUnitTestCase to perform insert operation only. This can be changed by overriding cleanDataSet() and insertDataSet().
  16. Change DbUnitTestCase to execute clean.sql before inserting records from dataset if it is exists.
  17. Change DbUnitTestCase to execute before.sql before setUpDatabase() and after.sql after setUpDatabase() operation.
  18. Split setup method in DbUnitTestCase to loadMVC() and setUpDatabase().
  19. Add confirm() and message() to DialogUtils that will display message dialogs from EDT thread.
  20. Don’t execute String in templateRenderer node using SimpleTemplateEngine but treats it as property/function lookup (use closure for more complex expression).
  21. Add default popup menu for glazedTable() to copy a cell value and print table’s content.
  22. Allow using menuItem() inside glazedTable() to define menu items for popup menu.
  23. Add exp as synonym for expression in glazedColumn().

simple-jpa documentation is now generated by using AsciiDoctor. It should be mobile device friendly 🙂 You can visit simple-jpa documentation site in https://jockihendry.github.io/simple-jpa/html.

WARNING: The binary uploaded to Griffon repository is compiled by using Groovy 2.3. Coincidentally, Groovy 2.3 binary is not fully compatible with previous version. Griffon 1.5 by default shipped with Groovy 2.2. If you encounter missing class exception, try to upgrade Groovy library used by Griffon to Groovy 2.3. This version is not compatible with Griffon 2.0.

Menyimpan Template simple-escp Di Database

Ada beberapa pengguna aplikasi yang sering kali ingin mengubah hasil percetakan di printer dari waktu ke waktu. Bila kode program percetakan disimpan di dalam aplikasi, ini berarti saya harus mengubah kode program setiap kali ada perubahan layout. Hal ini lama-lama bisa merepotkan! Oleh sebab itu, pada artikel Memakai simple-escp Di Griffon, saya mendefinisikan template percetakan dalam bentuk file JSON. Setiap kali ada yang ingin merubah layout percetakan, mereka bisa meng-edit file JSON ini sendiri tanpa harus menunggu saya.

Solusi di atas bekerja dengan baik bila template laporan terletak di server yang dipakai bersama (misalnya aplikasi web). Bagaimana dengan aplikasi desktop yang dikembangkan dengan menggunakan Griffon dan simple-jpa? Saya dapat menyimpan template JSON untuk simple-escp ke dalam database. Dengan demikian, perubahan template yang dibuat oleh satu pengguna tetap dapat dilihat dan dipakai oleh pengguna lainnya.

Sebagai contoh, saya bisa membuat sebuah entity untuk mewakili template simple-escp seperti berikut ini:

@DomainClass @Entity @Canonical
class TemplateFaktur {

    @NotBlank
    String nama

    @Lob
    String isi

}

Saya memakai annotation @Lob pada field isi untuk menandakan bahwa kolom tersebut dapat di-isi dengan banyak karaketer (large object type). Pada Hibernate JPA dan database MySQL Server, kombinasi ini akan menghasilkan field dengan tipe longtext yang dapat menampung hingga maksimum 4 GB karakter (bandingkan dengan VARCHAR yang menampung maksimal 255 karakter).

Untuk mencetak template simple-escp berdasarkan TemplateFaktur yang sudah disimpan di database, saya dapat menggunakan kode program seperti berikut ini:

TemplateFaktur template = findTemplateFakturByNama(model.nama)
JsonTemplate template = new JsonTemplate(template.isi)
PrintPreviewPane printPreviewPane = view.printPreviewPane
printPreviewPane.display(template, DataSources.from(model.dataSource, model.options))

Sekarang, saya perlu membuat sebuah MVC untuk mengedit template yang ada. Agar pengguna bisa lebih nyaman dalam meng-edit template, saya akan menggunakan groovy.ui.ConsoleTextEditor bawaan Groovy. ConsoleTextEditor mengandung sebuah JTextPane yang dilengkapi dengan syntax highlighting sehingga sangat berguna untuk menampilkan dokumen teks yang memiliki syntax seperti bahasa pemograman. Sebagai contoh, saya bisa mendefinisikan view seperti berikut ini:

actions {
    action(id: 'cari', name: 'Cari', closure: controller.cari)
    action(id: 'simpan', name: 'Simpan', closure: controller.simpan)
    action(id: 'reset', name: 'Reset', closure: controller.reset)
}

panel(id: 'mainPanel') {
    borderLayout()

    panel(constraints:PAGE_START) {
        flowLayout(alignment: FlowLayout.LEFT)
        comboBox(id: 'namaTemplateFaktur', model: model.namaTemplateFaktur)
        button(action: cari)
    }

    widget(new ConsoleTextEditor(), id: 'inputEditor', constraints: CENTER)

    panel(constraints: PAGE_END) {
        flowLayout(alignment: FlowLayout.LEFT)
        button(action: simpan)
        button(action: reset)
    }
}

Untuk menampilkan template dari database untuk di-edit, saya dapat menggunakan kode program seperti berikut ini di controller:

def cari = {
    execInsideUISync {
        String namaTemplateFaktur = model.namaTemplateFaktur.selectedItem
        if (namaTemplateFaktur) {
            String isi = findTemplateFakturBy(namaTemplateFaktur)?.isi
            TextEditor textEditor = view.inputEditor.textEditor
            DefaultStyledDocument doc = new DefaultStyledDocument()
            doc.setDocumentFilter(new SimpleEscpFilter(doc))
            doc.insertString(0, isi?: '', null)
            textEditor.setDocument(doc)
            textEditor.caretPosition = 0
        }
    }
}

Secara default, ConsoleTextEditor akan melakukan syntax highlighting berdasarkan format Groovy. Karena simple-escp memakai format yang berbeda, saya akan mendefinisikan sebuah DocumentFilter baru yang saya sebut sebagai SimpleEscpFilter yang isinya seperti berikut ini:

class SimpleEscpFilter extends StructuredSyntaxDocumentFilter {

    public static final String VARIABLES = /(?ms:${.*?})/
    public static final String FUNCTIONS = /(?ms:%{.*?})/
    public static final String CODE = /(?ms:{{.*?}})/

    SimpleEscpFilter(DefaultStyledDocument document) {
        super(document)

        StyleContext styleContext = StyleContext.getDefaultStyleContext()
        Style defaultStyle = styleContext.getStyle(StyleContext.DEFAULT_STYLE)

        Style variables = styleContext.addStyle(VARIABLES, defaultStyle)
        StyleConstants.setForeground(variables, Color.GREEN.darker().darker())
        getRootNode().putStyle(VARIABLES, variables)

        Style functions = styleContext.addStyle(FUNCTIONS, defaultStyle)
        StyleConstants.setForeground(functions, Color.BLUE.darker().darker())
        getRootNode().putStyle(FUNCTIONS, functions)

        Style code = styleContext.addStyle(CODE, defaultStyle)
        StyleConstants.setForeground(code, Color.MAGENTA.darker().darker())
        getRootNode().putStyle(CODE, code)
    }

}

StructuredSyntaxDocumentFilter adalah DocumentFilter bawaan Groovy yang melakukan syntax highlighting berdasarkan ekspresi Regex. Pada implementasi di atas, saya memberikan pewarnaan yang berbeda untuk setiap komponen dalam template simple-escp. Bila saya menjalankan program, saya akan memperoleh hasil seperti pada gambar berikut ini:

Tampilan editor

Tampilan editor

Bila pengguna men-klik tombol simpan, saya dapat menyimpan perubahan dengan kode program seperti berikut ini:

def simpan = {
    TemplateFaktur templateFaktur = findTemplateFakturByNama(namaTemplateFaktur)
    if (!templateFaktur) {
       templateFaktur = new TemplateFaktur(nama: model.namaTemplateFaktur.selectedItem)
       persist(templateFaktur)
    }
    templateFaktur.isi = view.inputEditor.textEditor.text
}

Sekarang, saya tidak perlu khawatir lagi harus men-deploy ulang aplikasi hanya karena perubahan kecil di layout percetakan. Bila pengguna tidak memiliki tim IT yang memahami syntax simple-escp, setidaknya saya masih bisa mengirim email berisi template yang sudah dimodifikasi untuk di-copy paste oleh mereka. Ini masih jauh lebih baik daripada harus men-deploy ulang aplikasi 😀

Membuat Tabel Swing Dengan Jumlah Kolom Dinamis Di simple-jpa

Pada simple-jpa, saya dapat mendefinisikan sebuah JTable dengan menggunakan glazedTable() yang didalamnya mengandung satu atau lebih glazedColumn(). Masing-masing glazedColumn() ini mewakili kolom yang akan ditampilkan pada tabel. Bagaimana seandainya bila jumlah kolom di tabel bisa berubah atau dinamis? Sebagai contoh, pada tabel untuk menampilkan jumlah stok tersedia di masing-masing gudang, saya perlu memiliki sebuah kolom jumlah untuk masing-masing gudang. Masalahnya adalah pengguna bisa menambah atau mengurangi jumlah gudang yang ada.

Salah satu kelebihan Swing builder dari Groovy dibandingkan dengan definisi dalam bentuk XML atau sejenisnya adalah Swing builder dapat diprogram! Saya dapat menyisipkan kode program pada builder. Hal ini tidak dapat dilakukan pada deklarasi UI dalam bentuk XML atau sejenisnya. Sebagai contoh, saya dapat mendeklarasikan sebuah JTable dengan menggunakan kode program seperti berikut ini di view:

glazedTable(...) {
  (1..20).each { x ->
    glazedColumn(name: "$x", expression: { x })    
  }
}

Program di atas memakai looping (dapat juga diganti dengan syntax for biasa) untuk membuat 20 kolom. Bila dijalankan, tabel akan terlihat seperti pada gambar berikut ini:

Tabel dengan 20 kolom yang dibuat secara dinamis

Tabel dengan 20 kolom yang dibuat secara dinamis

Dengan memberikan kode program pada builder yang menghasilkan tabel, saya dapat menghasilkan kolom secara dinamis. Kembali ke contoh tabel yang berisi jumlah stok per gudang, agar lebih jelas, anggap saja saya memiliki 3 domain class berupa Gudang, StokProduk, dan Produk. Isinya terlihat seperti berikut ini:

@DomainClass @Entity @Canonical
class Gudang {

   @NotEmpty @Size(min=2, max=50)
   String nama

}

@Embeddable
class StokProduk {

   @NotNull @ManyToOne
   Gudang gudang

   @NotNull
   Integer jumlah

}

@DomainClass @Entity @Canonical
class Produk {

   @NotEmpty @Size(min=2, max=50)
   String nama

   @ElementCollection(fetch=FetchType.EAGER)
   List<StokProduk> stok = []

}

Saya bisa membuat deklarasi tabel menjadi seperti berikut ini:

glazedTable(id: 'table', list: model.produkList) {
  glazedColumn(name: 'Nama', property: 'nama')
  controller.findAllGudang().each { Gudang g ->
    glazedColumn(name: "Qty ${g.nama}", expression: { it.stokUntuk(g)?.jumlah?: 0 }, columnClass: Integer)
  }
}

Contoh tampilan pada program di atas akan terlihat seperti:

Kolom qty gudang yang dibuat secara dinamis

Kolom qty gudang yang dibuat secara dinamis

Pada saat tabel akan ditampilkan, kode program yang memanggil controller.findAllGudang() akan dipanggil untuk membaca daftar gudang dari database. Dengan demikian, jumlah kolom jumlah stok yang dihasilkan akan tergantung pada jumlah gudang di database.

Bagaimana bila seandainya jumlah gudang menjadi banyak sekali suatu hari nanti? Tabel akan tetap muncul, tetapi kolom akan menjadi sangat sempit seperti yang terlihat pada gambar berikut ini:

Tampilan tabel bila tidak muat

Tampilan tabel bila tidak muat

Ini akan membuat tabel sulit dibaca. Karena saya tidak tahu secara persis jumlah kolom yang akan ditampilkan, akan lebih baik bila saya menampilkan scrollbar secara horizontal. Pada Swing, agar scrollbar horizontal ditampilkan di JTable, saya perlu mematikan modus auto resize dengan memberikan autoResizeMode: JTable.AUTO_RESIZE_OFF. Selain itu, karena auto resize tidak lagi bekerja, akan lebih baik bila saya menambahkan pengaturan width. Sebagai contoh, saya mengubah deklarasi tabel saya menjadi seperti berikut ini:

glazedTable(id: 'table', list: model.produkList, autoResizeMode: JTable.AUTO_RESIZE_OFF) {
  glazedColumn(name: 'Nama', property: 'nama', width: [100,200])
  controller.findAllGudang().each { Gudang g ->
    glazedColumn(name: "Qty ${g.nama}", expression: { it.stokUntuk(g)?.jumlah?: 0 },
      columnClass: Integer, width: [80, 100])
  }
}

Sekarang, scrollbar horizontal akan muncul bila ukuran tabel tidak muat, seperti yang terlihat pada gambar berikut ini:

Memunculkan scrollbar horizontal pada tabel

Memunculkan scrollbar horizontal pada tabel

Penggunaan nilai pada width dalam bentuk seperti [100,200] mungkin awalnya agak membingungkan. Nilai 100 akan diberikan sebagai argumen untuk setMinWidth() dan nilai 200 akan diberikan sebagai argumen untuk setPreferredWith(). Jangan lupa bahwa glazedColumn() adalah sebuah TableColumn yang memiliki property seperti minWidth, preferredWidth dan maxWidth. Bila ingin sebuah kolom memiliki ukuran yang statis dan tidak bisa di-resize, maka gunakan angka sebagai nilai pada width seperti width: 200.

Mengatasi Permasalahan N+1 Pada Query Di Hibernate

Apa itu permasalahan N+1? Sebagai contoh, anggap saja saya memiliki sebuah JPA entity seperti berikut ini:

@NamedEntityGraphs([
    @NamedEntityGraph(name='FakturJualOlehSales.Piutang', attributeNodes=[
        @NamedAttributeNode('listItemFaktur'),
        @NamedAttributeNode('piutang')
    ])
])
class FakturJualOlehSales extends FakturJual {

    @NotNull(groups=[Default,InputPenjualanOlehSales]) @ManyToOne
    Konsumen konsumen

    @NotNull(groups=[Default]) @Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    LocalDate jatuhTempo

    @OneToOne(cascade=CascadeType.ALL, orphanRemoval=true, fetch=FetchType.LAZY)
    KewajibanPembayaran piutang

    @OneToOne(cascade=CascadeType.ALL, orphanRemoval=true, fetch=FetchType.LAZY)
    BonusPenjualan bonusPenjualan

    @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, fetch=FetchType.EAGER)
    @JoinTable(name='FakturJual_retur')
    @OrderColumn
    List<PenerimaanBarang> retur = []

    ...

}

Class FakturJualOlehSales memiliki banyak relasi dengan class lainnya. Ini adalah sesuatu yang wajar terutama bila perancangan dilakukan dengan menggunakan metode DDD yang mengedepankan aggregation. Sebagai turunan dari FakturJual, class FakturJualOlehSales memiliki relasi one-to-many dengan ItemFaktur. Class FakturJualOlehSales juga memiliki relasi one-to-one dengan KewajibanPembayaran (untuk mewakili piutang yang ditimbulkan oleh faktur ini) dan BonusPenjualan (untuk mewakili bonus yang diberikan pada faktur ini). Class KewajibanPembayaran selanjutnya memiliki relasi one-to-many dengan Pembayaran. Begitu juga, class BonusPenjualan selanjutnya memiliki relasi one-to-many dengan ItemBarang. Selain itu, class FakturJualOlehSales juga memiliki relasi one-to-many dengan PenerimaanBarang untuk mewakili retur.

Sebuah class dengan relasi yang kompleks, bukan? Tapi saya tidak selalu butuh semua nilai relasi yang ada setiap kali berhadapan dengan FakturJualOlehSales. Sebagai contoh, pada screen untuk entry data faktur, saya tidak perlu menampilkan nilai piutang. Tetapi pada screen untuk memasukkan data pembayaran piutang, saya perlu nilai piutang tetapi tidak perlu informasi seperti bonusPenjualan.

Oleh sebab itu, saya memberikan nilai atribut fetch=FetchType.LAZY pada beberapa atribut agar Hibernate tidak men-query atribut tersebut. Teknik ini disebut lazy loading. Nilai dari atribut piutang atau bonusPenjualan hanya akan di-query pada saat ia diakses. Ini hanya berlaku selama entity masih berada dalam pengelolaan EntityManager. Bila sudah diluar cakupan EntityManager, saya akan memperoleh pesan kesalahan LazyLoadingException yang sangat terkenal.

Cara lain untuk membaca nilai yang lazy adalah dengan menggunakan query JP QL yang melakukan join fetch secara manual. Khusus untuk JPA 2.1, pilihan yang lebih nyaman adalah dengan menggunakan fasilitas named entity graph. Dengan fasilitas ini, saya tidak perlu menghabiskan banyak waktu memikirkan query! Sebagai contoh, saya mendeklarasikan sebuah named entity graph dengan nama FakturJualOlehSales.Piutang yang akan menyertakan nilai atribut piutang pada saat FakturJualOlehSales dibaca dari database.

Berikut adalah contoh kode program yang memakai named entity graph melalui simple-jpa:

FakturJualRepository repo = simplejpa.SimpleJpaUtil.instance
    .repositoryManager.findRepository('FakturJual')

int start = System.currentTimeMillis();
repo.findAllFakturJualOlehSalesFetchPiutang();
int stop = System.currentTimeMillis();

println "Delta = ${stop-start}"

Walaupun kode program di atas terlihat sederhana, tapi kinerjanya tidak memuaskan! Hibernate ternyata mengerjakan sangat banyak query, seperti:

select distinct ... from FakturJual fakturjual0_ left outer join KewajibanPembayaran kewajibanp1_ on fakturjual0_.piutang_id=kewajibanp1_.id left outer join FakturJual_listItemFaktur listitemfa2_ on fakturjual0_.id=listitemfa2_.FakturJual_id where ...

select ... from Produk produk0_ inner join Satuan satuan1_ on produk0_.satuan_id=satuan1_.id left outer join Supplier supplier2_ on produk0_.supplier_id=supplier2_.id where produk0_.id=?

select ... from Produk produk0_ inner join Satuan satuan1_ on produk0_.satuan_id=satuan1_.id left outer join Supplier supplier2_ on produk0_.supplier_id=supplier2_.id where produk0_.id=?

select ... from Produk produk0_ inner join Satuan satuan1_ on produk0_.satuan_id=satuan1_.id left outer join Supplier supplier2_ on produk0_.supplier_id=supplier2_.id where produk0_.id=?

...

select ... from Konsumen konsumen0_ inner join Region region1_ on konsumen0_.region_id=region1_.id left outer join Region region2_ on region1_.bagianDari_id=region2_.id inner join Sales sales3_ on konsumen0_.sales_id=sales3_.id inner join Gudang gudang4_ on sales3_.gudang_id=gudang4_.id where konsumen0_.id=?

select ... from Konsumen konsumen0_ inner join Region region1_ on konsumen0_.region_id=region1_.id left outer join Region region2_ on region1_.bagianDari_id=region2_.id inner join Sales sales3_ on konsumen0_.sales_id=sales3_.id inner join Gudang gudang4_ on sales3_.gudang_id=gudang4_.id where konsumen0_.id=?

select ... from Konsumen konsumen0_ inner join Region region1_ on konsumen0_.region_id=region1_.id left outer join Region region2_ on region1_.bagianDari_id=region2_.id inner join Sales sales3_ on konsumen0_.sales_id=sales3_.id inner join Gudang gudang4_ on sales3_.gudang_id=gudang4_.id where konsumen0_.id=?

...

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id=?

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id=?

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id=?

...

select ... from kewajibanpembayaran_items listpembay0_ left outer join BilyetGiro bilyetgiro1_ on listpembay0_.bilyetGiro_id=bilyetgiro1_.id where listpembay0_.KewajibanPembayaran_id=?

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id=?

select ... from kewajibanpembayaran_items listpembay0_ left outer join BilyetGiro bilyetgiro1_ on listpembay0_.bilyetGiro_id=bilyetgiro1_.id where listpembay0_.KewajibanPembayaran_id=?

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id=?

select ... from kewajibanpembayaran_items listpembay0_ left outer join BilyetGiro bilyetgiro1_ on listpembay0_.bilyetGiro_id=bilyetgiro1_.id where listpembay0_.KewajibanPembayaran_id=?

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id=?

Permasalahan ini sering kali disebut sebagai permasalahan N+1. Nilai 1 adalah query pertama untuk SELECT * FROM x. Setelah itu, untuk N jumlah record yang diperoleh dari query pertama, lakukan query lain untuk membaca nilai di tabel lain seperti SELECT * FROM y WHERE y.x_id = x.id. Dengan demikian, semakin banyak jumlah record yang hendak dibaca, semakin banyak juga query tambahan yang perlu dilakukan. Permasalahan N+1 biasanya adalah pemborosan kinerja yang tak seharusnya terjadi karena ia dapat digantikan dengan join dan/atau subquery.

Sebagai patokan, saya akan menyimpan hasil eksekusi program di atas dan membuat versi grafik-nya yang terlihat seperti pada gambar berikut ini:

Grafik yang menunjukkan kinerja awal

Grafik yang menunjukkan kinerja awal

Pada grafik di atas, pada eksekusi pertama kali, saya akan memperoleh penalti kinerja yang cukup tinggi. Ini adalah karakteristik dari simple-jpa. Selain itu, hal ini juga ditambah lagi dengan server database yang belum memiliki cache hasil query.

Saya akan mulai dengan berusaha menghilangkan query N+1 ke tabel konsumen. FakturJualOlehSales memiliki hubungan @ManyToOne dengan konsumen. Saya menemukan bahwa dengan menyertakan definisi atribut konsumen pada named entity graph, maka query N+1 untuk relasi ke konsumen tidak akan muncul lagi. Perubahan yang saya lakukan menyebabkan definisi named entity graph saya menjadi seperti berikut ini:

@NamedEntityGraph(name='FakturJualOlehSales.Piutang', attributeNodes=[
    @NamedAttributeNode('listItemFaktur'),
    @NamedAttributeNode(value='konsumen', subgraph='konsumen'),
    @NamedAttributeNode('piutang')
], subgraphs = [
    @NamedSubgraph(name='konsumen', attributeNodes=[
        @NamedAttributeNode('region'),
        @NamedAttributeNode('sales')
    ])
])

Sekarang, nilai untuk konsumen tidak akan di-query satu per satu lagi, melainkan diperoleh melalui join pada saat mengambil nilai FakturJualOlehSales seperti yang terlihat pada SQL yang dihasilkan oleh Hiberate:

select distinct ... from FakturJual fakturjual0_ ... left outer join Konsumen konsumen2_ on fakturjual0_.konsumen_id=konsumen2_.id left outer join Region region3_ on konsumen2_.region_id=region3_.id left outer join Sales sales4_ on konsumen2_.sales_id=sales4_.id ...

Sayang sekali saya tidak dapat melakukan hal yang sama untuk Produk karena listItemFaktur adalah sebuah @ElementCollection yang tidak dianggap sebuah entity sehingga tidak dapat diatur melalui named entity graph.

Sampai disini, apakah versi yang memakai left outer join akan lebih cepat dari versi N+1? Saya akan kembali melakukan sedikit percobaan dan menemukan hasil seperti yang terlihat pada gambar berikut ini:

Grafik yang menunjukkan kinerja setelah perubahan.

Grafik yang menunjukkan kinerja setelah perubahan.

Pada grafik di atas, terlihat bahwa perubahan yang saya lakukan memberikan sedikit peningkatan kinerja (sekitar 8%). Hal ini karena pada rancangan saya, tidak banyak yang bisa dioptimalkan dengan cara seperti ini.

Sebagai langkah berikutnya, saya akan menghindari query N+1 untuk retur dengan menjadikannya sebagai 1 query tunggal yang terpisah. Saya dapat menggunakan @Fetch(FetchMode.SUBSELECT) untuk keperluan seperti ini. Sebagai informasi, @Fetch adalah annotation khusus dari Hibernate dan bukan merupakan bagian dari JPA! Sebagai contoh, saya mengubah kode program menjadi seperti berikut ini:

class FakturJualOlehSales extends FakturJual {

   ...

   @OneToMany(...) @JoinTable()
   @Fetch(FetchMode.SUBSELECT)
   List<PenerimaanBarang> retur = []

   ...

}

Konfigurasi di atas akan menyebabkan seluruh query N+1 yang tadinya seperti:

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id=?

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id=?

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id=?

...

digantikan oleh sebuah query tunggal yang isinya seperti berikut ini:

select ... from FakturJual_retur retur0_ inner join PenerimaanBarang penerimaan1_ on retur0_.retur_id=penerimaan1_.id inner join Gudang gudang2_ on penerimaan1_.gudang_id=gudang2_.id where retur0_.FakturJual_id in (select fakturjual0_.id from FakturJual fakturjual0_ ...)

Saya segera menambahkan @Fetch(FetchMode.SUBSELECT) pada beberapa atribut lainnya yang memiliki permasalahan N+1. Setelah itu, saya mencoba menjalankan program dan kini memperoleh kinerja seperti yang terlihat pada grafis berikut ini:

Grafik yang menunjukkan kinerja setelah perubahan.

Grafik yang menunjukkan kinerja setelah perubahan.

Kali ini saya memperoleh peningkatan kinerja yang cukup drastis karena saya menemukan banyak atribut yang bisa dioptimalkan melalui @Fetch(FetchMode.SUBSELECT). Sebagai hasil akhir, setelah berupaya menghilangkan sebagian besar query N+1, saya memperoleh peningkatan kinerja sebesar 56%. Tidak ada perubahan yang perlu saya lakukan pada kode program yang membaca entitas; ia tetap merupakan sebuah baris yang polos seperti findAllFakturJualFetchPiutang().

Memakai Named Entity Graph Di simple-jpa 0.7

Salah satu fitur baru pada simple-jpa 0.7 adalah dukungan named entity graph. Fasilitas named entity graph yang sudah ada sejak JPA 2.1 memungkinkan pengguna untuk menentukan apa saja atribut yang akan di-load sesuai dengan kebutuhan. Sebagai contoh, saya memiliki sebuah class Faktur yang memiliki relasi one-to-many dengan ItemFaktur, Bonus dan Pengiriman. Pada definisi class secara default, semua relasi tersebut akan di-fetch secara LAZY, seperti pada contoh berikut ini:

@DomainClass @Entity @Canonical
class Faktur {

   @NotBlank
   String nomor

   @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
   Set<ItemFaktur> daftarItemFaktur = new HashSet<>()

   @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
   Set<Bonus> daftarBonus = new HashSet<>()

   @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
   Set<Pengiriman> daftarPengiriman = new HashSet<>()

}

Bila saya memakai salah satu atribut di luar transaksi, seperti pada kode program berikut ini:

Faktur faktur = repo.findFakturByNomor("FA-001")
println faktur.daftarItemFaktur

Saya akan memperoleh pesan kesalahan org.hibernate.LazyInitializationException bila memakai provider Hibernate. Hal ini karena atribut yang di-fetch secara LAZY tidak ikut diambil melalui JOIN ke tabel bersangkutan di query SQL.

Salah satu penyelesaian yang mungkin adalah menambahkan FetchType.EAGER pada seluruh asosiasi yang ada. Akan tetapi ini akan menimbulkan masalah kinerja karena query yang diberikan akan melalukan join ke tiga tabel lain (masing-masing untuk ItemFaktur, Bonus dan Pengiriman).

Saya jarang sekali membutuhkan ketiganya secara bersamaan. Pada layar untuk pengiriman, saya hanya membutuhkan ItemFaktur dan Pengiriman. Pada layar untuk bonus, saya hanya membutuhkan ItemFaktur dan Bonus. Oleh sebab itu, saya bisa mendefinisikan dua entity graph, seperti pada contoh berikut ini:

@NamedEntityGraphs([
   @NamedEntityGraph(name="Faktur.Pengiriman", attributeNodes=[
      @NamedAttributeNode("daftarItemFaktur"),
      @NamedAttributeNode("daftarPengiriman")
   ]),
   @NamedEntityGraph(name="Faktur.Bonus", attributeNodes=[
      @NamedAttributeNode("daftarItemBonus"),
      @NamedAttrbiuteNode("daftarPengiriman")
   ])
])
@DomainClass @Entity @Canonical
class Faktur {
   ...
}

Pada deklarasi Faktur di atas, saya membuat dua entity graph yang bernama Faktur.Pengiriman dan Faktur.Bonus.

Saya dapat menambahkan FetchXXX pada dynamic finders simple-jpa untuk memakai salah satu entity graph yang ada. Sebagai contoh, untuk memakai entity graph Faktur.Pengiriman, saya dapat menggunakan kode program seperti:

Faktur faktur = repo.findFakturByNomorFetchPengiriman("FA-001")
println faktur.daftarItemFaktur
println faktur.daftarPengiriman
println faktur.daftarBonus // <-- ERROR: LazyInitializationException

Saya dapat mengakses daftarItemFaktur dan daftarPengiriman dengan baik. Walaupun demikian, tidak terjadi join ke seluruh tabel yang berelasi. Hal ini dapat dibuktikan karena saya tetap mendapatkan kesalahan bila mengakses daftarBonus karena atribut tersebut tidak didefinisikan pada entity graph Faktur.Pengiriman.

Untuk memakai entity graph Faktur.Bonus, saya dapat menggunakan kode program seperti:

Faktur faktur = repo.findFakturByNomorFetchBonus("FA-001")
println faktur.daftarItemFaktur
println faktur.daftarBonus
println faktur.daftarPengiriman // <-- ERROR: LazyInitializationException

Saya dapat mengakses daftarItemFaktur dan daftarBonus dengan baik. Tetapi, saya tidak boleh membaca daftarPengiriman karena atribut tersebut tidak terdaftar pada entity graph Faktur.Bonus.

Karena masih terbilang baru, implementasi named entity graph pada provider Hibernate masih memiliki sedikit masalah terutama bila berhubungan dengan inheritance. Terkadang saya menjumpai masalah seperti Hibernate menghasilkan query invalid yang memiliki klausa CROSS JOIN LEFT JOIN. Untuk mengatasi hal tersebut, sementara ini, saya memakai Hibernate yang telah dimodifikasi dengan mengubah baris yang memakai Hibernate JPA di BuildConfig.groovy menjadi seperti berikut ini:

griffon.project.dependency.resolution = {
   ...
   dependencies {
       runtime "jockihendry:hibernate-entitymanager:4.3.7-EXPERIMENT"
       ...
   }
   ...
}

Belajar Membuat Dialog Login Di Griffon

Pada panduan sederhana ini, saya akan membuat sebuah aplikasi desktop yang menampilkan dialog login. Program baru akan ditampilkan bila dialog di-isi dengan nama pengguna dan password yang benar. Untuk keperluan tersebut, saya akan menggunakan Griffon 1.5 dan JXLoginPane dari SwingX. Untuk melakukan akses database, saya akan memakai simple-jpa 0.6. Karena simple-jpa sudah menyertakan SwingX builder, saya tidak perlu men-install swingx-builder lagi.

Langkah pertama yang saya lakukan adalah membuat sebuah proyek baru dengan perintah berikut ini:

C:\> griffon create-app login
C:\> cd login
C:\login> _

Setelah proyek baru dibuat, saya akan men-install plugin simple-jpa 0.6 dengan menggunakan perintah berikut ini:

C:\login> griffon install-plugin simple-jpa 0.6

Saya perlu menyiapkan koneksi ke sebuah database MySQL di komputer lokal dengan memberikan perintah berikut ini:

C:\login> griffon create-simple-jpa -user=snake -password=12345 -database=latihanLogin -rootPassword=admin12345

Bila ingin memakai database yang sudah ada, saya perlu menambahkan perintah -skip-database pada perintah di atas.

Karena terbiasa memakai IDE, saya akan membuat proyek IntelliJ IDEA dengan memberikan perintah berikut ini:

C:\login> griffon integrate-with --idea

Setelah ini, saya membuka file proyek yang dihasilkan dengan memilih menu File, Open… di IntelliJ IDEA.

Saya kemudian membuka file LoginController.groovy dan mengubahnya menjadi seperti berikut ini:

package login

import domain.DatabaseLoginService
import org.jdesktop.swingx.JXLoginPane

class LoginController {

    def view

    void mvcGroupInit(Map args) {
        DatabaseLoginService loginService = new DatabaseLoginService()
        loginService.buatUserDefault()
        execInsideUISync {
            JXLoginPane panel = new JXLoginPane(loginService)
            JXLoginPane.Status status = JXLoginPane.showLoginDialog(app.windowManager.getStartingWindow(), panel)
            if (status != JXLoginPane.Status.SUCCEEDED) {
                app.shutdown()
            }
        }
    }

}

Pada kode program di atas, saya memakai JXLoginPane dari SwingX. Selain itu, saya menggunakan app.windowManager.getStartingWindow() untuk mendapatkan JFrame yang biasanya ditampilkan pertama kali (sesuai dengan konfigurasi di Application.groovy).

Berikutnya, saya perlu membuat kode program untuk DatabaseLoginService yang akan memeriksa apakah login sukses atau tidak berdasarkan informasi yang tersimpan di database. Ini adalah kanditat yang tepat untuk services. Griffon mendukung application services dengan perintah create-service. Di Griffon, services adalah artifact sama seperti controller. Akan tetapi, agar sederhana, saya tidak akan menggunakan fasilitas dari Griffon tersebut. Saya akan menganggap DatabaseLoginService sebagai domain service (bila mengikuti domain driven design, domain juga memiliki services). Untuk itu, saya membuat file src\main\domain\DatabaseLoginService.groovy yang isinya seperti berikut ini:

package domain

import org.jdesktop.swingx.auth.LoginService
import simplejpa.transaction.Transaction

@Transaction
class DatabaseLoginService extends LoginService {

    @Override
    boolean authenticate(String nama, char[] password, String server) throws Exception {
        return false
    }

    void buatUserDefault() {}

}

Setelah itu, saya menambahkan import domain.DatabaseLoginService pada file LoginController.groovy.

Sampai disini, bila saya menjalankan kode program, saya akan memperoleh tampilan seperti pada gambar berikut ini:

Tampilan Dialog Login

Tampilan Dialog Login

Bila saya mengisi dengan nama pengguna atau password yang saya, saya akan memperoleh tampilan seperti pada gambar berikut ini:

Tampilan Dialog Login Bila Terjadi Kesalahan

Tampilan Dialog Login Bila Terjadi Kesalahan

Apapun yang saya isi, login tidak akan pernah sukses, karena saat ini method DatabaseLoginService.authenticate() selalu mengembalikan nilai false.

Saya akan membuat sebuah domain class yang mewakili pengguna. Class ini akan saya beri nama Pengguna. Untuk itu, saya memilih menu Tools, Griffon, Run Target, lalu mengisinya dengan:

create-domain-class Pengguna

Setelah itu, saya mengubah kode program Pengguna.groovy yang dihasilkan menjadi seperti berikut ini:

package domain

import groovy.transform.*
import simplejpa.DomainClass
import javax.persistence.*
import javax.validation.constraints.*
import org.hibernate.validator.constraints.*
import java.security.MessageDigest

@DomainClass @Entity @Canonical
class Pengguna {

    @NotBlank @Size(min=2, max=50)
    String nama

    @NotNull
    byte[] passwordHash

    private byte[] getMD5(String plain) {
        MessageDigest digester = MessageDigest.getInstance('MD5')
        digester.update(plain.bytes)
        digester.digest()
    }

    void setPassword(String password) {
        passwordHash = getMD5(password)
    }

    boolean login(String password) {
        if (password.isAllWhitespace()) return false 
        Arrays.equals(passwordHash, getMD5(password))
    }

}

Pada class di atas, saya tidak menyimpan password secara langsung melainkan hanya MD5 hash-nya saja. Walaupun tidak sangat aman, menyimpan dalam bentuk MD5 hash jauh lebih baik daripada menyimpan password apa adanya. Hal ini karena orang yang melihat isi tabel secara langsung tetap tidak bisa mengetahui apa password yang harus diketik. Saya memilih menyimpan passwordHash sebagai byte[]. Secara default, Hibernate JPA akan memetakan byte[] dengan sebuah kolom BLOB di tabel MySQL.

Untuk memastikan tidak ada yang salah pada class di atas, saya akan membuat sebuah unit test dengan memberikan perintah berikut ini:

create-unit-test Pengguna

Saya kemudian mengisi file PenggunaTests yang dihasilkan menjadi seperti berikut ini:

package login

import domain.Pengguna
import griffon.test.*

class PenggunaTests extends GriffonUnitTestCase {

    protected void setUp() {
        super.setUp()
    }

    protected void tearDown() {
        super.tearDown()
    }

    void testLogin() {
        Pengguna pengguna = new Pengguna(nama: 'Solid')
        pengguna.setPassword('Snake')

        // Kasus password benar
        assertTrue(pengguna.login('Snake'))

        // Kasus password salah
        assertFalse(pengguna.login('Liquid Snake'))
        assertFalse(pengguna.login(''))
    }
}

Saya kemudian menghapus file LoginControllerTests.groovy dan LoginModelTests.groovy (yang dihasilkan oleh Griffon) karena mereka tidak berisi test case dan tidak diperlukan pada proyek latihan ini.

Berikutnya saya perlu menjalankan unit test. Untuk itu, saya memberikan perintah berikut ini:

test-app unit:

Bila semuanya sesuai dengan harapan, saya akan harus memperoleh hasil seperti berikut ini:

...
-------------------------------------------------------
Running 1 unit test...
Running test login.PenggunaTests...PASSED
Tests Completed in 562ms ...
-------------------------------------------------------
Tests passed: 1
Tests failed: 0
-------------------------------------------------------

Setelah yakin bahwa kode program Pengguna sudah dapat berfungsi sebagaimana seharusnya, kini saat yang tepat untuk mengubah kode program DatabaseLoginService menjadi seperti berikut ini:

package domain

import org.jdesktop.swingx.auth.LoginService
import simplejpa.transaction.Transaction

@Transaction
class DatabaseLoginService extends LoginService {

    @Override
    boolean authenticate(String nama, char[] password, String server) throws Exception {
        Pengguna pengguna = findPenggunaByNama(nama)
        pengguna?.login(String.valueOf(password))

    }

    void buatUserDefault() {
        if (!findPenggunaByNama('Solid')) {
            Pengguna me = new Pengguna(nama: 'Solid')
            me.password = 'Snake'
            persist(me)
        }
    }

}

Pada kode program di atas, method authenticate() akan dipanggil untuk memeriksa apakah password benar atau salah. Selain itu, juga ada method buatUserDefault() yang akan membuat sebuah pengguna dengan nama dan password default (bila belum ada) sehingga setidaknya saya bisa login ke aplikasi walaupun database masih kosong.

Sekarang, saya dapat mencoba menjalankan aplikasi. Pada proyek latihan ini, saya bisa masuk ke dialog utama hanya bila mengisi nama pengguna dengan Solid dan password berupa Snake.

Implementing Aggregate Root In JPA: @OneToMany or @ElementCollection?

In domain driven design, an aggregate root contains one or more entities that represent a bounded context. Those entities should be only manipulated from their aggregate root. In UML class diagram, this is represented as composition (a filled diamond in relationship). Note that UML class diagram also has a concept of aggregation (a hollow diamond in a relationship). Despite similarity in the name, the contained part in UML aggregation can exists without its container. Thus, it is not something like DDD’s aggregate root in which the contained part should not exist without their container.

For example, the following is an UML class diagram with composition:

Composition in UML Class Diagram

Composition in UML Class Diagram

Invoice is the root aggregate that manages LineItem. Every LineItem is a value object. No one should be able to add or delete LineItem directly without obtaining an Invoice first. Because instances of LineItem are value objects, they don’t have a global identity. In the other side, instances of Invoice class are entities so they can be searched by a global identity (for example: invoice number). Each LineItem is associated with a Product entity. This is valid in domain driven design though some people will recommend using value object instead. The value object will store the Product identity (for example: product number). See this article for more information: http://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_2.pdf.

The question is how to implement the classes in our diagram using JPA? Well, there are several possibilities with surprising caveats. The recommended way for @OneToMany relationship is bidirectional with owner on the many side. But this violates our aggregate root rules. No one should select existing LineItem or add new LineItem directly! They must manipulate LineItem from Invoice only. This can be solved by using unidirectional @OneToMany with @JoinColumn. But it is still not a containment. To implement the real containment, use @ElementCollection and mark all value objects as @Embeddable. Note that @ElementCollection is introduced in JPA 2.

For example, this is an implementation using @OneToMany and @JoinColumn in Groovy + Hibernate +simple-jpa:

import ...

@DomainClass @Entity @Canonical
class Invoice {

    @NotEmpty
    String number

    @OneToMany @JoinColumn
    List<LineItem> lineItems = []

    public void add(LineItem lineItem) {
        lineItems << lineItem
    }

}


@DomainClass @Entity @Canonical
class LineItem {

    @NotNull @ManyToOne
    Product product

    @NotNull
    Integer quantity

}


@DomainClass @Entity @Canonical
class Product {

    @NotEmpty
    String name

}

The code above will produce the following database tables:

Tables for @OneToMany with @JoinColumn

Tables for @OneToMany with @JoinColumn

Table for LineItem has an identity. This primary key is required for one to many relationships. In our case, it is pretty useless because LineItem should only be identified with their Invoice. The identity of LineItem has no meaning in global context.

This code will create several objects based on our domain classes:

def productA = new Product('Product A')
persist(productA)
def productB = new Product('Product B')
persist(productB)

def invoice = new Invoice('Invoice-01')
invoice.add(new LineItem(productA, 10))
invoice.add(new LineItem(productB, 20))
persist(invoice)

But if you execute the code above, you will get org.hibernate.TransientObjectException!! Every single LineItem must be persisted before persisting Invoice. This is a bit annoying. It doesn’t show that our Invoice is the boss – the aggregate root. To solve this problem, you will need to add @OneToMany(cascade=CascadeType.ALL) to Invoice.lineItems:

...
@OneToMany(cascade=CascadeType.ALL) @JoinColumn
List<LineItem> lineItems = []
...

Now if you run the code, Hibernate will actually perform the following SQL queries:

insert into Product (createdDate, deleted, modifiedDate, name, id) values (?, ?, ?, ?, ?)
insert into Product (createdDate, deleted, modifiedDate, name, id) values (?, ?, ?, ?, ?)
insert into Invoice (createdDate, deleted, modifiedDate, number, id) values (?, ?, ?, ?, ?)
insert into LineItem (createdDate, deleted, modifiedDate, product_id, quantity, id) values (?, ?, ?, ?, ?, ?)
insert into LineItem (createdDate, deleted, modifiedDate, product_id, quantity, id) values (?, ?, ?, ?, ?, ?)
update LineItem set lineItems_id=? where id=?
update LineItem set lineItems_id=? where id=?

Note that Hibernate will issue both insert and update query for every LineItem. So, if I insert 10 LineItem objects, Hibernate will issue 20 queries: 10 insert queries and another 10 update queries. Isn’t this a bit overwhelming?

What happened if I update existing LineItem or insert new LineItem such as shown in the following code:

def invoice = findInvoiceByNumberFetchComplete('Invoice-01')
invoice.lineItems[0].product = findProductByName('Product B')
invoice.lineItems[0].quantity = 999
invoice.lineItems.remove(1)
merge(invoice)

If I run the code, Hibernate will execute the following queries:

...
update LineItem set createdDate=?, deleted=?, modifiedDate=?, product_id=?, quantity=? where id=?
update LineItem set lineItems_id=null where lineItems_id=? and id=?
...

Hibernate only issue two update queries! Even when we remove the second LineItem in our Invoice in the source code, Hibernate doesn’t actually remove it from database. Hibernate only set LineItem.lineItems_id to null so in the next select query, we will not see that second item. To force Hibernate to delete the second item, add orphanRemoval=true to @OneToMany as shown in the following code:

...
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true) @JoinColumn
List<LineItem> lineItems = []
...

The updated mapping will generate the following queries:

update LineItem set createdDate=?, deleted=?, modifiedDate=?, product_id=?, quantity=? where id=?
update LineItem set lineItems_id=null where lineItems_id=? and id=?
delete from LineItem where id=?

While it is possible to implement aggregate roots and their managed objects using @OneToMany, developers can still manipulate objects directly without their aggregate roots. Our LineItem is required to have an identity in the mapping but we know that value objects shouldn’t have a global identity. Now imagine if you have several genius kids in your team who don’t like to follow your domain driven design rules! They code in whatever direction they want because they think they can!! When the system grows larger, some of the genius kids resigned and new kids join your team. They even do a big refactoring. At the end, you may have a big ball of mud. See http://laputan.org/mud/ for more information about this anti pattern.

To create a more restricted implementation, you can use @ElementCollection. One of the possible implementation using @ElementCollection will be:

@DomainClass @Entity @Canonical
class Invoice {

    @NotEmpty
    String number

    @ElementCollection
    List<LineItem> lineItems = []

    public void add(LineItem lineItem) {
        lineItems << lineItem
    }

}

@Embeddable @Canonical
class LineItem {

    @NotNull @ManyToOne
    Product product

    @NotNull
    Integer quantity

}

@DomainClass @Entity @Canonical
class Product {

    @NotEmpty
    String name

}

Note that LineItem is annotated with @Embeddable not @Entity. In JPA, @Embeddable is used for value object. @Embeddable class doesn’t have an identity just like what a value object should be. But this imposes a limitation: an embeddable class can have collections, but if it is embedded in another embeddab class, it can’t have collections. This is an important limitation if you’re going to implement all managed classes as @Embeddable!

The new domain classes will produce the following tables:

Tables for @ElementCollection and @Embeddable

Tables for @ElementCollection and @Embeddable

To insert new objects, I use the same code as in @OneToMany mapping. While it is the same code, Hibernate now generates different queries:

insert into Product (createdDate, deleted, modifiedDate, name, id) values (?, ?, ?, ?, ?)
insert into Product (createdDate, deleted, modifiedDate, name, id) values (?, ?, ?, ?, ?)
insert into Invoice (createdDate, deleted, modifiedDate, number, id) values (?, ?, ?, ?, ?)
insert into Invoice_lineItems (Invoice_id, product_id, quantity) values (?, ?, ?)
insert into Invoice_lineItems (Invoice_id, product_id, quantity) values (?, ?, ?)

Note that there are no more annoying updates like in @OneToMany mapping. How about update and delete? The same code now will generate the following queries:

delete from Invoice_lineItems where Invoice_id=?
insert into Invoice_lineItems (Invoice_id, product_id, quantity) values (?, ?, ?)

Wait, this is a big difference!! Hibernate will always delete all LineItem records from our Invoice before re-inserting old, updated and new records. Hibernate must do this because our LineItem doesn’t have an identity. Hibernate doesn’t know which records to update or delete if there is no identity on the records.
This behavior is acceptable in small collection. But this is not efficient for a large collection because Hibernate will re-create all records even when only one LineItem is changed. To avoid such case, you can use @OrderColumn in the List, such as:

...
@ElementCollection @OrderColumn
List<LineItem> lineItems = []
...

This new mapping will add a new field to Lineitem table that stores index number (remember that List is a number indexed collection).

Tables for @ElementCollection with @OrderColumn

Tables for @ElementCollection with @OrderColumn

With this mapping, the update and delete code will execute the following queries:

delete from Invoice_lineItems where Invoice_id=? and lineItems_ORDER=?
update Invoice_lineItems set product_id=?, quantity=? where Invoice_id=? and lineItems_ORDER=?

Now, Hibernate will not delete all line item but only delete the second line item (because it was deleted in the following code: invoice.lineItems.remove(1)).

A sample application for simple-jpa: laundry

The source code for this article can be found at https://github.com/JockiHendry/simple-jpa-demo-laundry

Griffon is a presentation layer framework for Java desktop-based application. This framework provides infrastructure to implement MVC pattern in desktop application. Griffon is best used with Groovy programming language although this may be subjective. In fact, Groovy is the default language for Griffon’s artifacts. Groovy is a Ruby-like dynamic programming language for Java platform. Example of Groovy’s cool features are closures (lambda expression) and metaprogramming. While it is a bit slower, code written in Groovy is much more straightforward and simpler than code written in old Java. But this may be changing in the future as the upcoming release of Java will introduce lambda expression.

Griffon is an application framework for presentation tier. Modern enterprise applications will likely have multiple tiers, such as presentation tier, business logic tier, and data access tier. In such architecture, Griffon based desktop application is the presentation tier. Presentation tier doesn’t have business logic. Instead, they invoke business logic in other tiers by using REST or SOAP. Griffon is doing a great job here.

Unfortunately, not all desktop applications are ‘thin client’. Some may want to access database and process the data directly. They have both presentation and business logic. While it is not attractive from the point of view of system architecture, ‘fat client’ is often effective for small organization with limited resources.

Does Griffon support fat desktop applications? Yes, in fact, Griffon supports a wide range implementation of desktop applications through its plugins. Griffon has a lot of plugins. For example, to create a desktop application that access database directly, we can use Griffon’s gsql plugin, hibernate4 plugin, jpa plugin, etc. And, of course, simple-jpa plugin is one of them. What is simple-jpa? It is something like JPA plugin but integrated deep into Griffon and exploits Groovy’s potential. simple-jpa is not a lightweight plugin. It has custom Swing’s nodes, validation and presentation of validation errors, scaffolding, and many mores. So, why use simple-jpa? Because it is the simplest way to create real life domain based desktop application in Griffon.

As a relatively new technology, can Groovy, Griffon and simple-jpa produce working application painlessly in real life? Yes, at least in my case! In this article, I’m introducing a sample application developed using these technologies. User interface and constants are declared using Bahasa Indonesia to reflect the requirements in my country. This sample application manages orders for laundry business. It records customers, orders and their status. Orders can be in one of the following states: received, in-progress, done, and delivered. To keep things simple, this sample application doesn’t include identity managements (login, user & privileges managements).

When user starts the application, Hibernate JPA will recreate database tables based on domain entities and executes griffon-app\resources\import.sql to populate tables with predefined data. This is configured in griffon-app\meta-inf\persistence.xml. Populating tables with predefined data every time application is launched makes manual testing become easy. Plugin simple-jpa also supports similar features for integration testing by using dbUnit but it is not used in this sample application.

Customer Screen

Customer Screen

Display Orders From Customer Screen

Display Orders From Customer Screen

There are two types of clients: corporate and outsider (or personal). User can filter customers by name or member type. They can also display orders details on selected customer. I use mvcPopupButton() node from simple-jpa to generate a JButton that will display another MVCGroup when clicked.

Order Screen

Order Screen

The add order view is quite complex. It has a button to select customer. This will invoke the same MVCGroup that is used to display customers in the previous menu.

Add Line Items From Order Screen

Add Line Items From Order Screen

One order has one or many line items. Clicking on the ‘add line items’ button will display another MVCGroup so user can add or edit line items here.

Find Job From Line Item View

Find Job From Line Item View

Every line item is associated with a job. Every job has a category (such as children, gentlemen, or ladies) and a job type (such as dry cleaning, laundry, or pressing). To help user in finding the right job, the sample application allow searching by job’s name, job’s category or job’s type. Another useful feature is that it will remember the last search criteria so user doesn’t need to enter the same search criteria in the next invocation.

Express Order Will Double The Cost

Express Order Will Double The Cost

Business rule states that express orders will double the cost (increase by 100%). So, if user checks on express checkbox, the total amount will be increased by 100%. There are also hidden text fields in this screen. When user selects one of the payment methods, new text fields will appear so he can enter required information. For example, if user selects credit card payment, he must enter credit card number. If user select signed bill payment, he must enter the amount of down payment.

Invoice Preview Will Be Displayed After Saving New Order

Invoice Preview Will Be Displayed After Saving New Order

When user clicks on save button, a preview window for invoice will be displayed. User can then print this invoice or save it as pdf file. The sample application use Jasper Reports to generate and preview all invoices and reports.

Order In-Progress Screen

Order In-Progress Screen

Delivery Screen

Delivery Screen

The last step of orders processing is delivery. This screen will display balance due. User can also click on status button to display more information about orders’ status.

Displaying Order Status Detail From Delivery Screen

Displaying Order Status Detail From Delivery Screen

History Screen

History Screen

The history screen will display all orders. User can search them by order number, customer’s name, or order’s state.

Day-End Closing Screen

Day-End Closing Screen

Day end closing menu will display daily total amount of cash, credit card or debit card transaction. User can use the values to confirm balance in cash drawer and card terminals for that day.

Selecting Report To Display

Selecting Report To Display

Filtering Data For Report

Filtering Data For Report

Reports

Reports

When user selects a report to display, he will be presented by a dialog where he can add filter criteria for that report.

Maintenance Menu

Maintenance Menu

Maintenance menu is an example of how to create drop down menu in Griffon.

That is a pretty long introduction. For convenience’ sake, the following class diagram shows domain classes in this sample application:

UML Class Diagram

UML Class Diagram

So, what’s next? Explore the code! See how Groovy, Griffon and simple-jpaplugin produces such a simple and easy to understand code. They will increase the agility of your project.

What’s New In simple-jpa 0.6?

One of the major changes in simple-jpa 0.6 is upgrade from JPA 2.0 to 2.1. This allow the implementation of generate-schema and fetchGraph/loadGraph in finders config. The next major change is @Transaction now can be used in any classes inside domain packages. This means any domain classes can act as repository if they have @Transaction on them. In the previous version, only Griffon’s artifacts such as controllers and services that can have dynamic finders on them.

The new generate-schema command will generate database objects to database or SQL scripts based on current domain models. For example, to drop existing tables and recreate new tables based on current domain models to JDBC connection defined in persistence.xml, you can use the following command:

griffon generate-schema -target=database -action=drop-and-create

By default, projects that use simple-jpa will recreate database objects (such as tables and its foreign keys) when they are launched. This mean generating database objects to database manually is unusual.

It is more typical to use generate-schema to generate SQL scripts that can be executed on another database (such as production database). generate-schema can generate two SQL scripts, one for drop statements and another one for create statements. For example, you can use the following command to generate SQL scripts based on current domain models:

griffon generate-schema -target=script -action=drop-and-create
                        -dropTarget=drop.sql -createTarget=create.sql

The command above will generate two files in current project directory: drop.sql containing SQL drop statements and create.sql containing SQL create statements.

simple-jpa now supports JPA 2.1 entity graphs. To use entity graphs, you must define a named entity graph (using @NamedEntityGraph annotation) or define it programmatically. For example, the following entity declares a named entity graph called StudentScores:

@DomainClass @Entity @TupleConstructor
@ToString(excludes = 'scores')
@NamedEntityGraph(name='StudentScores',
    attributeNodes = [@NamedAttributeNode('scores')]
)
class Student {

    String nama

    @OneToMany(mappedBy='student')
    List scores= []

}

By default, collections fetching strategy is lazy. For example, the following finder will only select from student table in database:

findAllStudent()

If you are passing the student entities outside transaction scope and trying to get their scores, you will get the famous org.hibernate.LazyInitializationException. To avoid that problem, you can instruct JPA provider to fetch scores for student (as defined in named entity graph) using the following code:

findAllStudent([loadGraph: 'StudentScores'])

simple-jpa 0.6 supports both fetchGraph and loadGraph. In Hibernate JPA, they do the same thing. This behaviour may be different in other JPA providers. For example, in EclipseLink, fetchGraph will not fetch unlisted attributes (making them lazy) while loadGraph will use fetchType specified in mapping.

To use dynamic finders in domain classes, they must be annotated with @Transaction in class level. For example, the following code implements repository pattern:

package domain.repository

...

@Transaction
class ProductRepository {

  public Produk save(Product product) {
     if (findProductByName(product.name)) {
        throw new DuplicateData(product)
     }
     persist(product)
     product
  }

}

Because simple-jpa always injects finders as public methods, the following code is also possible:

ProductRepository productRepo = new ProductRepository()
Product p = productRepo.findProductByName(name)