Living life and Make it Better

life, learn, contribute

Endy Muhardin

Software Developer berdomisili di Jabodetabek, berkutat di lingkungan open source, terutama Java dan Linux.

Mengaktifkan commit email Subversion

Salah satu best practices dalam praktek pemrograman adalah Peer Review. Kode yang disubmit seseorang ke version control akan direview oleh anggota tim yang lain. Beberapa keuntungan dari pelaksanaan Peer Review antara lain:

  • meningkatnya collective code ownership. Semua anggota tim akan merasa memiliki.
  • membantu penyebaran pengetahuan dan pengalaman. Kode yang kurang optimal akan dikomentari di ruang publik (milis developer), sehingga semua anggota akan mendapat pencerahan.
  • meningkatkan kualitas kode program secara keseluruhan. Bila setiap commit direview, anggota tim akan mendapat peer pressure untuk tidak mengulangi kesalahan yang pernah dibahas. Kalau ada kode yang kurang optimal, feedback akan datang dengan cepat, sehingga kualitas yang rendah tidak akan terakumulasi.

Baiklah, sekarang kita sudah tahu manfaat praktek peer review. Sama seperti kita tahu kalau lari pagi itu sehat. Tapi seperti halnya lari pagi, banyak orang yang tidak melakukannya.

Salah satu alasan peer review tidak dilakukan adalah semakin banyak kode program, semakin sulit reviewnya. Mau mulai dari mana? Apa kode yang kemarin sudah direview perlu dilihat lagi?

Masalah yang sama dihadapi oleh seluruh pengembang aplikasi open source di seluruh dunia. Dan mereka sudah punya solusinya, yaitu commit email.

Prinsipnya sederhana. Kita pasang trigger di version control. Setiap kali ada yang commit, trigger tersebut akan melihat apa yang baru saja dicommit. Kemudian membandingkannya dengan versi sebelumnya. Hasil perbandingan kemudian dikirim ke milis developer supaya bisa dilihat orang banyak.

Dengan commit email ini, reviewer tidak perlu melihat keseluruhan kode program, tapi cukup yang berubah saja. Ini akan sangat meringankan kegiatan review.

Subversion sudah mempaketkan script trigger untuk commit email. Di halaman Tools & Contrib ada beberapa script untuk keperluan ini.

Saya menggunakan commit script yang dibuat dengan Perl. Sebelum dipakai, script ini harus diedit sedikit, di bagian SMTP server.

$smtp_server = "127.0.0.1";

dan bagian path menuju perintah svnlook.

my $svnlook = "@SVN_BINDIR@/svnlook";

Kalau di Windows, svnlook ada di C:\Program Files\Subversion\bin, sedangkan kalau di Linux biasanya ada di /usr/bin.

Selanjutnya, kita harus menyuruh Subversion untuk menjalankan file ini pada saat ada orang commit. Caranya mudah, masuk ke folder repository Subversion, di dalam folder hooks.

Untuk Linux mudah, cukup rename post-commit.tmpl menjadi post-commit, dan ganti modenya menjadi executable.

chmod +x post-commit

Kemudian edit sedikit untuk memasukkan beberapa parameter:

  • Recipient : penerima commit email. Biasanya ini saya arahkan ke milis developer
  • Subject : subject pada email yang dikirim
  • Reply To : kalau penerima email menekan tombol Reply, inilah alamat yang akan muncul di field To
  • Host : nama host otomatis yang akan ditambahkan ke nama user yang commit. Misalnya username yang commit endy, dan hostnya artivisi.com, maka nama pengirim otomatis menjadi endy@artivisi.com

Berikut isi dari file post-commit saya

#!/bin/sh
REPOS="$1"
REV="$2"

RECIPIENT=devteam@artivisi.com
SUBJECT=automated-commit-email
REPLY_TO=devteam@artivisi.com
HOST=artivisi.com

$REPOS/hooks/commit-email.pl "$REPOS" "$REV" -h $HOST -r $REPLY_TO  -s $SUBJECT $RECIPIENT

Untuk di Windows, kita harus membuat file post-commit.bat, di folder hooks juga. Berikut isi dari post-commit.bat

@ECHO OFF

SET RECIPIENT=devteam@artivisi.com
SET SUBJECT=automated-commit-email
SET REPLY_TO=devteam@artivisi.com
SET HOST=artivisi.com

SET PERL=c:\usr\bin\perl
SET REPOS=%1
SET REV=%2

%PERL% -w %REPOS%/hooks/commit-email.pl %REPOS% %REV% -h %HOST% -r %REPLY_TO%  -s %SUBJECT% %RECIPIENT%

Tentunya kita harus menginstal dulu Perl untuk Windows agar script ini bisa berjalan.

Selamat mencoba


Annotation dan XML

Salah satu komentator bertanya seperti ini,

kenapa annotation lebih diprefer daripada xml configuration ? bukannya xml configuration di spring membuat kontrol kita lebih sentralisasi, sehingga lebih mudah dimaintain ?

Hmm… saya tidak ingin terlibat flame war annotation vs xml. Masing-masing memiliki plus minusnya. Ada saatnya kita memakai annotation dan ada saatnya XML lebih tepat. Mari kita bahas.

Beberapa waktu yang lalu, saya pernah ditugaskan untuk memeriksa aplikasi finansial yang error. Pada salah satu halaman, bila tombol Submit ditekan, aplikasi akan hang. Aplikasi ini dibuat menggunakan teknologi VB 6 (RIP) untuk komponen akses database, ASP untuk tampilan, dan Stored Procedure MS-SQL Server 2005 untuk sebagian logika bisnis.

