Whiteship's Note

스프링 DAO 3파전

모하니?/Coding : 2010.07.02 13:00


JDBC를 사용하는 부류가 있고, iBatis를 사용하는 부류가 있고, 하이버네이트를 사용하는 부류가 있다. 사실 이보다 더 다양한 영속화 기술들이 있지만.. 이 세 부류로 간추려 보려고 한다. 자 이들은 각각 어떻게 코딩을 하고 있을까.

public interface MemberDao {
void add(Member member);
void update(Member member);
Member get(int id);
List<Member> list();
void delete(int id);

}

이런 인터페이스를 각 부류에서 구현한다고 생각해보자.

아참, 전제는 스프링을 사용한다는 것이다.

1. JDBC

@Repository
public class MemberDaoJdbc implements MemberDao{
@Autowired SimpleJdbcTemplate jdbcTemplate;
public void setMemberMapper(RowMapper<Member> memberMapper) {
this.memberMapper = memberMapper;
}

RowMapper<Member> memberMapper = new RowMapper<Member>(){
public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
Member member = new Member();
member.setId(rs.getInt("id"));
member.setName(rs.getString("name"));
member.setJoined(rs.getDate("joined"));
return member;
}};
public void add(Member member) {
jdbcTemplate.update("insert into member(id, name, joined) values (?, ?, ?)", 
member.getId(), member.getName(), member.getJoined());
}

public void delete(int id) {
jdbcTemplate.update("delete from member where id = ?", id);
}

public Member get(int id) {
return jdbcTemplate.queryForObject("select * from member where id = ?", memberMapper, id);
}

public List<Member> list() {
return jdbcTemplate.query("select * from member", memberMapper);
}

public void update(Member member) {
jdbcTemplate.update(
"update member set name = :name, joined = :joined where id = :id", 
new BeanPropertySqlParameterSource(member));
}

}

대충 이런 코드가 된다. 스프링이 제공해주는 SimplJdbcTemplate은 멀티 쓰레드 환경에서 공유하면서 사용해도 안전한 객체이다. 따라서 빈으로 등록해놓고 주입받아서 쓴다고 한들 문제될 것이 없다. 

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/testContext.xml")
@Transactional
public class MemberDaoJdbcTest {
@Autowired MemberDaoJdbc memberDao;
@Test
public void di(){
assertThat(memberDao, is(notNullValue()));
}
@Test
public void crud(){
Member member = new Member();
member.setId(1);
member.setName("whiteship");
member.setJoined(new Date());
memberDao.add(member);
assertThat(memberDao.list().size(), is(1));
member.setName("기선");
memberDao.update(member);
assertThat(memberDao.get(1).getName(), is("기선"));
memberDao.delete(1);
assertThat(memberDao.list().size(), is(0));
}
}

테스트는 대충 만들었기 때문에 너그럽게 봐주시길...^_^;;;

2. iBatis

스프링에 iBatis의 SqlMapClient를 만들어 주는 팩토리빈을 등록해야 한다.
   
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="SqlMapConfig.xml" />
</bean>

아이바티스 설정 파일도 추가한다. 저 설정에 보이는 SqlMapConfig.xml이 필요하다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
<sqlMap resource="sample/ibatis/Member.xml" />
</sqlMapConfig>

그 다음 Member와 관련있는 SQL을 모아둔 Member.xml 파일을 만든다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="Member">
<typeAlias alias="Member" type="sample.domain.Member" />

<delete id="delete" parameterClass="int">
delete from member where id = #id#
</delete>

