Spring Framework
[iBatis] iBatis에서 batch 기능 활용하기
헤르메스의날개
2011. 1. 20. 14:05
728x90
원문 : http://fantazic.com/page/47
iBatis는 아래와 같은 방법으로 batch 처리가 가능하다. iBatis 내부 코드를 확인해 본 바로는 PreparedStatement.addBatch()를 사용하고 있고, 동일한 쿼리가 반복해서 들어올 때 하나의 batch로 처리해준다.
try { SqlMapClient.startTransaction(); SqlMapClient.startBatch(); while (...) { SqlMapClient.insert(query, params); } SqlMapClient.executeBatch(); SqlMapClient.commitTransaction(); } catch (Exception e) { log.error(e, e); } finally { SqlMapClient.endTransaction(); }
이 기능을 활용해서 BatchManager를 만들어서 사용하고 있는데, 사용자의 로그인 시간을 기록하거나 게시물의 조회수를 늘리는 등 빈번하게 동일한 update가 발생하는 서비스에 사용하면 효과가 있다.
사용법은 기존의 서비스 코드 수정을 최소화하는 방법으로 고안했다. SqlMapClient.insert(query, params)를 BatchManager.insert(query, params)로 수정하면 된다.
관련 코드)
BatchManager.java
public class BatchManager { private static BatchWorker worker = BatchWorker.getInstance(); static { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { worker.flushAll(); worker.stop(); } catch (Exception e) { e.printStackTrace(); } } }); } public static void insert(String query, Object params) { worker.put(new BatchQuery(INSERT, query, params)); } public static void update(String query, Object params) { worker.put(new BatchQuery(UPDATE, query, params)); } }BatchWorker.java
public class BatchWorker { public static final int HEARTBEAT = 1000; public static int MAX_WAIT = 30000; public static final int SIZE_OF_ONE_BATCH = 200; public static final int MAX_SIZE = 100; private long lastTime; private final Timer timer; private Vectorqueue; private static BatchWorker singletonWorker; private BatchWorker() { lastTime = System.currentTimeMillis(); timer = new Timer(true); queue = new Vector (); startWorker(); } public static synchronized BatchWorker getInstance() { if (singletonWorker == null) singletonWorker = new BatchWorker(); return singletonWorker; } private void startWorker() { timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { try { doBatch(); } catch (Exception e) { // ignore e.printStackTrace(); } } }, 0, HEARTBEAT); } private void doBatch() { if (System.currentTimeMillis() - lastTime > MAX_WAIT || queue.size() >= MAX_SIZE) { executeQuery(); lastTime = System.currentTimeMillis(); } } private synchronized void executeQuery() { if (queue.size() == 0) return; try { int cnt = 0; SqlMapClient.startTransaction(); SqlMapClient.startBatch(); while (cnt++ < SIZE_OF_ONE_BATCH) { if (queue.size() == 0) break; BatchQuery batchQuery = queue.remove(0); switch (batchQuery.type) { case INSERT: SqlMapClient.insert(batchQuery.query, batchQuery.params); break; case UPDATE: SqlMapClient.update(batchQuery.query, batchQuery.params); break; default: break; } } SqlMapClient.executeBatch(); SqlMapClient.commitTransaction(); } catch (Exception e) { log.error(e, e); } finally { SqlMapClient.endTransaction(); } } public int size() { return queue.size(); } public void put(BatchQuery query) { queue.add(query); } public void flushAll() { while (queue.size() > 0) executeQuery(); } public void stop() { timer.cancel(); } } BatchQuery.java
public class BatchQuery { public enum QueryType { INSERT, UPDATE } public String query; public Object params; public QueryType type; public BatchQuery(QueryType type, String query, Object params) { this.type = type; this.query = query; this.params = params; } }
참고)
- Oracle10g 환경에서는 batch로 처리할 경우 쿼리 수행은 빨라지나 batch 처리마다 쿼리 파싱이 발생해서 CPU 비용은 증가하는 경우도 보였다.
- 예전에 찾아본 바로는 한번에 만건 이상도 batch 처리가 가능하다고 한다. 환경에 따라 가장 효율적인 batch 크기를 결정해야 한다.
- batch 처리할 경우 수행속도가 빨라지는 장점이 있고 transaction lock이 적게 잡혀 DB 부담을 줄여주는 효과도 있다.
728x90