Anda di halaman 1dari 57

Intro Framework

Framework, sesuai dengan terjemahannya, adalah kerangka kerja. Jadi, di dalam framework ada
beberapa kode program yang kalau kita gunakan, akan memberikan kerangka atau struktur ke
aplikasi yang dihasilkan. Singkatnya seperti itu. Nanti akan lebih jelas setelah kita lihat contoh
kasus dan contoh kode.

Untuk memahami framework, mari kita lihat ke dunia yang minim framework, yaitu PHP.
Sebelum tahun 2005-2006 ini, (setau saya) programmer PHP di Indonesia masih asing dengan
framework.

Misalnya saya buat aplikasi shopping cart. Aplikasi yang sangat umum dalam PHP. Setidaknya
saya pernah mencoding ulang shopping cart minimal tiga kali. Dan semuanya arsitekturnya beda.

Pertama, waktu masih belajar. Semua kode campur aduk jadi satu. Kalau ada perubahan warna
font, beberapa kode SQL harus ikut diupdate. Ubah nama tabel, 7 file kode harus diedit.
Singkatnya, perubahan sedikit saja memaksa saya mengubah di banyak tempat di kode program.

Kemudian, setelah semakin banyak pengalaman, saya mulai memisahkan kode HTML dengan
kode SQL. Dengan ini, aplikasi bisa berganti wajah dengan mudah. Cukup edit satu file
konfigurasi, tampilan berubah seketika.

Terakhir, saya mulai mempertimbangkan multi bahasa. Semua string yang berkaitan dengan
label, dikeluarkan dalam satu file sendiri. Jadi kalau kita mau ganti bahasa, cukup bikin replika
file tersebut, dan terjemahkan. Kemudian edit file konfigurasi, dan bahasa berubah seketika.

Rapi dan bersih ..

Setelah mendapatkan struktur yang optimal, saya mulai me-reuse struktur tersebut. Kapan-kapan
membuat aplikasi baru, saya akan langsung memisahkan kode HTML, SQL, dan bahasa.

Skenario di atas terjadi karena saya tidak menggunakan framework. Butuh banyak sekali trial
and error buat saya sampai menemukan struktur yang rapi dan bersih. Setelah ditemukan,
struktur tersebut saya jadikan framework. Kerangka untuk membuat aplikasi selanjutnya.

Sekarang kita pindah dari dunia yang minim framework, ke dunia yang kebanjiran framework,
Java.

Di dunia Java, kelihatannya orang sangat suka coding framework, sehingga kalau kita search di
sourceforge, mungkin lebih banyak framework daripada aplikasi siap pakai. Mengapa ini terjadi,
saya tidak tahu. Yang jelas, kalau kita akrab dengan Java, pasti akan pakai framework.

Framework yang ideal harusnya merupakan intisari dari best practices. Pengalaman bertahun-
tahun membuat aplikasi yang mirip, disimpulkan dan dibakukan menjadi framework siap pakai.
Tapi saking banyaknya framework di Java, akhirnya banyak juga framework sampah. Artinya,
dibuat bukan dari best practices dan experiences, tapi lebih menjadi ajang belajar membuat
framework.

Penggunaan framework hasil belajar seperti ini sangat buruk dampaknya. APInya tidak stabil,
sehingga kalau framework tersebut menerbitkan edisi baru, kode program kita harus diubah
semua. Tentu saja, karena framework merupakan kerangka utama. Jadi kalau kerangkanya
berubah, maka seluruh aplikasi akan terpengaruh. Selain itu, strukturnya juga belum terbukti
keandalannya. Masalah sederhana menjadi kompleks, waktu banyak terbuang untuk ‘mengurusi’
framework. Menggunakan framework jelek lebih fatal akibatnya daripada tanpa framework.

Sampai sejauh ini, harusnya sudah ada gambaran tentang framework.

Framework vs Library

Jangan keliru membedakan framework dengan library. Framework, by definition, sangat invasif.
Dia memaksakan bagaimana kita harus coding, bagaimana memberi nama class, di mana harus
meletakkan file, dan banyak aturan-aturan lain. Sedangkan library, tinggal baca dokumentasi,
daftar function, input dan output masing-masing function, selesai.

Dengan demikian, menggunakan framework lebih sulit daripada library. Coba kita bedakan
framework dengan library Framework Java:

 Spring Framework
 Struts

Library Java:

 Joda Time
 JFreeChart
 Freemarker
 Jakarta Commons

Framework PHP:

 Seagull
 Cake

Library PHP:

 PEAR
 AdoDb
 Smarty

Ada lagi satu kategori, yaitu toolkit. Toolkit adalah kode yang kita gunakan dalam development,
tapi tidak disertakan dalam produk akhir. Contohnya di Java antara lain: Ant dan CruiseControl.
Tapi ada juga beberapa yang tidak jelas pengelompokannya. JUnit misalnya, di satu sisi, dia
menyediakan perlengkapan untuk testing sehingga bisa dikategorikan sebagai library. Di sisi
lain, dia mengharuskan ada prefix test di setiap nama method, sehingga layak disebut framework.

Plus Minus

Sekarang pertanyaan yang penting. Apa keuntungan dan kerugian menggunakan framework?
Asumsikan framework yang ingin digunakan bagus dan teruji.

Keuntungan

 Struktur yang konsisten. Sangat berguna bila developer banyak dan turnover tinggi.
 Struktur merupakan best practices. Semua sudah ditempatkan di tempat yang paling
sesuai.
 Dapat belajar tentang desain aplikasi yang baik.

Kerugian

 Butuh investasi waktu belajar dan adaptasi


 Ada overhead. Untuk project sangat kecil, mungkin overheadnya lebih besar dari
projectnya sendiri. Lagipula, tidak semua fitur framework kita pakai, sehingga cuma akan
menjadi bloat.
 Menimbulkan dependensi. Ini terutama terasa di aplikasi yang berarsitektur plugin.
Upgrade framework dapat merusak plugin.

Memilih Framework

Sekarang, bagaimana kita memutuskan pakai framework atau tidak? Kalau pakai, pilih yang
mana? Sering sekali ada banyak framework yang menyelesaikan masalah yang sama.

Pertama, pertimbangkan benefit vs cost. Di beberapa project, saya lebih memilih tanpa
framework, karena ukuran projectnya kecil.

Untuk playbilling, saya pakai iBatis alih-alih Hibernate yang lebih lengkap fiturnya.
Pertimbangannya adalah, saya tidak menggunakan banyak tipe database. Sehingga cross-
database tidak terlalu dibutuhkan. Sekarang saya sedang mempertimbangkan menghilangkan
iBatis, dan pakai JDBC biasa untuk mengurangi dependensi dan ukuran donlod.

Kedua, lihat kualitas dokumentasinya. Alasan yang jelas adalah, kalau tidak ada dokumentasi,
gimana tau cara pakainya? Kita butuh dokumentasi untuk bisa menggunakan framework.

Ada alasan kedua yang juga penting. Kualitas dokumentasi mencerminkan kualitas framework.
Mengapa? Karena dokumentasi biasanya adalah by product (produk sampingan), apalagi di
project open source. Logikanya, jika tim developer menghasilkan dokumentasi bagus, pasti
kualitas dan desain frameworknya sendiri lebih bagus lagi.
Sejauh ini, kesimpulan saya konsisten. Framework terbaik secara desain dan kualitas kode adalah
Spring Framework. Coba lihat dokumentasinya. Sangat rinci, detail, dan minim kesalahan.
Contoh lain adalah Hibernate. Dokumentasinya sangat lengkap. Bahkan di dalamnya juga
dijelaskan tentang konsep Object Relational Mapping. Dengan membaca dokumentasinya saja,
kita akan menambah wawasan. Apalagi menggunakan frameworknya.

Ketiga, lihat aktivitas komunitasnya. Framework, apalagi open source, membutuhkan interaksi
dengan sesama pengguna. Keuntungan utama tentu saja technical support dan tutorial gratis.
Apalagi dengan perkembangan blog yang sangat pesat beberapa tahun terakhir. Semua
pengalaman baik, buruk, mudah, sulit, semua diceritakan para pengguna framework di blognya
masing-masing.

Kalau kita menggunakan framework minoritas, jarang digunakan orang, tutorialnya sedikit,
diskusi di berbagai forum juga tidak banyak. Jadi, kalau kita search dengan Google, lebih sulit
menemukan permasalahan (dan solusi) yang sama dengan kebutuhan kita.

Terakhir, learning curve, atau kurva belajar. Apakah framework tersebut sulit atau mudah
dipelajari?

Di bagian atas tadi saya menyebutkan keunggulan framework adalah dia dapat menyeragamkan
struktur kode di antara banyak developer. Jadi, agar efektif, semua developer harus bisa
menggunakan framework tersebut.

Nah, seorang system architect, software designer, senior developer, development team leader,
atau apapun istilahnya di tempat Anda, orang yang bertugas memilih framework, harus
mempertimbangkan kurva belajar ini.

Ini sering diabaikan banyak orang. Sebagai pemilih framework, tentunya orang tersebut memiliki
keahlian/pengalaman teknis di atas rekan-rekannya. Penting bagi orang tersebut untuk
memperhitungkan keahlian/pengalaman anggota tim yang lain yang tidak secanggih dirinya pada
waktu memilih framework. Jangan sampai framework yang dipilih terlalu rumit, sehingga
bukannya di-reuse malahan di-abuse.

Memahami Dependency Injection


Spring Framework merupakan framework yang sangat populer dan banyak digunakan orang di
seluruh dunia. Jargon utama yang sering kita dengar bersamaan dengan Spring Framework
adalah prinsip Dependency Injection. Ini adalah teknik pemrograman yang digadang-gadang
mampu merapikan aplikasi yang kita buat sehingga mudah dipahami dan dikelola.

Tapi apakah yang dimaksud dengan Dependency Injection atau Inversion of Control itu?
Contoh Kasus

Sebagai programmer, kita akan lebih mudah memahami suatu konsep bila sudah melihat sendiri
contoh kode program dan aplikasinya. Untuk itu, kita akan membuat sebuah contoh kasus
sederhana, yaitu menyimpan data ke tabel produk dalam database.

Untuk titik awal, misalnya kita memiliki class Produk sebagai berikut:

Produk.java
public class Produk {
private Integer id;
private String kode;
private String nama;
private BigDecimal harga;

// getter dan setter tidak ditampilkan


}

Supaya rapi, kode program untuk Create (insert), Read (select), Update, dan Delete (CRUD),
akan kita kumpulkan di dalam class ProdukDao sebagai berikut:

ProdukDao.java
public class ProdukDao {
private DataSource dataSource;

public void create(Produk p) throws Exception {


String sql = "insert into produk (kode, nama, harga) ";
sql += "(?,?,?)";

Connection databaseConnection = dataSource.getConnection();


PreparedStatement ps = databaseConnection.prepareStatement(sql);
ps.setString(1, p.getKode());
ps.setString(2, p.getNama());
ps.setBigDecimal(3, p.getHarga());
ps.executeUpdate();
databaseConnection.close();
}

public Produk cariById(Integer id){


// implementasi tidak ditulis
}

public void update(Produk p){


// implementasi tidak ditulis
}

public void delete(Produk p){


// implementasi tidak ditulis
}
}

Sebagai demonstrasi, kita akan menggunakan/memanggil ProdukDao ini dalam class


ProdukDaoTest sebagai berikut:

ProdukDaoTest.java
public class ProdukDaoTest {
public static void main(String[] xx){
Produk p = new Produk();
p.setKode("P-001");
p.setNama("Produk 001");
p.setHarga(new BigDecimal(10000.00);

ProdukDao pd = new ProdukDao();


pd.create(p);
}
}

Dari ketiga class di atas, kita akan melihat:

 bagaimana implementasi tanpa Dependency Injection


