참조 : Spring 프레임워크 워크북

Spring 트랜잭션 처리를 예제 코드로 살펴보기 위해 먼저 JDBC 기반의 트랜잭션 처리의 불편함을 몸소 체험할 수 있는 코드를 작성했습니다.

트랜잭션 처리는 비즈니스 계층에서 해야겠죠. 하지만 왠지 트랜잭션! 이라고 하면 DB랑 연관이 있는 것 같아서 왠지 DAO 계층에서 처리해야 될 것만 같은 느낌을 받았었습니다. 하지만 생각해 보면 DAO는 그냥 DB에 접근하는 CRUD 하는 SQL을 날릴 뿐이고 진짜 고객이 원하는 작업의 단위는 Service 계층의 메소드 단위일 것이고 그 메소드 하나에는 여러 DAO 를 사용하여 Logical Unit of Works(=transaction)을 구현할 것입니다.

Anyway.. 스프링 워크북에 나온대로 퍼시스턴스 계층에서 책임지도록 코딩해 보았습니다.

public class MemberServiceImplTransactionInPersistence implements MemberService {

    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); 만 호출합니다. 그 안에는 아래와 같이 다른 작업과 트랜잭션처리도 담당합니다.

public class MemberDaoJdbcTransactionInPersistence implements MemberDao {

    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와 서비스 계층이 서로 바뀐 것 같은 모양입니다.

테스트 코드는 아래와 같습니다. 예외를 던지게 해뒀기 때문에 예외가 발생했으면 제대로 동작한 것이고 예외를 안던지면 커밋된 것으로 볼 수 있습니다.

    @Test
    public void testRollBackTransaction() {
        Member member = getMember();
        try {
            memberService.add(member);
            fail();
        }
        catch (SQLException e) {
            e.printStackTrace();
        }
    }

bean 설정은 다음과 같습니다.

    <!-- jdbc transaction in persistence -->
    <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>