Aplikasi Web dengan Spring 2.5 [bagian 2]
Posted by Endy Muhardin | Filed under Java
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:
* Checkout project
* Download WAR
* Download JAR
* Download keseluruhan project folder
Jangan lupa membaca instruksi untuk menjalankannya.
Demikian sekilas tentang framework Spring MVC. Semoga bermanfaat.
January 7, 2008 at 10:28 am
tutorial yang bagus, mudah2an bisa dilanjutkan dengan Hibernate..
Thanks
September 4, 2008 at 2:31 am
Allow mas,
Wah itu controller dengan annotation apa cuma di Spring 2.5, selama ini saya selalu pakai SimpleFormController dengan mendefinisikan command, view, dan success.
Keren deh, entar saya implementasikan
~ saiful haqqi ~
September 4, 2008 at 8:56 am
Di Spring edisi sebelumnya belum ada. Di framework lain saya kurang tau juga.
Ya namanya framework kan selalu ada peningkatan untuk mempermudah user. Dengan annotation ini, Spring memudahkan programmer tidak perlu mengingat aturan harus extends class ini-itu, urutan parameter, dsb.
Dengan annotation, aturan jadi lebih mudah diingat.
April 22, 2009 at 10:05 am
Enlightening tutorial
bisa kasih contoh untuk yang CRUD lengkapnya pak
Saya agak kesulitan terutama untuk update dan delete.
Thanks
April 22, 2009 at 3:16 pm
mas Endy, untuk line :
new PersonValidator().validate(person, result);
tapi kenapa methode validate() nya menerima kelas Error bukan kelas BindingResult
maaf, newbie nih
April 22, 2009 at 4:08 pm
kelas Error itu superclassnya BindingResult. Jadi object BindingResult bisa dipassing ke method yang minta parameter bertipe Error
April 23, 2009 at 8:01 am
Tanya lagi mas
Saya coba buat form edit data menggunakan FreeMarker, berikut cuplikan smscform.html :
…
${error}
…
Berikut cuplikan di SmscFormController.java untuk POST (submit) :
@RequestMapping(method = RequestMethod.POST)
public String processForm(@ModelAttribute(“smsc”) Smsc smsc, BindingResult result, SessionStatus status) {
new SmscValidator().validate(smsc, result);
if (result.hasErrors()) {
return “smscform”;
} else {
dao.save(smsc);
status.setComplete();
return “redirect:smsc.do”;
}
}
Saat form pertama di load (smscform.html?id=2), data yang akan diedit di tampilkan tanpa masalah, tapi saat submit ada error di BindingResult :
freemarker.core.NonStringException: Error on line 15, column 76 in smscform.html Expecting a string, date or number here, Expression spring.status.value?default(”) is instead a freemarker.template.SimpleSequence …
Berikut pojo smsc (struktur data yg akan diedit) :
public class Smsc implements java.io.Serializable {
private long id;
private String name;
private String type;
private String address;
private long port;
private String sid;
private String password;
private String systype;
private long tx;
private long rx;
private long trx;
private long status;
Kira2 kenapa ya ?
Makasih sebelumnya
April 23, 2009 at 8:05 am
Cuplikan FreeMarkernya ketinggalan
…
${error}
…
April 23, 2009 at 8:12 am
Cuplikan FreeMarkernya, maaf jadi menuh2in
<#import “spring.ftl” as spring />
…
<@spring.bind “smsc.id”/>
<input type=”text” name=”${spring.status.expression}” value=”${spring.status.value?default(”)}” /><br>
</td>
<td><#list spring.status.errorMessages as error> <b>${error}</b> <br> </#list></td>
…
April 26, 2009 at 1:41 pm
maaf mas, saya baru dalam menggunakan spring, saya udah coba tutorial mas endy dan berhasil.. programnya jalan dengan lancar,untuk proses update,edit,dan save…tapi saya mendapatkan pesan kesalahan pada saat menjalankan method getById… kurang lebih exception sperti brikut :
Apr 26, 2009 2:38:32 PM tutorial.spring25.dao.springjdbc.PersonDaoImpl getById
WARNING: Incorrect result size: expected 1, actual 0
org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0
at org.springframework.dao.support.DataAccessUtils.requiredSingleResult(DataAccessUtils.java:71)
at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:697)
at org.springframework.jdbc.core.simple.SimpleJdbcTemplate.queryForObject(SimpleJdbcTemplate.java:169)
at tutorial.spring25.dao.springjdbc.PersonDaoImpl.getById(PersonDaoImpl.java:75)
Yang saya ingin tanyakan, bagaimana memecahkan kesalahan diatas dan apa penyabnya, saya sudah berusaha, namun masih selalu gagal, atas pencerahannya saya ucapkan terimakasih…
April 30, 2009 at 4:08 pm
Kalu pake hibernate kenapa fieldnya gak mau dibinding ya mas endy…
Request processing failed; nested exception is org.springframework.beans.NotReadablePropertyException: Invalid property ‘nama’ of bean class [model.Alamat]: Bean property ‘nama’ is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:488)
Apa beda ya setingannya…
May 2, 2009 at 12:40 pm
Lah itu ada di error messagenya. Spring mencari method getNama() di dalam class Alamat.
Ada gak methodnya?
May 2, 2009 at 5:30 pm
Semua method getter setternya ada mas endy…saya punya dua kelas, yang satu anggota, yg satu alamat… mereka punya hub one to one,dan di velocity bindingnya ..seperti ini : #springBind(anggota.nama),#springBind(anggota.alamat.jalan) dst …
Nah, pas input data bisa,tapi kalau validasinya(dikelas validator/binding result) yang kebaca cuma kelas alamat.. yang kelas anggota gak kebaca..sy buat controller nya seperti ini :
@RequestMapping(method = RequestMethod.POST)
private String proccesForm(@ModelAttribute(“Anggota”) Anggota anggota,
@ModelAttribute(“alamat”) Alamat alamat, BindingResult result, SessionStatus
status) {
new AnggotaValidator().validasi(anggota, alamat, result);
if (result.hasErrors()) {
return “anggotaform”;
} else {
anggota.setAlamat(alamat);
anggotaDao.simpan(anggota);
status.setComplete();
return “redirect:anggotalist”;
}
Buat baca bisa, buat simpan bisa jika tidak pake validator..apa salah dalam melakukan bindingnya jika ingin menangkap dua objek sekaligus dengan cara seperi diatas…
Mohon pencerahannya ya mas endy…
May 6, 2009 at 10:28 am
Weh, udah bisa mas…
Thanks ya….he…
May 8, 2009 at 9:47 am
Pada kode program di atas, Anda menggunakan 2 @ModelAttribute pada method parameter.
Untuk melakukan validasi terhadap 2 @MA ini, Anda membutuhkan 2 BindingResult juga.
Ini disebutkan (walaupun sekilas saja) pada Spring Reference bagian 16.9.4 dan 16.9.5
1 BindingResult akan dipasangkan dengan @ModelAttribute yang berada tepat didepannya.
Jadi, untuk melakukan binding ke 2 @MA sekaligus, method signaturenya seperti ini :
public String processForm(@MA Anggota ang, BindingResult anggotaBinding, @MA Alamat add, BindingResult alamatBinding, SessionStatus status)
August 8, 2009 at 10:14 am
maaf mas, numpang nanya.
saya pakai spring + hibernate. juga ada dua table yang berhubungan seccara many to many. masalahnya, ketika data saya masukkan agak banyak, loading menjadi lama dan muncul error : outofmemory java heave space.
kira2 masalahnya apa ya?
terima kasih.
August 12, 2009 at 12:50 pm
Wah kurang tau juga ya …. informasinya kurang. Coba ditanyakan di milis Java.
February 9, 2010 at 11:46 am
Mas,
Correct me if i’m wrong.
Apakah benar spring annottion memiliki automatic conversion?
Seperti contoh berikut :
@RequestMapping(method = RequestMethod.GET)
public ModelMap displayForm(@RequestParam(value = “person_id”, required = false) Long id) {..}
Dimana “id” dibinding request parameter “person_id”. Yang unik request parameter bertipe object string dan Id bertipe Object Long.
Thanks sebelumnya.
February 10, 2010 at 12:15 am
Iya benar. Bukan cuma tipe sederhana seperti String, Long, BigDecimal, Date. Tapi juga object. Misalnya kita punya class Kategori dengan property id, kode, nama. Kita bisa terima id_kategori dan dikonversi jadi object Kategori