 bagaimana konsep Dependency Injection
 bagaimana menggunakan Spring Framework untuk melakukan Dependency Injection

Sebelumnya, apa itu dependency injection? Kalau diterjemahkan ke bahasa Indonesia, kira-kira
artinya adalah menyediakan kebutuhan. Kebutuhan apa yang dimaksud?

Coba lihat class ProdukDao. Untuk bisa menjalankan tugasnya dengan baik, dia membutuhkan
object DataSource, yaitu koneksi ke database. Bagaimana ProdukDao mendapatkan
DataSource inilah yang menjadi pembahasan dalam Dependency Injection (DI).

Tanpa DI

Kalau kita tidak menggunakan prinsip DI, maka ProdukDao harus mengadakan sendiri object
DataSource. Kira-kira begini implementasinya:

public class ProdukDao {


private DataSource dataSource;

public ProdukDao() throws Exception {


dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("admin");
dataSource.setUrl("jdbc:mysql://localhost/belajar");
}

public void create(Produk p) throws Exception {


String sql = "insert into produk (kode, nama, harga) ";
sql += "(?,?,?)";

Connection databaseConnection = dataSource.getConnection();


PreparedStatement ps = databaseConnection.prepareStatement(sql);
ps.setString(1, p.getKode());
ps.setString(2, p.getNama());
ps.setBigDecimal(3, p.getHarga());
ps.executeUpdate();
databaseConnection.close();
}

public Produk cariById(Integer id){


// implementasi tidak ditulis
}

public void update(Produk p){


// implementasi tidak ditulis
}

public void delete(Produk p){


// implementasi tidak ditulis
}
}

Perlu kita sadari bahwa pada aplikasi yang sebenarnya, kode akses database tidak hanya
ProdukDao saja. Nantinya juga ada CustomerDao, PenjualanDao, dan sebagainya. Di aplikasi
berskala menengah, bisa ada ratusan class seperti ini, sehingga untuk memahami situasinya, kita
tidak boleh berpikir hanya di satu class ini saja.

Ada beberapa kelemahan dari cara tanpa DI ini, diantaranya:

 Konfigurasi koneksi database tersebar di banyak tempat, yaitu di semua XxxDao


 Object dataSource juga tersebar, tidak bisa satu dataSource dipakai bersama oleh semua
XxxDao
 Karena konfigurasi dan inisialisasinya tersebar, bila ada perubahan (misalnya menggunakan
connection pooling), harus dilakukan di banyak tempat.
 Semua perubahan di atas mengharuskan full compile karena banyaknya class yang terlibat.

Untuk mengatasi keterbatasan di atas, kita gunakan prinsip DI.

DI manual

Bila kita gunakan prinsip DI, maka ProdukDao tidak lagi mengurus inisialisasi dataSource. Dia
cukup tahu beres dan tinggal pakai. Lalu siapa yang melakukan inisialisasi? Boleh siapa saja,
tapi untuk kesederhanaan ilustrasi, mari kita tulis saja di dalam ProdukDaoTest, sebagai berikut:

public class ProdukDaoTest {


public static void main(String[] xx){
Produk p = new Produk();
p.setKode("P-001");
p.setNama("Produk 001");
p.setHarga(new BigDecimal(10000.00);

DataSource dataSource = new BasicDataSource();


dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("admin");
dataSource.setUrl("jdbc:mysql://localhost/belajar");

ProdukDao pd = new ProdukDao();


pd.create(p);
}
}

Lalu bagaimana cara memasukkan (inject) ke dalam ProdukDao? Kita harus sediakan jalan
masuknya. Ada dua pilihan:

 membuat method untuk mengisi data (setter-injection)


 menambah argumen di constructor (constructor-injection)

Agar jelas, kita akan buatkan dua-duanya.

public class ProdukDao {


private DataSource dataSource;

// ini kalau mau inject melalui constructor


public ProdukDao(DataSource ds) {
this.dataSource = ds;
}

// ini kalau mau method sendiri (setter-injection)


public void setDataSource(DataSource ds) {
this.dataSource = ds;
}

// method lain tidak ditampilkan supaya tidak bikin penuh


}

Selanjutnya, dalam ProdukDaoTest kita bisa isikan object DataSource tersebut melalui
constructor seperti ini:

ProdukDao pd = new ProdukDao(dataSource);

ataupun melalui method setter seperti ini:

ProdukDao pd = new ProdukDao();


pd.setDataSource(dataSource);

Cara manual ini sudah lumayan merapikan kode program kita, karena bila ada perubahan
terhadap inisialisasi dataSource, seperti misalnya:
 perubahan konfigurasi koneksi
 perubahan implementasi connection pooling
 ingin menggunakan managed DataSource melalui JNDI
 dsb

Kita cukup melakukan perubahan di satu tempat saja, yaitu dimana dia diinisialisasikan.

DI Spring XML

Walaupun bisa dilakukan secara manual, tetapi ada baiknya kita menggunakan Spring
Framework untuk melakukan DI. Beberapa alasannya antara lain:

 AOP (tidak dibahas pada artikel ini)


 keseragaman antar project/aplikasi
 standarisasi skillset. Bila cari programmer baru, cukup mensyaratkan pengetahuan Spring
Framework. Tidak perlu lagi ditraining tentang teknik DI yang kita karang sendiri.

Spring Framework umumnya dikonfigurasi dengan file XML (walaupun bisa juga full Java).
Berikut adalah contoh file konfigurasinya, misalnya kita beri nama konfig-spring.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">


<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/belajar" />
<property name="username" value="root" />
<property name="password" value="admin" />
</bean>

<bean id="produkDao" class="com.muhardin.endy.belajar.spring.ProdukDao">


<property name="dataSource" ref="dataSource"/>
</bean>

</beans>

File konfigurasi tersebut kita baca dan gunakan dalam class ProdukDaoTest, perhatikan
perbedaan inisialisasi DataSource dan ProdukDao

public class ProdukDaoTest {


public static void main(String[] xx){
Produk p = new Produk();
p.setKode("P-001");
p.setNama("Produk 001");
p.setHarga(new BigDecimal(10000.00);
ApplicationContext ctx
= new ClassPathXmlApplicationContext("classpath:konfig-
spring.xml");

ProdukDao pd = ctx.getBean(ProdukDao.class);
pd.create(p);
}
}

Pada contoh di atas, kita bisa lihat beberapa perbedaan yaitu:

 inisialisasi DataSource pindah ke dalam konfig-spring.xml


 tidak perlu lagi mengisikan DataSource ke dalam ProdukDao, karena sudah dilakukan oleh
Spring.

Walaupun demikian, masih ada sedikit ganjalan, yaitu:

 bila XxxDao jumlahnya ratusan, maka file konfig-spring.xml akan membengkak.

Apa solusinya?

DI Spring @Autowired

Spring Framework menyediakan fitur component-scan, yaitu dia akan melihat isi package yang
kita sebutkan, kemudian akan mencari class-class yang diberi anotasi berikut:

 @Repository
 @Service
 @Controller
 @Component

Setelah ditemukan, maka dia akan melakukan inisialisasi terhadap class tersebut, dan lalu
mengisi (inject) semua kebutuhannya (dependency). Untuk injection ini, kita juga tidak perlu lagi
menyediakan setter method maupun menambahkan argumen di constructor. Kita dapat
menggunakan anotasi @Autowired.

Berikut adalah konfigurasi konfig-spring.xml yang baru:

<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">


<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/belajar" />
<property name="username" value="root" />
<property name="password" value="admin" />
</bean>

<context:component-scan base-package="com.muhardin.endy.belajar.spring"
/>

</beans>

Perhatikan bahwa deklarasi produkDao telah digantikan dengan perintah context:component-


scan.

Berikut adalah perubahan di ProdukDao

@Repository
public class ProdukDao {
@Autowired private DataSource dataSource;

public void create(Produk p){


String sql = "insert into produk (kode, nama, harga) ";
sql += "(?,?,?)";

Connection databaseConnection = dataSource.getConnection();


PreparedStatement ps = databaseConnection.prepareStatement(sql);
ps.setString(1, p.getKode());
ps.setString(2, p.getNama());
ps.setBigDecimal(3, p.getHarga());
ps.executeUpdate();
databaseConnection.close();
}

// method lain tidak ditampilkan supaya tidak bikin penuh


}

Perhatikan bahwa setter dan constructor injection sudah dihapus, dan digantikan dengan anotasi
@Autowired. Semua field/property yang memiliki anotasi @Autowired akan diisikan oleh Spring
dengan object bertipe-data sesuai. Bila tidak ditemukan, maka aplikasi akan gagal start dengan
pesan error seperti ini:

SEVERE: Context initialization failed


org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'produkDao':
Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field:
private javax.sql.DataSource dataSource;
nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No matching bean of type [javax.sql.DataSource] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this
dependency.
Bila menemukan pesan error tersebut, artinya Spring tidak memiliki satupun object bertipe
DataSource dalam daftar pengelolaannya. Untuk bisa dikelola oleh Spring, ada beberapa
caranya, yaitu:

 dideklarasikan secara tertulis seperti object dataSource di atas


 discan otomatis melalui component-scan dan anotasi @Repository, @Service,
@Controller, ataupun@Component. Contohnya object produkDao di atas.

Ada beberapa kesalahan yang umum terjadi sehingga muncul pesan error di atas, diantaranya

 sudah ada anotasi, tapi package tempatnya berada tidak didaftarkan dalam component-scan
 package sudah didaftarkan dalam component-scan, tapi classnya tidak diberikan anotasi
@Repository, @Service, @Controller, ataupun@Component.
 sudah ada anotasi, packagenya sudah didaftarkan, tapi file xml yang memuat konfigurasi
tersebut tidak diload oleh aplikasi. Ini biasa terjadi kalau satu aplikasi terdiri dari banyak file
konfigurasi Spring (yang mana ini adalah hal yang umum terjadi)

Lalu kapan dan bagaimana Spring membaca file konfigurasi? Ada beberapa cara:

 Ditulis dalam kode program : new


ClassPathXmlApplicationContext("classpath:konfig-spring.xml")
 Bila aplikasinya berbasis web, biasanya diinisialisasi melalui web.xml seperti ini:

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:konfig-spring.xml
</param-value>
</context-param>
<listener>
<listener-
class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Konfigurasi Koneksi Database dengan


Spring
27 May 2013

Di Java, ada banyak cara untuk mengakses database, diantaranya:

 JDBC polos tanpa framework apapun


 JDBC dengan Spring (Spring JDBC)
 JDBC dengan iBatis/MyBatis
 Hibernate
 JDO
 JPA
 Spring Data JPA

Masing-masing memiliki kelebihan dan kekurangan masing-masing yang tidak akan kita bahas
di artikel ini. Kali ini kita hanya akan membahas metode Spring JDBC dan perbandingannya
dengan JDBC murni.

Perbandingan JDBC polos dan Spring JDBC

Menggunakan JDBC polos tanpa library memang mudah, karena tidak perlu pusing mempelajari
library lain. Tapi ada beberapa keterbatasan dan kesulitan, diantaranya:

 semua method throws Exception, sehingga kode program kita menjadi kotor dengan try-catch
 tidak ada manajemen koneksi database, kita harus buka-tutup sendiri
 tidak ada declarative transaction, kita harus secara manual melakukan begin-commit/rollback

Dengan berbagai keterbatasan tersebut, ada baiknya kita menggunakan bantuan Spring
Framework, yaitu modul spring-jdbc untuk memudahkan berbagai kegiatan di atas. Tentunya
masih banyak hal yang harus kita lakukan sendiri, tidak seperti penggunaan library Object
Relational Mapping (ORM) seperti Hibernate atau JPA, di antaranya:

 mapping dari result set menjadi Java object


 mapping dari Java object menjadi isi parameter PreparedStatement
 cache
 cascading operation
 generate primary key secara otomatis
 dsb

Ada beberapa tahapan dalam menggunakan Spring JDBC, yaitu :

1. Konfigurasi Koneksi Database


2. Membuat class Data Access Object (DAO)
3. Membuat class implementasi business process/service
4. Membuat test otomatis menggunakan JUnit

Studi Kasus

Agar lebih konkrit, kita akan menggunakan skema database yang umum digunakan di aplikasi
bisnis, yaitu memiliki tabel :

 Master Data / Referensi


 Header Transaksi
 Detail Transaksi

Berikut adalah skema database yang akan kita gunakan:


create table m_produk (
id int primary key auto_increment,
kode varchar(20) not null,
nama varchar(255) not null,
harga decimal(19,2) not null
) engine=InnoDB ;

create table t_penjualan (


id int primary key auto_increment,
waktu_transaksi datetime not null
) engine=InnoDB AUTO_INCREMENT=100;

create table t_penjualan_detail (


id int primary key auto_increment,
id_penjualan int not null,
id_produk int not null,
harga decimal(19,2) not null,
jumlah int not null,
foreign key(id_penjualan) references t_penjualan(id) on delete cascade,
foreign key(id_produk) references m_produk(id) on delete restrict
) engine=InnoDB AUTO_INCREMENT=100;

Untuk keperluan test, jangan lupa kita sertakan beberapa baris data.

insert into m_produk (id,kode,nama,harga) values


(1, 'K-001', 'Keyboard USB', 150000),
(2, 'M-001', 'Mouse USB', 50000),
(3, 'L-001', 'Laptop', 10000000);

insert into t_penjualan (id,waktu_transaksi) values


(1,'2013-01-01 20:30:30'),
(2,'2013-01-02 15:15:15'),
(3,'2013-02-02 09:09:09');

insert into t_penjualan_detail (id,id_penjualan, id_produk, harga, jumlah)


values
(1,1,1,150000,2),
(2,1,2,50000,5),
(3,2,1,150000,3),
(4,2,2,50000,3),
(5,3,3,10000000,1);

Tabel dan data di atas kita masukkan ke database dengan rincian sebagai berikut:

 Jenis Database : MySQL


 Server Database : localhost
 Nama Database : belajar
 Username Database : root
 Password Database : admin

Selanjutnya kita akan mengkonfigurasi Spring supaya bisa terkoneksi dengan database tersebut.
Konfigurasi Koneksi Database

Dependensi

Kita membutuhkan beberapa library, dinyatakan dalam dependensi Maven sebagai berikut:

Driver database MySQL

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>

Library manajemen koneksi database (database connection pooling)

<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${commons-dbcp.version}</version>
</dependency>

Spring JDBC

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework.version}</version>
<scope>test</scope>
</dependency>

Selanjutnya, kita membuat konfigurasi database untuk Spring. Kita beri nama saja file
konfigurasinya spring-jdbc-ctx.xml dan kita letakkan di folder src/main/resources. File
ini berisi :

