Sqlsession Batch

자이로 체펠리·2021년 11월 20일
0

서론

이번에 진행하는 프로젝트에서 한번에 1억개의 rows를 insert한다.
데이터는 대신증권 cybos api를 통해 각 종목별 모들 history_data를 요청하고 이를 dto로 가공하여 db에 insert하는 방식인데 history_data를 요청하고 응답 받는데만 너무 많은 시간이 걸렸고 이를 dto로 가공하여 하나씩 insert했을 때 1시간이 넘개 걸려도 작업을 완료하지 못했다.

<update id = 'insertItem' parameterType = 'ItemDto'>
		MERGE INTO ITEM
		USING DUAL
			ON(id = #{id})
		WHEN MATCHED THEN
			UPDATE SET
				NAME = #{name}
				,IS_ACTIVE = #{isActive}
				,MARKET = #{market}
				,INDUSTRY = #{industry}
				,CORP_SIZE = #{corpSize}
		WHEN NOT MATCHED THEN
			INSERT(
				ID,
				NAME,
				IS_ACTIVE,
				CURRENCY_ID,
				MARKET,
				CATEGORY,
				INDUSTRY,
				CORP_SIZE,
				LISTING_DATE
			)
			VALUES(
				#{id},
				#{name},
				#{isActive},
				#{currencyId},
				#{market},
				#{category},
				#{industry},
				#{corpSize},
				#{lis![](https://velog.velcdn.com/images%2Fkiki3700%2Fpost%2F2306180d-7895-45bf-9310-27941bf1c83c%2Fimage.png)tingDate}
				)
	</update>

매번 session을 열고 닫는데 cost를 무시할 수 없었다. 그러다 생각한게 동적 쿼리를 사용해 rows를 insert하는 방법을 구상해봤다.

결과를 먼저 살펴보면 확실해 빨랐다. 그럼에도 불구하고 1시간이 넘게 걸렸다. 또한 list의 크기에도 속도에 영향을 받았다.

또한 foreach문을 작성하는데에 크기에 제한이 있었다.

<insert id = 'initHistoryDataDtoList' parameterType = 'java.util.List'>
		<foreach  collection="list" item="item" open="INSERT ALL" close="select * from dual">
				INTO HISTORY_DATA(
					id,
					trading_date,
					item_id,
					open,
					close,
					low,
					high,
					volume
					)
				VALUES
					(
					TO_CHAR(#{item.tradingDate}, 'YYYYMMDD')||#{item.itemId},
					#{item.tradingDate},
					#{item.itemId},
					#{item.open},
					#{item.close},
					#{item.low},
					#{item.high},
					#{item.volume}
				)
       </foreach>
	</insert>

서칭 끝에 알아 낸 방법은 batch를 사용해 insert하는 것이다.
sqlsessionFactory bean에 직접 설정하는 방법도 있지만 sqlSession을 생성할때 Executor.Batch를 파라미터로 넣어 배치를 실행했다.

dataAccess.config

package com.example.demo.config;

import javax.sql.DataSource;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

@Configuration
@MapperScan(basePackages = "com.example.demo")
public class DataAccessConfig {
	@Autowired
	ApplicationContext applicationContext;
	@Bean(name = "sqlSessionFactory")
	public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception{
		SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
		sessionFactory.setDataSource(dataSource);
		sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
		sessionFactory.setVfs(SpringBootVFS.class);
		sessionFactory.setTypeAliasesPackage("com.example.demo.vo");
		org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
		configuration.setMapUnderscoreToCamelCase(true);
		
		configuration.setJdbcTypeForNull(null);
		sessionFactory.setConfiguration(configuration);
		return sessionFactory.getObject();
	}
	@Bean(name ="sqlSessionTemplate")
	public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
		return new SqlSessionTemplate(sqlSessionFactory);
	}

BatchDao.class

public void initHistoryDataDtoList(List<HistoryDataDto> historyDtoList) {
		SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
		try {
			for( HistoryDataDto historyDataDto : historyDtoList) {
				sqlSession.insert("com.example.demo.data.mapper.ItemMapper.insertHistoryDataDtoBatch",historyDataDto);
			}
		}finally {
			sqlSession.flushStatements();
			sqlSession.close();
			sqlSession.clearCache();
		}
     }

service에서 5000개씩 짤라 dao의 매서드를 호출한다.
foreach문을 사용하는 mapper도 작성해 보았지만 그렇게 효율적이지 못했다.

그럼에도 불구하고 45분이나 걸렸다.

profile
"경의를 표해라. 경의를 갖고 회전의 다음 단계로 나아가는 거다…… [LESSON 4] 다."

0개의 댓글