Mencetak ‘Object’ Dengan JasperReports: Merancang Laporan


Sesuai dengan paradigma pemograman berorientasi object (OOP),  saya meletakkan business logic ke dalam class.   Sebagai contoh, class Faktur dan ItemFaktur memiliki Diskon yang didalamnya mengandung rumus perhitungan diskon (baik untuk diskon berupa persen maupun potongan langsung).  UML Class Diagram berikut ini menunjukkan contoh rancangannya:

Contoh Rancangan OOP

Contoh Rancangan OOP

Selain business logic, sebuah sistem juga memiliki application logic seperti mencetak laporan, mengirim email, scheduling, dsb.  Saat ini saya perlu mencetak sebuah object Faktur ke printer, misalnya sebagai bukti pembayaran.   Apakah saya bisa langsung mencetak sebuah object?  Biasanya sebuah laporan selalu identik dengan hasil query dari database.   Tapi bila memakai SQL, maka saya tidak bisa memakai perhitungan yang sudah ada di class.   Seandainya saya bisa langsung mencetak object, maka akan lebih baik lagi karena business logic yang berkaitan dengan object tersebut akan ikut terbawa.  Apakah bisa?

Yup, bisa!  JasperReports dapat dipakai untuk keperluan ini.  Apa itu JasperReports?  JasperReports adalah sebuah library Java yang memungkinkan untuk menghasilkan dokumen pixel-perfect yang nantinya dapat ditampilkan, dicetak atau di-ekspor ke dalam format lain (seperti PDf atau Excel).   Pixel-perfect berarti dokumen yang dihasilkan memiliki elemen dengan posisi dan ukuran sesuai yang ditentukan; pixel-perfect merupakan kebalikan dari grid-based layout HTML <table> dimana posisi dan ukuran bersifat relatif.

Gambar berikut ini menunjukkan cara kerja JasperReports (diambil dari dokumentasi resminya):

Arsitektur JasperReports (Dari JasperReports Ultimate Guide)

Arsitektur JasperReports (Dari JasperReports Ultimate Guide)

Sebuah rancangan laporan ditulis dalam format XML yang disebut JRXML.   File XML ini akan menghasilkan sebuah JasperDesign.   Untuk laporan yang layoutnya sangat dinamis, JasperDesign dapat dibuat langsung dari kode program tanpa harus berdasarkan file XML.   JasperDesign perlu di-compile menjadi sebuah JasperReport.   Tujuannya untuk melakukan validasi dan memastikan tidak ada kesalahan pada JasperDesign.   Berikutnya, untuk menampilkan sebuah JasperReport, developer perlu menentukan data yang akan dipakai oleh JasperReport tersebut.   Setelah melakukan proses pengisian data dengan JasperFillManager,  hasilnya adalah sebuah dokumen pixel-perfect yang dapat ditampilkan, dicetak, atau di-export ke berbagai format lain.

Yang menarik disini adalah proses ‘pengisian data’ bukan bagian dari rancangan struktur laporan.   Sebuah JasperReport yang sama dapat di-isi dengan data yang berbeda berkali-kali untuk menghasilkan output laporan yang berbeda.   Menariknya, tidak ada yang mensyaratkan bahwa sebuah JasperReport harus selalu di-isi dengan data dari database.   JasperReport dapat di-isi melalui apa saja yang mengimplementasikan interface JRDataSource yang mewajibkan dua method, yakni  next() dan getFieldValue().

Contoh implementasi JRDataSource bawaan adalah:

  • JRResultSetDataSource: mengisi berdasarkan hasil query SQL melalui JDBC. Ini yang sering dijumpai.
  • JRBeanArrayDataSource: mengisi berdasarkan sebuah array yang berisi object-object JavaBean.
  • JRBeanCollectionDataSource: mengisi berdasarkan sebuah Collection yang berisi object-object JavaBean.
  • JRMapArrayDataSource: mengisi berdasarkan sebuah array yang berisi Map yang mewakili sebuah ‘baris’.
  • JRTableModelDataSource: mengisi berdasarkan TableModel dari Swing, dengan kata lain, berdasarkan isi dari sebuah JTable.
  • JRXmlDataSource: mengisi berdasarkan dokumen XML.
  • JRCsvDataSource: mengisi berdasarkan file teks dalam format CSV (comma-separated value).
  • JRXlsDataSource: mengisi berdasarkan isi file Microsoft Excel (*.xls).
  • JREmptyDataSource: mengisi dengan baris yang seluruhnya datanya bernilai null.

