이러쿵저러쿵


JAVA Spring Framework에서는 iBatis 를 사용하여 DB에 접근하기 위해 SqlMapClientDaoSupport 이란 DAO 클래스를 사용합니다.

 

그리고 SqlMapClientDaoSupport 클래스는 실질적인 데이터 조작 및 접근을 위해 다음의 2가지 형태의 메서드를 제공합니다.

 

* getSqlMapClientTemplate()

* getSqlMapClient()

 

스프링 프레임워크 문서(http://docs.spring.io/spring/docs/3.2.11.RELEASE/javadoc-api/org/springframework/orm/ibatis/support/SqlMapClientDaoSupport.html)를 참조하면 위의 메서드는 다음과 같이 정의 되어 있습니다.

 

 

getSqlMapClient

public final SqlMapClient getSqlMapClient()

Return the iBATIS Database Layer SqlMapClient that this template works with.

 

getSqlMapClientTemplate

public final SqlMapClientTemplate getSqlMapClientTemplate()

Return the SqlMapClientTemplate for this DAO, pre-initialized with the SqlMapClient or set explicitly.

 

 

하나는 iBatis 데이터베이스 레이어(SqlMapClient)를 반환하고, 다른 하나는 명시적이으로 미리 초기화된 SqlMapClient 객체 또는 집합과 함께 SqlMapClientTemplate를 반환합니다. 둘다 DB의 실질적인 데이터에 접근하여 처리하는 것에는 변함이 없습니다.

 

그럼 2개의 메서드의 동작 처리 방식에는 어떤 차이가 있을까요? 

데이터를 삽입(INSERT)하는 동작을 처리하는 코드를 짠다고 하면 다음과 같이 작성합니다.

(둘다 동일한 메스드를 가지고 있습니다.)

 

getSqlMapClientTemplate().insert()
getSqlMapClient().insert()

 

두 메서드는 동일하게 iBatis의 INSERT 를 처리하나 트랜잭션에 차이가 있습니다.

 

* getSqlMapClientTemplate().insert()
-> 요청한 insert를 즉시 실행하며, 바로 트랜잭션 완료 처리(commit)됩니다. 즉 INSERT 쿼리에 대해 자동으로 트랜잭션 처리가 됩니다.

 

* getSqlMapClient().insert()
-> 사용자가 명시적으로 최종 트랜잭션 처리를 할 때까지는 트랜잭션 완료 처리가 되지 않습니다. 이러한 방식을 로컬 트랜잭션(Local Transaction) 이라하고 일괄 처리(Batch Processing) 기법이라 합니다.

 

실제 사용 예제 코드를 한 번 보겠습니다.

 

 

Example1 - getSqlMapClientTemplate

for (i = 0; i < 100; i++) {

    getSqlMapClientTemplate().insert();

}

 

Example2 - getSqlMapClient

getSqlMapClient().startTransaction();

getSqlMapClient().startBatch();

for (i = 0; i < 100; i++) {

    getSqlMapClient().insert();

}

getSqlMapClient().executeBatch();

getSqlMapClient().endTransaction();

 

 

하나는 getSqlMapClientTemplate() 을 사용하였고, 다른 하나는 getSqlMapClient()를 사용합니다.

 

위의 2쿼리가 for루프를 수행하다가 91번째 쿼리에서 예상치 못한 오류가 발생하였고, 그래서 91번째 요청을 처리하지 못하고 Exception 에러가 발생하였다고 가정합니다.

 

Example1의 경우, 매 insert()요청마다 자동 트랜잭션 완료 처리가 되므로,  90번까지의 insert 요청이 DB에 정상적으로 반영(commit)됩니다.

 

Example2의 경우, insert()요청이 실행 된 후, 자동 트랜잭션 완료 처리가 되지 않으므로 사용자가 명시적으로 for 루프 시작 과 끝 부분에 startTransaction(), endTransaction()을 호출하고 있습니다. 90번까지 수행된 상태애서 에러가 발생하였고, endTransaction()이 정상적으로 호출되지 않은 상태로 루프가 종료되었기 때문에 startTransaction() 이후에 실행된 모든 insert()요청(90번 루프를 도는 동안 요청된)이 취소되고, 롤백(rollback) 됩니다. 즉 단 하나의 insert 요청도 처리되지 않은 상태가 됩니다.

 

한 가지 더 덧붙이자면 위의 100번의 루프가 정상적으로 수행되었다고 가정하면

Example1은 100번의 쿼리 실행과 100번의 commit이 수행되며,

Example2은 100번의 쿼리가 실행되었지만, 단 1번의 commit 만 수행됩니다.

 

일반적으로는 한 건의 쿼리만 처리한다면 getSqlMapClientTemplate()를 사용하면 됩니다. 그러나 여러 건의 쿼리를 하나의 트랜잭션으로 묶어 처리해야 하는 경우에는 getSqlMapClient()를 사용하는 것이 속도나 효율성 면에서 더 좋습니다.

 

덧붙여서 예를 들면 회원 가입 처리를 위해 3가지

* 회원 인적 사항 정보 DB에 기록

* 회원 가입 축하 포인트 적립 정보 DB에 기록

* 가입과 동시에 자동 로그인 후, 로그인 로그를 DB에 기록

의 쿼리를 만들었는데, 이러한 3개의 쿼리는 3개가 동시에 모두 처리되어야 하며, 3개중의 일부만 수행되어서는 안됩니다. (회원 가입은 되었는데, 문제가 생겨서 포인트 적립이 안되면 안되겠죠?)

그러므로 위의 3개의 쿼리는 함께 모두 실행되거나 모두 실행되어야 하지 말아야 합니다. 즉, 3개의 쿼리중에 단 하나의 쿼리라도 오류가 발생하는 경우, 이미 실행된 다른 쿼리를 모두 rollback하고, 사용자에게 안내메시지(회원 가입을 다시 시도하라는 안내 등)를 띄워주어야 합니다. 즉, 이러한 경우의 처리라면 반드시 getSqlMapClient() 의 transaction 기능을 써야 합니다.

 

 

참조 :

Interface SqlMapClient : http://ibatis.apache.org/docs/java/dev/com/ibatis/sqlmap/client/SqlMapClient.html?is-external=true

Class SqlMapClientTemplate : http://docs.spring.io/spring/docs/3.2.11.RELEASE/javadoc-api/org/springframework/orm/ibatis/SqlMapClientTemplate.html

저작자 표시 비영리 변경 금지
신고


Comment +2

  • Terry 2014.10.09 21:46 신고

    지나가다 한마디 남깁니다..

    getSqlMapClientTemplate() 이 즉시 commit을 할지는 모르겠지만 일반적으로 Spring 사용시 TransactionManager를 이용하기 때문에 이로 인해 즉시 commit이 발생하지는 않습니다..AOP가 됐든 @Transactional이 됐든 둘중 하나라도 사용하게 되면 즉시 commit이 되질 않죠..

    이 둘의 차이점은 세세한 동작에서 봐야 할 사항은 아닙니다..
    getSqlMapClientTemplate()은 그냥 사용할 수 있는게 아니라 클래스가 SqlMapClientDaoSupport 클래스를 상속받아야 사용이 가능합니다..
    만약 DAO가 하나의 DB만 사용한다면 상관없지만 둘 이상의 DB를 사용할 경우 이 메소드를 사용할 수 없습니다..왜냐면 자바는 다중상속을 지원하지 않기 때문이죠..
    이런 문제는 ibatis의 배치모드를 사용할 수 없는 문제도 발생하게 됩니다..
    DB는 하나를 사용하고 있지만 ibatis의 batch 모드를 사용할려면 ibatis 설정에서 작업을 해주어야 하는데 이럴 경우 batch 설정이 되어 있는 것과 그렇지 않은 것을 각각 하나씩 만들어서 사용하게 됩니다..
    그러나 DAO에서 배치를 사용하는 것과 그렇지 않은 것 모두 있을 경우 SqlMapClientDaoSupport 클래스는 하나뿐이 상속 못받기 때문에 DAO 하나에서 배치잡을 돌리는데 문제가 발생하죠..

    그래서 개인적으로는 SqlMapClientDaoSupport를 상속받아 하는 것 보단

    @Autowired
    private SqlMapClientTemplate sqlMapClientTemplate;

    이렇게 Injection 받아서 하는 것을 추천합니다..
    이 방법을 사용하면 위에서 예를 들은 batch 작업도 하나의 DAO에서 가능합니다..
    batch 작업을 하는 SqlMapClientTemplate 과 그렇지 않은 SqlMapClientTemplate 을 모두 Injection 받으면 되기 때문이죠..

  • 안녕하세요. Terry 님
    블로그에 관심을 가져주시고, 소중한 답변 감사드립니다. 좀 더 많이 배우게 되었습니다.

    다양하고 새로운 트랜잭션 관리 기법을 활용할 수 있는 상황에서 너무 단편적이고 기초적인 게시물을 작성하여 조금은 관점이 다르게 보였던 것 같습니다.
    말씀하신 내용들이 모두 도움이 되었습니다. 제가 단순히 트랜잭션에 대해 설명하려고 했던 주제 보다는 보다 광범위하게 설명해 주신 것 같습니다.
    트랜잭션 처리를 위해 Spring AOP단의 트랜잭션 처리(TransactionManager)나 보통 DB자체의 Transaction을 많이 활용합니다.

    그리고 하나의 DAO에 하나의 DB만 접근하여 SqlMapClientDaoSupport 상속을 통한 구현에 큰 문제가 없었던 저였는데,
    (여러 DB접근 필요시, DB링크를 사용하거나, SqlMapConfig의 다중DB정의 후, DAO에서 extends하여 구현.)
    말씀하신대로 다중DB접속을 DAO하나로 처리하여야 하는 상황이 새로 주어진다면 추천해주신 방법으로 구현해보도록 하겠습니다.

티스토리 툴바