 konfigurasi data source untuk koneksi ke database. Kita menggunakan pustaka commons-dbcp
untuk menangani connection pooling ke database.
 transaction manager. Ini dibutuhkan supaya kita tidak perlu lagi membuat coding untuk
rangkaian begin-commit/rollback.
 component scan. Ini dibutuhkan agar object DAO dan Service kita otomatis dideteksi dan
diinisialisasi oleh Spring

Koneksi Database

Berikut konfigurasi koneksi database

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">


<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/belajar" />
<property name="username" value="root" />
<property name="password" value="admin" />
<property name="maxActive" value="80" />
<property name="maxWait" value="40000" />
<property name="maxIdle" value="20" />
</bean>

Penjelasannya sebagai berikut:

Database Connection Pooling

Koneksi ke database sebetulnya merupakan operasi yang mahal. Kenapa mahal? Karena setiap
kali kita membuat koneksi, ada serangkaian kegiatan yang harus dilakukan oleh database server
seperti:

 memeriksa username dan password


 memeriksa apakah komputer kita (dilihat dari alamat IP atau nama host) diijinkan untuk masuk
 memeriksa apakah database, tabel, dan tindakan yang kita lakukan memiliki ijin akses yang
mencukupi

Oleh karena itu, idealnya koneksi database dibuat sekali saja dan digunakan terus sepanjang
aplikasi berjalan. Tentunya kalau koneksi database hanya satu, setiap request dari user akan
mengantri. Untuk itu kita buat banyak koneksi sekaligus yang nantinya akan dipinjamkan pada
request yang membutuhkan. Teknik ini disebut dengan istilah database connection pooling.

Library yang kita gunakan untuk itu adalah Apache Commons DBCP, yang ditandai dengan
penggunaan class org.apache.commons.dbcp.BasicDataSource di atas.

Ada banyak hal yang bisa disetting, tapi kita akan fokus ke beberapa saja yaitu:

 driverClassName : nama class untuk koneksi ke database. Ini harus sesuai dengan merek dan
versi database yang digunakan
 url : informasi koneksi database. Biasanya berisi alamat server (IP atau Hostname) dan nama
database
 username : username untuk connect ke database
 password : passwordnya user tersebut
Keempat informasi di atas adalah informasi umum yang kita butuhkan apapun metode koneksi
database yang kita gunakan, tidak terkait dengan penggunaan Apache Commons DBCP.
Konfigurasi berikut barulah berkaitan dengan Apache Commons DBCP:

 maxActive : jumlah koneksi yang boleh aktif secara berbarengan. Ini harus disetting
dibawah angka yang kita ijinkan di database server. Misalnya di MySQL kita ijinkan 100
koneksi berbarengan, maka angkanya harus dibawah 100. Jangan juga dihabiskan 100,
untuk berjaga-jaga siapa tahu kita butuh koneksi langsung ke MySQL tanpa lewat
aplikasi (misalnya untuk keperluan debug). Pertimbangkan juga apabila ada aplikasi lain
yang menggunakan database yang sama.
 maxIdle : ada kalanya aplikasi kita sedang sepi dari request user sehingga banyak
koneksi database yang menganggur (idle). Angka maxIdle ini menentukan berapa
koneksi yang akan tetap dipegang walaupun idle. Bila ada 20 koneksi idle, padahal
maxIdle berisi 15, maka 5 koneksi akan ditutup. Ini merupakan trade-off. Bila terlalu
banyak idle, maka memori database server akan terpakai untuk koneksi yang standby ini.
Tapi bila terlalu sedikit, pada waktu aplikasi mendadak diserbu user, akan butuh waktu
lama untuk dia membuatkan lagi koneksi baru.
 maxWait : bila semua koneksi sebanyak maxActive sedang terpakai semua, request
berikutnya akan menunggu salah satu selesai menggunakan koneksi. Nilai maxWait
menentukan berapa milidetik request tersebut menunggu. Bila lebih dari maxWait dan
belum juga kebagian koneksi, maka request tersebut akan mendapatkan Exception.
Konfigurasi ini perlu diperhatikan karena nilai defaultnya adalah indefinitely yaitu
menunggu selamanya.

Saya pernah mendapatkan masalah karena setting default ini. Aplikasi bengong seolah hang.
Dicek ke log file tidak ada error. Ternyata masalahnya ada query yang kurang optimal sehingga
memakan waktu lama. Pada saat banyak request yang menjalankan query tersebut, request lain
menunggu lama tanpa ada pemberitahuan, sehingga terkesan hang. Setelah nilai maxWait saya
ganti menjadi 30 detik, mulai banyak error message bermunculan dari request yang menunggu >
30 detik. Dengan adanya error message, query bermasalah tersebut menjadi terlihat sehingga bisa
diperbaiki.

Pesan moral pertama : pesan error itu penting untuk mengetahui sumber masalah. Dalam
bugfixing, yang paling penting adalah menemukan masalah. Kalau masalah sudah ditemukan,
siapa saja bisa memperbaiki. Jadi kalau aplikasi kita bermasalah, prioritas pertama kita adalah
membuat dia mengeluarkan pesan error yang jelas.

Baca artikel ini untuk mengetahui apa yang dimaksud dengan pesan error yang jelas.

Pesan moral kedua : Dalam bugfixing, sering kali kita tidak langsung mendapatkan masalah
utama. Pada kasus di atas, pertama kali saya menemukan bahwa perilaku defaultnya Commons
DBCP adalah menunggu koneksi dengan sabar sampai selamanya. Setelah ini diubah, barulah
saya menemukan masalah utama, yaitu ada query yang tidak optimal.

Transaction Manager
Setelah terhubung ke database, selanjutnya kita akan mengkonfigurasi transaction manager. Ini
adalah fitur dari Spring Framework yang membebaskan kita dari coding manual untuk urusan
transaction. Bila tidak menggunakan ini, maka kode program kita akan tampak seperti ini:

public void simpan(Produk p){


Connection conn; // inisialisasi koneksi di sini

try {
conn.setAutocommit(false);

String sql = "insert into m_produk ... ";


conn.createStatement().executeUpdate(sql);

conn.commit();

} catch (Exception err){


conn.rollback();
} finally {
conn.setAutocommit(true);
conn.close();
}
}

Untuk dua baris perintah seperti di atas, kita harus menambahkan 8 baris hanya untuk mengurus
transaction pada setiap method. Bayangkan kalau aplikasi kita punya 100 method, maka kode
program untuk mengelola transaksi saja sudah 800 baris. Dengan fitur transaction manager,
maka method di atas bisa ditulis ulang seperti ini:

@Transactional
public void simpan(Produk p){
String sql = "insert into m_produk ... ";
conn.createStatement().executeUpdate(sql);
}

Jauh lebih bersih dan rapi. Kode program jadi mudah dibaca dan akibatnya tentu memudahkan
kita pada waktu bugfix, perubahan, ataupun penambahan fitur.

Nah ini dia konfigurasi transaction manager:

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<tx:annotation-driven/>

Class transaction manager yang digunakan berbeda tergantung metode akses database yang
digunakan dan juga bagaimana cara kita mendeploy aplikasi. Beberapa pilihan transaction
manager yang lain antara lain:

 DataSourceTransactionManager : digunakan untuk koneksi database menggunakan


javax.sql.DataSource. Ini artinya koneksi langsung dari aplikasi ke database server.
 JtaTransactionManager : digunakan bila aplikasi kita dideploy di application server yang
memiliki transaction manager sendiri (seperti Glassfish, JBoss, Websphere, Weblogic, dsb) dan
menggunakan transaction manager yang disediakan tersebut. Bila kita deploy di Tomcat, hampir
pasti kita tidak menggunakan JTA. Bila kita deploy ke Glassfish dan menggunakan konfigurasi
dataSource Apache Commons DBCP, berarti kita juga tidak menggunakan JTA.
 HibernateTransactionManager : seperti ditunjukkan oleh namanya, gunakan ini bila kita
menggunakan Hibernate
 JpaTransactionManager : ini juga sudah jelas dari namanya. Bila kita pakai JPA, gunakan
transaction manager ini.

Insert Update Delete dengan Spring JDBC

Setup DAO

Supaya bisa diinisialisasi oleh Spring Framework, kita harus menambahkan annotation
@Repository di class ProdukDao dan juga class DAO lainnya. Pembahasan lebih detail tentang
cara kerja Spring Framework dan fungsinya annotation @Repository dapat dibaca di artikel ini.

Selanjutnya, kita akan menambahkan beberapa variabel yang nantinya akan kita butuhkan, yaitu
JdbcTemplate dan perintah SQL yang ingin dijalankan.

JDBC Template

JdbcTemplate adalah class utama dalam Spring JDBC. Semua operasi database dilakukan
melalui JdbcTemplate. Kita juga butuh rekannya yang bernama NamedParameterJdbcTemplate.
Kelebihan dari NamedParameterJdbcTemplate ini, dia bisa menerima SQL dengan variabel
yang diberi nama. Tanpa dia, kita cuma bisa menjalankan SQL yang variabelnya ditandai dengan
?, sehingga membingungkan dan rawan terjadi kesalahan kalau jumlah variabelnya banyak.

Kedua object ini kita deklarasikan menjadi object/instance variable, supaya bisa digunakan oleh
semua method. Inisialisasinya membutuhkan object DataSource, sehingga sebaiknya kita
lakukan inisialisasi di dalam setter injection. Bagi yang belum paham apa itu setter injection
silahkan baca dulu artikel ini.

Berikut adalah kode programnya.

@Repository
public class ProdukDao {

private JdbcTemplate jdbcTemplate;


private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.namedParameterJdbcTemplate = new
NamedParameterJdbcTemplate(dataSource);
}
}

SQL

Agar lebih rapi, semua perintah SQL yang kita akan gunakan di dalam method sebaiknya kita
deklarasikan sebagai konstanta. Berikut adalah berbagai SQL yang kita gunakan.

@Repository
public class ProdukDao {
private static final String SQL_INSERT = "insert into m_produk
(kode,nama,harga) values (:kode,:nama,:harga)";
private static final String SQL_CARI_BY_ID = "select * from m_produk
where id = ?";
private static final String SQL_CARI_BY_KODE = "select * from m_produk
where kode = ?";
private static final String SQL_HITUNG_SEMUA = "select count(*) from
m_produk";
private static final String SQL_CARI_SEMUA = "select * from m_produk
limit ?,?";
private static final String SQL_HITUNG_BY_NAMA = "select count(*) from
m_produk where lower(nama) like ?";
private static final String SQL_CARI_BY_NAMA = "select * from m_produk
where lower(nama) like ? limit ?,?";
}

Harap diperhatikan bahwa saya hanya menampilkan potongan kode program yang berkaitan
dengan penjelasan saat ini saja, berikut sedikit tambahan supaya jelas di mana penempatannya.
Untuk isi kode program yang lengkap silahkan lihat di Github.

Insert Data

Setelah kita punya JdbcTemplate dan perintah SQL, kita bisa gunakan untuk menyimpan data
ke database. Mari kita implement method simpan.

Menggunakan parameter domain object

Class Produk memiliki properti yang bernama kode, nama, dan harga. Karena kita mengikuti
aturan penamaan yang baku, maka kita buatkan method getter dan setter. Class yang dibuat
mengikuti standar ini bisa langsung diproses oleh Spring JDBC. Dia akan secara otomatis
memasangkan properti yang namanya sama dengan yang ada di perintah SQL. Jadi perintah SQL
seperti ini

insert into m_produk (kode,nama,harga) values (:kode,:nama,:harga)

Akan diisikan dengan nilai yang didapat dari pemanggilan method berikut:

Produk p; // nantinya p diisi dari method parameter

p.getKode();
p.getNama();
p.getHarga();

Spring JDBC juga akan secara otomatis mendeteksi tipe data baik itu Integer, String,
BigDecimal, dan sebagainya.

Berikut adalah implementasi method simpan yang menggunakan konsep di atas.

public void simpan(Produk p) {


SqlParameterSource namedParameters = new
BeanPropertySqlParameterSource(p);
KeyHolder keyHolder = new GeneratedKeyHolder();
namedParameterJdbcTemplate.update(SQL_INSERT, namedParameters,
keyHolder);
p.setId(keyHolder.getKey().intValue());
}

BeanPropertySqlParameterSource adalah class dari Spring JDBC yang berfungsi mengambil


parameter dari Java object seperti telah dijelaskan di atas. Selain mengambil dari object dengan
getter dan setter, ada lagi class lain yang bernama MapSqlParameterSource, digunakan untuk
mengambil parameter dari object bertipe java.util.Map.

Mendapatkan nilai yang auto generated

Di tabel m_produk, primary key ada di kolom id. Nilainya digenerate otomatis oleh database
pada saat kita melakukan insert. Kita perlu mengetahui nilai berapa yang dibuatkan database
untuk data yang barusan kita insert agar bisa dipasang di object Produk yang kita insert. Untuk
menampung nilai yang otomatis dibuatkan database, kita menggunakan class
GeneratedKeyHolder.

Setelah kita melakukan insert, keyHolder akan menampung nilai. Nilai inilah yang kita pasang
di object produk dengan kode program berikut.

namedParameterJdbcTemplate.update(SQL_INSERT, namedParameters, keyHolder);


p.setId(keyHolder.getKey().intValue());

Menggunakan SimpleJdbcInsert

Object produk di atas hanya memiliki 4 property di Java dan 4 kolom di database. Bila
propertynya banyak, tentu kita akan kerepotan menulis variabel dalam perintah SQL. Untuk
membantu kita, Spring JDBC menyediakan fitur SimpleJdbcInsert. Cara kerjanya, dia akan
menanyakan ke database kolom apa saja yang ada di tabel tertentu berikut tipe data untuk
masing-masing kolom. Setelah itu, informasi tersebut digunakan untuk membuatkan SQL insert
buat kita, sehingga kita tidak perlu membuat sendiri.

Berikut adalah cara inisialisasi SimpleJdbcInsert, dilakukan di setter injection.

@Repository
public class ProdukDao {
private SimpleJdbcInsert insertProduk;

@Autowired
public void setDataSource(DataSource dataSource) {
this.insertProduk = new SimpleJdbcInsert(dataSource)
.withTableName("m_produk")
.usingGeneratedKeyColumns("id");
}
}

Kita menyebutkan juga kolom mana yang nilainya digenerate database, sehingga dia bisa
mengambilkannya. Selanjutnya, mari kita ganti implementasi method simpan dengan
memanfaatkan SimpleJdbcInsert.

public void simpan(Produk p) {


SqlParameterSource produkParameter = new
BeanPropertySqlParameterSource(p);
Number idBaru = insertProduk.executeAndReturnKey(produkParameter);
p.setId(idBaru.intValue());
}

Insert Data Header dan Detail

Kenapa kita perlu mengambil primary key yang digenerate database?

Jawabannya karena kita membutuhkannya dalam skenario header dan detail. Di tabel detail, ada
relasi foreign key ke tabel header. Sehingga untuk mengisi foreign key tersebut, kita harus
mengetahui dulu primary key header.

Selain masalah foreign key ini, sisanya sama dengan pembahasan insert sebelumnya. Berikut
implementasi method simpan(Penjualan p)

public void simpan(Penjualan p) {


SqlParameterSource namedParameters = new
BeanPropertySqlParameterSource(p);
KeyHolder keyHolder = new GeneratedKeyHolder();
namedParameterJdbcTemplate.update(SQL_INSERT, namedParameters,
keyHolder);
p.setId(keyHolder.getKey().intValue());

for (PenjualanDetail detail : p.getDaftarPenjualanDetail()) {


detail.setPenjualan(p);
penjualanDetailDao.simpan(detail);
}
}

Seperti kita lihat pada kode program di atas, kita insert ke tabel t_penjualan, kemudian kita
ambil nilai id. Nilai tersebut kita isikan ke tiap object detail untuk selanjutnya kita insert juga
menggunakan method simpan(PenjualanDetail pd) di class PenjualanDetailDao. Berikut
implementasinya.
public void simpan(final PenjualanDetail p) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {

@Override
public PreparedStatement createPreparedStatement(Connection conn)
throws SQLException {
PreparedStatement ps = conn.prepareStatement(SQL_INSERT, new
String[]{"id"});
ps.setInt(1, p.getPenjualan().getId());
ps.setInt(2, p.getProduk().getId());
ps.setBigDecimal(3, p.getHarga());
ps.setInt(4, p.getJumlah());
return ps;
}
}, keyHolder);
p.setId(keyHolder.getKey().intValue());
}

Pada waktu menyimpan PenjualanDetail, kita menggunakan teknik yang agak berbeda, tidak
menggunakan SimpleJdbcInsert maupun NamedParameterJdbcTemplate. Ini disebabkan
karena banyak kolom yang namanya tidak mengikuti aturan penamaan, yaitu id_penjualan dan
id_produk. Karena itu kita menggunakan cara yang lebih manual, membuat sendiri
PreparedStatement dan kemudian mengisi parameternya.

Walaupun demikian, kita tidak mengeksekusi PreparedStatement tersebut, karena urusan


mengeksekusi akan ditangani Spring JDBC supaya bisa dibungkus dalam transaction. Kenapa
harus dibungkus dalam transaction? Jawabannya bisa dibaca di artikel ini.

Update Data

Pada prinsipnya, update data tidak berbeda dengan insert data. Kita dapat melakukannya dengan
cara yang sama seperti insert data di atas. Tapi karena kita ingin melihat fitur-fitur Spring JDBC,
baiklah kita lakukan dengan cara yang sedikit berbeda.

Menggunakan parameter bertipe Map

Kalau di atas kita sudah menggunakan parameter menggunakan ? dan domain object, kali ini kita
akan menggunakan Map. Cara ini digunakan bila nama variabel di SQL berbeda dengan nama
variabel di domain object, sehingga kita tidak bisa menggunakan
BeanPropertySqlParameterSource. Kita juga tidak mau menggunakan ? karena rawan terjadi
salah ketik.

Kita ubah sedikit method simpan(Produk p ) agar bisa menangani penyimpanan data produk
baru (insert) maupun penyimpanan data produk lama (update). Berikut kode program method
simpan(Produk p ) yang baru.

public void simpan(Produk p) {


if (p.getId() == null) {
SqlParameterSource produkParameter = new
BeanPropertySqlParameterSource(p);
Number idBaru = insertProduk.executeAndReturnKey(produkParameter);
p.setId(idBaru.intValue());
} else {
SqlParameterSource produkParameter = new MapSqlParameterSource()
.addValue("id_produk", p.getId())
.addValue("kode_produk", p.getKode())
.addValue("nama_produk", p.getNama())
.addValue("harga_produk", p.getHarga());
namedParameterJdbcTemplate.update(SQL_UPDATE_PRODUK,
produkParameter);
}
}

Agar lebih jelas, kita tampilkan juga deklarasi SQL_UPDATE_PRODUK.

private static final String SQL_UPDATE_PRODUK


= "update m_produk set kode = :kode_produk, nama = :nama_produk, harga =
:harga_produk where id = :id_produk";

Penggunaan MapSqlParameterSource dapat dilihat di blok else. Argumen kiri dari method
addValue adalah nama variabel dalam SQL_UPDATE_PRODUK, sedangkan argumen kanan adalah
nilai yang ingin diisikan.

Delete Data

Bila kita sudah menguasai insert dan update, maka delete seharusnya tidak menjadi masalah.
Berikut kode program untuk menghapus data produk.

public void hapus(Produk p) {


jdbcTemplate.update(SQL_HAPUS, p.getId());
}

Berikut deklarasi SQL_HAPUS

private static final String SQL_HAPUS = "delete from m_produk where id = ?";

Menghapus penjualan yang memiliki relasi header detail juga tidak sulit. Pastikan kita
menghapus detailnya dulu sebelum menghapus headernya agar tidak terjadi pelanggaran
referential integrity.

Berikut contoh pesan error bila kita menghapus data produk yang sudah dipakai dalam transaksi.

org.springframework.dao.DataIntegrityViolationException:
PreparedStatementCallback;
SQL [delete from m_produk where id = ?];
Cannot delete or update a parent row:
a foreign key constraint fails (`belajar`.`t_penjualan_detail`,
CONSTRAINT `t_penjualan_detail_ibfk_2` FOREIGN KEY (`id_produk`)
REFERENCES `m_produk` (`id`));
nested exception is
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Cannot delete or update a parent row:
a foreign key constraint fails (`belajar`.`t_penjualan_detail`,
CONSTRAINT `t_penjualan_detail_ibfk_2` FOREIGN KEY (`id_produk`)
REFERENCES `m_produk` (`id`))

Pada pesan error di atas, data produk tidak bisa dihapus karena sudah digunakan sebagai relasi
foreign key dari penjualan detail.

Query dengan Spring JDBC

Pada dasarnya, untuk mengambil data dari database hanya ada dua varian yang kita gunakan,
yaitu:

 mengambil data tunggal, gunakan method queryForObject


 mengambil data banyak, gunakan method queryForList atau query saja

Kita lihat dulu implementasi yang paling mendasar, ambil semua data produk dari database.
Berikut deklarasi SQLnya.

private static final String SQL_CARI_SEMUA


= "select * from m_produk limit ?,?";

Perintah SQL di atas akan dipakai dalam method cariSemua sebagai berikut

public List<Produk> cariSemua(Integer halaman, Integer baris) {


return jdbcTemplate.query(SQL_CARI_SEMUA,
new ResultSetJadiProduk(),
PagingHelper.halamanJadiStart(halaman, baris),
baris);
}

Pada contoh di atas, kita menggunakan method query yang menerima tiga argumen, yaitu:

 SQL yang akan dijalankan, berupa String


 Class yang bertugas mengubah ResultSet menjadi object yang kita inginkan, dalam hal ini object
Produk. Class ini akan kita buat sendiri, yaitu class ResultSetJadiProduk.
 argumen lain-lain (varargs). Ini merupakan fitur Java sejak versi 5 yang artinya kita bisa
memasukkan argumen sebanyak-banyaknya dalam method. Argumen ini akan dikonversi
menjadi List untuk diproses oleh method query

Jadi, bukan salah ketik kalau di atas saya sebut tiga argumen padahal kita memasukkan empat
variabel ke dalamnya.

SQL select tentu tidak perlu kita bahas lagi, mari masuk ke class ResultSetJadiProduk

Membuat Mapper
Class ResultSetJadiProduk bertugas mengkonversi hasil query ke database yang ada dalam
class ResultSet menjadi object Produk. Implementasinya tidak rumit, ini dia.