Berdasarkan daftar di atas, untuk mencetak sebuah ‘object‘, saya memiliki banyak pilihan.   Pada tulisan ini, saya akan memakai JRBeanCollectionDataSource untuk mengisi laporan.   Data source tersebut membutuhkan sebuah Collection seperti List yang didalamnya berisi satu atau lebih object JavaBean.  Apa yang dimaksud sebagai JavaBean?   JavaBean adalah sebuah class di Java yang mengikuti beberapa syarat:

  • Harus memiliki default constructor yaitu sebuah constructor tanpa parameter.
  • Property yang bisa diakses oleh pihak luar harus dipublikasikan melalui setter seperti setXXX() dan getter seperti getXXX() atau isXXX().   Sebagai contoh, sebuah property String namaLengkap harus memiliki setNamaLengkap() dan getNamaLengkap().   Beberapa pelajar yang tidak mengetahui tentang JavaBean mungkin merasa heran melihat saya suka menggabungkan bahasa Inggris dan bahasa Indonesia.   Beberapa bahkan melakukan improvisasi dengan mengubah method menjadi ambilNamaLengkap() dan tulisNamaLengkap(), tapi ini bukan lagi sebuah JavaBean.   Groovy menyederhanakan proses yang ada dimana bila saya mendeklarasikan sebuah String namaLengkap, maka getter dan setter secara otomatis akan dibuat, dan variabel namaLengkap tersebut akan selalu diakses berdasarkan getter/setter-nya.
  • JPA Entity yang dihasilkan oleh simple-jpa mengikuti aturan JavaBean, sehingga saya dapat memakai JRBeanCollectionDataSource.

Untuk merancang laporan, saya tidak akan mengetik JRXML secara manual.  Sebagai gantinya, saya memakai editor berbasis GUI yang disebut iReport.   Dengan iReport, saya dapat membuat isi JRXML, men-compile laporan, mengisi data dan men-preview laporan secara mudah (visual).

Langkah pertama yang saya lakukan adalah membuat laporan baru dengan memilih File, New.   Setelah itu, saya memiliki template Blank A4.   Pada dialog berikutnya, saya menentukan lokasi penyimpanan file JRXML.   Setelah itu saya men-klik tombol Next dan memilih Finish.

Langkah kedua adalah menambahkan classpath sehingga iReport mengetahui isi file class JavaBean yang akan dipakai.  Dalam hal ini, class yang dibutuhkan adalah Faktur.class, ItemFaktur.class, Diskon.class dan sebagainya.   Karena saya memakai Griffon, maka saya memberikan perintah:

griffon package jar

untuk menghasilkan file Jar dari aplikasi saya.  Di dalam file Jar tersebut sudah berisi seluruh domain class.

Kemudian kembeli ke iReport, saya memilih menu Tools, Options.   Pada tab Classpath, saya menklik tombol Add JAR.   Kemudian saya men-browse ke lokasi file JAR yang dihasilkan Griffon, yaitu di folder dist/jar di lokasi proyek saya.

Langkah ketiga adalah men-klik icon Report Query dan memilih tab JavaBean Datasource.   Pada Class name, saya mengisi dengan domain.Faktur,  kemudian men-klik tombol Read attributes.  Setelah itu, saya memilih field yang perlu dicetak dan menambahkannya dengan men-klik tombol Add selected field(s), seperti yang terlihat pada gambar berikut ini:

Membaca field dari JavaBean

Membaca field dari JavaBean

Setelah ini, saya dapat mulai merancang faktur yang akan dicetak.   Saya akan membuang beberapa band yang tidak dibutuhkan seperti Title, Column Header, Column Footer, dan Page Footer.   Cara untuk membuang sebuah band adalah dengan men-klik kanan pada wilayah band tersebut dan memilih Delete Band.   Saya menyisakan band Summary untuk menampilkan total di akhir laporan.

Pada band Page Header akan terdapat judul dan informasi nomor halaman.  Lalu, pada band Detail akan terdapat isi laporan yang terlihat seperti pada gambar berikut ini:

Rancangan 'master' laporan

Rancangan ‘master’ laporan

Sebuah faktur pada dasarnya adalah sebuah formulir master-detail.   Saya telah menambahkan informasi master, lalu bagaimana dengan detail-nya? Detail untuk sebuah faktur adalah setiap line item yang berisi barang, jumlah, dan harga.  Bagaimana cara menambahkan detail tersebut?

Pada JasperReports versi lama, saya harus membuat report baru dan menyisipkannya sebagai subreport.   Beruntungnya, sejak JasperReports memperkenalkan konsep component di laporan, saya dapat menggunakan table component.   Sebuah table component hampir mirip seperti subreport tetapi ia merupakan bagian dari laporan yang sama.   Keuntungannya?  Table component lebih mudah dipakai karena tidak perlu repot membuat report baru dan menggabungkannya.

Saya segera men-drag icon table component ke band Detail:

Menambahkan table component ke detail

Menambahkan table component ke detail

Akan muncul sebuah Table wizard.   Saya men-klik tombol New dataset.   Pada Dataset name, saya mengisi dengan itemFakturDataset. Kemudian saya memilih Create an empty dataset dan men-klik tombol Finish.   Setelah itu, saya men-klik tombol Next untuk memasuki halaman Table Style.   Saya menghilangkan tanda centang pada Create a new set of styles for this table, Add Table Header dan Add Table Footer.   Setelah itu, saya men-klik tombol Finish.

Sekarang, saya bisa berpindah antara Main report untuk mengubah laporan secara keseluruhan, atau Table 1 khusus untuk mengubah detail faktur, seperti yang terlihat pada gambar berikut ini:

Table component merupakan bagian dari report yang sama.

Table component merupakan bagian dari report yang sama.

Saya akan melakukan beberapa pengaturan terlebih dahulu pada itemFakturDataset dengan men-klik dataset tersebut dan memilih Edit Query seperti yang terlihat pada gambar berikut ini:

Mengubah Dataset

Mengubah Dataset

Pada dialog Report query yang muncul, saya memilih tab JavaBean Datasource.   Setiap line item yang perlu ditampilkan diwakili oleh sebuah object ItemFaktur, sehingga saya mengisi domain.ItemFaktur pada Class name.   Setelah itu, saya men-klik tombol Read attributes, memilih property yang perlu ditampilkan, men-klik tombol Add selected field(s), lalu menekan tombol Ok untuk selesai.

Berikutnya saya perlu memberi tahu iReport bahwa data source untuk table component ini diambil dari property itemList milik Faktur.   Untuk itu, saya men-klik kanan di bagian yang kosong dan memilih menu Edit table datasource.   Pada Connection/Datasource exp, saya mengisi data source expression seperti pada gambar berikut ini:

Mengisi datasource expression

Mengisi datasource expression

Ekspresi di atas akan membuat sebuah JRBeanCollectionDataSource baru yang datanya diambil dari field itemList.  Bila dilihat dari class diagram, itemList adalah sebuah List yang berisi satu atau lebih ItemFaktur (relasi one-to-many).  Nantinya, table component akan menampilkan masing-masing ItemFaktur yang dimiliki oleh Faktur yang sedang diproses.

Langkah terakhir sebelum mulai merancang adalah menentukan field apa saja yang perlu dipakai.  Berikut ini adalah definisi field untuk table component ini:

Definisi fields untuk itemFakturDataset

Definisi fields untuk itemFakturDataset

Class ItemFaktur memiliki relasi many-to-one dengan class Barang.   Untuk mengakses property milik class Barang dari ItemFaktur, saya dapat menggunakan dengan operator titik.   Misalnya, barang.nama akan memanggil method getNama() dari atribut barang yang dimiliki oleh ItemFaktur yang sedang diproses.   Sebuah Barang juga memiliki hubungan many-to-one dengan class Jenis.   Property milik class Jenis juga dapat diakses dengan tanda titik, misalnya seperti barang.jenis.nama.

Berikutnya saya akan merancang isi detail untuk table component dimana hasil akhirnya akan terlihat seperti pada gambar berikut ini:

Rancangan untuk table component

Rancangan untuk table component

Untuk menambah kolom baru, saya men-klik bagian kosong dan memilih Add Column To The End.