Setelah memasukkan trace statement ke dalam Stored Procedure (ternyata bukan hal yang mudah, tidak semudah menyisipkan System.out.println(), terlihat penyebabnya karena ada cyclic dependency antar transaksi database. Transaksi A mencoba mengurangi saldo, tapi sebelumnya memeriksa posisi saldo, kalau-kalau tidak mencukupi. Pemeriksaan saldo dilakukan melalui transaksi B. Transaksi B mencoba melihat tabel saldo, yang ternyata sudah dikunci oleh transaksi A. Ya sampai kapanpun transaksi A dan B akan saling menunggu.

Baiklah, masalah sudah ditemukan, saatnya untuk memperbaiki. Memperbaiki jauh lebih mudah daripada menemukan bug, kan?

Sayangnya tidak demikian. Ada berbagai faktor, salah satunya yang paling signifikan adalah saya awam dengan teknologi Microsoft. Saya sama sekali tidak mengerti bagaimana cara mengatur transaksi di VB dan Stored Procedure.

Akhirnya setelah google kesana kemari, saya menemukan empat cara untuk melakukan setting transaksi:

  • coding di VB
  • coding di Stored Procedure
  • konfigurasi class properties di VB
  • konfigurasi di COM service pada Control Panel Windows

Waduh … ini mimpi buruk. Berarti ada banyak kombinasi yang harus dicoba. Saya yakin para MVP pasti dengan cepat bisa memilih, tapi saya hanyalah seorang programmer Java. Mana saya tahu hal beginian.

Dari kejadian ini, saya mendapat pelajaran penting. Pengaturan transaksi haruslah dekat dengan kode yang melakukan transaksi. Alasannya, kode program yang mengakses database sangat erat hubungannya dengan pengaturan transaksi. Tidak mungkin kita menulis kode untuk transfer antar rekening tapi menon-aktifkan transaksi database.

Kode seperti ini mudah dipahami dan mudah didebug.

@Transactional(readOnly=false)
public void transferAntarRekening(Rekening asal, Rekening tujuan, BigDecimal jumlah){
  asal.credit(jumlah);
  tujuan.debet(jumlah);
}

Kode seperti ini perlu banyak Alt-Tab untuk mendebugnya.

Di Java:

public void transferAntarRekening(Rekening asal, Rekening tujuan, BigDecimal jumlah){
  asal.credit(jumlah);
  tujuan.debet(jumlah);
}

Di XML

<property name="transactionAttributes">
  <props>
    <prop key="transfer*">PROPAGATION_REQUIRED</prop>
  </props>
</property>

Bedakan antara cara melakukan transaksi, dan cara melakukan konfigurasi. Di Java, kita bisa memilih beberapa cara transaksi:

  • Local Transaction: mengelola transaksi melalui java.sql.Connection dalam source code
  • Programmatic Transaction: mengambil object Transaction dari JTS provider, kemudian menggunakannya untuk commit/rollback. Ini juga dilakukan dalam source code.
  • Declarative Transaction: mengatur transaksi melalui konfigurasi.

Kalau kita pilih declarative transaction, ada beberapa cara konfigurasinya:

  • Melalui XML
  • Melalui Annotation

Yang mana yang paling dekat dengan source code yang bertransaksi? Tentu saja annotation.

Ok, sudah jelas mengenai transaction, sebaiknya di annotation saja.

Berikutnya, deklarasi DAO. Sebelumnya kita melakukan deklarasi DAO di XML. Yang biasa saya lakukan kalau ada DAO baru biasanya sama:

  • copy paste deklarasi DAO di atasnya
  • ganti bean id
  • ganti nama class

Beres. Tidak ada added value di sini. Deklarasi manual di XML tidak membuat konfigurasi Spring jadi lebih mudah dipahami. Juga tidak membuat kita makin pintar. Ini adalah overhead pemrograman dengan Spring. Boilerplate code. Kode yang ditulis hanya untuk memuaskan framework.

Jadi, lebih baik kalau kita suruh Spring autodetect saja dari annotation @Repository.

Bagaimana dengan relasi dengan datasource? Bukankah kalau di XML jadi terlihat DAO mana menggunakan DataSource mana?

Hmm … untuk kasus datasource, ada dua kemungkinan:

  • menggunakan satu datasource
  • menggunakan lebih dari satu datasource

Untuk kasus pertama, biasanya sebagian aplikasi seperti ini, tidak perlu ada deklarasi eksplisit untuk injeksi dataSource. Lha wong cuma satu, apanya yang ambigu? @Autowired saja semuanya.

Untuk kasus kedua, mau tidak mau memang harus XML. Soalnya @Autowired by type tidak akan bisa, karena ada lebih dari satu bean bertipe DataSource.

Sekarang kita lihat, apa yang tertinggal di konfigurasi XML, setelah banyak hal kita pindahkan ke annotation:

  • deklarasi datasource
  • deklarasi transaction manager

Kedua hal ini tidak logis bila kita pindahkan ke annotation. Dua-duanya adalah konfigurasi, sangat mungkin berubah tergantung strategi deployment. Bila kita mengelola koneksi database melalui application server, maka kita akan menggunakan JNDI. Bila kita deploy di Winstone, maka kita tidak menggunakan JTA, jadi transaction manager menggunakan LocalTransactionManager.

Setelah selesai dengan isu backend, mari sekarang kita bahas presentation layer. Ada beberapa anotasi di sini:

  • @Controller : untuk menandai class Controller
  • @RequestMapping : untuk mapping URL atau Request Method ke handlernya
  • @RequestParam : untuk mem-bind request parameter ke variabel
  • @ModelAttribute : untuk mem-bind object ke request attribute
  • @SessionAttribute : untuk mem-bind object ke session attribute

@Controller sama dengan @Repository. Keberadaannya menghilangkan boilerplate deklarasi di XML. Menurut saya, ini sangat mengurangi clutter dan duplikasi di XML.

@RequestMapping, saya 50:50 di sini.

Di satu sisi, seharusnya kita tidak mengikat handler method dengan URL pattern, supaya kita bisa mengganti skema URL sesuai trend terbaru (RESTful, etc) atau sebaliknya, supaya bisa mengganti handler method tanpa mengubah URL.

Tapi di sisi lain, saya ingin menggunakan Convention over Configuration ala Rails. Yaitu nama view otomatis diambil dari request URL. Ini benar-benar mengurangi jumlah konfigurasi yang harus ditulis. Lagipula, seberapa sering sih kita mengganti pasangan URL-Handler?

Jadi untuk @RequestMapping, posisi saya adalah, tergantung situasi. Ada saatnya mapping di XML, dan ada saatnya mapping dengan annotation.

@RequestParam, @ModelAttribute, dan @SessionAttribute setahu saya tidak punya padanan XML. Ini adalah anotasi untuk memudahkan coding saja. Jadi tidak perlu diperdebatkan apakah sebaiknya di XML saja.

Yah, begitulah sekilas perbandingan antara konfigurasi melalui anotasi dan XML. Sepertinya menambah bingung. Tapi jangan khawatir, untuk bisa mengerti harus bingung dulu.

Semoga bermanfaat.

:D


Aplikasi Web dengan Spring 2.5 [bagian 2]

Pada artikel Spring bagian ketiga ini, kita akan membuat form untuk mengedit data Person. Di sini kita akan lihat kemampuan form binding dari Spring, cara menyuplai data ke form, melakukan validasi, dan memproses form ketika tombol Submit ditekan.

Kita akan menggunakan template yang sama untuk pengeditan Person yang sudah ada maupun pendaftaran Person baru. Templatenya bernama personform.html. Berikut kodenya.

personform.html

<html>

<head>
<title>:: Edit Person ::</title>
</head>

<body>
<form method="POST">
<input type="hidden" name="id" value="$!person.Id">

<table>
  <tr>
    <td>Nama</td>
    <td><input type="text" name="name" value="$!person.Name"></td>
  </tr>
  <tr>
    <td>Email</td>
    <td><input type="text" name="email" value="$!person.Email"></td>
  </tr>
  <tr>
    <td colspan="2"><input type="submit" value="Save"></td>
  </tr>
</table>

</form>
</body>

</html>

Kita melihat ada variabel yang agak berbeda pada contoh di atas, yaitu $!person. Ini merupakan variabel opsional dalam Velocity. Bila variabel $person tidak ada isinya, Velocity akan menampilkan apa adanya, yaitu $person ke halaman web. Kita ingin bila $person null, jangan tampilkan apa-apa. Untuk itu, kita mengubah variabel $person menjadi $!person.

Kalau dijadikan kode Java, kira-kira $!person sama dengan ini:

    String personName;
    if(person != null && person.getName() != null) {
      personName = person.getName();
    } else {
      personName = "";
    }

Variabel $!person ini digunakan karena form ini menangani New Person dan juga Edit Person. Untuk kasus Edit Person, kita dapat memberikan object person yang sudah ada di database. Sedangkan untuk New Person, objectnya belum ada atau null. Dengan $!person, kita dapat menangani kedua skenario ini.

Berikut kerangka class PersonFormController.

    package tutorial.spring25.ui.springmvc;
    
    @Controller
    @RequestMapping("/personform")
    public class PersonFormController {	
      private PersonDao personDao;
    
      @Autowired
      public void setPersonDao(final PersonDao personDao) {
        this.personDao = personDao;
      }
    
      @RequestMapping(method = RequestMethod.GET)
      public ModelMap displayForm(@RequestParam(value = "person_id", required = false) Long id) {
      
      }
    
      @RequestMapping(method = RequestMethod.POST)
      public String processForm(@ModelAttribute("person") Person person, BindingResult result, SessionStatus status) {
    
      }
    }

Ada dua method di sini, yaitu displayForm dan processForm. Yang satu untuk menampilkan form, dan satu lagi untuk memproses hasil submit. Nama method bebas saja, tidak ada aturan yang harus dipatuhi.

Kedua method dimapping ke request /personform. Dengan demikian, request ke http://localhost:8080/tutorial-spring25/tutorial/personform akan memanggil class PersonFormController. Di dalam form htmlnya juga action setelah submit dikosongkan. Artinya, kalau dia disubmit, form tersebut akan memanggil URL yang sama dengan yang memanggilnya.

Tetapi, bagaimana kita memilih kapan harus mendisplay form dan memproses form? Kita membedakannya dengan memasang annotation @RequestMapping dengan parameter RequestMethod. Bila requestnya GET (terjadi bila kita mengetik http://localhost:8080/tutorial-spring25/tutorial/personform di browser dan menekan Enter), maka jalankan method displayForm. Tapi bila requestnya POST (terjadi bila kita menekan tombol Submit di personform.html), maka jalankan method processForm.

Sekarang mari kita isi method displayForm. Berikut isinya

    @RequestMapping(method = RequestMethod.GET)
    public ModelMap displayForm(@RequestParam(value = "person_id", required = false) Long id) {
      Person person = personDao.getById(id);
    
      if (person == null) person = new Person();
    
      return new ModelMap(person);
    }

Di sini kita melakukan binding untuk request parameter person_id. Berbeda dengan tampilan detail pada artikel sebelumnya, di form ini parameter person_id belum tentu ada. Bila kita membuat object Person baru, field id akan berisi null. Untuk itu, kita berikan parameter required yang bernilai false pada anotasi @RequestMapping.

Logika pada method ini tidak rumit. Ambil object Person dari database berdasarkan id. Kalau tidak ada, berikan saja object baru.

Method ini bisa langsung dicoba dengan mengakses personform dengan memberikan parameter person_id, misalnya dengan URL http://localhost:8080/tutorial-spring25/tutorial/personform?person_id=100. Tentunya kita harus memiliki record di tabel T_PERSON dengan id 100. Kalau codingnya benar, maka akan tampil form yang terisi dengan data record tersebut.

Berikutnya, kita akan implementasi method untuk memproses form. Berikut isi method processForm

    @RequestMapping(method = RequestMethod.POST)
    public String processForm(@ModelAttribute("person") Person person) {
      personDao.save(person);
      return "redirect:personlist";
    }

Mudah kan? Cukup gunakan personDao untuk menyimpan object ke database, kemudian redirect ke halaman personlist.

Begitu saja? Tidak ada yang lupa?

Ya untuk memproses form begitu saja langkahnya, tidak perlu susah-susah.

Bagaimana dengan validasi? Mana ada form tanpa validasi.

Baiklah, mari kita tambahkan kode validasi. Untuk itu, method processForm perlu dimodifikasi menjadi seperti ini

    @RequestMapping(method = RequestMethod.POST)
    public String processForm(@ModelAttribute("person") Person person, BindingResult result, SessionStatus status) {
      new PersonValidator().validate(person, result);
      if (result.hasErrors()) {
        return "personform";
      } else {
        personDao.save(person);
        status.setComplete();
        return "redirect:personlist";
      }
    }

Tidak terlalu rumit, kan? Cukup buat class PersonValidator, kemudian jalankan method validate dengan input object person yang ingin divalidasi, dan object result untuk menampung error validasi bila ada.

Selanjutnya, kita periksa object result. Bila ada errornya, kembali ke form. Bila tidak ada, langsung save dengan personDao, set status menjadi complete, dan redirect ke personlist.

Isi class PersonValidator juga tidak banyak. Berikut kodenya.

    package tutorial.spring25.validator;
    public class PersonValidator {
    
      private static final String EMAIL_FORMAT = ".*@.*\\.com";
    
      public void validate(Person person, Errors errors) {
        // field nama harus diisi
        if(!StringUtils.hasText(person.getName())) {
          errors.rejectValue("name", "required", "nama harus diisi");
        }
    
        // bila field email diisi, formatnya harus benar
        if (StringUtils.hasLength(person.getEmail()) && !person.getEmail().matches(EMAIL_FORMAT) ) {
          errors.rejectValue("email", "email.format", "format email salah");
        }
      }
    }

Mudah bukan?

Para penggemar framework berbasis komponen (seperti Tapestry atau JSF) mungkin bertanya, untuk apa saya belajar lagi Spring MVC? Sepertinya tidak lebih mudah.

Coba perhatikan URL yang kita gunakan:

  • http://localhost:8080/tutorial-spring25/tutorial/personlist

  • http://localhost:8080/tutorial-spring25/tutorial/persondetail?person_id=100

  • http://localhost:8080/tutorial-spring25/tutorial/personform

  • http://localhost:8080/tutorial-spring25/tutorial/personform?person_id=100

Semuanya bersih dan bookmarkable. Dengan Spring MVC kita bisa mengatur URL sesuai keinginan.

Selanjutnya, coba perhatikan kode Java kita. Jangankan lokasi template Velocity kita, bahkan dia tidak tahu menahu kalau kita pakai Velocity. Tugas kode Java cuma menerima input dan mengembalikan data. Terserah data itu mau diformat seperti apa. Dia tidak peduli teknologi view yang digunakan.

Implikasinya, selama data yang disuplai tidak berubah, hanya dengan mengubah konfigurasi kita dapat mengubah tampilan. Tentunya kita harus menyediakan template yang sesuai.

Kita bisa membuat template dengan teknologi yang lain, misalnya JSP, Freemarker, atau Jasper Report. Kita juga bisa merender tampilan tidak hanya dalam format HTML, tapi juga PDF, XLS, XML, JSON, plain-text, atau mengkonversinya menjadi grafik SVG.

Kelebihan lainnya, kita mengendalikan secara penuh output HTML aplikasi kita. Implikasinya, kita bisa menerapkan teknik-teknik teruji dalam protokol HTTP seperti Cache Control pada HTTP Header.

Atau kita bisa manfaatkan HTTP Response Code 304 Not Modified untuk memberi tahu client bahwa halaman yang dia akses belum berubah sejak terakhir diakses, sehingga client tidak mendownload lagi keseluruhan page, melainkan langsung menampilkan local cache-nya.

Teknik seperti ini sederhana, mudah, sudah teruji di lapangan, berlaku untuk berbagai bahasa pemrograman, dan sangat efektif. Hanya dengan mengubah HTTP response code, kita bisa menghemat bandwidth dan mengurangi load application server. Sayangnya teknik ini belum tentu dapat digunakan pada framework yang terlalu canggih. Spring MVC memungkinkan kita untuk memanipulasi HTTP response dengan mudah kalau kita mau.

Source code untuk rangkaian artikel ini sudah dipublish di GoogleCode. Anda bisa:

Demikian sekilas tentang framework Spring MVC. Semoga bermanfaat.


Aplikasi Web dengan Spring 2.5 [bagian 1]

Setelah pada artikel sebelumnya kita berhasil mengakses database, kali ini kita akan membuat tampilan berbasis web yang menggunakan kode program kita kemarin.

Fiturnya tidak terlalu sulit, dari tabel T_PERSON kemarin kita akan buatkan beberapa tampilan untuk mengelola data Person. Tampilan yang akan kita sediakan adalah:

  • daftar semua Person
  • informasi Person yang dipilih
  • form untuk membuat object Person baru
  • form untuk mengedit object Person yang sudah ada

Sebelum kita mulai, ada baiknya kita mengetahui cara kerja Spring dalam mengelola aplikasi web. Sequence diagram berikut akan memudahkan pemahaman kita.

Alur kerja Spring MVC

Seperti kita lihat pada gambar, semua request akan diterima oleh DispatcherServlet. Mereka yang pernah membaca buku Core J2EE Pattern akan segera mengenali jurus ini, yang sering disebut dengan istilah FrontController. DispatcherServlet akan menyuruh handler mapping untuk memilih class yang akan menangani request. Ada beberapa implementasi handler mapping, diantaranya:

  • BeanNameUrlHandlerMapping
  • SimpleUrlHandlerMapping
  • ControllerClassNameHandlerMapping

Class yang menangani request disebut dengan istilah Controller. Class Controller ini yang akan kita tulis sendiri. Spring menyediakan beberapa superclass Controller yang bisa kita subclass untuk mengurangi kode yang harus ditulis. Beberapa superclass yang disediakan Spring antara lain:

  • Controller
  • MultiActionController
  • SimpleFormController
  • AbstractWizardController

Selain membuat turunan dari superclass di atas, kita juga bisa membuat class biasa yang dilengkapi dengan annotation. Pada artikel ini kita tidak akan membuat turunan apa-apa, karena semua bisa dikerjakan dengan annotation.

Tanggung jawab controller selain memproses request adalah menentukan nama template yang akan digunakan untuk menampilkan hasil pemrosesan controller. Spring menyebut template ini dengan istilah View. Kita cuma perlu menyebutkan nama View dan Spring yang akan mencarikan file template yang sesuai dan kemudian mengisi datanya. Proses mencarikan template ini ditangani oleh ViewResolver. Ada beberapa implementasi ViewResolver, antara lain untuk memproses template berjenis:

  • JSP dan JSTL
  • Freemarker atau Velocity
  • XML dengan XSLT
  • Jasper Report
  • Document View (PDF dan XLS)

Sekarang setelah kita mengetahui arsitektur umum dari aplikasi web Spring, kita bisa segera coding. Class-class yang akan kita buat adalah:

  • PersonController. Class ini akan menangani tampilan daftar Person dan detail Person.
  • PersonFormController. Class ini akan menangani tampilan pengeditan object Person, baik yang belum terdaftar maupun yang sudah ada di dalam database.

File konfigurasi yang akan kita buat adalah:

  • web.xml. Ini adalah konfigurasi standar untuk semua aplikasi web dengan Java.
  • tutorial-servlet.xml. Ini adalah konfigurasi DispatcherServlet untuk menampung deklarasi HandlerMapping, Controller, dan ViewResolver.

Untuk menampilkan halaman web, kita akan menggunakan template engine Velocity. Velocity adalah template engine yang kecil dan ringan, tapi fiturnya cukup lengkap dan mudah digunakan. Saya lebih suka menggunakan Velocity daripada JSP, karena JSP membutuhkan kompilasi menjadi Servlet dan kemudian menjadi bytecode. Ini menyebabkan halaman JSP lebih sulit didebug bila terjadi error. Selain itu, kompilasi JSP membutuhkan dua kompiler, satu untuk JSP ke Servlet, dan satu lagi untuk Servlet menjadi bytecode. Dengan demikian, kita harus menginstal JDK di server. Tanpa JSP, kita dapat menggunakan Servlet container yang ringan dan kecil seperti Jetty atau Winstone dan tidak perlu menginstal JDK, cukup JRE saja.

Template untuk menampilkan daftar orang dibuat dalam HTML yang sudah disisipi kode Velocity, disimpan dengan nama personlist.html. Kodenya terlihat seperti ini.

personlist.html

<html>
<head>
<title>:: List of All Person ::</title>
</head>
<body>

<table border="0" cellpadding="2" cellspacing="2">
  <tr>
    <th>Name</th>
    <th>Email</th>
    <th> </th>
  </tr>
  
  #foreach($person in $personList)
  <tr>
    <td>$person.Name</td>
    <td>$person.Email</td>
    <td>
      <a href="personform?person_id=$person.Id">edit</a> | 
      <a href="persondetail?person_id=$person.Id">view</a>
    </td>
  </tr>
#end

</table>

</body>
</html>

Kode yang diawali dengan tanda # merupakan perintah dalam Velocity. Dengan menggunakan perintah #foreach, kita melakukan looping untuk setiap baris record.

Kode yang diawali tanda $ merupakan variabel dalam Velocity. Isi variabel ini nantinya akan kita sediakan melalui controller Spring.

Untuk menampilkan detail informasi Person, kita buat persondetail.html. Kodenya seperti ini.

persondetail.html

<html>

<head>
<title>:: $person.Name's Detail Info ::</title>
</head>

<body>
<table>
  <tr>
    <td>Nama</td>
    <td>$person.Name</td>
  </tr>
  <tr>
    <td>Email</td>
    <td>$person.Email</td>
  </tr>
</table>
</body>

</html>

Sekarang mari kita isi template tersebut dengan data yang dibutuhkannya. Template personlist.html membutuhkan data List dengan nama variabel personList, sedangkan `persondetail.html` membutuhkan data Person dengan nama variabel person.

Pertama, kita akan mengisi personlist.html. Template ini akan disuplai oleh PersonController, melalui method yang bernama list. Berikut kode programnya.

PersonController.java

package tutorial.spring25.ui.springmvc;

@Controller
public class PersonController {

  private PersonDao personDao;

  @Autowired
  public void setPersonDao(PersonDao personDao) {
    this.personDao = personDao;
  }

  @RequestMapping("/personlist")
  public ModelMap list(){
    return new ModelMap(personDao.getAll());
  }
}

Mudah bukan? Cukup panggil method personDao.getAll, kemudian masukkan hasilnya ke dalam ModelMap.

Kita melihat beberapa annotation pada kode ini. Annotation @Controller merupakan penanda bagi Spring bahwa class ini adalah sebuah Controller. Kelas yang memiliki annotation ini akan dipindai pada saat start-up dan diregistrasi ke ApplicationContext. Annotation @Autowired menyuruh Spring untuk menginjeksikan object PersonDao. Dengan annotation @RequestMapping, kita menentukan bahwa request menuju ke http://namaserver:port/namaaplikasi/namaservlet/personlist akan ditangani oleh method ini.

Pada saat dideploy, DispatcherServlet milik Spring akan menemukan dan memanggil method ini. Kemudian, dia akan menerima hasilnya berupa ModelMap untuk kemudian diserahkan ke ViewHandler Velocity untuk digabungkan dengan template dan menghasilkan halaman HTML.

Object yang kita berikan pada ModelMap akan diberi nama oleh Spring secara otomatis. Karena kita mensuplai object dengan tipe List<Person>, maka Spring akan memberikan nama personList. Demikian juga pada controller berikutnya kita akan memberikan object bertipe Person ke controller, Spring akan memberikan nama person pada object tersebut. Dengan nama itulah ($person) kita mengaksesnya di template Velocity.

Cukup satu dulu implementasi kita. Sekarang tiba saatnya konfigurasi. Aplikasi kita akan dideploy dengan nama context tutorial-spring25. Di dalamnya, kita akan memasang DispatcherServlet yang akan mengambil semua request dengan path /tutorial/*. Jadi, method public ModelMap list akan diakses melalui URL http://localhost:8080/tutorial-spring25/tutorial/personlist.

Konfigurasi pertama ada di web.xml. Di sini kita akan mengkonfigurasi DispatcherServlet dan mendaftarkan applicationContext.xml. Berikut isi web.xml

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app version="2.4" 
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
         http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <display-name>Tutorial Spring</display-name>

  <description>Tutorial Spring 2.5</description>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>tutorial</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>tutorial</servlet-name>
    <url-pattern>/tutorial/*</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>

</web-app>

ApplicationContext yang berisi konfigurasi database dan transaksi, yang dibahas pada artikel sebelumnya, didaftarkan melalui context-param. File konfigurasinya, applicationContext.xml disimpan di classpath, yaitu di folder WEB-INF/classes. Oleh karena itu, kita tulis pathnya classpath:applicationContext.xml

ApplicationContext ini harus diaktifkan pada saat aplikasi web dideploy dan dinon-aktifkan pada saat aplikasi web di-undeploy. Untuk itu, kita harus memasang ContextLoaderListener untuk memonitor aktifitas aplikasi web.

Selanjutnya, kita daftarkan DispatcherServlet dengan nama tutorial. Spring akan mencari file bernama tutorial-servlet.xml sebagai file konfigurasi DispatcherServlet ini di dalam folder WEB-INF. DispatcherServlet tutorial akan dimapping untuk menangani semua request dengan pola /tutorial/*

Sekarang, mari kita lihat isi tutorial-servlet.xml.

tutorial-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <context:component-scan base-package="tutorial.spring25.ui.springmvc" />

  <bean id="velocityConfig"
        class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
    <property name="resourceLoaderPath" value="/WEB-INF/templates/velocity/" />
  </bean>

  <bean id="viewResolver"
        class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
    <property name="cache" value="true" />
    <property name="prefix" value="" />
    <property name="suffix" value=".html" />
  </bean>

</beans>

Pertama, kita konfigurasi Spring agar memindai isi package tutorial.spring25.ui.springmvc dan mendaftarkan semua class yang beranotasi @Controller. Kedua, kita mengkonfigurasi VelocityConfigurer untuk mencari template di dalam folder WEB-INF/templates/velocity. Terakhir, kita melakukan konfigurasi VelocityViewResolver untuk menerjemahkan nama view menjadi nama file template. Misalnya kita memberikan nama view personlist, maka VelocityViewResolver akan memberikan file WEB-INF/templates/velocity/personlist.html.

Pembaca yang teliti akan segera protes, “Kita kan tidak pernah menyebutkan nama view di dalam Controller. Lalu dari mana nama view itu didapatkan?”

Baiklah, mari lihat lagi method tersebut.

Method list

  @RequestMapping("/personlist")
  public ModelMap list(){
    return new ModelMap(personDao.getAll());
  }

Method tersebut dimapping untuk menerima request http://localhost:8080/tutorial-spring25/tutorial/personlist. Kalau kita tidak melakukan konfigurasi apa-apa, Spring secara default akan menganggap nama request sama dengan nama view. Jadi method di atas akan menghasilkan nama view personlist.

Setelah semua konfigurasi di atas selesai, kita bisa langsung membuat paket war untuk dideploy.

Berikutnya, kita akan membuat tampilan informasi detail per Person. Untuk ini, kita membutuhkan parameter person\_id yang ingin ditampilkan. Sehingga bila kita ingin menampilkan Person dengan id 100, URLnya adalah http://localhost/tutorial-spring25/tutorial/persondetail?person\_id=100

Class PersonController yang sudah ditambahi method detail tampak seperti ini.

PersonController.java

package tutorial.spring25.ui.springmvc;

@Controller
public class PersonController {

  private PersonDao personDao;

  @Autowired
  public void setPersonDao(PersonDao personDao) {
    this.personDao = personDao;
  }

  @RequestMapping("/personlist")
  public ModelMap list(){
    return new ModelMap(personDao.getAll());
  }

  @RequestMapping("/persondetail")
  public ModelMap detail(@RequestParam("person_id") Long personId){
    return new ModelMap(personDao.getById(personId));
  }
}

Untuk mengambil parameter person\_id, kita tinggal membuat method parameter biasa yang dilengkapi annotation @RequestParam. Konversi tipe data akan dilakukan oleh Spring. Dengan kata lain, kode ini

@RequestParam("person_id") Long personId

sama dengan ini

Long personId = Long.valueOf(httpRequest.getParameter("person_id"));

bedanya, kita tidak perlu mengimport javax.servlet.HttpServletRequest.

Setelah selesai, redeploy aplikasi dan coba akses http://localhost/tutorial-spring25/tutorial/personlist. Dari sana, klik link view. Tampilan detail dari object Person yang dipilih akan segera terlihat.

Demikianlah bagian kedua dari seri Spring 2.5. Pada artikel selanjutnya, kita akan melihat cara mengimplementasikan form untuk mengedit object Person yang sudah ada, maupun membuat object Person yang baru.


Akses Database dengan Spring 2.5

Akses database dengan Spring 2.5

Spring 2.5 baru saja keluar. Rilis kali ini membawa penambahan fitur yang cukup signifikan di sisi konfigurasi. Dalam Spring yang baru ini, kita bisa mengkonfigurasi aplikasi melalui annotation. Suatu hal yang sangat bermanfaat untuk mengurangi jumlah baris kode XML kita.

Sebetulnya tidak ada yang salah dengan XML. Walaupun demikian, ada beberapa hal yang menurut saya kurang tepat kalau dikonfigurasi melalui XML, diantaranya:

  • konfigurasi transaction
  • deklarasi bean standar

Konfigurasi transaction biasanya tergantung dari kode program yang ingin ber-transaction. Bila kita konfigurasi di XML, maka untuk memikirkan satu logika akses database, kita harus melihat di dua tempat yang berbeda; file java dan file XML. Menurut pendapat saya, fitur declarative transaction walaupun kelihatannya mirip konfigurasi, tapi pada dasarnya adalah logika aplikasi. Tempatnya bukan di konfigurasi XML, tapi di kode Java.

Di Spring, kita harus mendaftarkan object aplikasi kita ke dalam object ApplicationContext agar bisa dikelola oleh Spring. Pada rilis sebelumnya, pendaftaran ini dilakukan dalam file XML. Cara ini memiliki incremental cost yang tinggi. Bila kita punya 100 object yang ingin dikelola, maka kita harus punya 100 deklarasi di konfigurasi XML Spring. Sekarang kita bisa menandai object yang akan dikelola Spring melalui annotation. Jadi walaupun ada 100 object, konfigurasi XML kita tidak bertambah.

Ok, cukup berteori. Saatnya melihat contoh kode.

Domain Model

Pada artikel kali ini, kita akan membuat kode akses database untuk class Person. Class ini tidak istimewa, cuma POJO biasa dengan tiga property: id, name, dan email. Berikut kode program Person.java.

package tutorial.spring25.model;
public class Person {
  private Long id;
  private String name;
  private String email;
}

Jangan lupa membuat getter dan setter.

Skema Database

Class ini akan kita simpan di database dalam tabel bernama T_PERSON. Berikut definisinya untuk database MySQL.

create table T_PERSON (
  id BIGINT PRIMARY KEY AUTO_INCREMENT, 
  name VARCHAR(255), 
  email VARCHAR(255)
);

Interface Akses Database

Operasi database yang akan kita buat dijelaskan oleh interface PersonDao, sebagai berikut.

package tutorial.spring25.dao
public interface PersonDao {
  public List<person> getAll();
  public Person getById(Long id);
  public void save(Person p);
}

Untuk tahap pertama, kita akan lihat cara mengakses database dengan JDBC helper yang disediakan Spring. Akses database dengan Hibernate akan dijelaskan pada artikel terpisah.

Implementasi Akses Database

Berikut adalah kerangka implementasi PersonDao dengan JDBC helper dari Spring. Kita simpan di file bernama PersonDaoSpringJdbc.java

package tutorial.spring25.dao.springjdbc;

@Repository("personDao")
@Transactional(readOnly=true)
public class PersonDaoSpringJdbc implements PersonDao {	
	@Autowired
	public void setDataSource(final DataSource dataSource) {

	}
	
	@Override
	public List<person> getAll() {
		return null;
	}

	@Override
	public Person getById(final Long id) {
		return null;
	}

	@Override
	@Transactional(readOnly=false)
	public void save(final Person person) {

	}
}

Ada beberapa hal yang baru pada kode di atas. Kita melihat ada annotation @Repository, @Transactional, dan @Autowired.

Annotation @Repository memberi tahu pada Spring bahwa class ini adalah salah satu @Component dalam aplikasi kita. Semua @Component akan dipindai pada waktu inisialisasi dan kemudian diregistrasi ke dalam object ApplicationContext milik Spring. Selain @Repository, @Component juga memiliki turunan @Service dan @Controller. @Service biasanya digunakan untuk menandai class-class facade atau business delegate. Sedangkan @Controller digunakan untuk aplikasi web. @Service dan @Controller akan kita bahas di artikel terpisah.

Annotation @Transactional menandakan bahwa semua method dalam class ini akan dijalankan dalam transaksi database. Kita memberikan nilai readOnly=true pada deklarasi class, menandakan bahwa secara default transaksi hanya digunakan untuk mengambil data dari database. Perhatikan method save. Pada method ini, kita akan memasukkan atau mengubah data dalam database. Untuk satu method ini, kita membutuhkan transaksi yang tidak readOnly. Karena itu, kita override konfigurasi default dengan cara memberikan annotation @Transactional(readOnly=false).

Konfigurasi Spring Framework

Sekarang mari kita lihat konfigurasi Application Context. File ini disave dengan nama applicationContext.xml.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
	http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

	<context:property-placeholder location="classpath:jdbc.properties"/>
	<context:annotation-config/>
	<context:component-scan base-package="tutorial.spring25"/> <!-- tidak perlu deklarasi masing2 DAO -->
	<tx:annotation-driven /> <!-- tidak perlu deklarasi transaction setting per method -->
	
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource"
		destroy-method="close" 
		p:driverClassName="${jdbc.driver}"
		p:url="${jdbc.url}" 
		p:username="${jdbc.username}"
		p:password="${jdbc.password}" />

	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
		p:dataSource-ref="dataSource" />

</beans>

Seperti kita lihat di atas, kita tidak lagi membutuhkan deklarasi untuk object personDao seperti pada Spring sebelumnya. Kita juga tidak perlu membuat konfigurasi transaksi untuk masing-masing method dalam PersonDao. Sebagai gambaran, kita menghilangkan beberapa baris yang seperti ini.

<bean id="personDaoImpl" class="tutorial.spring25.dao.springjdbc.PersonDaoSpringJdbc">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<bean id="personDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="target" ref="personDao"></property>
    <property name="transactionManager" ref="transactionManager"></property>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="save*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

Semakin banyak class DAO kita, deklarasinya juga akan semakin banyak. Pada aplikasi skala menengah, jumlah DAO bisa mencapai ratusan. Bisa dibayangkan dampaknya terhadap file xml tersebut. Dengan mencantumkan satu baris seperti ini,

<context:component-scan base-package="tutorial.spring25"/>

Spring dapat secara otomatis memeriksa seluruh package tutorial.spring dan mendaftarkan semua class yang memiliki annotation @Component, @Repository, @Service, dan @Controller.

Kita sudah lihat bagaimana keseluruhan kode program ditulis. Kecuali implementasi sebenarnya tentu saja. Sebelum melihat secara detail bagaimana kode program untuk INSERT dan SELECT, terlebih dulu kita lihat bagaimana class PersonDaoSpringJdbc ini digunakan.

Automated Testing

Daripada menggunakan cara yang kurang berwawasan (menggunakan method main), saya akan mengambil pendekatan yang lebih berpendidikan, yaitu menggunakan Unit Test. Lihat artikel saya tentang Unit Test dan Integration Test untuk memahami kode berikut.

Ini adalah kerangka class test untuk PersonDaoSpringJdbc. Class ini dibuat dengan menggunakan JUnit 4. Isinya masih belum lengkap. Kita akan lengkapi sambil jalan.

package test.spring25.dao.springjdbc
public class PersonDaoSpringJdbcTest {

  private static final ApplicationContext applicationContext;
  private static final DataSource dataSource;
  private static final PersonDao personDao;

  @BeforeClass public static void init(){}
  @Before public void resetDatabase(){}

  @Test public void testGetById(){}
  @Test public void testGetAll(){}
  @Test public void testSave(){}
}

Seperti kita lihat, kita sudah menggunakan annotation untuk menandai method test dan inisialisasi. Penjelasan tentang JUnit 4 akan dibahas pada artikel terpisah.

Sekarang kita lihat isi masing-masing method. Method init isinya seperti ini.

@BeforeClass public static void init(){
  ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  dataSource = (DataSource) ctx.getBean("dataSource");
  personDao = (PersonDao) ctx.getBean("personDao");
}

Method resetDatabase dijalankan sebelum masing-masing test method. Fungsinya untuk menghapus isi tabel T_PERSON dan mengisi sampel data sesuai yang kita inginkan. Ini dilakukan menggunakan DBUnit. Isinya sebagai berikut

@Before public void resetDatabase() throws Exception {
  final Connection conn = ds.getConnection();
  DatabaseOperation.CLEAN_INSERT.execute(new DatabaseConnection(conn), new FlatXmlDataSet(new FileInputStream("fixtures/person.xml")));
  conn.close();
}

Sample Data

Method di atas akan menggunakan sampel data yang ada di file person.xml. Isinya seperti ini,

<dataset>
  <T_PERSON
    id="100"
    name="Endy Muhardin"
    email="endy.muhardin@gmail.com" 
  />
</dataset>

Cukup satu record saja.

Implementasi Query Database

Pertama kali, kita akan implementasi method getById. Isi testnya tidak rumit. Cukup jalankan method getById dan periksa hasilnya.

@Test public void testGetById() throws Exception {
  Person endy = personDao.getById(100L);
  assertEquals("Endy Muhardin", endy.getName());
  assertEquals("endy.muhardin@gmail.com", endy.getEmail());
}

Implementasi getById dalam PersonDaoSpringJdbc seperti ini.

public Person getById(Long id) {
  return simpleJdbcTemplate.queryForObject("select * from T_PERSON where id=?", new PersonMapper(), id);
}

Cukup satu baris saja.

Mapping dari ResultSet menjadi Person

Method ini membutuhkan class PersonMapper untuk mengkonversi object ResultSet menjadi object Person. Class ini dibuat menjadi static final inner class dalam PersonDaoSpringJdbc.

public class PersonDaoSpringJdbc implements PersonDao {
  private static final class PersonMapper implements ParameterizedRowMapper<Person>{
    @Override
    public Person mapRow(final ResultSet rs, final int rowNum) throws SQLException {
      final Person result = new Person();
      result.setId(rs.getLong("id"));
      result.setName(rs.getString("name"));
      result.setEmail(rs.getString("email"));
      return result;
    }
  }
}

Implementasi Query lainnya

Selanjutnya, kita akan implementasikan method getAll. Berikut test methodnya.

@Test public void testGetAll() throws Exception {
  List<Person> result = personDao.getAll();
  assertEquals(1, result.size());
  Person endy = result.get(0);
  assertEquals("Endy Muhardin", endy.getName());
  assertEquals("endy.muhardin@gmail.com", endy.getEmail());
}

Dan ini implementasi dari method getAll.

public List<Person> getAll() {
  return simpleJdbcTemplate.query("select * from T_PERSON", new PersonMapper(), new HashMap<String, String>());
}

Implementasi Insert Data

Terakhir, mari kita implementasi method save. Method testnya sedikit lebih panjang, karena untuk yakin akan hasilnya, kita harus melakukan query ke database dengan JDBC murni.

@Test public void testSave() throws Exception {
  Person dhiku = new Person();
  dhiku.setName("Hadikusuma Wahab");
  dhiku.setEmail("dhiku@gmail.com");
  assertNull(dhiku.getId());
  personDao.save(dhiku);
  assertNotNull(dhiku.getId());

  final Connection conn = ds.getConnection();
  final PreparedStatement ps = conn.prepareStatement("select * from T_PERSON where id=?");
  ps.setLong(1, dhiku.getId());
  final ResultSet rs = ps.executeQuery();
  assertTrue(rs.next());

  assertEquals(dhiku.getName(), rs.getString("name"));
  assertEquals(dhiku.getEmail(), rs.getString("email"));

  ps.close();
  rs.close();
  conn.close();
}

Untungnya implementasi method save juga hanya satu baris.

@Transactional(readOnly=false)
public void save(final Person person) {
  person.setId(simpleJdbcInsert.executeAndReturnKey(new BeanPropertySqlParameterSource(person)).longValue());
}

Ada beberapa hal yang perlu dijelaskan dari kode di atas. Pertama, kita perlu mengaktifkan transaksi database untuk mengubah isi database. Kita lakukan dengan @Transactional(readOnly=false).

Kedua, kita bisa menggunakan fitur terbaru Spring JDBC, yaitu SimpleJdbcInsert. Fitur ini mampu melihat ke dalam database dan mengambil daftar nama fieldnya. Object ini diinisialisasi pada saat kita menginjeksi DataSource. Berikut kodenya.

@Autowired
public void setDataSource(final DataSource dataSource) {
  this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
  this.simpleJdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("T_PERSON").usingGeneratedKeyColumns("id");
}

Pada saat menginisialisasi object SimpleJdbcInsert kita memberi tahu Spring tentang nama tabel dan field yang isinya autogenerated, misalnya id.

Selama nama field dalam T_PERSON sama dengan nama properti di class Person, kita bisa menghilangkan kode untuk mapping properti ke PreparedStatement. Kode yang biasanya tiga baris seperti ini

PreparedStatement ps = conn.prepareStatement("insert into T_PERSON (name, email) values(?,?)");
ps.setString(1, person.getName());
ps.setString(2, person.getEmail());

Dapat direduksi menjadi satu baris seperti ini:

SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(person);

Object parameterSource ini bisa langsung diumpankan ke simpleJdbcInsert seperti ini untuk melakukan insert sekaligus mengambil nilai id yang digenerate database.

Long newId = simpleJdbcInsert.executeAndReturnKey(parameterSource).longValue()

Fitur SimpleJdbcInsert ini sangat bermanfaat kalau entity class kita terdiri dari puluhan field. Adanya method executeAndReturnKey untuk mengambil auto-generated primary key dari database juga akan sangat membantu kita untuk menghilangkan perbedaan antar database. Biasanya masing-masing merek database memiliki cara yang berbeda-beda untuk mengambil nilai ini.

Sebagai contoh, bila kita lakukan secara manual untuk database MySQL, kodenya akan tampak seperti ini.

@Transactional(readOnly=false)
public void save(final Person person) {
  simpleJdbcInsert.execute(new BeanPropertySqlParameterSource(person));
  Long newId = simpleJdbcInsert.getJdbcOperations().queryForLong("select last_insert_id()");
  person.setId(newId);
}

Demikianlah sekilas tentang penggunaan fitur Spring terbaru untuk mengakses database. Pada artikel selanjutnya, kita akan lihat fitur-fitur baru di sisi MVC framework.