[Spring] MyBatis Batch + Transaction 을 이용한 대용량 SQL작업


마을에서 오크잡는 퀘스트 하고 있는데 갑자기


중간보스를 잡아오라는 퀘스트가 떨어졌다...



일정시간마다 라즈베리파이에서 받아온 원시데이터를 재가공하여


DB에 insert 해주어야 하는 작업


로우수가 적다면 그냥 만들겠지만 대용량 작업일 경우 답이 안나온다.


약 1만건~10만건 정도의 데이터를 날려줘야 하는데 ㅂㄷㅂㄷㅂㄷ


그래서 찾아본 방법은 Batch와 Transaction 을 이용한 대용량 sql 작업



나도 정확히 내가 뭘 한건지도 모르고 그냥 스택오버 플로우, 오키, 전자정부 뒤적거리면서 이것 저것 다 때려 박느라

필요 없는 설정이 있을 수도 있으니 아는 사람은 댓글좀 달아주시길 바랍니다.



1.  XML 설정


1) mapper 설정

context-mapper.xml 파일이나 context-sqlMap.xml 파일에


마이바티스 연동을 위해 만들어놨던 설정을 아래와 같이 바꿔준다.


9번 라인의 batch설정으로 batch 사용이 가능 하도록 하는 듯 하다.


1
2
3
4
5
6
7
8
9
10
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:/egovframework/sqlmap/example/sql-mapper-config.xml" />
        <property name="mapperLocations" value="classpath:/egovframework/sqlmap/example/mappers/mssql/*.xml" />
    </bean>
 
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
        <constructor-arg index="0" ref="sqlSession" />      
        <constructor-arg index="1" value="BATCH" />
    </bean>
cs



2) datasource 설정

DB접속 정보 작성하는 곳에 트렌젝션메니저 설정을 하는데 이걸 해야 하는 건진 잘 모르겠다.


1
2
3
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
cs



3) dispatcher-servlet 설정


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:task="http://www.springframework.org/schema/task" 
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
                   http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                http://www.springframework.org/schema/context 
                http://www.springframework.org/schema/context/spring-context-4.0.xsd
                http://www.springframework.org/schema/task
                http://www.springframework.org/schema/task/spring-task.xsd
                http://www.springframework.org/schema/mvc 
                http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
 
 
<tx:annotation-driven proxy-target-class="true"/>
cs


7번 라인과 17번 라인을 추가해주고 20번라인의 내용을 넣어 주도록 한다.



4) pom.xml


1
2
3
4
5
6
7
8
<!-- 트랜젝션 처리를 위함 -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
cs



디펜던시에 추가를 해줘야 트랜젝션 처리가 가능한건진 모르겠음. 전자정부 자체에 관련된게 이미 추가 되어 있을 수도 있고

정확히 모르겠음 저건



2. DAO or impl 작성


나는 DAO를 사용하지 않고 공통DAO하나 만들어 놓고 impl에서 바로 쿼리를 날려주는 방식을

좋아 한다.


어차피 DAO에서 특별히 해줄것도 없고.. 해줘야 하는것이 있어도 impl에서 해주면 되니까



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
    public void updateSchedulerHistoryRow(List<MinHistoryVO> historyList) {
        // TODO Auto-generated method stub
        
       // 트렌젝션 시작
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        long startTime = System.currentTimeMillis();
            try {
                
                for (MinHistoryVO list : historyList) {                     
                    sqlSession.update("scheduler.updateSchedulerHistoryRow", list);
                }
 
            } finally {
                sqlSession.flushStatements();
                sqlSession.close();
            }
 
        long endTime = System.currentTimeMillis();
        long resutTime = endTime - startTime;
        System.out.println("트랜젝션 배치" + " 소요시간  : " + resutTime/1000 + "(ms)");
    }
cs



난 이런식으로 작성 했다.


가끔 6번 라인에 ExecutorType.BATCH 이 매개변수를 안넣고 시작 할 수 있는데

그러면 트렌젝션 안돌고 커넥션 다 찍으면서 돌게 된다.


저렇게 해도 수만건이 돌게 되면 세션에저장될 데이터들이 넘쳐흘러서 그런지 버벅거릴때가 있는데