Saya memakai variabel $V{REPORT_COUNT} untuk mendapatkan nomor line item yang berurut.  Tapi bagaimana dengan perhitungan subtotal?  Bukankah class ItemFaktur sudah memiliki method total() yang akan menghitung total setelah dikurangi diskon?  Bagaimana cara memanggilnya?

Method total() tidak mengikuti standar penamaan getter atau setter JavaBean sehingga ia bukanlah sebuah property yang dapat diakses sebagai field.   Tapi ada cara lain untuk mengaksesnya.   JasperReports memungkinkan pengguna untuk mendefinisikan sebuah field dengan nama _THIS.   Nilai dari field _THIS selalu merupakan instance dari objek yang sedang diproses (dalam hal ini adalah sebuah objek ItemFaktur).   Dengan demikian, ekspresi $F{_THIS}.total() akan memanggil method total() dari objek ItemFaktur yang sedang diproses.

Mendefinisikan field _THIS

Mendefinisikan field _THIS

Memanggil method total()

Memanggil method total()

Setelah ini, saya kembali ke Main Report.   Saya juga mendefinisikan field _THIS serta menambahkan informasi summary (total Faktur secara keseluruhan), seperti yang terlihat pada gambar berikut ini:

Contoh rancangan akhir report

Contoh rancangan akhir report

Pada total gross, saya memakai ekspresi Groovy berikut ini untuk menghitung total seluruh item faktur:

$F{_THIS}.itemList*.total().sum()

Hasil perhitungan total net sebenarnya sudah dikalkulasi oleh method total() milik class Faktur.   Saya dapat memanggil method tersebut dengan menggunakan ekspresi seperti:

$F{_THIS}.total()

Dengan demikian, bila saya melakukan perubahan rumus kalkulasi pada method total() di domain class, maka selain diterapkan di aplikasi (tampilan form), perubahan juga akan langsung mempengaruhi laporan (atau percetakan).

Sampai disini, saya dapat langsung memakai report di aplikasi.   Tapi akan lebih baik bila saya melihat seperti apa tampilan report di iReport terlebih dahulu.   Caranya adalah dengan membuat sebuah class dengan method static yang menghasilkan data dummy sebagai informasi yang akan ditampilkan.  Class ini terletak di JAR yang sama dengan yang diberikan di classpath.   Sebagai contoh, saya membuat class dengan isi seperti ini:

package util

import ...

class ReportTest {

    static List getDataFaktur() {
        List hasil = []

        Faktur faktur = new Faktur(nomor: '12345', tanggal: LocalDate.now())
        Jenis jenis1 = new Jenis("JENIS1", "JENIS1")
        (1..20).each {
            Barang barang = new Barang("BRG1$it", "Barang1$it", jenis1, 12345)
            faktur.tambahItem(barang, new BigDecimal(Math.random() * 10000000), (Math.random() * 1000).intValue(),
                    new Diskon(Math.random()*50, Math.random() * 1000))
        }
        Jenis jenis2 = new Jenis("JENIS2", "JENIS2")
        (1..20).each {
            Barang barang = new Barang("BRG2$it", "Barang2$it", jenis2, 54321)
            faktur.tambahItem(barang, new BigDecimal(Math.random() * 10000000), (Math.random() * 1000).intValue(),
                    new Diskon(Math.random()*50, Math.random() * 1000))
        }
        faktur.potonganRetur = 300000
        faktur.proses(false, LocalDate.now().plusDays(30), 3, null)

        hasil << faktur
        hasil
    }

}

Pada kode program di atas, agar cepat, saya membuat instance objek secara langsung tanpa mengambil dari database.   Tujuan utamanya hanya menciptakan objek yang akan ditampilkan di laporan nantinya.   Proses pengambilan object dari database pada aplikasi nanti tetap dilakukan melalui JPA atau simple-jpa.

Berikutnya, saya menambahkan sumber data tersebut dengan men-klik icon Report Datasources, dan men-klik New pada dialog yang muncul.

Menambahkan datasource baru

Menambahkan datasource baru

Pada kotak dialog Datasource yang muncul, saya memilih JavaBeans set datasource dan men-klik tombol Next.   Saya mengisi dialog sesuai dengan informasi class saya, kemudian men-klik tombol Test untuk mengujinya.   Setelah semua berjalan dengan benar, saya men-klik tombol Save untuk menyimpan perubahan.