private class ResultSetJadiProduk implements RowMapper<Produk> {

@Override
public Produk mapRow(ResultSet rs, int i) throws SQLException {
Produk p = new Produk();
p.setId((Integer) rs.getObject("id"));
p.setKode(rs.getString("kode"));
p.setNama(rs.getString("nama"));
p.setHarga(rs.getBigDecimal("harga"));
return p;
}
}

Kita membuatnya sebagai class di dalam class (inner class). Kalau lupa dengan strukturnya,
silahkan baca lagi bab dua tentang Struktur Aplikasi.

Class ini harus implement interface RowMapper<T> milik Spring. T diganti dengan class yang
menjadi tujuan konversi. Interface RowMapper<T> ini mewajibkan kita membuat method mapRow.
Isi method tersebut sudah cukup jelas sehingga tidak perlu dijelaskan.

Class ini nantinya bisa kita gunakan juga di query yang menghasilkan satu object Produk seperti
cariById dan cariByKode. Berikut implementasinya

public Produk cariById(Integer id) {


try {
return jdbcTemplate.queryForObject(SQL_CARI_BY_ID,
new ResultSetJadiProduk(), id);
} catch (EmptyResultDataAccessException err) {
return null;
}
}

public Produk cariByKode(String kode) {


try {
return jdbcTemplate.queryForObject(SQL_CARI_BY_KODE,
new ResultSetJadiProduk(), kode);
} catch (EmptyResultDataAccessException err) {
return null;
}
}

Kedua method ini sama saja prinsipnya dengan cariSemua yang sudah kita bahas sebelumnya.

Mengambil data berelasi

Setelah berhasil mengambil data dari satu produk, mari kita coba untuk berurusan dengan data
berelasi. Sebetulnya prinsipnya sama saja, yaitu membuatkan class konversi. Bedanya hanya
terletak pada query SQL yang menggunakan join, tidak ada hubungannya dengan Spring JDBC.
Pada service interface, kita memiliki fitur rekap transaksi untuk satu produk tertentu, yang
dimuat dalam method cariPenjualanDetailByProdukDanPeriode. Method tersebut
memanggil method cariByProdukDanPeriode dalam class PenjualanDetailDao. Berikut
implementasinya.

public class PenjualanDetailDao {

private static final String SQL_CARI_BY_PRODUK_DAN_PERIODE


= "select pd.*, p.waktu_transaksi, "
+ "produk.kode as kode_produk, produk.nama as nama_produk,"
+ "produk.harga as harga_produk "
+ "from t_penjualan_detail pd "
+ "inner join t_penjualan p on pd.id_penjualan = p.id "
+ "inner join m_produk produk on pd.id_produk = produk.id "
+ "where pd.id_produk = ? " +
+ "and (p.waktu_transaksi between ? and ?) " +
+ "limit ?,?";

public List<PenjualanDetail> cariByProdukDanPeriode(Produk p,


Date mulai, Date sampai, Integer halaman, Integer baris) {
return jdbcTemplate.query(SQL_CARI_BY_PRODUK_DAN_PERIODE,
new ResultSetJadiPenjualanDetail(),
p.getId(),
mulai,
sampai,
PagingHelper.halamanJadiStart(halaman, baris),
baris);
}
}

Konversi dari ResultSet menjadi PenjualanDetail dilakukan dalam class


ResultSetJadiPenjualanDetail berikut

private class ResultSetJadiPenjualanDetail


implements RowMapper<PenjualanDetail> {

@Override
public PenjualanDetail mapRow(ResultSet rs, int i)
throws SQLException {
PenjualanDetail p = new PenjualanDetail();
p.setId((Integer) rs.getObject("id"));
p.setHarga(rs.getBigDecimal("harga"));
p.setJumlah((Integer) rs.getObject("jumlah"));

// relasi ke produk
Produk px = new Produk();
px.setId((Integer) rs.getObject("id_produk"));
px.setKode(rs.getString("kode_produk"));
px.setNama(rs.getString("nama_produk"));
px.setHarga(rs.getBigDecimal("harga_produk"));
p.setProduk(px);

// relasi ke penjualan
Penjualan jual = new Penjualan();
jual.setId((Integer) rs.getObject("id_penjualan"));
jual.setWaktuTransaksi(rs.getDate("waktu_transaksi"));
p.setPenjualan(jual);

return p;
}
}

Selain class PenjualanDetail itu sendiri, kita juga membuatkan object Produk dan Penjualan
yang kemudian akan dipasang pada object PenjualanDetail.

Pagination

Dalam mengambil data yang berjumlah banyak seperti data transaksi, biasanya kita akan
melakukan pagination, yaitu membagi data menjadi beberapa halaman. Di MySQL, kita
menggunakan keyword LIMIT untuk melakukan hal ini. Keyword LIMIT menerima dua argumen,
yaitu nomer record pertama yang mau diambil dan jumlah record yang mau diambil. Jadi bila
kita ingin mengambil record 11 - 15, kita menggunakan keyword LIMIT 11, 5.

Ini agak berbeda dengan argumen yang diterima dalam method pencarian kita. Yang diminta di
situ adalah nomer halaman dan jumlah record per halaman. Jadi kalau misalnya data kita
berjumlah 56 record dan kita ingin setiap halaman berisi 10 record, maka data tersebut akan
terbagi menjadi 6 halaman. Bila kita ingin mengambil halaman terakhir, kita memberikan
argumen 6 dan 10 ke dalam method pencarian.

Tentunya harus ada konversi dari nomer halaman menjadi nomer baris. Ini kita lakukan di class
PagingHelper yang isinya sebagai berikut.

public class PagingHelper {


public static Integer halamanJadiStart(Integer halaman,
Integer baris){
if (halaman < 1) {
return 0;
}
return (halaman - 1) * baris;
}
}

Header Detail

Dalam aplikasi, pasti ada fitur untuk menampilkan daftar transaksi dalam periode tertentu.
Seperti sudah kita bahas, satu transaksi terdiri dari satu header dan beberapa detail. Data ini tentu
ingin kita ambil semua.

Caranya sederhana :

1. Query dulu headernya: select * from t_penjualan where id = ?


2. Query detailnya: select * from t_penjualan_detail where id_penjualan = ?
3. Gabungkan keduanya
Berikut contohnya, pada waktu kita ingin mencari Penjualan berdasarkan id

public Penjualan cariById(Integer id) {


try {
Penjualan p = jdbcTemplate.queryForObject(SQL_CARI_BY_ID,
new ResultSetJadiPenjualan(), id);
List<PenjualanDetail> daftarDetail = penjualanDetailDao
.cariByPenjualan(p);
p.setDaftarPenjualanDetail(daftarDetail);
return p;
} catch (EmptyResultDataAccessException err) {
return null;
}
}

Di sana, kita memanggil method cariByPenjualan yang ada di class PenjualanDetailDao.


Berikut kode programnya

public List<PenjualanDetail> cariByPenjualan(Penjualan p){


List<PenjualanDetail> hasil
= jdbcTemplate.query(SQL_CARI_BY_ID_PENJUALAN,
new ResultSetJadiPenjualanDetail(), p.getId());

// set relasi ke penjualan


for (PenjualanDetail penjualanDetail : hasil) {
penjualanDetail.setPenjualan(p);
}

return hasil;
}

Kembali ke PenjualanDao, setelah kita mendapatkan List<PenjualanDetail> dari method di


atas, kita pasang di object penjualan yang sudah kita dapatkan di baris ini

p.setDaftarPenjualanDetail(daftarDetail);

Demikianlah cara kita mengambil data dari database menggunakan Spring JDBC. Hal ini tentu
tidak rumit asalkan kita sudah paham dasar-dasar SQL termasuk cara melakukan join antar tabel.
Jadi kesimpulannya, untuk mengambil data menggunakan Spring JDBC, yang perlu kita lakukan
hanyalah:

1. Membuat SQL, lengkap dengan join bila perlu. Contohnya bisa dilihat di class
PenjualanDetailDao yang memiliki banyak join.
2. Membuat class untuk mengkonversi dari ResultSet menjadi object yang kita inginkan,
misalnya Produk atau Penjualan

Mengetes Akses Database

Setup Test
Seperti telah dijelaskan sebelumnya, test class kita akan terdiri dari dua bagian:

1. Abstract superclass : berisi seluruh kode program pengetesan aplikasi


2. Concrete subclass : berisi kode program untuk melakukan inisialisasi

Berikut adalah kerangka abstract superclass test untuk Produk

package com.muhardin.endy.training.java.aksesdb.service;

// import statement, generate menggunakan IDE

public abstract class ProdukServiceTest {

public abstract PenjualanService getPenjualanService();


public abstract DataSource getDataSource();

@Before
public void bersihkanDataTest() throws Exception {

@Test
public void testSimpanUpdateHapusProduk() throws Exception {

// test method lain tidak ditampilkan


}

Dan berikut ini adalah concrete subclass yang berfungsi melakukan inisialisasi konfigurasi
Spring JDBC

package com.muhardin.endy.training.java.aksesdb.service.springjdbc;

// import statement generate menggunakan IDE

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:com/muhardin/**/spring-jdbc-ctx.xml")
public class ProdukServiceSpringJdbcTest extends ProdukServiceTest {

@Autowired private DataSource dataSource;


@Autowired private PenjualanService penjualanService;

@Override
public PenjualanService getPenjualanService() {
return penjualanService;
}

@Override
public DataSource getDataSource() {
return dataSource;
}

}
Database Reset

Pada kode program abstract superclass di atas, kita melihat ada method untuk membersihkan
data test. Method ini diberikan annotation @Before supaya dia dijalankan sebelum masing-
masing test.

Berikut adalah isi method tersebut

@Before
public void bersihkanDataTest() throws Exception {
DataSource ds = getDataSource();
Connection conn = ds.getConnection();

String sql = "delete from m_produk where kode like ?";


PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"T-001%");
ps.executeUpdate();
conn.close();
}

Di method tersebut, kita menghapus semua produk yang kodenya diawali T-001. Ini adalah data
yang kita insert selama proses tes. Agar tidak mengganggu tes lainnya, kita hapus data percobaan
tersebut.

Test Insert Update Delete

Proses insert, update, dan delete mengubah data yang ada dalam database. Supaya kita tidak
repot mengurus data sample yang sudah ada di database, ketiga proses ini kita tes dalam satu
kesatuan. Dengan demikian, setelah selesai dijalankan, datanya kembali bersih seperti
sebelumnya. Berikut kode programnya.

@Test
public void testSimpanUpdateHapusProduk() throws Exception{
Produk p = new Produk();
p.setHarga(new BigDecimal(125000));
p.setKode("T-001");
p.setNama("Produk Test 001");

PenjualanService service = getPenjualanService();


service.simpan(p);
assertNotNull(p.getId());

Connection conn = getDataSource().getConnection();


PreparedStatement psCariById
= conn.prepareStatement
("select * from m_produk where id = ?");
psCariById.setInt(1, p.getId());
ResultSet rs = psCariById.executeQuery();

// test nilai awal


assertTrue(rs.next());
assertEquals("T-001",rs.getString("kode"));

// update record
p.setKode("T-001x");
service.simpan(p);

// test query setelah update


rs = psCariById.executeQuery();

assertTrue(rs.next());
assertEquals("T-001x",rs.getString("kode"));

// test delete
service.hapus(p);

// test query setelah hapus


rs = psCariById.executeQuery();

assertFalse(rs.next());
}

Dari komentar yang ada dalam kode program, sudah jelas apa maksud dari masing-masing
bagian.

Test Query

Selanjutnya kita melakukan tes terhadap query. Kita mulai dari yang sederhana dulu, yaitu tabel
produk. Berikut kode program pengetesannya.

@Test
public void testCariProdukById() {
PenjualanService service = getPenjualanService();
assertNotNull(service.cariProdukById(1));
assertNull(service.cariProdukById(99));
}

@Test
public void testCariProdukByKode() {
PenjualanService service = getPenjualanService();
assertNotNull(service.cariProdukByKode("K-001"));
assertNull(service.cariProdukByKode("X-001"));
}

@Test
public void testHitungSemuaProduk() {
PenjualanService service = getPenjualanService();
assertEquals(Long.valueOf(3),
Long.valueOf(service.hitungSemuaProduk()));
}

@Test
public void testCariSemuaProduk() {
PenjualanService service = getPenjualanService();
List<Produk> hasil = service.cariSemuaProduk(1, 100);
assertNotNull(hasil);
assertTrue(hasil.size() == 3);
for (Produk produk : hasil) {
assertNotNull(produk.getId());
assertNotNull(produk.getKode());
assertNotNull(produk.getNama());
assertNotNull(produk.getHarga());
}
}

Logika pengetesan tidak kompleks. Kita query datanya menggunakan method di ProdukDao
yang telah kita buat, lalu kita bandingkan dengan kondisi yang seharusnya. Perbandingan
dilakukan menggunakan method yang telah disediakan JUnit, yaitu method berawalan assert,
misalnya assertNotNull, assertEquals, dan lainnya.

Yang harus diperhatikan di sini adalah, kita harus benar-benar tahu persis isi database supaya test
ini bisa berjalan dengan baik. Ada banyak teknik yang bisa digunakan untuk memastikan isi
database sebelum tes dijalankan, salah satunya menggunakan tools yang bernama DBUnit. Lebih
lanjut tentang cara menggunakan DBUnit bisa dibaca di artikel ini.

Test Relasi

Pengetesan terhadap relasi pada prinsipnya tidak berbeda. Hanya ada sedikit tambahan yaitu kita
juga harus memastikan apakah relasinya berhasil terisi dengan sempurna. Berikut pengetesannya.

@Test
public void testCariPenjualanDetailByProdukDanPeriode(){
Produk p = new Produk();
p.setId(1);

Date mulai = new DateTime(2013,1,1,0,0,0,0).toDate();


Date sampai = new DateTime(2013,2,1,0,0,0,0).toDate();

List<PenjualanDetail> hasil = getPenjualanService()


.cariPenjualanDetailByProdukDanPeriode(p,
mulai, sampai, 1, 100);

assertNotNull(hasil);
assertTrue(hasil.size() == 2);

for (PenjualanDetail penjualanDetail : hasil) {


verifikasiPenjualanDetail(penjualanDetail);
}
}

Pada kode program di atas, terlihat bahwa kita melakukan looping yang di dalamnya ada
verifikasi PenjualanDetail. Berikut isi methodnya.

private void verifikasiPenjualanDetail


(PenjualanDetail penjualanDetail) {
assertNotNull(penjualanDetail.getProduk().getHarga());
assertNotNull(penjualanDetail.getPenjualan().getWaktuTransaksi());
}

Kita cukup memastikan bahwa relasi yang dimiliki PenjualanDetail yaitu Produk dan
Penjualan tidak null. Demikian juga variabel yang ada di dalam objectnya yaitu harga dan
waktuTransaksi.

Struktur Aplikasi Java dengan Spring dan


Maven
Daftar class yang akan dibuat

Class yang akan dibuat kita bagi menjadi empat fungsi utama, yaitu:

 domain object : class yang mewakili struktur data dalam aplikasi kita
 interface business service : class yang mendefinisikan daftar fitur-fitur dalam aplikasi
 implementasi business service : implementasi dari interface business service. Kalau di interface
hanya ada nama method, argumen, dan tipe data kembalian (return value), di sini sudah ada
implementasi konkritnya, yaitu bagaimana query database, logika perhitungan, dan sebagainya.
 automated test : memeriksa apakah method kita berjalan benar itu melelahkan. Jadi kita
buatkan kode program untuk mengetesnya, sehingga tes yang sama bisa dijalankan berulang-
ulang tanpa membuat kita lelah. Lebih lanjut tentang automated test bisa dibaca di artikel lain
yang membahas masalah ini.

Domain Object

Sesuai dengan skema database, kita akan membuat tiga class, yaitu:

 Produk
 Penjualan
 PenjualanDetail

Buat yang sudah pernah coding JDBC biasanya akan bertanya,

Kenapa repot-repot membuat domain class, kemudian harus konversi bolak balik? Kan bisa saja
kita kirim ResultSet ke tampilan, ataupun insert langsung dari array ke PreparedStatement.

Pertanyaan ini biasanya muncul dari programmer PHP yang terbiasa langsung menampilkan
kembalian mysql_fetch_array dalam looping tabel.

Ada beberapa alasan:

1. Sebenarnya bisa saja kita buat aplikasi dengan menggunakan tipe data yang disediakan
Java seperti Integer, String, Map, List, dan lainnya. Tapi akibatnya kode program kita
menjadi sulit dimengerti. Coba bandingkan, lebih mudah dimengerti public void
simpan(Produk p) atau public void simpan(Map p)? Dengan membuat tipe data
sesuai istilah yang digunakan di domain kita, maka kode program akan lebih mudah
dipahami.
2. Java merupakan bahasa yang strongly-typed, dia memeriksa tipe data/class dari tiap
variabel. Pada ilustrasi di atas, method public void simpan(Map p) akan menerima
apapun data yang kita masukkan ke dalam variabel p. Kalau ada kesalahan dalam nama
variabel (misalnya nama ditulis name), baru akan terdeteksi pada waktu aplikasi
dijalankan. Berbeda dengan public void simpan(Produk p) yang akan langsung
menimbulkan pesan error apabila kita isi dengan tipe data selain Produk. Bug yang
ditemukan pada waktu coding (compile-time) akan jauh lebih cepat diperbaiki daripada
bug yang baru ditemukan pada waktu aplikasi dijalankan (runtime). Programmer PHP
ada benarnya juga. Di bahasa PHP memang domain class ini tidak diperlukan, karena
PHP tidak ada pemeriksaan compile-time. Tapi karena kita menggunakan Java, ada
baiknya kita manfaatkan pemeriksaan compile-time ini.
3. Memisahkan antara layer database dan layer antarmuka. Apabila ada perubahan skema
database, asalkan fitur di tampilan tidak berubah, kita cukup mengubah mapping domain
object dan skema database. Tidak perlu mengubah kode program di layer antarmuka.
4. Pustaka siap pakai untuk validasi. Di Java, ada yang namanya JSR-303, yaitu suatu
pustaka yang berguna untuk validasi. Dengan menggunakan JSR-303 ini kita tidak perlu
lagi melakukan pengecekan if(produk.getKode() == null). Cukup kita gunakan
deklarasi @NotNull private String kode; dalam class Produk

Interface Business Service

Interface di Java artinya class yang methodnya abstrak semua. Lebih detail tentang method
abstrak bisa dibaca di artikel ini. Ada beberapa alasan kenapa kita harus memisahkan interface
dan implementasinya, antara lain:

 pada waktu membuat aplikasi client-server, kita cukup memberikan domain object dan
interface ini kepada programmer aplikasi client. Sedangkan implementasinya (yang berisi
kode program akses database) tetap di server. Ini akan meringankan ukuran aplikasi
client, karena tidak perlu menyertakan implementasi (beserta library pendukungnya yang
biasanya besar) yang tidak dia gunakan.
 kita bebas mengubah strategi implementasi (misalnya ganti database dari MySQL
menjadi PostgreSQL) tanpa perlu mengganggu aplikasi client
 fitur declarative transaction yang dimiliki Spring akan lebih optimal bekerja bila kita
memisahkan interface dan implementasi.

Interface ini cukup satu class saja, yaitu AplikasiPenjualanService.

Implementasi Business Service

Ini merupakan implementasi dari interface AplikasiPenjualanService. Pada prakteknya, ada


dua variasi yang biasa saya gunakan dalam membuat implementasi, yaitu:

 cukup membuat class implementasi service saja


 membuat class implementasi service dan juga class data access object (DAO)

Kapan memilih variasi yang mana?

 Bila menggunakan framework Spring Data JPA, kita harus pakai DAO karena frameworknya
minta seperti itu
 Selain Spring Data JPA, bebas mau pakai yang mana. Pilih saja yang lebih rapi dan mudah
maintenance. Untuk aplikasi kecil, class implementasi service saja sudah cukup. Kalau
aplikasinya besar, akan lebih mudah membaca 10 class DAO yang masing-masingnya terdiri dari
100 baris kode daripada 1000 baris dalam satu class implementasi service. Walaupun demikian,
tidak ada pertimbangan teknis yang signifikan (seperti isu performance dan lainnya) antara
pakai DAO atau tidak.

Automated Test

Ini adalah kode program yang fungsinya mengetes kode program lainnya, dalam hal ini class
implementasi dan class DAO. Konsep dasar tentang automated testing dibahas di artikel ini.
Sedangkan untuk pengetesan database dibahas di sini.

Pada contoh aplikasi ini, kita menghadapi tantangan khusus, yaitu bagaimana caranya
menggunakan test case yang sama untuk konfigurasi berbeda. Nantinya aplikasi ini akan
dikembangkan untuk mendemonstrasikan akses database menggunakan framework lain seperti
Hibernate, Spring Data JPA, dan JDBC polos tanpa framework. Logika pengetesan akan sama
persis, yaitu:

 test insert
 test update
 test delete
 test cari berdasarkan id
 test ambil semua data dari tabel tertentu
 test cari data dengan kriteria tertentu

Tabel database yang diakses sama, sample data sama, bahkan nama method yang dijalankan juga
sama. Bedanya hanyalah class implementasi mana yang digunakan dan konfigurasi mana yang
dipakai.

Untuk itu, kita akan menggunakan teknik khusus yang disebut abstract junit test case.
Secara garis besar, langkahnya seperti ini:

1. Buat semua method test di superclass. Superclass ini memiliki abstract method, sehingga
dengan sendirinya dia juga abstract.
2. Untuk mendapatkan class implementasi dan melakukan inisialisasi konfigurasi, gunakan abstract
method
3. Buat subclass untuk masing-masing implementasi (Spring JDBC, Hibernate, dst) yang hanya
berisi implementasi dari abstract method tersebut.

Agar lebih jelas, silahkan lihat superclassnya dan subclass untuk Spring JDBC.
Struktur folder

Sekian banyak class, bagaimana penempatannya? Silahkan lihat struktur folder berikut:

Tidak ada yang istimewa dari struktur di atas, cuma struktur folder standar Maven. Mari kita
lihat source code aplikasi.
Di sini kita bisa lihat class sudah diatur ke dalam package berbeda sesuai fungsinya, yaitu
domain, service, dao. Untuk implementasi service dengan Spring JDBC kita buatkan package
tersendiri. Selanjutnya kita lihat lokasi file konfigurasi.
File konfigurasi ditaruh dalam package. Sebetulnya ditaruh di top level juga boleh, ini hanya
sekedar kebiasaan saja.
Lokasi penempatan test class bisa dilihat di atas. Abstract class yang saya ceritakan di atas
terlihat di package com.muhardin.endy.training.java.aksesdb.service, sedangkan
implementasi konfigurasinya ada di subpackage springjdbc di bawahnya.

Setelah kita melihat penempatan file dan folder, mari kita lihat kerangka kode program di
masing-masing class/file. Supaya bisa mendapatkan big-picture, kita akan lihat kerangka class
dan method saja. Implementasinya menyusul pada bagian selanjutnya.

Domain Object

Class Produk

Class ini merupakan padanan tabel m_produk di database. Dia memiliki beberapa property
sesuai dengan kolom di database. Berikut penjelasannya

Nama Property Nama Kolom Database Tipe Data Java Tipe Data MySQL

id id Integer integer

kode kode String varchar


Nama Property Nama Kolom Database Tipe Data Java Tipe Data MySQL

nama nama String varchar

harga harga BigDecimal decimal(19,2)

Berikut kode program untuk class Produk.

package com.muhardin.endy.training.java.aksesdb.domain;

import java.math.BigDecimal;

public class Produk {


private Integer id;
private String kode;
private String nama;
private BigDecimal harga;

// getter dan setter generate dari IDE


}

Class Penjualan

Mapping Java ke SQL

Nama Property Nama Kolom Database Tipe Data Java Tipe Data MySQL

id id Integer integer

waktuTransaksi waktu_transaksi Date datetime

Kode program class Penjualan

package com.muhardin.endy.training.java.aksesdb.domain;

// import generate dari IDE

public class Penjualan {


private Integer id;
private Date waktuTransaksi;
private List<PenjualanDetail> daftarPenjualanDetail
= new ArrayList<PenjualanDetail>();

// getter dan setter generate dari IDE


}

Class Penjualan Detail


Mapping Java ke SQL

Nama Property Nama Kolom Database Tipe Data Java Tipe Data MySQL

id id Integer integer

penjualan id_penjualan Penjualan integer foreign key

produk id_produk Produk integer foreign key

jumlah jumlah Integer integer

harga harga BigDecimal decimal(19,2)

Kode program class PenjualanDetail

package com.muhardin.endy.training.java.aksesdb.domain;

import java.math.BigDecimal;

public class PenjualanDetail {


private Integer id;
private Penjualan penjualan;
private Produk produk;
private BigDecimal harga;
private Integer jumlah;

// getter dan setter generate dari IDE


}

Yang perlu diperhatikan di sini adalah perbedaan cara perlakuan relasi antara Java dan database.
Di Java, kita perlu mendefinisikan relasi di dua tempat, yaitu variabel daftarPenjualanDetail
di class Penjualan dan variabel penjualan di class PenjualanDetail. Sedangkan di database,
relasi ini cukup didefinisikan melalui foreign key id_penjualan di tabel t_penjualan_detail.

Perbedaan lain, di database relasi ini cukup diwakili satu nilai saja, yaitu nilai foreign key.
Sedangkan di Java diwakili satu class penuh (Produk atau Penjualan) yang di dalamnya
memuat banyak nilai.

Untuk menjembatani perbedaan ini, kita perlu membuat mapper untuk mengubah data dari
database menjadi object Java dan sebaliknya. Contoh kode program untuk mapper ini akan
dibahas pada bagian selanjutnya.

Interface Business Service

Ini adalah daftar fitur yang ada di aplikasi, didefinisikan berupa class/interface dan method.
package com.muhardin.endy.training.java.aksesdb.service;

// import generate dari IDE

public interface PenjualanService {


// service berkaitan dengan produk
void simpan(Produk p);
Produk cariProdukById(Integer id);
Produk cariProdukByKode(String kode);
Long hitungSemuaProduk();
List<Produk> cariSemuaProduk(Integer halaman, Integer baris);
Long hitungProdukByNama(String nama);
List<Produk> cariProdukByNama(String nama, Integer halaman, Integer
baris);

// service yang berkaitan dengan transaksi


void simpan(Penjualan p);
Penjualan cariPenjualanById(Integer id);
Long hitungPenjualanByPeriode(Date mulai, Date sampai);
List<Penjualan> cariPenjualanByPeriode(Date mulai, Date sampai, Integer
halaman, Integer baris);
Long hitungPenjualanDetailByProdukDanPeriode(Produk p, Date mulai, Date
sampai);
List<PenjualanDetail> cariPenjualanDetailByProdukDanPeriode(Produk p,
Date mulai, Date sampai, Integer halaman, Integer baris);
}

Implementasi Business Service

Implementasi dari interface di atas kita bagi menjadi dua kategori, yaitu class implementasi
service yang nantinya akan memanggil class DAO. Pertimbangan dan alasan mengapa begini
sudah dijelaskan di atas.

Class ServiceSpringJdbc

Class ini sebetulnya hanya memanggil class DAO saja, jadi baiklah kita tampilkan seluruh isinya
di sini.

package com.muhardin.endy.training.java.aksesdb.service.springjdbc;

// import statement generate dari IDE

@Service @Transactional
public class PenjualanServiceSpringJdbc implements PenjualanService{
@Autowired private ProdukDao produkDao;
@Autowired private PenjualanDao penjualanDao;
@Autowired private PenjualanDetailDao penjualanDetailDao;

@Override
public void simpan(Produk p) {
produkDao.simpan(p);
}

@Override
public Produk cariProdukById(Integer id) {
return produkDao.cariById(id);
}

@Override
public Produk cariProdukByKode(String kode) {
return produkDao.cariByKode(kode);
}

@Override
public Long hitungSemuaProduk() {
return produkDao.hitungSemua();
}

@Override
public List<Produk> cariSemuaProduk(Integer halaman, Integer baris) {
return produkDao.cariSemua(halaman, baris);
}

@Override
public Long hitungProdukByNama(String nama) {
return produkDao.hitungByNama(nama);
}

@Override
public List<Produk> cariProdukByNama(String nama, Integer halaman,
Integer baris) {
return produkDao.cariByNama(nama, halaman, baris);
}

@Override
public void simpan(Penjualan p) {
penjualanDao.simpan(p);
}

@Override
public Penjualan cariPenjualanById(Integer id) {
return penjualanDao.cariById(id);
}

@Override
public Long hitungPenjualanByPeriode(Date mulai, Date sampai) {
return penjualanDao.hitungByPeriode(mulai, sampai);
}

@Override
public List<Penjualan> cariPenjualanByPeriode(Date mulai, Date sampai,
Integer halaman, Integer baris) {
return penjualanDao.cariByPeriode(mulai, sampai, halaman, baris);
}

@Override
public Long hitungPenjualanDetailByProdukDanPeriode(Produk p,
Date mulai, Date sampai) {
return penjualanDetailDao.hitungByProdukDanPeriode(p, mulai, sampai);
}
@Override
public List<PenjualanDetail> cariPenjualanDetailByProdukDanPeriode(Produk
p,
Date mulai, Date sampai, Integer halaman, Integer baris) {
return penjualanDetailDao.cariByProdukDanPeriode(p, mulai, sampai,
halaman, baris);
}
}

Class DAO

Class DAO akan kita bahas secara mendetail di bagian selanjutnya. Pada kesempatan ini kita
hanya tampilkan deklarasi class dan method saja supaya jelas mana method yang dipanggil dari
implementasi service di atas.

ProdukDAO

package com.muhardin.endy.training.java.aksesdb.dao.springjdbc;

// import generate dari IDE

@Repository
public class ProdukDao {

public void simpan(Produk p) {}

public Produk cariById(Integer id) {}

public Produk cariByKode(String kode) {}

public Long hitungSemua() {}

public List<Produk> cariSemua(Integer halaman, Integer baris) {}

public Long hitungByNama(String nama) {}

public List<Produk> cariByNama(String nama, Integer halaman, Integer


baris) {}

private class ResultSetJadiProduk implements RowMapper<Produk> {


@Override
public Produk mapRow(ResultSet rs, int i) throws SQLException {}
}
}

PenjualanDao

package com.muhardin.endy.training.java.aksesdb.dao.springjdbc;

// import generate dari IDE

@Repository
public class PenjualanDao {
public void simpan(Penjualan p) {}

public Penjualan cariById(Integer id) {}

public Long hitungByPeriode(Date mulai, Date sampai) {}

public List<Penjualan> cariByPeriode(Date mulai, Date sampai, Integer


halaman, Integer baris) {}

private class ResultSetJadiPenjualan implements RowMapper<Penjualan> {


@Override
public Penjualan mapRow(ResultSet rs, int i) throws SQLException {}
}
}

PenjualanDetailDao

package com.muhardin.endy.training.java.aksesdb.dao.springjdbc;

// import generate dari IDE

@Repository
public class PenjualanDetailDao {

public void simpan(final PenjualanDetail p) {}

public List<PenjualanDetail> cariByPenjualan(Penjualan p) {}

public Long hitungByProdukDanPeriode(Produk p, Date mulai, Date sampai)


{}

public List<PenjualanDetail> cariByProdukDanPeriode(Produk p, Date mulai,


Date sampai, Integer halaman, Integer baris) {}

private class ResultSetJadiPenjualanDetail implements


RowMapper<PenjualanDetail> {
@Override
public PenjualanDetail mapRow(ResultSet rs, int i) throws
SQLException {}
}
}

Automated Test

Seperti dijelaskan di atas, automated test kita bagi menjadi dua kategori, yaitu abstract class yang
menampung semua logika pengetesan, dan concrete class yang menyediakan konfigurasi.

Abstract Base Class

ProdukServiceTest

package com.muhardin.endy.training.java.aksesdb.service;
// import generate dari IDE

public abstract class ProdukServiceTest {

public abstract PenjualanService getPenjualanService();


public abstract DataSource getDataSource();

@Before
public void bersihkanDataTest() throws Exception {}

@Test
public void testSimpanProduk() {}

@Test
public void testCariProdukById() {}

@Test
public void testCariProdukByKode() {}

@Test
public void testHitungSemuaProduk() {}

@Test
public void testCariSemuaProduk() {}

@Test
public void testHitungProdukByNama() {}

@Test
public void testCariProdukByNama() {}
}

PenjualanServiceTest

package com.muhardin.endy.training.java.aksesdb.service;

// import generate dari IDE

public abstract class PenjualanServiceTest {

public abstract PenjualanService getPenjualanService();


public abstract DataSource getDataSource();

@Before
public void bersihkanDataTest() throws Exception {}

@Test
public void testSimpanPenjualan() throws Exception {}

@Test
public void testCariPenjualanById(){}

@Test
public void testHitungPenjualanByPeriode(){}
@Test
public void testCariPenjualanByPeriode(){}

@Test
public void testHitungPenjualanDetailByProdukDanPeriode(){}

@Test
public void testCariPenjualanDetailByProdukDanPeriode(){}
}

Seperti kita lihat di atas, kedua class tersebut memiliki dua abstract method, yaitu:

 public abstract PenjualanService getPenjualanService()