그럴땐 컨트롤러에서 조금씩 끊어서 날려 주도록 하자



3. controller


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
        //컨트롤러에서 일정 개수 단위로 끊어서 날려 준다.
        int insertCount = 0;
        List<MinHistoryVO> divHisList  = new ArrayList<MinHistoryVO>();
        for(int hisCount = 0, hisSize = historyList.size(); hisCount < hisSize; hisCount++){
            MinHistoryVO _tempData = new MinHistoryVO();
            _tempData = historyList.get(hisCount);
            divHisList.add(_tempData);
            if(insertCount == 1000 || hisSize-1 == hisCount){
                schedulerService.updateSchedulerHistoryRow(divHisList); // 트렌젝션
                divHisList =  new ArrayList<MinHistoryVO>();
                insertCount = 0;
            }
            else{                    
                insertCount++;
            }
        }
cs



while문 사용하는게 익숙하지 않아서 나는 for문을 주로 사용 한다.

리스트에 잔뜩 있는 데이터들을 새로운 작은 바구니에 담아서 끊어서 날려준다.








이렇게 하면 MsSql Server 2005버전 기준 1만건 insert하는데 5초정도 걸린다.


저기에  마이바티스 foreach까지 써서 벌크인서트 하면 시간은 더 단축 된다.


원시데이터를 쪼개서 60개컬럼에 따로 박아야 하기 때문에


넘겨주는 파라미터 개수 2100개 제한이 있어서 제대로 사용 못했는데


여러개로 쪼갠다음에  Mybatis foreach 돌려서 한번에 날려주면 시간은


훨씬 더더더더더 단축 된다.





포켓몬고 업데이트 2세대 추가 및 신규 아이템 추가



나이안틱에서 포켓몬고에 80마리 이상의 포켓몬을 추가할 예정이라고 16일 밝혔다고 한다.


성도지방의 포켓몬이 추가 되고 귀엽게 생긴 치코리타 브케인 리아코가 포함된다고 했다.


그리고 포켓몬의 움직임을 늦추는 나나열매와 포켓몬을 잡았을때 주는 사탕을 두배로 늘려주는


파인열매가 추가 되고


트레이너의 복장과 악세사리도 추가 된다고 하니


재미가 시들해질 한국인들에겐 나이스 타이밍의 업데이트라고 생각된다.


외국에서야 벌써 1년가까이 되었지만 한국에선 오픈한지 이제 한달 정도인데


한국인의 게임컨텐츠 소비속도는 어마어마 하니까


어쩌면 이런 한국인의 특성을 알고 한국서버를 늦게 열어준 나이안틱의 그림이 아닐까 라고 상상해 본다 ㅋ


업데이트 일정은 빠르면 이번주말이라고 하는데 너무 믿고 있진 말자


그리고 대부분의 업데이트와 이벤트는 태평양시간 기준이라고 한다.


추가적으로


16일부터 포켓몬고의 인벤토리 확장 아이템을 50% 세일한 가격에 판다고 한다.


아마 특정 기간까지의 이벤트일듯




추가적으로 나이안틱의 CEO 존행크가 인터뷰를 통해 조만간 유저간 트레이드와 대결모드를 추가할 예정이라고


말한것을 보니 이번 업데이트엔 트레이드와 대결모드는 없고 다음으로 미뤄지는듯 하다.




2017/02/01 - [Yame Game Life/Poketmon Go] - [포켓몬고] 개체값, 등급 과연 중요한가? 개체값과 스킬 뭐가 더 중요할까요

2017/01/25 - [Yame Game Life/Poketmon Go] - [포켓몬고] 체육관 gym 점령 방법

2017/01/24 - [Yame Game Life/Poketmon Go] - [포켓몬고] 포켓몬 위치 확인 (포켓모아 포켓비전) 인증방법

2017/01/24 - [Yame Game Life/Poketmon Go] - [포켓몬고] 우리동네 포케스탑 체육관 위치 확인 방법

2017/01/24 - [Yame Game Life/Poketmon Go] - [포켓몬고] GPS 오류 에러 해결 방법 gps signal not found , gps failed to detect location

2017/01/24 - [Yame Game Life/Poketmon Go] - [포켓몬고] 개체값 바로 알수 있는 방법 , 어플 포닥

2017/01/24 - [Yame Game Life/Poketmon Go] - [포켓몬 고] 설치 오류 400 해결 방법

2017/01/24 - [Yame Game Life/Poketmon Go] - [포켓몬 고] 포켓몬고 한국 서비스 시작

2016/08/01 - [Yame Game Life/Poketmon Go] - [여행] 포켓몬 GO! 속초마을 하드코어 24시간 여행기 -2부 :부화,진화-

2016/08/01 - [Yame Game Life/Poketmon Go] - [여행,게임] 포켓몬 GO! 속초마을 하드코어 24시간 여행기 -1부 :소소한 팁-


Oracle MERGE INTO를 활용해 값이 있는지 확인후 insert , update , delete ,selete 하는 방법 


이전에 포스팅 했던 MSSQL의 EXISTS 기능을 오라클에서 사용하는 방법이다


2016/08/12 - [Yame Programmer/SQL] - [MSSQL] 값이 존재하는지 확인 후 update insert [EXISTS] 활용



MSSQL을 사용했던 프로젝트를 Oracle로 마이그레이션 하게 되면서 다시 사용하게 되었다.


입사 초기에 Oracle를 사용하다 이후 쭉 MSSQL만 써서 기억이 가물가물 했다ㅋㅋ



1. 기본 포맷


1
2
3
4
5
6
MERGE INTO 테이블명  USING ( 서브쿼리 ) 
ON  ( 조건 )
WHEN MATCHED THEN 
조건에 값이 있는 경우 실행할 쿼리 
WHEN NOT MATCHED THEN 
조건에 값이 없는 경우 실행할 쿼리 
cs



2. 간단한 예제


1) 서브쿼리 없이 사용


1
2
3
4
5
6
7
8
9
10
11
12
13
MERGE INTO 
    TABLENAME  
USING DUAL 
    ON (PK_CD = 'PKCD001'  AND DATA_NAME = 'dataName')
    WHEN MATCHED THEN     
        UPDATE SET     
                DATA_COL1 = '업데이트 내용 1',             
                DATA_COL2 = '업데이트 내용 2',             
                DATA_COL3 = '업데이트 내용 3',         
        
    WHEN NOT MATCHED THEN
        INSERT (PK_CD, DATA_NAME,  DATA_COL1, DATA_COL2, DATA_COL3)
        VALUES('PKCD001''dataName''넣을 내용1''넣을 내용3''넣을 내용3')
cs




2) 서브쿼리 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
MERGE INTO 
    TABLE_NAME  TARGETTB
USING(
    SELECT 
        CHECKTB.PK_CD
        ,CHECKTB.DATA_NAME
    FROM
        TABLE_NAME CHECKTB
     LEFT JOIN
        JOIN_TB JT
    ON
        JT.DATA = CHECKTB.DATA 
        ) CHECK
 
    ON (TARGETTB.PK_CD = CHECK.PK_CD    AND   TARGETTB.DATA_NAME = CHECK.DATA_NAME)
    WHEN MATCHED THEN     
        UPDATE SET     
                DATA_COL1 = '업데이트 내용 1',             
                DATA_COL2 = '업데이트 내용 2',             
                DATA_COL3 = '업데이트 내용 3',         
        
    WHEN NOT MATCHED THEN
        INSERT (PK_CD, DATA_NAME,  DATA_COL1, DATA_COL2, DATA_COL3)
        VALUES('PKCD001''dataName''넣을 내용1''넣을 내용3''넣을 내용3')
cs

꼭 INSERT UPDATE만 넣을 수 있는게 아니라 SELECT DELETE를 넣어서 사용 할 수도 있다.




Mssql 에서는 IF EXISTS 함수를 사용해 값이 존재하는지 확인 후


값이 있을때와 없을때 다른 쿼리를 사용 할 수 있다.


oracle에서는 MERGE 함수를 사용해 유사한 기능을 구현 할 수 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
 MERGE INTO 
     TBL_NAME -- table명
