10 Langkah membuat Generic DAO dengan Spring 2.5 + Hibernate

Setiap mengerjakan aplikasi dengan Java EE, gw selalu terbiasa menerapkan layer untuk

  • Model atau domain sistem seperti, User, Group, Departement, dsb tergantung ruang lingkup aplikasi
  • Data Access Object atau biasa disingkat DAO, untuk menenkapsulasi akses database terhadap domain sistem misalnya UserDao untuk operasi getAllUser(), findUserById(), saveUser(), deleteUser() dsb
  • Service layer untuk mengenkapsulasi berbagai macam Dao ke dalam satu service agar mudah digunakan dari sisi client (bagian lain dari aplikasi yang mengakses kode kita), misalnya UI atau antar muka
  • UI layer untuk logika2, validasi, dan flow yang bersifat interaksi dengan user

Concern gw adalah semakin banyak atau kompleks model atau domain sistem, biasanya Dao pun akan semakin banyak dan gw cenderung melakukan hal yang sama untuk hal2 kecil dan remeh, contohnya method untuk save user dan find user (menggunakan HibernateDaoSupport dari Spring),

[sourcecode language=’java’]
public class UserDaoHibernate extends HibernateDaoSupport implements UserDao {

public void save(User user){
getHibernateTemplate().saveOrUpdate(user);
getHibernateTemplate().flush();
}

public User findUser(Integer id){
final User user = (User) getHibernateTemplate().load(User.class, id);
getHibernateTemplate().initialize(user);
return user;
}

}[/sourcecode]
Kalo gw mau buat Dao untuk Group ya gw lakukan hal yang sama dengan mengubah User menjadi Group dan seterusnya. Ini jelas tidak mengikuti prinsipnya pragmatic programmer DRY (Dont Repeat Yourself), solusinya adalah dengan menggunakan konsep Generic dari Java 5, yang perlu gw lakukan cuma declare class GenericDao dan setiap Dao akan extends kelas ini. Kode diatas akan menjadi,
[sourcecode language=’java’]
public class UserDaoHibernate extends GenericDaoHibernate implements UserDao {
}[/sourcecode]
oh mau buat dao untuk group? gampang!
[sourcecode language=’java’]
public class GroupDaoHibernate extends GenericDaoHibernate implements GroupDao {
}[/sourcecode]
Konsep GenericDao ini sebenarnya sudah cukup lama dipulikasikan, namun karena sekarang lagi booming Spring 2.5, gw akan coba sharing GenericDao dengan Spring 2.5 + Hibernate dan meminimalisir jumlah konfigurasi yang ada di xml untuk dipindahkan ke source code. Gw ga bilang konfigurasi di xml itu jelek, tapi menurut gw ga semuanya harus ada di xml dan berikut ada post yang menarik mengenai konfigurasi dengan XML vs annotation. Anyway post ttg spring 2.5 juga dibahas oleh Endy Muhardin untuk akses ke DB dengan JDBC dan akses web.Langsung saja kita bahas ttg GenericDao dengan Spring 2.5 dan Hibernate,

Requirement library yang dibutuhkan:

  • Spring 2.5
  • Hibernate
  • Junit 4
  • DBUnit
  • MySQL JDBC Driver
  • Jangan lupa dependensi2 nya ๐Ÿ™‚

Langkah2:

1. Buat interface GenericDao, kita tentukan saja method2 yang umum dipakai,
[sourcecode language=’java’]
public interface GenericDao {
public T findById(final ID id);
public void save(final T domain);
public void delete(final T domain) ;
public Long count();
public List findAll();
public List findByExample(T exampleInstance, String… excludeProperty);
}[/sourcecode]
2. Buat implementasinya dan perhatikan bahwa,

  • T adalah dynamic class sehingga kita bisa isi dengan berbagai jenis kelas, dalam hal ini gw sebut domain atau model.
  • Constructor akan mengambil jenis dari kelas yang kita berikan lewat subclass (bla bla extends GenericDaoHibernate)
  • Varargs String… excludeProperty untuk pengecualian nilai2 dalam method findByExample()
  • @SuppressWarnings akan meng-ignore compiler warning

