[GenericDao] 하이버네이트 GenericDao
먼저, GenericDao를 만들어 쓰면 좋은 이유를 생각해보겠습니다.
- 모든 DAO에서 중복되거나 반복되는 코드를 상당량 줄일 수 있습니다.
- 테스트도 그만큼 줄일 수 있습니다.
- 개발이 좀 더 빨라집니다.
- 비슷한 기능을 하는 메서드 이름을 통일할 수 있습니다.
Entity 당 DAO를 쓰면 좋은 점과 타입-안정성을 제공하는 DAO 패턴을 사용하면 좋은 점 등은 이 글에 정리되어 있으니 궁금하신 분들은 참고하세요
TDD로 다 만든 다음, 맨 마지막에 이클립스 리팩터링 기능 중에 extract interface로 뽑아 낸 인터페이스는 다음과 같습니다.
void add(E entity);
List<E> getAll();
E getById(Serializable id);
void delete(E entity);
void update(E entity);
void flush();
E merge(E entity);
}
이것을 구현한 실제 DAO 구현체는 이렇게 생겼습니다.
protected Class<E> entityClass;
@SuppressWarnings("unchecked")
public HibernateGenericDao() {
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
Type type = genericSuperclass.getActualTypeArguments()[0];
if (type instanceof ParameterizedType) {
this.entityClass = (Class) ((ParameterizedType) type).getRawType();
} else {
this.entityClass = (Class) type;
}
}
@Autowired
protected SessionFactory sessionFactory;
public void add(E entity) {
getCurrentSession().save(entity);
}
private Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
@SuppressWarnings("unchecked")
public List<E> getAll() {
return getCurrentSession().createCriteria(entityClass)
.list();
}
@SuppressWarnings("unchecked")
public E getById(Serializable id){
return (E) getCurrentSession().get(entityClass, id);
}
public void delete(E entity){
getCurrentSession().delete(entity);
}
public void update(E entity){
getCurrentSession().update(entity);
}
public void flush(){
getCurrentSession().flush();
}
@SuppressWarnings("unchecked")
public E merge(E entity){
return (E) getCurrentSession().merge(entity);
}
}
특징이라고 할 수 있는 걸 꼽자면..
- 하이버네이트 SessionFactory를 사용하는 GenericDAO 라는 것.
- 별도로 엔티티 타입을 인자로 넘겨줄 필요가 없다는 것.
- 타입-안전성을 보장하기 때문에 별도의 캐스팅 등이 필요없고, 컴파일 시점에 체크 가능하다는 것.
이 클래스는 다음과 같은 테스트 클래스를 이용해서 TDD로 만들었습니다.
@ContextConfiguration(locations="/testContext.xml")
@Transactional
public class HibernateGenericDaoTest extends DBUnitSupport{
@Autowired TestDao dao;
@Test
public void add() throws Exception {
TestDomain entity = new TestDomain();
dao.add(entity);
assertThat(dao.getAll().size(), is(1));
}
@Test
public void getAll() throws Exception {
insertXmlData("testData.xml");
assertThat(dao.getAll().size(), is(2));
}
@Test
public void getById() throws Exception {
insertXmlData("testData.xml");
assertThat(dao.getById(1).getName(), is("keesun"));
}
@Test
public void delete() throws Exception {
insertXmlData("testData.xml");
TestDomain entity = dao.getById(1);
dao.delete(entity);
assertThat(dao.getAll().size(), is(1));
}
@Test
public void update() throws Exception {
insertXmlData("testData.xml");
// entity is (similar)detached object
TestDomain entity = new TestDomain();
entity.setId(1);
entity.setName("whiteship");
dao.update(entity);
// now, entity has been persistent object
entity.setName("helols");
dao.flush();
assertThat(dao.getById(1).getName(), is("helols"));
}
@Test
public void merge() throws Exception {
insertXmlData("testData.xml");
// entity is detached object
TestDomain entity = new TestDomain();
entity.setId(1);
entity.setName("whiteship");
TestDomain newEntity = dao.merge(entity);
// newEntity is persistent object, but entity is still detached object
newEntity.setName("helols");
entity.setName("nije");
dao.flush();
assertThat(dao.getById(1).getName(), is("helols"));
}
}
이 테스트의 특징은 다음과 같습니다.
- 하이버네이트의 update()와 merge()의 특징과 그 차이점을 이해할 수 있도록 작성했습니다.
- 스프링 테스트 컨텍스트를 사용했습니다.
- DBUnit과 그것을 확장한 클래스를 이용했습니다.
생각해볼 것
- GenericDao에 있는 update(), merge(), flush()는 Generic하지 않다는 생각이 듭니다.
- (위에는 보여드리지 않았지만)테스트에 사용된 TestDomain 클래스와 TestDao를 GenericDaoTest 내부에 포함 시키는 것이 좋치 않을까?
- 어떤 기능을 더 추가할 수 있을까?