USING DUAL 
    ON (column1 = '존재하는지 확인할 값')
WHEN MATCHED THEN -- 존재하는 경우 실행   
    UPDATE SET  
        column2= 'update할 값1',
        column3= 'update할 값2'
WHEN NOT MATCHED THEN -- 존재하지 않는 경우 실행
    INSERT 
        (column1, column2, column3)
    VALUES
        ('존재하는지 확인할 값','insert할 값1''insert할 값2')
cs



위와 같은 쿼리문을 작성하면 된다


더 간단하게 나타내자면


1
2
3
4
5
6
7
8
 MERGE INTO 
     TBL_NAME -- table명
USING DUAL 
    ON (column1 = '존재하는지 확인할 값')
WHEN MATCHED THEN 
    -- 값이 존재하 경우 실행할 쿼리 
WHEN NOT MATCHED THEN 
    -- 값이 존재하지 않을 경우 실행할 쿼리
cs



이렇게 나타낼 수 도 있으며


좀더 응용하자면


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
merge into target_table tt
    using 
        ( -- 조인하는 테이블
            select
                te.te_cd tt_cd
                ,te.description tt_disp
                ,te.jt_no jt_no
                ,decode(te.is_flag, 'normal''Y''N') is_flag
            from
                temp_table te
            left join join_table jt
                on te.te_no=jt.jt_no
            where
                1=1
        ) so
    on (tt.tt_cd=so.tt_cd and tt.jt_no=so.jt_no) -- 조인한 테이블의 값 
     
    when matched then
        update set
            tt.is_flag=so.is_flag
            ,tt.tt_disp=so.tt_disp
 
 
    when not matched then
            insert (tt_no, tt_cd, tt_disp, jt_no, is_flag, reg_date)
            values(
                sq_target_table.nextval
                , so.tt_cd
                , so.tt_disp
                , so.jt_no
                , so.is_flag
                , sysdate
            );
cs

(코드 출처 :  http://yangtaeho.tistory.com/entry/%EC%98%A4%EB%9D%BC%ED%81%B4-MERGE-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-update-insert-%EB%8F%99%EC%8B%9C%EC%97%90  


http://zinlee.tistory.com/185 에서 더 자세한 내용을 확인할 수 있습니다.

 )



이렇게 사용 할 수도 있다. 단순히 한 테이블에 대해서만 값이 존재하는지 확인 하는 것이 아니라


조인했을때의 값도 활용 할 수가 있다.

테이블의 값을 변경하기 전에 해당 인덱스의 값이 있는지 확인 하고


insert하거나 update 하는 방법입니다.


ms sql의 exists를 활용하여 아래와 같은 쿼리문을 작성 할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
IF EXISTS(
               해당 데이터가 존재 하는지 셀렉트
         )
    BEGIN
        --데이터가 있는 경우 실행할 쿼리
    END
    
    ELSE
        BEGIN
        --데이터가 없는 경우 실행할 쿼리
       END
cs

EXISTS 안에 확인할 데이터를 셀렉트 하는 쿼리를 넣고


데이터가 있는경우와 없는경우 실행할 쿼리를 작성 하면 됩니다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
IF EXISTS(
                SELECT CODE_ID FROM tbl
                WHERE CODE_ID = 'code_001'
         )
    BEGIN
        SELECT 99 cnt
    END
    
    ELSE
        BEGIN
        insert into tbl
            (
            DEVICE_ID
              ,CODE_ID
              ,CODE_NAME
            )
        values
            (
              ,'device_001'
              ,'code_001'
              ,''
            )
       END
cs



위의 코드는 code_id라는 컬럼에 code_001이라는 데이터가 있는지 확인하고


있다면 99라는 숫자를 반환하고


없다면 데이터를 입력하게 하는 쿼리 입니다.



insert가 아닌 update도 사용 가능하며 일반 select문도 가능합니다.



값을 입력할때 있는지 확인하기 위해서 컨트롤러에서 DOA까지 두번이나 왕복할 필요가 없겠죠


제 포스팅이 도움이 되었나요? 

그렇다면 공감하기 한번 눌러주세요 블로거에게 큰 힘이 됩니다


+ Recent posts