[sourcecode language=’java’]
public class GenericDaoHibernate extends
HibernateDaoSupport implements GenericDao {

public Class domainClass;

@SuppressWarnings(“unchecked”)
public GenericDaoHibernate() {
this.domainClass = (Class) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}

@SuppressWarnings(“unchecked”)
public T findById(ID id) {
final T domain = (T) getHibernateTemplate().load(domainClass, id);
getHibernateTemplate().initialize(domain);
return domain;
}

public void save(T domain) {
getHibernateTemplate().saveOrUpdate(domain);
getHibernateTemplate().flush();
}

public void save(List domains) {
for (T domain : domains) {
getHibernateTemplate().saveOrUpdate(domain);
getHibernateTemplate().flush();
}
}

public void delete(T domain) {
getHibernateTemplate().delete(domain);
}

@SuppressWarnings(“unchecked”)
public Long count() {
List list = getHibernateTemplate().find(
“select count(*) from ” + domainClass.getName() + ” x”);
Long count = (Long) list.get(0);
return count;
}

@SuppressWarnings(“unchecked”)
public List findAll() {
return getHibernateTemplate().find(“from ” + domainClass.getName());
}

@SuppressWarnings(“unchecked”)
public List findByExample(T exampleInstance, String… excludeProperty) {
Criteria crit = getSession().createCriteria(domainClass);
Example example = Example.create(exampleInstance);
for (String exclude : excludeProperty) {
example.excludeProperty(exclude);
}
crit.add(example);
return crit.list();
}
}[/sourcecode]
3. Buat model User, untuk simplifikasi kasus gw hanya definisikan id, name, dan email.

  • @Entity untuk menandakan bahwa kelas ini merupakan entitas yang merepresentasikan table DB, atribut dalam dari kelas ini akan menjadi column
  • @Table untuk meng-override nama table, defaultnya akan sama dengan nama class
  • @Id sebagai key dan bersifat auto generated atau biasanya lbh familiar dengan istilah auto increment
  • @Column untuk meng-override nama columnnya, defaultnya akan sama dengan nama atribut

[sourcecode language=’java’]
@Entity
@Table(name=”T_USER”)
public class User {
@Id
@Column(name = “user_id”)
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
private String email;

// Getter dan setter
}[/sourcecode]
4. Buat interface UserDao,
[sourcecode language=’java’]
public interface UserDao extends GenericDao {
}[/sourcecode]
5. Buat implementasi dari UserDao yaitu UserDaoHibernate,

  • @Repository untuk menandakan bahwa kelas ini berfungsi sebagai repository atau biasa disebut Data Access, yeah its Dao. Dan repository inigw kasi nama “userDao”
  • @Autowired untuk menginject SessionFactory ke superclass yaitu HibernateDaoSupport, tadinya gw mau langsung menggunakan setSessionFactory(), tapi ternyata HibernateDaoSupport mendefinisikan method ini sebagai final, sehingga gw ga bisa override, jadi gw lakukan injeksi melalui constructor

[sourcecode language=’java’]
@Repository(“userDao”)
public class UserDaoHibernate extends GenericDaoHibernate
implements UserDao {

@Autowired
public UserDaoHibernate(SessionFactory sessionFactory) {
super.setSessionFactory(sessionFactory);
}
}[/sourcecode]
6. Selanjutnya gw akan membuat service layer, gw buat dulu interface securityService,
[sourcecode language=’java’]
public interface SecurityService {
public void save(User user);
public User getUser(Integer id);
public List getUsers();
public void delete(User user);
}[/sourcecode]
7. Dan implementasinya, SecurityServiceImpl

  • @Service(“securityService”) untuk menandakan bahwa class ini merupakan service layer atau business facade, dan gw namakan “securityService”
  • Gw implementasi transaction berdasarkan annotation dengan @Transactional, by default semua method gw kasih readOnly=true, tapi untuk method save dan delete readOnly=false dan juga Propagation.REQUIRED untuk pilihan transaction yang umum dipakai.
  • Service ini gw inject dengan UserDao yang sudah gw buat sebelumnya dengan @Autowired, dan @Qualifier(“userDao”) gw definisikan untuk memastikan si @Autowired menginject userDao yang gw mau.

[sourcecode language=’java’]
@Service(“securityService”)
@Transactional(readOnly=true, propagation=Propagation.REQUIRED)
public class SecurityServiceImpl implements SecurityService{

private UserDao userDao;

@Autowired
public void setUserDao(@Qualifier(“userDao”)UserDao userDao) {
this.userDao = userDao;
}

@Override
public User getUser(Integer id) {
return userDao.findById(id);
}

@Override
@Transactional(readOnly=false)
public void save(User user) {
userDao.save(user);
}

@Override
@Transactional(readOnly=false)
public void delete(User user) {
userDao.delete(user);

}

@Override
public List getUsers() {
return userDao.findAll();
}
}[/sourcecode]
8. Siapkan jdbc.properties untuk konfigurasi JDBC dan hibernate,
jdbc.driver= com.mysql.jdbc.Driver
jdbc.url= jdbc:mysql://localhost/latihan
jdbc.username= latihan
jdbc.password= java
hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialecthibernate.hbm2ddl_auto=create
hibernate.show_sql=true
hibernate.show_statistics=true

9. Bagian yang membuat pusing kepala :), konfigurasi spring context, spring-ctx.xml

[sourcecode language=’xml’]







org.latihan.model.User ${hibernate.dialect} ${hibernate.show_sql} ${hibernate.show_statistics} ${hibernate.hbm2ddl_auto}



[/sourcecode]

10. Finally, unit test untuk memastikan semua berjalan dengan lancar, UserDaoHibernateTest

Pembahasan mengenai unit test dibahas lengkap disini, silahkan dibaca dulu ๐Ÿ™‚
http://endy.artivisi.com/blog/java/presentasi-ruthless-testing/
http://endy.artivisi.com/blog/java/ruthless-testing-1/
http://endy.artivisi.com/blog/java/ruthless-testing-2/
http://endy.artivisi.com/blog/java/ruthless-testing-3/
http://endy.artivisi.com/blog/java/ruthless-testing-4/
http://endy.artivisi.com/blog/java/ruthless-testing-5/
http://endy.artivisi.com/blog/java/akses-database-spring25/
[sourcecode language=’java’]
public class UserDaoHibernateTest {

private static ApplicationContext ctx;
private static DataSource dataSource;
private static SecurityService securityService;

@BeforeClass
public static void init() {
ctx = new ClassPathXmlApplicationContext(“spring-ctx.xml”);
dataSource = (DataSource) ctx.getBean(“dataSource”);
securityService = (SecurityService) ctx.getBean(“securityService”);
}

@Before
public void resetDatabase() throws Exception {
Connection conn = dataSource.getConnection();
DatabaseOperation.CLEAN_INSERT.execute(new DatabaseConnection(conn),
new FlatXmlDataSet(new FileInputStream(“fixtures/user.xml”)));
}

@Test
public void saveUser() throws SQLException {
User user = new User();
user.setName(“Endy Muhardin”);
user.setEmail(“emuhardin@gmail.com”);
securityService.save(user);

Connection conn = dataSource.getConnection();
PreparedStatement pStatement = conn
.prepareStatement(“select * from T_USER where name=?”);
pStatement.setString(1, user.getName());
ResultSet resultSet = pStatement.executeQuery();
assertTrue(“No user”,resultSet.next());
}

@Test
public void findById(){
User user = securityService.getUser(100);
assertEquals(“dhiku”, user.getName());
}

@Test
public void getAllUser(){
assertEquals(2, securityService.getUsers().size());
}

@Test
public void deleteUser() throws SQLException{
User user = new User();
user.setName(“dhiku”);
user.setId(100);
securityService.delete(user);

Connection conn = dataSource.getConnection();
PreparedStatement pStatement = conn
.prepareStatement(“select * from T_USER where user_id=?”);
pStatement.setInt(1, user.getId());
ResultSet resultSet = pStatement.executeQuery();
assertFalse(“User not deleted”,resultSet.next());
}
}[/sourcecode]
Jalankan semuanya, seharusnya kalo gw ga ngantuk atau ga ada step yang dilewat, akan muncul “ijo2” (test sukses – red). Kesimpulan gw ga terlalu sulit untuk mengimplementasikan GenericDao dengan Spring 2.5, dan hasilnya menghilangkan repetition dari membuat method2 sederhana untuk Dao, konfigurasi juga lebih sedikit, dan kode lebih mudah dibaca.Mudah2an membantu.