Mendefinisikan source JavaBean yang dipakai untuk preview laporan di iReport

Mendefinisikan source JavaBean yang dipakai untuk preview laporan di iReport

Untuk melihat seperti apa tampilan report, saya men-klik tombol Preview, seperti yang terlihat pada gambar berikut ini:

Contoh hasil preview laporan

Contoh hasil preview laporan

 

Contoh hasil preview laporan dengan summary

Contoh hasil preview laporan dengan summary

Perihal Solid Snake
I'm nothing...

5 Responses to Mencetak ‘Object’ Dengan JasperReports: Merancang Laporan

  1. Ping-balik: Mencetak ‘Object’ Dengan JasperReports: Menampilkan atau Mencetak | The Solid Snake

  2. Zaban Jauhari mengatakan:

    mas, mau nanya. kalo misalnya pas di netbeans di run report nya keluar. lah pas uda jadi file exe atau jar, file report tsb tdk terbuka. knapa y mas??
    mas bisa kasih contoh gk lbh jelasnya?? ;_)

    • Solid Snake mengatakan:

      Hal ini biasanya berkaitan dengan resources seperti file gambar untuk logo yang ditampilkan dalam laporan. Beberapa kemungkinan penyebabnya adalah:

      1. Apakah file gambar tersebut disertakan pada file JAR? Beberapa built system pada IDE secara default hanya akan menyertakan file *.class ke file JAR yang dihasilkan. Dengan demikian, file gambar tidak disertakan dalam file JAR. Sebuah file JAR adalah file zip yang dapat dilihat isinya melalui tools seperti 7zip.

      2. Pada saat dijalankan melalui NetBeans, bisa jadi kode program membaca file gambar berdasarkan API untuk membaca file. Dengan kata lain, file tersebut dibaca dengan path seperti C:\latihan\proyek\src\gambar.png (biasanya melalui path relatif). Ini tidak jadi masalah saat dijalankan di NetBeans. Akan tetapi saat dijalankan melalui file JAR, seluruh file telah digabungkan menjadi 1 dalam file JAR. Gunakan Thread.currentThread().contextClassLoader.getResourceAsStream() untuk membaca file yang berada dalam JAR. Sebagai contoh, getResourceAsStream('\com\latihan\gambar\gambar.png') akan membaca file com\latihan\gambar\gambar.png yang ada di dalam file JAR.

      • lah saya mengambil gambar tsb d IDE yg ud saya install jasper report. lah utk source dari gambar tsb kn default. bs kah kasih contoh dari jasper nya sendiri kalo ada gambar??
        apakah ditambahkan source lagi atau gk??

  3. Solid Snake mengatakan:

    Apa yang kamu maksud dengan: “source dari gambar tersebut adalah default”? Dengan anggapan bahwa laporan dirancang memakai iReport, maka ada 2 nilai property yang perlu di-isi yaitu Image Expression (mewakili nilai gambar yang akan ditampilkan) dan Expression Class (mewakili jenis nilai gambar, default-nya adalah String). JasperReport mendukung nilai imageExpression berupa java.lang.String, java.io.File, java.net.URL, java.io.InputStream, java.awtImage, dan sebagainya.

    Bila nilai imageExpression di-isi dengan java.lang.String, maka dianggap adalah sebuah file eksternal dengan lokasi relatif. Bila ingin tetap memakai cara ini, maka file gambar harus berada di luar file JAR, dengan lokasi relatif terhadap file JAR.

    Untuk mengisi nilai imageExpression dengan java.io.InputStream, gunakan parameter, misalnya isi dengan $P{logo}. Kemudian, pada kode program yang menampilkan laporan, tambahkan:

    InputStream logoStream = Thread.currentThread().
        contextClassLoader.getResourceAsStream("gambar/logo.png");
    Map parameter = new HashMap();
    parameter.put("logo", logoStream);
    JasperPrint print = JasperFillManager.fillReport(fileLaporan, parameter, dataSource);
    

    Ini hanya salah satu kemungkinan untuk kesalahan yang kamu alami. Tanpa sebuah pesan kesalahan (Exception) dan hasil stack trace saat terjadi kesalahan akan sulit mengetahui penyebab kesalahan.

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: