Spring과 MyBatis연동시 배치를 처리할 경우가 있다. 한꺼번에 인서트나 업데이트가 필요한 경우있다. 이 때 SqlSession을 반복적으로 처리하는 방법과 xml에서 foreach를 처리하는 방법이 있다.

먼저 DB는 Mariadb(MySql)을 기준으로 설명한다. test_book_origin 테이블이 있다. 이 테이블에는 originPrice 라는 컬럼이 있고, 각각의 값들이 있다. originPrice 값을 일괄적으로 0으로 변경해주는 배치를 만들어본다. 물론 쿼리에서 한번에 할 수 있으나, 여기선 배치를 해보기위해 테스트한다.

1.SqlSession에서 배치처리

applicationContext.xml에 다음을 추가한다.

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
	<constructor-arg index="0" ref="sqlSessionFactory" />  	
	<constructor-arg index="1" value="BATCH" />
</bean>

그리고 mybatis용 SQL처리 xml에는 아래와 같이 일반적으로 작성한다.

<update id="updateBatch1" parameterType="TestBook">
	UPDATE test_book SET originPrice = 0  WHERE bookID = #{bookID}
</update>

그리고 나서 DAO 클래스에서는 다음과 같이 처리한다.

@Override
public void updateBatch1(List<TestBook> list) {
	for (TestBook testBook : list) {
		sqlSession.update("TestBook.updateBatch1", testBook);
	}
}

TestBook은 test_book_origin의 엔티티 클래스이다.
Controller클래스에서 executeUpdateBatch를 만들었는데 test_book_origin 테이블의 레코드를 읽여들여서 originPrice를 0으로 업데이트하는 메소드이다. 여기서는 batchType=1(SqlSession방식)로 실행된다.

/**
 * 배치업데이트
 * @param batchType
 */
public void executeUpdateBatch(String batchType) {
	List<TestBook> list = null;
	Map<String, Object> map = null;
	int start = 0;
	final int LIMIT = 500;
	Map<String, Object> paramMap = null;

	try {
		while (true) {
			map = new HashMap<String, Object>();
			map.put("start", start);
			map.put("limit", LIMIT);
			list = bookSerive.selectListTestBook(map);
			start += list.size();

			//더이상 가져올 것이 없다면 빠져나간다.
			if (list == null || list.size() == 0) {
				System.out.println("All finished");
				break;
			}
			//sqlSession 배치라면
			if (batchType.equals("1")) {
				bookSerive.updateBatch1(list);
			}
			//xml의 foreach  배치라면
			else if (batchType.equals("2")) {
				paramMap = new HashMap<String, Object>();
				paramMap.put("list", list);
				bookSerive.updateBatch2(paramMap);
			}				
		} //end while 							

	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		if (list != null) list.clear();
	}		
}

Main클래스에서 호출하는 executeUpdateBatch 메소드이다.

/**
 * 배치처리 시작
 * @param batchType
 */
private void executeUpdateBatch(String batchType) {
	long startTime = System.currentTimeMillis();
	bookController.executeUpdateBatch(batchType);
	long endTime = System.currentTimeMillis();
	long resutTime = endTime - startTime;
	String batchTypeDesc = "";
	if (batchType.equals("1")) {
		batchTypeDesc = "[sqlSession batch]";
	} else {
		batchTypeDesc = "[foreach batch]";
	}
	System.out.println(batchTypeDesc + " 소요시간  : " + resutTime/1000 + "(ms)");
}

실제로 위 메소드를 실행한 소요시간이다. (10만건 업데이트)

[sqlSession batch] 소요시간  : 68(ms)

이것을 이번에는 xml에서 foreach 처리로 바꾸어보자.

2. XML에서 foreach구문으로 배치처리

sql구문처리하는 xml소스를 보자.updateBatch2 메소드가 추가된다.

<update id="updateBatch2" parameterType="map">
	UPDATE test_book SET originPrice = 0 WHERE bookID IN
	<foreach item="testBook" index="index" collection="list" open="(" separator="," close=")">
		#{testBook.bookID}
	</foreach>
</update>

bookID를 IN으로 처리하고 그 부분을 foreach로 처리한다. collection은 list라는 맴변수이고 거기에는 TestBook의 클래스들이 리스트로 담겨져있다. 그러므로 item은 testBook으로 하면 #{testBook.bookID}로 접근해야 한다. DAO클래스를 보자.

@Override
public void updateBatch2(Map<String, Object> map) {
	sqlSession.update("TestBook.updateBatch2", map);
}

DAO클래스는 간단하게 작성되었다.(Serive 클래스도 같다) 중요한 것은 실제로 호출하는 Controller클래스이다.

List<TestBook> list = bookSerive.selectListTestBook(map);
...중략...
Map<String, Object>paramMap = new HashMap<String, Object>();
paramMap.put("list", list);
bookSerive.updateBatch2(paramMap);

즉, Map파라미터에 list라는 값을 넣고 실제로 collection값을 넣어야 한다. 그래서 foreach구문에 collection값에 map에 할당된 변수를 넣어주고, item에 변수를 지정하여 그 값으로 반복처리하는 것이다.

이렇게 해서 실행시켜보면 아래과 같은 속도가 나왔다. (10만건 업데이트)

All finished
[foreach batch] 소요시간  : 7(ms)

무려 9배의 속도가 단축되었다. (어찌보면 당연한다 한번에 쿼리로 일괄처리하는 것이 더 효율적인 것이다)

인서트부분도 같은 10만건에 데이트를 다른 테이블에 인서트하는 테스트를 해보았는데 역시 같은 수준의 속도가 나왔다.

SqlSession 배치 : 75(ms) -> XML에서 Foreach 처리 : 12(ms)

MySQL, MariaDB에서는 대량 부분을 다음과 같이 쿼리로 적용할 수 있다. 즉 VALUES에 해당되는 부분만 반복해서 넣어주면 된다.

INSERT INTO TEST(id, name) VALUES
(1,'KIM1'),
(2,'KIM2'),
(3,'KIM3')

따라서 VALUES 부분만 foreach로 처리해주면 된다.

<insert id="insertBatch2" parameterType="map">
	INSERT INTO test_book_backup(bookID, bookName, originPrice, registDate)
	VALUES
	<foreach item="testBook" index="index" collection="list" open="" separator="," close="">
		(#{testBook.bookID}, #{testBook.bookName}, #{testBook.originPrice}, NOW())
	</foreach>		
</insert>

()부분이 반복되므로 open, close는 비워주고, separator만 “,”로 처리하면 된다.
가급적 배치처리에는 Mybatis의 foreach를 사용하는 것이 훨씬 효율적이다.

위 예제전체소스는 https://github.com/yookeun/mybatis-batch-test 에서 확인할 수 있다.