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