JDBC 기반 트랜잭션 처리 1. Persistence Layer에서 책임
참조 : Spring 프레임워크 워크북
Spring 트랜잭션 처리를 예제 코드로 살펴보기 위해 먼저 JDBC 기반의 트랜잭션 처리의 불편함을 몸소 체험할 수 있는 코드를 작성했습니다.
트랜잭션 처리는 비즈니스 계층에서 해야겠죠. 하지만 왠지 트랜잭션! 이라고 하면 DB랑 연관이 있는 것 같아서 왠지 DAO 계층에서 처리해야 될 것만 같은 느낌을 받았었습니다. 하지만 생각해 보면 DAO는 그냥 DB에 접근하는 CRUD 하는 SQL을 날릴 뿐이고 진짜 고객이 원하는 작업의 단위는 Service 계층의 메소드 단위일 것이고 그 메소드 하나에는 여러 DAO 를 사용하여 Logical Unit of Works(=transaction)을 구현할 것입니다.
Anyway.. 스프링 워크북에 나온대로 퍼시스턴스 계층에서 책임지도록 코딩해 보았습니다.
private MemberDao memberDao;
public void setMemberDao(MemberDao memberDao) {
this.memberDao = memberDao;
}
public void add(Member member) throws SQLException {
memberDao.add(member);
}
}
서비스 계층은 위와 같이 단순합니다. 모든 책임이 memberDao의 add에 있고 사실 여기있는 add메소드에서 memberDao.add(member); 만 호출합니다. 그 안에는 아래와 같이 다른 작업과 트랜잭션처리도 담당합니다.
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void add(Member member) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = dataSource.getConnection();
con.setAutoCommit(false);
StringBuilder addMemberQuery = new StringBuilder();
addMemberQuery.append("INSERT INTO Member(id, password, name, email) ");
addMemberQuery.append("VALUES (?, ?, ?, ?)");
pstmt = con.prepareStatement(addMemberQuery.toString());
pstmt.setString(1, "whiteship3");
pstmt.setString(2, "pass");
pstmt.setString(3, "기선");
pstmt.setString(4, "keesun3@email.com");
pstmt.executeUpdate();
//TODO 예외 발생하는 코드
TransactionTestingUtil.generateSQLExceptionMethod();
con.commit();
}
catch (SQLException e) {
if(con != null)
con.rollback();
throw e;
}
finally {
if(pstmt != null)
pstmt.close();
if(con != null)
con.close();
}
}
public void add(Connection con, Member member) {
throw new UnsupportedOperationException();
}
}
와오~ DAO 열라 복잡합니다. 할 일은 파란 색 코드 두 줄입니다. 벌써 부터 이상합니다. DAO 계층이 서비스 계층 코드에서 할 일(예외 발생 부분) 까지 포함하게 됐습니다. 이건 마치 DAO와 서비스 계층이 서로 바뀐 것 같은 모양입니다.
테스트 코드는 아래와 같습니다. 예외를 던지게 해뒀기 때문에 예외가 발생했으면 제대로 동작한 것이고 예외를 안던지면 커밋된 것으로 볼 수 있습니다.
public void testRollBackTransaction() {
Member member = getMember();
try {
memberService.add(member);
fail();
}
catch (SQLException e) {
e.printStackTrace();
}
}
bean 설정은 다음과 같습니다.
<bean id="memberService" class="com.bookbuying.member.service.MemberServiceImplTransactionInPersistence">
<property name="memberDao" ref="memberDao" />
</bean>
<bean id="memberDao" class="com.bookbuying.member.dao.MemberDaoJdbcTransactionInPersistence">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:database.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${db.driver}"/>
<property name="jdbcUrl" value="${db.url}"/>
<property name="user" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>