Ibatisbatch

From JCFWiKi

Jump to: navigation, search

그림:check.gif

  • 산출물: iBatis Batch
  • 작성자: 박동영, 서경진
  • 최초작성일 : 2007/08/06
  • 최종작성일 : 2008/07/07

Copyright © 2008 Daewoo Information Systems Co., Ltd.

그림:information.gif

  • 이 문서는 JCF에서 iBatis Batch를 적용하는 방법에 관한 것이다.

목차

[편집] iBatis Batch 기본 개념

한 번에 여러 개의 쿼리를 보내야할 때는 batch로 묶어서 실행시키는 것이 실행속도에 유리하다고 합니다. (얼마나 유리한지는 테스트 안해봐서 모르겠습니다.)

Spring에서는 이를 위한 템플리트 메소드를 만들어놓고 callback 메소드를 사용자 정의하도록 하여 처리하고 있습니다.

callback 메소드 내에서는 iBatis의 SqlMapExecutor가 노출이 되고, 이를 이용하여 쿼리를 수행하면 되겠습니다.


구현 참조 :

성능 비교 :


 public class SqlMapAccountDao extends SqlMapClientDaoSupport implements AccountDao {
     public void insertAccount(Account account) {
         getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
             public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                 executor.startBatch();
                 executor.update("insertAccount", account);
                 executor.update("insertAddress", account.getAddress());
                 executor.executeBatch();
 
                 return null;
             }
         });
     }
 }

[편집] iBatis Batch 적용 사례

실체 업무를 처리하는 웹 어플리케이션에서는 insert, update, delete와 같이 Transactional 데이터 처리(OLTP)가 빈번하게 발생한다. OLTP에 대한 대용량의 데이터 요청이 발생하면, 한 번의 데이터 Connection으로 다수의 쿼리(설정된 쿼리의 갯수)를 처리하여 Connection의 횟수를 줄이므로 성능을 향상시킬 수 있다. SqlMapClientCallback 클래스에 정의된 doInSqlMapClient 메소드에 노출된 SqlMapExecutor에 대한 startBatch()를 통해 단일 Connection에 대한 Transaction을 시작하고 다수의 쿼리를 startBatch() 이후의 로직에서 처리한다. 저장된 다수의 쿼리와 바인딩 데이터를 executeBatch()를 통해 일괄적으로 실행하고 commint하게 된다. ibatis batch를 적용한면, 대용량 데이터 처리시간을 상당히 많이 단축할 수 있다.

  • ibatis batch 적용 Use Case
    • 다수의 insert, update, delete가 발생하는 배치작업
    • sequence를 보장하는 Transaction 관리가 필요한 작업 (commit, rollback, logging 처리 필요)
    • 일정한 데이터 범위(chunk) 단위로 Transaction 관리가 필요한 작업
    • DB Connection 횟수의 감소가 절대적으로 필요한 작업 (성능 향상의 목적)
  • ibatis batch 적용 시 고려사항
    • 임시로 다수의 쿼리와 데이터를 저장할 수 있는 충분한 메인 메모리 확보
    • 실운영 시 다른 업무처리에 대한 영향도 분석
    • 일련의 동일한 작업을 처리하는지에 대한 업무적 특성 파악
    • 성능, 용량 등의 비기능적 요구사항 분석

[편집] ibatis batch 적용이 가능한 경우

  • 업무 로직이 insert, update, delete의 작업으로 구성됨
  • 쿼리 실행 시 sqlmap에 작성된 쿼리의 변경이 없음
  • 대상 쿼리에 정형화된 데이터가 파라미터로 전달됨 (model(Bean), list, map 등)
  • 대용량의 OLTP성 데이터 처리가 필요함

[편집] ibatis batch 적용이 불가능한 경우

  • 업무 로직이 select와 같이 OLAP성 데이터 처리로 구성됨
  • sqlmap에 변수로 처리되어 쿼리 실행 시마다 쿼리의 변경이 발생함
  • 쿼리에 변경이 발생하여 전달되는 파라미터가 비정형화된 형태로 제공됨

[편집] 적용 예제

ibatis batch를 구성하기 위해서 Dao 클래스에서 batch 처리를 위한 로직을 구성한다. 다음은 사용자를 등록하는 로직에 batch를 적용하여 구성한 것이다. createBatchUsers(final List userList)는 User 모델을 리스트로 구성한 파라미터를 받아서 insert 작업을 batch로 처리한다. createBatchHashMap(final List mapList)는 Map을 리스트로 구성한 파라미터를 받아서 insert 작업을 batch로 처리한다.

