본문 바로가기
DB

Oracle에서 iBatis batch 사용 - 대용량 업데이트 시

by 수앙 2013. 1. 2.

대용량 업데이트 시 속도 향상을 위해 batch를 사용하여 insert/update를 한다.

ojdbc.jar에 있는 ORACLE 전용 batch를 사용하면 더 빠른 수행결과를 보여주나

iBatis에서는 Java에 있는 batch 방법만을 사용할 수 있다.

 

하지만 대용량의 데이터를 한번에 executeBatch() 해준다면 서버에 메모리를 많이 사용하게 되어 메모리 부족현상이 발생할 수 있다.

그래서 5~30 (Oracle에서 제시한 배치 size라고 함)를 한 묶음으로 배치 처리해주는 것이 효율적이다.

임시테이블에 컬럼 2개를 생성하여 10만건을 insert/update를 해본 결과

배치 크기를 30으로 줬을 땐 약 5.5초가 걸렸고

배치 크기를 1000으로 줬을 땐 약 4초가 걸렸다.

참고로 배치 사용 안하면 약 30초가 걸렸다.

 

※ 배치 사용할 시 테이블에 인덱스를 사용안하면 일반 업데이트와 큰 차이가 없다.

IbatisTest.java

import java.io.IOException;
import java.io.Reader;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import org.springframework.util.StopWatch;
import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;

public class IbatisTest {

    private static final Logger log = Logger.getLogger(IbatisTest.class);
    private static SqlMapClient sqlMapper;
    private static StopWatch stopWatch = new StopWatch("IbatisTest");
    
    static {
        try {
            Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
            sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
            reader.close();
        } catch (IOException e) {
            throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
        }
    }
    
    public static void updateTest() throws SQLException {
        stopWatch.start("updateTest");
        
        sqlMapper.startTransaction();
        sqlMapper.startBatch();
        
        Map<String, Object> params = new HashMap<String, Object>();
        for (int i = 1; i <= 100000; i++) {
            params.put("seq", i);
            params.put("content", "HELLO");
            sqlMapper.update("updateTest", params);
            if (i % 30 == 0) {
                sqlMapper.executeBatch();
                sqlMapper.startBatch();
            }
        }
        
        sqlMapper.executeBatch();
        sqlMapper.commitTransaction();
        sqlMapper.endTransaction();
        stopWatch.stop();
        
        log.info(stopWatch.prettyPrint());
    }
    
    public static void main(String[] args) throws Exception {
        updateTest();
    }
    
}

 

IbatisTest.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="ibatisTest">

    <update id="updateTest" parameterClass="map">
        MERGE INTO TEST
        USING DUAL
        ON (
            SEQ = #seq#
        )
        WHEN MATCHED THEN
        UPDATE SET CONTENT = #content# || #seq#
        WHEN NOT MATCHED THEN
        INSERT (SEQ, CONTENT)
        VALUES (#seq#, #content#) 
    </update>
    
</sqlMap>

 

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>

    <settings
        cacheModelsEnabled="true"
        enhancementEnabled="true"
        lazyLoadingEnabled="true"
        useStatementNamespaces="false"
    />

    <transactionManager type="JDBC">
        <dataSource type="DBCP">
            <property name="JDBC.Driver" value="oracle.jdbc.driver.OracleDriver" />
            <property name="JDBC.ConnectionURL" value="jdbc:oracle:thin:@localhost:1521:orcl" />
            <property name="JDBC.Username" value="hr" />
            <property name="JDBC.Password" value="hr" />
            <property name="Pool.MaximumActiveConnections" value="10" />
            <property name="Pool.MaximumIdleConnections" value="5" />
            <property name="Pool.MaximumWait" value="60000" />
            <property name="Pool.ValidationQuery" value="SELECT 1 FROM DUAL" />
            <property name="Pool.LogAbandoned" value="false" />
            <property name="Pool.RemoveAbandoned" value="false" />
            <property name="Pool.RemoveAbandonedTimeout" value="50000" />
            <property name="Driver.DriverSpecificProperty" value="SomeValue" />
        </dataSource>
    </transactionManager>
    
    <sqlMap resource="ibatis/IbatisTest.xml" />

</sqlMapConfig>

 

log4j.properties

log4j.rootLogger=INFO, stdout

log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss} - %m%n

 

사용된 라이브러리

cglib-nodep-2.2.3.jar
commons-collections-3.2.1.jar
commons-dbcp-1.4.jar
commons-logging-1.1.1.jar
commons-pool-1.6.jar
ibatis-2.3.4.726.jar
log4j-1.2.17.jar
ojdbc6.jar
oscache-2.4.1.jar
spring.jar

 

순수 Java 코딩이라면 OracleConnection과 OraclePrepareStatement를 사용하는 batch 방법을 추천한다. 이 방식이 더 빠르다.

 

끝.

'DB' 카테고리의 다른 글

Centos 7 기준 redis 5.0.3 tar server 실행  (0) 2018.12.31
디비툴 DBeaver  (0) 2016.06.25
MySQL 5.1 사용자(계정) 추가  (0) 2012.02.04
SQL로 카테고리, 트리, 계층 구조의 구현  (0) 2011.10.18
Apache Derby (더비)  (0) 2011.09.11

댓글