Programming/Spring

mysql mybatis 다중 insert 처리 방법

Jan92 2024. 1. 14. 00:49

MySQL MyBatis foreach를 사용한 다중 insert 처리 방법

mybatis 다중 insert 처리 방법

해당 포스팅은 mysql + mybatis 환경에서 여러 건의 데이터를 insert 해야 할 때 mybatis에서 지원하는 foreach 문을 활용하여 효율성을 높이는 방법에 대한 내용입니다.

(아래 예시를 통해 살펴보겠지만 자바 내부적으로 for 문을 사용하여 1건씩 insert 처리를 하는 것은 상당히 비효율적인 방식입니다.)

 

* mysql의 경우 아래 예시 방식이 적용되지만 oracle 등 다른 RDBMS의 경우 방식이 다를 수 있습니다.

 


for 문을 통한 방식의 비효율성

public void insertExample1() {	
    List<Map<String, Object>> productList = createProduct(2);
    for (Map<String, Object> productMap : productList) {
        productDao.insert(productMap);
    }
}

(Service 단의 insertExample1 메서드)

 

@Repository
public class ProductDao {
	
    @Autowired
    SqlSessionTemplate sqlSessionTemplate;

    public int insert(Map<String, Object> map) {
        return this.sqlSessionTemplate.insert("productMapper.insert", map);
    }
}

(ProductDao class)

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE  mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="productMapper">
	
    <insert id="insert">
        INSERT INTO PRODUCT
            (name, code, price)
        VALUES
            (#{name}, #{code}, #{price})
    </insert>
   
</mapper>

(ProductMapper.xml)

 

먼저 자바 내부적으로 for 문을 통해 여러 건의 데이터를 1건씩 insert 하는 방법이 비효율적인 이유를 아래 로그를 통해 살펴보겠습니다.

 

insertExample1() method log

위 Service의 insertExample1() 메서드가 호출되었을 때 발생하는 로그는 다음과 같습니다.

(createProduct 메서드는 insert 할 Map 형식의 데이터를 생성해 주는 메서드입니다. 해당 예시에서는 2건의 데이터가 생성되어 insert 되도록 하였습니다.)

 

보시는 것처럼 2건의 데이터에 각각에 대해 insert 쿼리가 실행되며, 각 insert 쿼리에 대해 'Create a new SqlSession' -> 'Closing non transactional SqlSession'가 반복되며 'SqlSession'을 생성하고 닫는 것을 볼 수 있는데요.

 

예시처럼 데이터가 몇 건이 되지 않을 때는 체감할 수 없지만, 엑셀 일괄 등록 등의 기능으로 다량의 데이터를 한 번에 insert 해야 할 때는 이러한 과정으로 인해 비용과 시간이 많이 소모된다는 특징이 있습니다.

 

* 실제 1만 건의 데이터를 해당 방식으로 insert 했을 때 18269ms(약 18초)의 시간이 걸린 반면, 아래서 살펴볼 foreach 방식을 사용했을 때는 1180ms(약 1초)의 시간 밖에 걸리지 않았습니다.

 

 


mybatis에서 지원하는 foreach 문을 활용한 방식

@Override
public void insertExample2() {
    Map<String, Object> requestMap = new HashMap<String, Object>();	
    
    List<Map<String, Object>> productList = createProduct(2);
    requestMap.put("productList", productList);

    productDao.multiInsert(requestMap);
}

(Service 단의 insertExample2 메서드)

 

@Repository
public class ProductDao {
	
    @Autowired
    SqlSessionTemplate sqlSessionTemplate;

    public int multiInsert(Map<String, Object> map) {
        return this.sqlSessionTemplate.insert("productMapper.multiInsert", map);
    }
}

(ProductDao class)

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE  mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="productMapper">

    <insert id="multiInsert" parameterType="java.util.HashMap">
        INSERT INTO PRODUCT
            (name, code, price)
        VALUES
        <foreach item="item" collection="productList" separator=",">
            (#{item.name}, #{item.code}, #{item.price})
        </foreach>
    </insert>

</mapper>

(ProductMapper.xml)

 

이어서 mybatis에서 지원하는 foreach 문을 활용한 방식입니다.

해당 방식의 경우 ProductMapper.xml 파일의 multiInsert 부분을 보시면 아래와 같은 형식으로 쿼리가 실행될 것이라고 예측할 수 있습니다.

 

- collection: 전달받은 인자(= 파라미터 타입으로 넘어온 map 안에 대상 list에 대한 key 값)

(예시에서는 service 단에서 map에 productList라는 키 값으로 대상 list를 넘겨주는 것을 확인할 수 있습니다.)

- item: 전달받은 인자 값을 해당 alias 명으로 대체

- separator: 반복되는 사이에 출력할 문자열

 

INSERT INTO 테이블명 (컬럼1, 컬럼2, ...)
VALUES
('값1','값2',  ...),
('값1','값2',  ...),
...
('값1','값2',  ...);

 

실제 로그에서도 같은 방식의 쿼리가 실행되는 것을 확인할 수 있으며, foreach 문을 활용한 방식의 경우 한 번의 insert로 데이터를 처리하기 때문에 데이터가 몇 건인지 상관없이 'SqlSession'에 대해 'Creating a new SqlSession' -> 'Closing non transactional SqlSession' 과정이 한 번만 발생한다는 것을 확인할 수 있습니다.

 

insertExample2() method log

 

 

 

이상으로 mysql mybatis 환경에서 foreach를 사용한 다중 insert 방식에 대한 내용입니다.

잘못된 부분이나 궁금한 점은 댓글 남겨주시면 확인하겠습니다. 감사합니다.