<insert id="add" parameterClass="Member">
insert into member (id, name, joined) values(#id#, #name#, #joined#)
  </insert>
 
<update id="update" parameterClass="Member">
update member set name = #name#, joined = #joined# where id = #id#
</update>
 
<select id="get" parameterClass="int" resultClass="Member">
select * from member where id = #id#
  </select>

<select id="list" resultClass="Member">
select * from member order by id
  </select>
</sqlMap>

XML 엘리먼트와 어트리뷰트는 별도의 학습이 필요하지 않을만큼 직관적이다.. 이제 DAO 코드를 작성하자.

@Repository
public class MemberDaoIbatis implements MemberDao {
@Autowired SqlMapClientTemplate sqlMapClientTemplate;
public void add(Member member) {
sqlMapClientTemplate.insert("add", member);
}

public void delete(int id) {
sqlMapClientTemplate.delete("delete", id);
}

public Member get(int id) {
return (Member) sqlMapClientTemplate.queryForObject("get", id);
}

@SuppressWarnings("unchecked")
public List<Member> list() {
return sqlMapClientTemplate.queryForList("list");
}

public void update(Member member) {
sqlMapClientTemplate.update("update", member);
}

}

흠 많이 짧아졌다. 그런데.. 전체 코드량은 더 많이 늘은것 같지 않은가? 코드량이 척도의 전부는 아니지만.. 왠지 작업이 줄었다가 보다는 DAO에서 할일을 설정파일로 미뤘다는 느낌이 강하다. 아참 여기서 사용한 SqlMapClientTemplate도 SimleJdbcTemplate과 마찬가지로 빈으로 등록해서 사용해도 별 지장이 없는 쓰레드-안전한 클래스기 때문에 빈으로 등록해놓고 사용했다.

3. Hibernate

이것도 역시 빈을 하나 설정해줘야한다.
 
   <!-- ============================================================= -->
    <!--  Hibernate                                                    -->
    <!-- ============================================================= -->
    <bean id="transactionManager"
          class="org.springframework.orm.hibernate3.HibernateTransactionManager"
          p:sessionFactory-ref="sessionFactory"/>

    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="sample.domain" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

이번건 설정이 제법 길다. 그리고 빈이 하나가 아니라 두갠데.. 그건 나중에 TransactionManager에 대한 글을 올릴 때 설명해야겠다. 아니면 그냥 토비님 책을 보기 바란다. 11장에 자세히 설명해주시고 있다. TransactionManager와 기반 기술의 관계(?)랄까나.. 암튼.. 이제 바로 DAO를 만들 수 있다. 아 아니다 일단 Member 클래스에 애노테이션 부터 추가하자.

@Entity
public class Member {

@Id
int id;

...
}

이렇게 두 군데에 각각 하나씩만 붙여주면 된다. 이제 DAO를 만들자.

@Repository
public class MemberDaoHibernate implements MemberDao{
@Autowired SessionFactory sessionFactory;

public void add(Member member) {
getSession().save(member);
}

public void delete(int id) {
getSession().createQuery("delete from Member where id = ?")
.setInteger(0, id)
.executeUpdate();
}

public Member get(int id) {
return (Member) getCriteria()
.add(Restrictions.eq("id", id))
.uniqueResult();
}

@SuppressWarnings("unchecked")
public List<Member> list() {
return getCriteria().list();
}

public void update(Member member) {
getSession().update(member);
}

private Criteria getCriteria() {
return getSession().createCriteria(Member.class);
}

private Session getSession() {
return sessionFactory.getCurrentSession();
}
}

SQL이 없다. SQL처럼 보이는 HQL(굵은 글씨)만 보인다. 이렇게만 하면 정말 DAO가 구현되는지 의심스럽겠지만 정말 구현이 끝난다. 하지만 하이버네이트 Session과 Criteria API는 약간 학습이 필요하다. 자주 쓰는 API는 몇개 안되기 떄문에 대략 1시간 정도면 익힐 수 있다.

셋 개 다 해본결과.. 난 역시 하이버네이트가 편하다.
top

  1. Favicon of http://lf.hisfy.com/ BlogIcon 엽우 2010.07.02 18:08 PERM. MOD/DEL REPLY

    쭉 하이버네이트만 써봐서 iBatis는 어떨까 궁금했는데 덕분에 궁금증이 해소되었네요.
    저도 하이버네이트가 가장 예뻐보입니다.

    다만 애노테이션을 추가해줘야 하는게 쫌 아쉽네요.
    뭐랄까... 모델은 모델대로 따로 놔두고 설정을 분리하고 싶은 강박증 때문이랄까? ^^;

    Favicon of http://whiteship.me BlogIcon 기선 2010.07.03 13:32 PERM MOD/DEL

    그러게요. 저도 그부분이 아쉽긴한데;
    XML로 설정 빼내기가 귀찮아서 그냥 애노테이션으로..ㅋ;;

  2. 코바 2010.07.03 14:32 PERM. MOD/DEL REPLY

    SqlMapConfig.xml 설정은

    스프링2.5 최신버전부터 p:mappingLocations="classpath*:xxx/**/sqlMap.xml"

    이런 속성으로 설정을 간편화가 됩니다.

    의외로 sql에 비중이 높은 프로젝트에서 ibatis도 쓸모가 많습니다.

    전 하이버네이트를 제대로 안해봐서 한번 해보고 싶네요.

    Favicon of http://whiteship.me BlogIcon 기선 2010.07.03 22:50 PERM MOD/DEL

    오호! 그렇군요~
    아이바티스는 저도 수년만에 첨써보는거라;;

  3. Favicon of http://jjaeko.tistory.com BlogIcon 째코 2010.07.06 18:39 PERM. MOD/DEL REPLY

    지금까지 ibatis만으로 프로젝트를 진행했는데 과연 지금 하고 있는 이 프로젝트가
    ibatis대신 하이버네이트로 가능했을까? 라는 생각이 들곤했어요.
    핑계이긴 하지만 쿼리자체가 곧 업무였고, 길고 복잡했기 때문에요.
    복잡한 쿼리는 hql로는 감당이 안될거 같았고, native sql을 쓰자니 ibatis의 다이나믹 태그가 필요했고...
    기선님은 복잡한 쿼리를 어떻게 해결하시나요?

    하이버네이트 공부할땐 잼있었는데 저도 꼭 한번 프로젝트에 써보고 싶네요 ㅎ

    Favicon of http://whiteship.me BlogIcon 기선 2010.07.06 19:42 PERM MOD/DEL

    흠.. 간단하게 말씀드리자면 저는 복잡한 쿼리를 작성하지 않습니다.

    복잡한 비즈니스 로직이 있긴한데.. 그게 꼭 복잡한 쿼리로 연결되진 않더라구요. 복잡한 서비스 코드가 만들어지긴 하더군요.

    오늘 회사에서 쬐끔 복잡한 부분을 코딩하고 왔는데. 내일 낮에 포스팅 하겠습니다.

Write a comment.




: 1 : ··· : 42 : 43 : 44 : 45 : 46 : 47 : 48 : 49 : 50 : ··· : 2638 :