public class UserDao extends BaseSqlMapClientDAO {
 
......
public int createBatchUsers(final List userList) throws SQLException {
		Integer result = (Integer) getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
			public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
				executor.startBatch();
				for (Iterator iterator = userList.iterator(); iterator.hasNext();) {
					User user = (User) iterator.next();
					executor.insert("user.createUser", user);
				}
				return new Integer(executor.executeBatch());
			}
		});
		return result.intValue();
	}
 
	public int createBatchHashMap(final List mapList) throws SQLException {
		Integer result = (Integer) getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
			public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
				executor.startBatch();
				for (Iterator iterator = mapList.iterator(); iterator.hasNext();) {
					HashMap map = (HashMap) iterator.next();
					executor.insert("user.createHashMap", map);
				}
				return new Integer(executor.executeBatch());
			}
		});
		return result.intValue();
	}
......
 
}

[편집] 테스트케이스 구성

테스트케이스는 DBUnit 기반으로 테스트를 수행할 수 있는 BaseDaoDBunitTestCase를 상속받아서 구성하였다. ibatis batch에 대한 테스트는 testCreateBatchUsers()와 testCreateBatchHashMap()의 케이스에서 수행한다. 작성된 테스트케이스에 대한 수행결과는 아래의 실행 로그를 통해 확인할 수 있다.

public class UserDaoDBunitTest extends BaseDaoDBunitTestCase {
	private SqlMapClient sqlMapClient=null;
	private UserDao userDao=null;
 
	protected void setUp() throws Exception {
		System.out.println("setUp start");
		init("sqlMapClient");
	}
 
	@Override
	protected IDataSet getDataSet() throws Exception {
		IDataSet dataSet = new XlsDataSet(new FileInputStream("test/userDaoTestData.xls"));
		return dataSet;
	}
 
	@Override
	protected IDatabaseConnection getConnection() throws Exception {
		return new DatabaseConnection(sqlMapClient.getDataSource().getConnection());
	}
 
	protected void tearDown() throws Exception {
		super.tearDown();
		System.out.println("tearDown end");
	}
 
	public void init(String sqlMapClientId) throws Exception {
		userDao = (UserDao) ctx.getBean("userDao");
		super.init(sqlMapClientId);
		sqlMapClient = super.getSqlMapClient();
	}
 
......
 
	public void testSetUpIDataConnection() throws Exception {
		assertNotNull(this.getConnection());
	}
 
	public void testInsertIDataSet() throws Exception {
		DatabaseOperation.CLEAN_INSERT.execute(this.getConnection("oracle"), this.getDataSet());
	}
 
......
 
        //모델에 대한 리스크를 파라미터로 전달하여 처리는 배치모듈 테스트
	public void testCreateBatchUsers() throws Exception {
		List userList = new ArrayList();
		for (int i=11;i<100;i+=11) {
			User user = new User();
			Code code = new Code();
			Date regDate = new Date(5880228);
			code.setCategory("seoul");
			code.setId("seoul");
			code.setName("seoul");
			user.setId(Integer.toString(i));
			user.setName(Integer.toString(i));
			user.setPassword(Integer.toString(i));
			user.setAddress(code);
			user.setDescription(Integer.toString(i));
			user.setGender("F");
			user.setRegDate(regDate);
			user.setHasHouse("true");
			userList.add(user);
			if (i==99) break;
 
		}
		int count = userDao.createBatchUsers(userList);
		assertEquals(count, 0);
		User user2 = userDao.findUser("11");
		assertEquals(user2.getName(),"11");
	}
 
	//Map에 대한 리스트를 파라미터로 전달하여 처리는 배치모듈 테스트
	public void testCreateBatchHashMap() throws Exception {
		List mapList = new ArrayList();
		for (int i=10;i<100;i+=10) {
			Map map = new HashMap();
			Code code = new Code();
			Date regDate = new Date(5880228);
			code.setCategory("seoul");
			code.setId("seoul");
			code.setName("seoul");
			map.put("id", Integer.toString(i));
			map.put("name", Integer.toString(i));
			map.put("password", Integer.toString(i));
			map.put("address", code);
			map.put("description", Integer.toString(i));
			map.put("gender", "F");
			map.put("regdate", regDate);
			map.put("hashouse", "true");
			mapList.add(map);
			if (i==90) break;
 
		}
		int count = userDao.createBatchHashMap(mapList);
		assertEquals(count, 0);
		User user2 = userDao.findUser("30");
		assertEquals(user2.getName(),"30");
	}
}
  • 테스트 실행

