mysql mybatis 다중 insert 처리 방법
MySQL MyBatis foreach를 사용한 다중 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 하는 방법이 비효율적인 이유를 아래 로그를 통해 살펴보겠습니다.
위 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' 과정이 한 번만 발생한다는 것을 확인할 수 있습니다.
이상으로 mysql mybatis 환경에서 foreach를 사용한 다중 insert 방식에 대한 내용입니다.
잘못된 부분이나 궁금한 점은 댓글 남겨주시면 확인하겠습니다. 감사합니다.