 public abstract DataSource getDataSource();

Kedua object PenjualanService dan DataSource didapatkan dari konfigurasi Spring.


Konfigurasi Spring dibuat berdasarkan teknologi yang digunakan. Konfigurasi Spring JDBC
berbeda dengan konfigurasi Hibernate ataupun Spring Data JPA.

Dengan teknik ini, bila di kemudian hari kita membuat implementasi dengan Hibernate atau
Spring Data JPA, kita tidak perlu lagi copy-paste test class, cukup buat subclass yang
menyediakan kedua object tersebut.

Berikut adalah subclassnya

Implementasi Test Business Service

Karena hanya beberapa baris dan tidak butuh banyak penjelasan, kita tampilkan di sini full
source code, bukan hanya kerangkanya saja.

ProdukServiceSpringJdbcTest

package com.muhardin.endy.training.java.aksesdb.service.springjdbc;

// import generate dari IDE

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:com/muhardin/**/spring-jdbc-ctx.xml")
public class ProdukServiceSpringJdbcTest extends ProdukServiceTest {

@Autowired private DataSource dataSource;


@Autowired private PenjualanService penjualanService;

@Override
public PenjualanService getPenjualanService() {
return penjualanService;
}

@Override
public DataSource getDataSource() {
return dataSource;
}

PenjualanServiceSpringJdbcTest

package com.muhardin.endy.training.java.aksesdb.service.springjdbc;

// import generate dari IDE

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:com/muhardin/**/spring-jdbc-ctx.xml")
public class PenjualanServiceSpringJdbcTest extends PenjualanServiceTest {

@Autowired private DataSource dataSource;


@Autowired private PenjualanService penjualanService;

@Override
public PenjualanService getPenjualanService() {
return penjualanService;
}

@Override
public DataSource getDataSource() {
return dataSource;
}

Seperti kita lihat di atas, dataSource dan penjualanService disediakan melalui Dependency
Injection.

Cara kerjanya sebagai berikut:

1. Kita jalankan JUnit melalui IDE atau Maven. IDE atau Maven akan membaca semua file dalam
folder src/test/java dan memproses semua class yang namanya berakhiran Test seperti
PenjualanServiceSpringJdbcTest. IDE/Maven juga memproses
PenjualanServiceTest, tapi karena classnya abstract maka tidak diproses lebih lanjut.
2. JUnit melihat annotation @RunWith, jadi dia tidak menjalankan sendiri melainkan menyuruh
SpringJUnit4ClassRunner untuk menjalankan test
3. SpringJUnit4ClassRunner membaca annotation @ContextConfiguration, lalu
menggunakan nilai di dalamnya untuk melakukan inisialisasi ApplicationContext, kemudian
mengisi variabel yang ditandai dengan @Autowired
4. Karena PenjualanServiceSpringJdbcTest merupakan subclass dari
PenjualanServiceTest, maka dia akan mewarisi semua method @Test yang dimiliki
PenjualanServiceTest. Method @Test ini akan dijalankan oleh IDE/Maven.
5. Pada waktu method @Test dijalankan, bila perlu object PenjualanService, maka akan
didapat dengan cara memanggil method getPenjualanService. Karena methodnya sudah
dibuatkan implementasinya (tidak abstract lagi) dan sudah ada isinya, maka method @Test
dapat bekerja dengan baik.
Konfigurasi lokasi logfile pada Spring MVC
Di mana kita harus menyimpan log output aplikasi kita? Tentunya kita ingin menggunakan
lokasi yang dinamis sesuai dengan lokasi deployment. Misalnya, di Windows kita mungkin
mendeploy aplikasi kita di

C:\Program Files\Apache Tomcat\webapps\aplikasi-saya

Sedangkan di Linux, kita mendeploy aplikasi di

/opt/apache-tomcat/webapps/aplikasi-saya

Dengan kemungkinan seperti di atas, bagaimana kita harus menulis konfigurasi log4j? Mudah,
bila kita menggunakan Spring MVC.

Kita bisa menggunakan Log4jConfigListener yang disediakan Spring. Class ini


memungkinkan kita menggunakan variabel di konfigurasi log4j kita. Kita mendaftarkan class ini
di dalam web.xml, sebelum ContextLoaderListener, seperti ini :

<listener>
<listener-
class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

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

Dengan adanya Log4jConfigListener ini, kita bisa menyebutkan lokasi konfigurasi log4j
seperti ini :

<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:artivisi-log4j.properties</param-value>
</context-param>

Isi artivisi-log4j.properties terlihat seperti ini :

# Konfigurasi kategori
log4j.rootLogger=INFO,fileout

# File output
log4j.appender.fileout=org.apache.log4j.DailyRollingFileAppender
log4j.appender.fileout.file=${webapp.root.path}/WEB-INF/logs/application.log
log4j.appender.fileout.datePattern='.'yyyy-MM-dd
log4j.appender.fileout.layout=org.apache.log4j.PatternLayout
log4j.appender.fileout.layout.conversionPattern=%d [%t] %p (%F:%L) %m%n
Perhatikan konfigurasi log4j.appender.fileout.file. Kita menggunakan variabel
${webapp.root.path} yang akan diisi dengan nilai lokasi deployment aplikasi web kita.
Variabel ${webapp.root.path} ini didefinisikan dalam web.xml sebagai berikut :

<context-param>
<param-name>webAppRootKey</param-name>
<param-value>webapp.root.path</param-value>
</context-param>

Dengan konfigurasi ini, kita dapat meletakkan log output kita di

C:\Program Files\Apache Tomcat\webapps\aplikasi-saya\WEB-


INF\logs\application.log

bila kita mendeploy di Windows, dan di

/opt/apache-tomcat/webapps/aplikasi-saya/WEB-INF/logs/application.log

bila kita deploy di Linux.

Konfigurasi di atas bisa disederhanakan lagi bila kita mengikuti nilai default yang disediakan
Spring, yaitu cukup seperti ini dalam web.xml :

<listener>
<listener-
class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

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

Kemudian memberi nama file konfigurasi logger kita log4j.properties yang berada di top
level dalam classpath, dan berisi seperti ini :

# Konfigurasi kategori
log4j.rootLogger=INFO,fileout

# File output
log4j.appender.fileout=org.apache.log4j.DailyRollingFileAppender
log4j.appender.fileout.file=${webapp.root}/WEB-INF/logs/application.log
log4j.appender.fileout.datePattern='.'yyyy-MM-dd
log4j.appender.fileout.layout=org.apache.log4j.PatternLayout
log4j.appender.fileout.layout.conversionPattern=%d [%t] %p (%F:%L) %m%n

Nilai variabel ${webapp.root} secara default akan diisi dengan lokasi deployment tanpa harus
mengkonfigurasi webAppRootKey

Staged Deployment
Staged Deployment

Pada waktu kita coding, tentunya kita melakukan test terhadap kode program yang kita tulis. Kita
jalankan langkah-langkah sesuai yang telah didefinisikan dalam test scenario. Setelah test di
komputer kita sendiri (local) selesai dilakukan, tentunya kode program tersebut tidak langsung
kita deploy ke production. Best practicesnya adalah, kita deploy aplikasinya ke server testing
untuk kemudian ditest oleh Software Tester. Barulah setelah dinyatakan OK oleh tester, aplikasi
versi terbaru tersebut kita deploy ke production.

Dengan demikian, kita memiliki tiga deployment environment, yaitu :

 development (komputer si programmer)


 testing (test server)
 production (live system)

Environment ini bisa lebih banyak lagi kalau aplikasi kita harus dites kompatibilitasnya dengan
berbagai hardware atau sistem operasi.

Cara kerja seperti ini disebut dengan istilah staged deployment atau deployment bertahap.
Dengan menggunakan staged deployment, kita mencegah terjadinya bug fatal di production/live
system.

Tantangan yang kita hadapi adalah, bagaimana cara mengelola konfigurasi aplikasi kita sehingga
bisa dideploy di berbagai environment secara baik. Teknik bagaimana cara melakukan ini
berbeda-beda, tergantung bahasa pemrograman, framework, dan library yang kita gunakan.

Pada artikel ini, kita akan membahas cara mengelola konfigurasi deployment menggunakan
teknologi yang biasa digunakan di ArtiVisi, yaituSpring Framework dan Logback.

Alternatif Solusi

Manajemen konfigurasi ini bisa kita lakukan dengan dua pendekatan, yaitu dikelola dengan
Maven Profile, atau dengan konfigurasi Spring Framework.

Jika kita menggunakan Maven Profile, kita menambahkan opsi pada saat melakukan build, kira-
kira seperti ini :

mvn -P production clean install

atau

mvn -Denv=production clean install

Dalam konfigurasi profile, kita bisa memilih file mana yang akan diinclude di dalam hasil build.
Hasilnya, kita bisa menghasilkan artifact yang berbeda tergantung dari opsi yang kita berikan
pada saat build.
Walaupun demikian, berdasarkan hasil Googling, ternyata metode ini tidak direkomendasikan.
Justru konfigurasi melalui Spring lebih disarankan.

Dengan menggunakan konfigurasi Spring, artifact yang dihasilkan oleh build hanya satu jenis
saja. Artifact ini berisi semua pilihan konfigurasi. Konfigurasi mana yang akan aktif pada saat
dijalankan (runtime) akan ditentukan oleh setting environment variable, bukan oleh artifactnya.

Selanjutnya, kita akan membahas metode manajemen konfigurasi menggunakan Spring.

Konfigurasi Database

Konfigurasi yang biasanya berbeda adalah informasi koneksi database. Untuk membedakan
masing-masing environment, kita akan membuat tiga file, yaitu:

 jdbc.properties : digunakan di laptop programmer


 jdbc.testing.properties : digunakan di server test
 jdbc.production.properties : digunakan di live

Berikut contoh isi jdbc.properties, yaitu konfigurasi koneksi database di laptop saya :

hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost/kasbon?zeroDateTimeBehavior=convertToNull
jdbc.username = kasbon
jdbc.password = kasbon

Kemudian, ini file jdbc.testing.properties :

hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url =
jdbc:mysql://localhost/kasbon_testing?zeroDateTimeBehavior=convertToNull
jdbc.username = root
jdbc.password = admin

Perhatikan bahwa informasi nama database, username, dan password databasenya berbeda
dengan yang ada di konfigurasi laptop.

Terakhir, jdbc.production.properties

hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url =
jdbc:mysql://localhost/kasbon_live?zeroDateTimeBehavior=convertToNull
jdbc.username = root
jdbc.password = admin

Ketiga file konfigurasi ini akan dibaca oleh konfigurasi Spring, yaitu di file
applicationContext.xml. Isi lengkap dari file ini adalah sebagai berikut.
<?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:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<context:property-placeholder location="
classpath*:jdbc.properties,
classpath*:jdbc.${stage}.properties
" />

<tx:annotation-driven />

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"


destroy-method="close" p:driverClassName="${jdbc.driver}"
p:url="${jdbc.url}"
p:username="${jdbc.username}" p:password="${jdbc.password}"
p:maxWait="40000"
p:maxActive="80" p:maxIdle="20" />

<bean id="transactionManager"

class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory" />

<bean id="sessionFactory"

class="org.springframework.orm.hibernate3.annotation.AnnotationSession
FactoryBean"
p:dataSource-ref="dataSource"
p:configLocations="classpath*:com/artivisi/**/hibernate.cfg.xml">
<property name="hibernateProperties">
<props>
<prop
key="hibernate.dialect">${hibernate.dialect}</prop>
</props>
</property>
</bean>

<bean id="messageSource"

class="org.springframework.context.support.ResourceBundleMessageSource
">
<property name="basenames">
<list>
<value>messages</value>
</list>
</property>
</bean>
</beans>

Untuk lebih spesifik, konfigurasinya ada di baris berikut

<context:property-placeholder location="
classpath*:jdbc.properties,
classpath*:jdbc.${stage}.properties
" />

Di sana kita melihat ada variabel ${stage}. Variabel ${stage} ini akan dicari dari beberapa
tempat, diantaranya environment variabel yang bisa diset di JVM ataupun di sistem operasi. Cara
mengeset variabel ${stage} akan kita bahas sebentar lagi.

Di situ kita menyuruh Spring untuk membaca file jdbc.properties dan jdbc.${stage}.properties.
Jika ada nilai variabel yang sama (misalnya jdbc.username), maka nilai variabel di file yang
disebutkan belakangan akan menimpa nilai yang didefinisikan file di atasnya.

Contohnya, misalnya variabel ${stage} nilainya adalah testing. Maka Spring akan membaca file
jdbc.properties dan jdbc.testing.properties. Karena kedua file memiliki variabel jdbc.url, maka isi
jdbc.url di file jdbc.testing.properties akan menimpa nilai jdbc.url di jdbc.properties.

Bila variabel ${stage} tidak ada isinya, Spring akan mencari file yang namanya
jdbc.${stage}.properties, dan tidak akan ketemu. Dengan demikian, nilai yang digunakan adalah
yang ada di jdbc.properties.

Dengan demikian, behavior aplikasi adalah sebagai berikut

Bila variabel stage diset production atau testing, maka yang digunakan adalah nilai konfigurasi di
jdbc.production.properties atau jdbc.testing.properties. Bila tidak diset atau diset selain itu, maka
yang digunakan adalah konfigurasi di jdbc.properties

Behavior seperti inilah yang kita inginkan. Selanjutnya, tinggal kita isi nilai variabel stage.

Setting Environment Variabel

Variabel stage bisa diset dengan berbagai cara. Bila kita menggunakan Apache Tomcat, maka
kita mengedit file startup.sh atau startup.bat. Modifikasi baris yang berisi CATALINA_OPTS
menjadi seperti ini :

export CATALINA_OPTS="-Dstage=production"

Atau, kita bisa jalankan dengan Jetty melalui Maven

mvn jetty:run -Dstage=testing

Bisa juga melalui environment variabel sistem operasi, di Linux kita set seperti ini.
EXPORT stage=production

Konfigurasi Logger

Dengan menggunakan Spring seperti di atas, kita bisa membaca konfigurasi apa saja, misalnya

 Konfigurasi email : bila aplikasi kita mengirim/menerima email


 Konfigurasi server lain : bila aplikasi kita berinteraksi dengan aplikasi orang lain,
misalnya webservice atau koneksi socket
 dsb

Walaupun demikian, konfigurasi logger biasanya tidak diload oleh Spring, melainkan langsung
dibaca oleh library loggernya.

Kita di ArtiVisi menggunakan SLF4J dan Logback. Cara konfigurasinya mirip dengan Spring.
Kita punya satu master file yang akan membaca file lain sesuai isi variabel stage. Untuk itu kita
siapkan beberapa file berikut:

 logback.xml : file konfigurasi utama


 logback.production.xml : konfigurasi logger production, akan diinclude oleh logback.xml
 logback.testing.xml : konfigurasi logger testing, akan diinclude oleh logback.xml
 logback.development.xml : konfigurasi logger development, akan diinclude oleh
logback.xml

Berikut isi file logback.xml.

<?xml version="1.0" encoding="UTF-8"?>


<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">


<encoder>
<pattern>%d %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.FileAppender">


<file>${catalina.home:-.}/logs/kasbon-${stage:-
development}.log</file>
<encoder>
<pattern>%d %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>

<include resource="logback-${stage:-development}.xml"/>

</configuration>

Seperti kita lihat, file ini berisi konfigurasi yang berlaku umum, seperti appender yang
digunakan. Di file ini kita menulis variabel seperti ini
${stage:-development}

Yang artinya adalah, isi dengan variabel stage, kalau variabel tersebut tidak diset, defaultnya
adalah development. Ini sesuai dengan keinginan kita seperti pada waktu mengkonfigurasi
Spring di atas.

Anda mungkin juga menyukai