Mybatis Parameter 개수 제한 문제

ddindo·2022년 11월 24일
0
post-thumbnail

환경

Spring-boot 2.2.9
Mybatis 2.1.3
Postgresql 14

문제점

Mybatis에서 쿼리에 데이터를 넣을 때 #$를 사용하는 것은 모두가 알 것이다.

$은 입력받은 데이터를 그대로 쿼리에 집어넣기 때문에 SQL Injection같은 문제에 취약하고 Parameter로 사용되지 않기 때문에 속도도 느려지게 된다.
#의 경우 데이터를 'Data'와 같은 형태로 묶어서 쿼리에 집어넣어 주고 또한 Parameter로 사용되어 PrepareStatement로 활용 가능하게 만들어 준다.

위와 같은 이유로 데이터를 넣을 때 #을 사용하여 parameter로 제공을 했다. 하지만, Bulk insert로 변경하며 한 번에 많은 양의 데이터를 쿼리에 집어 넣다 보니

Caused by: java.io.IOException: Tried to send an out-of-range integer as a 2-byte value: 70663
       at org.postgresql.core.PGStream.sendInteger2(PGStream.java:211) ~[postgresql-9.4.1212.jar:9.4.1212]
       at org.postgresql.core.v3.QueryExecutorImpl.sendParse(QueryExecutorImpl.java:1409) ~[postgresql-9.4.1212.jar:9.4.1212]
       at org.postgresql.core.v3.QueryExecutorImpl.sendOneQuery(QueryExecutorImpl.java:1729) ~[postgresql-9.4.1212.jar:9.4.1212]
       at org.postgresql.core.v3.QueryExecutorImpl.sendQuery(QueryExecutorImpl.java:1294) ~[postgresql-9.4.1212.jar:9.4.1212]
       at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:280) ~[postgresql-9.4.1212.jar:9.4.1212]
       ... 113 common frames omitted

이러한 유형의 에러를 접할 수 있었다.

문제 원인

해당 문제의 원인은 Postgresql JDBC에서 PrepareStatement의 Parameter 개수의 upper limit이 존재한 다는 것이다.
바로 Java의 short 크기 만큼(32,767) 전달할 수 있는 것이다.

그래서 Mybatis의 #을 사용한 데이터의 개수가 위의 크기를 초과할 때 이런 에러가 발생하게 된것이다.

해결법

검색을 통해 총 2가지의 해결 방법을 찾을 수 있었다.

쿼리 수정

기존에 작성한 쿼리는 #을 사용하여 Parameter로 전달하였다. 그러므로 애초에 쿼리에서 전달하는 Parameter의 개수를 줄이면 제한에 도달하지 않을 수 있다.

하지만 이 방법을 쿼리를 직접 수정해야 하므로 문제점이 많이 발생할 수 있다.

Chunking

Bulk insert로 인해 많은 양의 데이터를 한 번에 삽입하려고 하니 에러가 발생하게 된것이다. 그러니 애초에 mapper를 통해 데이터를 전달할 때 모든 값을 전달하는 것이 아니라 일정한 사이즈로 쪼개어 여러번 요청하면 초과하지 않을 수 있다.

하지만 이 방법 또한 데이터베이스의 상태에 따라 원할하게 동작하지 않을 수 있다. 그래도 대부분의 문제점은 이 방법을 통해 해결할 수 있다.

public class TestService(){

	@Autowired
    private TestMapper mapper;
    
    // application.yml에 미리 크기를 할당해 가져옴
    @Value("${ChunkSize}")
    private int chunkSize;
    
    pbulic void insertTest(List<String> insertData){
    	int insertDataSize = insertData.size();
		for(int idx = 0; idx < insertDataSize; idx += chunkSize){
			mapper.insertGroupModelingData(insertData.subList(idx, Math.min(idx + chunkSize, insertDataSize)));
		}
    }
    
}

후기

나의 경우 제공된 리스트를 쪼개 mapper에 넣는 Chunking 방법을 주로 사용하였다. for문을 돌며 미리 설정한 사이즈별로 데이터를 삽입하면 되기 때문이다. 그리고 여기에 Batch 방식으로 작업을 진행하면 훨씬 빠르게 동작할 수 있었다.
그런데 특정 데이터베이스에서 구조가 하나의 row에 array로 여러 데이터가 들어가는 경우가 있었다. 파싱해서 사용하는 데이터라 한 번에 들어가 있는 거 같았는데, 이럴 경우 데이터를 쪼개어 나누기가 어렵다고 판단했다. 그래서 결국 쿼리를 수정하는 방향으로 진행했다. Chunking으로 다 할 수 있다고 생각했는데 데이터베이스의 상황에 따라 달라질 수 있다는걸 배웠다.

Reference

https://luppeng.wordpress.com/2020/05/20/postgresql-jdbc-driver-upper-limit-on-parameters-in-preparedstatement/

0개의 댓글