28 comments

  1. Wah kenapa dari dulu gw gak berpikir tentang generic dao ini ya? :)) Toh emang bener beberapa CRUD process cuman gitu-gitu doang dan bisa dibikin generic. Thanks Dhiku, gw akan coba terapin ini di projexion gw.

  2. Kalo pke wordpress gratisan tinggal enclose source code java dengan
    [sourcecode language=’java’]
    // kode java
    [sourcecode]

    Bisa macem2 juga koq. coba ke panduan.wordpress.com

  3. Service Layer itu semacam Business Facade yah?. Bisa ga satu service layer berisi semua DAO?sehingga di client cukup mengakses 1 service layer ini

  4. @neonerdy
    Betul!
    Bisa saja semua dao dimasukkan ke dalam service layer. Tapi bayangkan kalo kasusnya kita punya dua modul, misalnya modul security dan report, masing2 modul membutuhkan dao yang berbeda2. Akan lebih mudah jika dao2 tsb dikelompokkan ke dalam service layer yang berbeda, sehingga kita akan punya SecurityService dan ReportService. Dengan begitu aplikasi menjadi lebih modular, misalnya kita ingin merubah implementasi SecurityService ke LDAP, hal ini juga dapat dilakukan dengan mudah tanpa menggangu modul lain.

    Dan yg pasti kalo kamu masukkan semua dao dalam satu service layer, pasti service tsb akan jadi bloated dan sulit dibaca ๐Ÿ˜€

  5. bagaimana caranya membuat transaksi untuk menyimpan dao group parent-child dan setting agar bisa rollback kalau ada kesalahan penyimpanan dari salah satu dao tersebut terima kasih banyak

    1. Hal tsb bisa dilakukan dengan memanfaatkan fitur transaction yang ada di spring. Coba dilihat class service, disitu ada anotasi @Transactional(readOnly=true, propagation=Propagation.REQUIRED) yang menunjukkan bahwa kelas ini bersifat transactional. Sehingga method2 didalamnya akan dirollback jika terjadi kegagalan.

      Silahkan dicoba.

  6. hmm nice article..
    btw di unit testnya ada manggil bean “securityService”
    tp di sping-ctx ga blm didefinisiin…
    brarti bener spring-ctx bikin sakit kepala ๐Ÿ˜€

  7. did you test this?
    as far as i know.. the getClass() method will return Class type of “this”
    and the .getGenericsSuperclass() will return Class type of this.super, which is HibernateDaoSupport.
    which.. /if/ in turn u call getActualTypeArguments, u’ll have nothing, because HibernateDaoSupport is not generics.
    thus have no type arguments.

  8. Saya baru dengan spring, dan senang bisa melihat teknik coding DAO yang efektif seperti yang dipaparkan di atas, saya juga bisa tau bgmana implementasi autowired dan transaction spring. Bahkan unit test, selama ini saya blm pernah pakai jUnit. Thks ya, ini sangat bermanfaat ๐Ÿ™‚

  9. Mas itu kan untuk 1 Model user..
    kebetulan saya buat project spring hibernate with RMI..
    ntar di service saya buat 1 interface untuk semua model?
    atau saya buat 1 interface untuk masing2 model?
    kalo 1 interface untuk masing2 model bingung saya mau publish service na gmna?
    kalo 1 interface untuk semua model ntar di bagian client na kepanjangan donk untuk kelas mengimplementasikan nya, kebetulan model saya ada 6 mas.
    mohon pencerahan na mas.. ๐Ÿ™‚

Leave a Reply