10 Langkah membuat Generic DAO dengan Spring 2.5 + Hibernate

17 12 2007

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),


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;
	} 

}

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,


public class UserDaoHibernate extends GenericDaoHibernate<User, ID extends Serializable> implements UserDao {
}

oh mau buat dao untuk group? gampang!


public class GroupDaoHibernate extends GenericDaoHibernate<Group, ID extends Serializable> implements GroupDao {
}

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,


public interface GenericDao<T, ID extends Serializable>  {
	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);
}

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

public class GenericDaoHibernate<T, ID extends Serializable> extends
		HibernateDaoSupport implements GenericDao<T, ID extends Serializable>  {

	public Class domainClass;

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

	@SuppressWarnings("unchecked&quot ;)
	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&quot ;)
	public Long count() {
		List list = getHibernateTemplate().find(
				"select count(*) from " + domainClass.getName() + " x");
		Long count = (Long) list.get(0);
		return count;
	}

	@SuppressWarnings("unchecked&quot ;)
	public List findAll() {
		return getHibernateTemplate().find("from " + domainClass.getName());
	}

	@SuppressWarnings("unchecked&quot ;)
	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();
	}
}

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

@Entity
@Table(name="T_USER&quot ;)
public class User {
	@Id
	@Column(name = "user_id&quot ;)
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer id;
	private String name;
	private String email;

	// Getter dan setter
}

4. Buat interface UserDao,


public interface UserDao extends GenericDao<T, ID extends Serializable> {
}

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

@Repository("userDao&quot ;)
public class UserDaoHibernate extends GenericDaoHibernate<User, Serializable>
		implements UserDao {

	@Autowired
	public UserDaoHibernate(SessionFactory sessionFactory) {
		super.setSessionFactory(sessionFactory);
	}
}

6. Selanjutnya gw akan membuat service layer, gw buat dulu interface securityService,


public interface SecurityService {
	public void save(User user);
	public User getUser(Integer id);
	public List getUsers();
	public void delete(User user);
}

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.

@Service("securityService&quot ;)
@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();
	}
}

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


<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
			http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
			http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
			http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

        <context:property-placeholder location="classpath:jdbc.properties" />
        <!-- Scan komponen seperti @Repository, @Service -->
	<context:component-scan base-package="org.latihan" />
	<!-- Transaction dengan anotation -->
        <tx:annotation-driven transaction-manager="txManager"/>

	<!-- definisikan datasource berdasarkan jdbc.properties -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource"
		p:driverClassName="${jdbc.driver}" p:url="${jdbc.url}"
		p:username="${jdbc.username}" p:password="${jdbc.password}" />

	<!-- session factory untuk setiap dao -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
		p:dataSource-ref="dataSource">
		<property name="annotatedClasses">
			<list>
				<value>org.latihan.model.User</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">
					${hibernate.dialect}
				</prop>
				<prop key="hibernate.show_sql">
					${hibernate.show_sql}
				</prop>
				<prop key="hibernate.generate_statistics">
					${hibernate.show_statistics}
				</prop>
				<prop key="hibernate.hbm2ddl.auto">
					${hibernate.hbm2ddl_auto}
				</prop>
			</props>
		</property>
	</bean>

	<!-- transaction manager -->
	<bean id="txManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager"
		p:sessionFactory-ref="sessionFactory" />
</beans>


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/


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());
	}
}

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.


Actions

Information

10 responses to “10 Langkah membuat Generic DAO dengan Spring 2.5 + Hibernate”

18 12 2007
dwi ardi irawan (05:35:32) :

i missed the last one, unit testing…. ^_^ thnx…nice article

29 12 2007
Trackback Sejagad Untuk Akhir Tahun | Blog Shares Everything (07:53:06) :

[...] juga konfirmasi klo benbego sekarang dah bisa di akses di domain benbego.com. Setidaknya ini sekarang yg disebut dream come true. Dah lama [...]

3 01 2008
SiHendra (09:50:52) :

Wah thanks bgt tutorialnya, inspiring ! (untuk belajar ;)) soalnya tutorial lain pada belibet)

7 01 2008
Joshua (03:35:01) :

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.

15 01 2008
Ari (08:49:36) :

Wah textbox buat nulis source code nya bagus, gimana cara bikinnya tuh? di wordpress dah ada templatenya?

18 01 2008
dhiku (10:08:23) :

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

28 01 2008
neonerdy (02:25:58) :

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

28 01 2008
dhiku (04:54:20) :

@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 :D

11 07 2008
adegaos (04:32:24) :

Terima kasih Om Dhiku,

Membantu banget..!!

23 07 2008
technology (09:03:53) :

keren banget neh pak dhe dhiku … tak pelajari dulu ya pak dhe

Leave a comment

You can use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>