반응형

이슈

rewriteBatchedStatements=true

고객사에서 위와 같은 mysql 옵션을 추가하자 다음과 같은 오류가 발생하였다.

java.lang.NullPointerException
at com.mysql.cj.ClientPreparedQuery.computeMaxParameterSetSizeAndBatchSize(ClientPreparedQuery.java:66)

 

오류가 발생한 코드는 Batch JDBC 코드를 작성할 때 자주 사용하는 보일러 플레이트 코드로 executeBatch()가 실행될 때 오류가 발생하였다.

Blob blob = ...
try (Connection connection = DBUtil.getInstance().getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(query)) {
        connection.setAutoCommit(false);
        for (TransactionBatch batch : batches) {
            try {                   
                preparedStatement.setString(1, batch.getDeviceID());
                preparedStatement.setBlob(2, blob);
                preparedStatement.addBatch();
            } catch (Exception e) {
               e.printStackTrace();
            }
        }

        preparedStatement.executeBatch();
        blob.free();

 

해결

연구소에서 재현을 위해 rewriteBatchedStatements=true 옵션을 추가했는데 동일한 현상이 발생하지 않았고 추후 확인해보니 고객사와 mysql-connector-java 버전에 차이가 있었다.

연구소는 버전이 5.1.38, 고객사는 8.0.19였다. 버전을 고객사와 동일하게 맞춘 후 동일한 현상이 재현되었다.

구글링을 해보니 setBlob() 또는 setBinaryStream()을 사용할 때 문제가 발생하며 해당 메서드 대신 setBytes()를 사용해야 했다. setBytes()를 사용하면 오류가 발생하지 않는다.

이유

이유를 좀 더 찾아보자.

 

2015년 6월에 스택오버플로우에서 해당 관련 글이 올라왔다. (computeMaxParameterSetSizeAndBatchSize가 PreparedStatement에 있는 것으로 보아 버전이 달라보인다.). 해당 답변에서는 rewrite를 하기 위해 사이즈를 재 계산해야 하지만 input stream에서는 할 수 없다라는 의미처럼 보이지만, 정확히 이해가 되지 않는다.

 

 

2017년 3월에 mysql 커뮤니티에 버그 리포트(#85317)로 해당 관련 글이 올라왔다. mysql 개발자가 해당 글을 당일 확인하고 버그로 판단하고 해결되기까지...........무려 5년이 걸렸다.

 

5년 뒤인 8.0.29 버전에 이슈가 패치되었다고 한다. => 링크

  • When the connection property rewriteBatchedStatements was set to true, inserting a BLOB using a prepared statement and executeBatch() resulted in a NullPointerException. (Bug #85317, Bug #25672958)

setBlob()을 사용한 기존 코드를 유지한 채 8.0.29 버전으로 수행하니 더 이상 오류가 발생하지 않았다.

 

8.0.19와 8.0.29로 코드를 비교해서 어떠한 점이 패치되었는지 판단하려 하였으나, 많은 부분이 변경 되어 확인하기가 어려웠다.

 

8.0.19 버전에서 NPE가 발생한 소스는 다음과 같다.

   @Override
    protected long[] computeMaxParameterSetSizeAndBatchSize(int numBatchedArgs) {
        long sizeOfEntireBatch = 0;
        long maxSizeOfParameterSet = 0;

        for (int i = 0; i < numBatchedArgs; i++) {
            ClientPreparedQueryBindings qBindings = (ClientPreparedQueryBindings) this.batchedArgs.get(i);

            BindValue[] bindValues = qBindings.getBindValues();

            long sizeOfParameterSet = 0;

            for (int j = 0; j < bindValues.length; j++) {
                if (!bindValues[j].isNull()) {

                    if (bindValues[j].isStream()) {
                        long streamLength = bindValues[j].getStreamLength();

                        if (streamLength != -1) {
                            sizeOfParameterSet += streamLength * 2; 
                        } else {
                            int paramLength = qBindings.getBindValues()[j].getByteValue().length; // NPE 발생
                            sizeOfParameterSet += paramLength;
                        }
                        
                        ....

 

getByteValue()를 하는 중 배열에 들어 있는 값이 null이라 발생하지 않았을 까 싶다..

 

 

 

반응형

+ Recent posts