JUnit으로 위의 구성한 테스트케이스를 실행한다.

JUnit으로 UserDaoDBunitTest 실행

  • 테스트 실행 로그

위의 UserDaoDBunitTest를 실행하면 batch 처리에 대한 다음과 같은 로그를 확인할 수 있다. testCreateBatchUsers()와 testCreateBatchHashMap()가 처리될 때 Connection이 한 번만 설정되고 각각의 쿼리가 Prepared Statement로 처리되는 것을 확인할 수 있다.

setUp start
2008-07-07 15:04:59,823 DEBUG (java.sql.Connection:27) - {conn-100006} Connection
User id: 11
2008-07-07 15:04:59,823 DEBUG (java.sql.Connection:27) - {conn-100006} Preparing Statement:
    INSERT INTO   USERS( id, name, password, address_code_id, description, reg_date, gender, has_house)   VALUES(?, ?, ?, ?, ?, ?, ?, ? )  
2008-07-07 15:04:59,823 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': flushed
User id: 22
2008-07-07 15:04:59,823 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': flushed
User id: 33
2008-07-07 15:04:59,823 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': flushed
User id: 44
2008-07-07 15:04:59,833 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': flushed
User id: 55
2008-07-07 15:04:59,833 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': flushed
User id: 66
2008-07-07 15:04:59,833 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': flushed
User id: 77
2008-07-07 15:04:59,833 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': flushed
User id: 88
2008-07-07 15:04:59,833 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': flushed
User id: 99
2008-07-07 15:04:59,833 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': flushed
2008-07-07 15:04:59,883 DEBUG (java.sql.Connection:27) - {conn-100008} Connection
2008-07-07 15:04:59,883 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27) - Cache 'user.user-cache': cache miss
2008-07-07 15:04:59,883 DEBUG (java.sql.Connection:27) - {conn-100008} Preparing Statement:
    SELECT id, name, password, address_code_id, description, reg_date, gender, has_house FROM USERS   WHERE id = ?  
2008-07-07 15:04:59,883 DEBUG (java.sql.PreparedStatement:27) - {pstm-100009} Executing Statement:
    SELECT id, name, password, address_code_id, description, reg_date, gender, has_house FROM USERS   WHERE id = ?  
2008-07-07 15:04:59,883 DEBUG (java.sql.PreparedStatement:27) - {pstm-100009} Parameters: [11]
2008-07-07 15:04:59,883 DEBUG (java.sql.PreparedStatement:27) - {pstm-100009} Types: [java.lang.String]
2008-07-07 15:04:59,893 DEBUG (java.sql.ResultSet:27) - {rset-100010} ResultSet
2008-07-07 15:04:59,893 DEBUG (java.sql.ResultSet:27) - {rset-100010} Header:
 [id, name, password, address_code_id, description, reg_date, gender, has_house]
2008-07-07 15:04:59,893 DEBUG (java.sql.ResultSet:27) - {rset-100010} Result: [11, 11, 11, seoul, 11, 1970-01-01 10:38:00.0, F, true]
2008-07-07 15:04:59,893 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27)
 - Cache 'user.user-cache': stored object 'jcf.showcase.user.model.User@2fb002'
tearDown end
setUp start
2008-07-07 15:04:59,963 DEBUG (java.sql.Connection:27) - {conn-100011} Connection
User id: 10
2008-07-07 15:04:59,963 DEBUG (java.sql.Connection:27) - {conn-100011} Preparing Statement:
    INSERT INTO   USERS( id, name, password, address_code_id, description, reg_date, gender, has_house)   VALUES(?, ?, ?, ?, ?, ?, ?, ? )  
User id: 20
User id: 30
User id: 40
User id: 50
User id: 60
User id: 70
User id: 80
User id: 90
2008-07-07 15:04:59,993 DEBUG (java.sql.Connection:27) - {conn-100013} Connection
2008-07-07 15:04:59,993 DEBUG (com.ibatis.sqlmap.engine.cache.CacheModel:27)
 - Cache 'user.user-cache': retrieved object 'jcf.showcase.user.model.User@2fb002'
tearDown end