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의 개수를 줄이면 제한에 도달하지 않을 수 있다.
하지만 이 방법을 쿼리를 직접 수정해야 하므로 문제점이 많이 발생할 수 있다.
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으로 다 할 수 있다고 생각했는데 데이터베이스의 상황에 따라 달라질 수 있다는걸 배웠다.