java.lang.ArrayIndexOutOfBoundsException: 50

1. 개요

잘 기동되던 인터페이스에서, 갑자기 ArrayIndexOutOfBoundsException이 발생했다.

java.lang.ArrayIndexOutOfBoundsException: 50
Caused by: java.lang.ArrayIndexOutOfBoundsException: 50
	at oracle.jdbc.driver.OracleParameterMetaDataParser.getParameterMetaDataSql(OracleParameterMetaDataParser.java:472)
	at oracle.jdbc.driver.OracleParameterMetaData.getParameterMetaData(OracleParameterMetaData.java:69)
	at oracle.jdbc.driver.OraclePreparedStatement.getParameterMetaData(OraclePreparedStatement.java:10545)
	at oracle.jdbc.driver.OraclePreparedStatementWrapper.getParameterMetaData(OraclePreparedStatementWrapper.java:1203)
	at org.apache.commons.dbcp2.DelegatingPreparedStatement.getParameterMetaData(DelegatingPreparedStatement.java:162)
	at org.apache.commons.dbcp2.DelegatingPreparedStatement.getParameterMetaData(DelegatingPreparedStatement.java:162)
	at org.apache.camel.component.sql.SqlProducer$2.doInPreparedStatement(SqlProducer.java:120)
	at org.apache.camel.component.sql.SqlProducer$2.doInPreparedStatement(SqlProducer.java:116)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:633)
	at org.apache.camel.component.sql.SqlProducer.process(SqlProducer.java:116)
	at 

이것저것 테스트해본 결과, prepareStatement사용 시, insert sql의 컬럼이 50개가 넘어가는 경우에 마지막 값을 const로 지정하는 경우 해당 에러가 발생하는 것으로 보였다.


2. 원인을 찾자

일단 const위치를 마지막이 아닌 위치로 변경해 정상 작동하는 것을 확인한 후, 어느 라이브러리의 문제인지 찾기로 했다. 로그 상 익셉션이 발생한 위치가 ojdbc8이었으므로, 먼저 ojdbc만을 이용해 insert하는 테스트케이스를 작성했다.


2.1. DB테이블 생성

create table TARGET
(
    ID       VARCHAR2(40) not null primary key,
    NAME     VARCHAR2(40),
    PASSWORD VARCHAR2(40),
    COL4     VARCHAR2(10),
    COL5     VARCHAR2(10),
    COL6     VARCHAR2(10),
    COL7     VARCHAR2(10),
    COL8     VARCHAR2(10),
    COL9     VARCHAR2(10),
    COL10    VARCHAR2(10),
    COL11    VARCHAR2(10),
    COL12    VARCHAR2(10),
    COL13    VARCHAR2(10),
    COL14    VARCHAR2(10),
    COL15    VARCHAR2(10),
    COL16    VARCHAR2(10),
    COL17    VARCHAR2(10),
    COL18    VARCHAR2(10),
    COL19    VARCHAR2(10),
    COL20    VARCHAR2(10),
    COL21    VARCHAR2(10),
    COL22    VARCHAR2(10),
    COL23    VARCHAR2(10),
    COL24    VARCHAR2(10),
    COL25    VARCHAR2(10),
    COL26    VARCHAR2(10),
    COL27    VARCHAR2(10),
    COL28    VARCHAR2(10),
    COL29    VARCHAR2(10),
    COL30    VARCHAR2(10),
    COL31    VARCHAR2(10),
    COL32    VARCHAR2(10),
    COL33    VARCHAR2(10),
    COL34    VARCHAR2(10),
    COL35    VARCHAR2(10),
    COL36    VARCHAR2(10),
    COL37    VARCHAR2(10),
    COL38    VARCHAR2(10),
    COL39    VARCHAR2(10),
    COL40    VARCHAR2(10),
    COL41    VARCHAR2(10),
    COL42    VARCHAR2(10),
    COL43    VARCHAR2(10),
    COL44    VARCHAR2(10),
    COL45    VARCHAR2(10),
    COL46    VARCHAR2(10),
    COL47    VARCHAR2(10),
    COL48    VARCHAR2(10),
    COL49    VARCHAR2(10),
    COL50    VARCHAR2(10),
    COL51    VARCHAR2(10),
)


2.2. ojdbc테스트케이스

에러케이스와 마찬가지로 51개의 insert sql을 설정해주고, 마지막 컬럼은 const값을 입력했다.

그리고 PrepareStatement의 getParameterMetaData메소드를 호출해주었더니 동일한 ArrayIndexOutOfBoundsException이 발생했다.

public class ArrayIndexOutOfBoundsTest {

  @Test
  public void test() throws SQLException {
    String url = "jdbc:oracle:thin:@localhost:1521:orcl";
    try (Connection conn = DriverManager.getConnection(url, "username", "password")) {
      PreparedStatement ps = conn.prepareStatement("INSERT INTO TARGET ("
          + "ID, NAME, PASSWORD, LOBDATA, NEW_DATE, COL6, COL7, COL8, COL9, COL10, "
          + "COL11, COL12, COL13, COL14, COL15, COL16, COL17, COL18, COL19, COL20, "
          + "COL21, COL22, COL23, COL24, COL25, COL26, COL27, COL28, COL29, COL30, "
          + "COL31, COL32, COL33, COL34, COL35, COL36, COL37, COL38, COL39, COL40, "
          + "COL41, COL42, COL43, COL44, COL45, COL46, COL47, COL48, COL49, COL50, "
          + "COL51) "
          + "VALUES ("
          + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
          + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
          + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
          + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
          + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
          + "'Y')");
      ps.setString(1, "4");
      ps.setString(2, "test");
      ps.setString(3, "test");
      ps.setString(4, null);
      ps.setString(5, null);
      ps.setString(6, "test");
      ps.setString(7, "test");
      ps.setString(8, "test");
      ps.setString(9, "test");
      ps.setString(10, "test");
      ps.setString(11, "test");
      ps.setString(12, "test");
      ps.setString(13, "test");
      ps.setString(14, "test");
      ps.setString(15, "test");
      ps.setString(16, "test");
      ps.setString(17, "test");
      ps.setString(18, "test");
      ps.setString(19, "test");
      ps.setString(20, "test");
      ps.setString(21, "test");
      ps.setString(22, "test");
      ps.setString(23, "test");
      ps.setString(24, "test");
      ps.setString(25, "test");
      ps.setString(26, "test");
      ps.setString(27, "test");
      ps.setString(28, "test");
      ps.setString(29, "test");
      ps.setString(30, "test");
      ps.setString(31, "test");
      ps.setString(32, "test");
      ps.setString(33, "test");
      ps.setString(34, "test");
      ps.setString(35, "test");
      ps.setString(36, "test");
      ps.setString(37, "test");
      ps.setString(38, "test");
      ps.setString(39, "test");
      ps.setString(40, "test");
      ps.setString(41, "test");
      ps.setString(42, "test");
      ps.setString(43, "test");
      ps.setString(44, "test");
      ps.setString(45, "test");
      ps.setString(46, "test");
      ps.setString(47, "test");
      ps.setString(48, "test");
      ps.setString(49, "test");
      ps.setString(50, "test");

      ParameterMetaData md = ps.getParameterMetaData();
      assertEquals(50, md.getParameterCount());
    }
  }
}


3. 해결

에러가 발생한 getParameterMetadata메소드는 PrepareStatement의 파라미터 갯수 및 타입을 조회하는 역할을 한다.

그런데 위처럼 PrepareStatement의 마지막 컬럼에 const값을 이용하게되면 ArrayIndexOutOfBoundsException이 발생하는데, ojdbc8의 12점대 버전까지만 해도 해당 익셉션을 그대로 던졌으나, 이후 버전에서는 익셉션을 try-catch 처리한다. (사실 관련해 오라클에 이슈 리포트를 하고 싶었으나, 상용 코드가 필요해 포기했다.)

다음은 동일한 OracleParameterMetaData.java에서 OracleParameterMetaDataParser를 생성하는 로직이다.

오픈소스가 아니라 디컴파일한 코드로 보기가 좀 어렵다(…)

결국 ojdbc라이브러리를 버전업해주면 해결되는 문제였다.


3.1. ojdbc8:12.2.0.1

static final ParameterMetaData getParameterMetaData(OracleSql var0, Connection var1, OraclePreparedStatement var2) throws SQLException {
    String var7 = null;
    if (!var0.sqlKind.isPlsqlOrCall() && var0.getReturnParameterCount() < 1 && var5 > 0) {
      var6 = new OracleParameterMetaDataParser();
      var6.initialize(var4, var0.sqlKind, var5);
      var7 = var6.getParameterMetaDataSql();
    }


3.2. ojdbc8:18.3.0.0

static final ParameterMetaData getParameterMetaData(OracleSql var0, Connection var1, OraclePreparedStatement var2) throws SQLException {
    String var7 = null;
    if (!var0.sqlKind.isPlsqlOrCall() && var0.getReturnParameterCount() < 1 && var5 > 0 && !BAD_SQL.contains(var4.hashCode())) {
      var6 = new OracleParameterMetaDataParser();
      var6.initialize(var4, var0.sqlKind, var5);

      try {
        var7 = var6.getParameterMetaDataSql();
      } catch (Exception var14) {
        var7 = null;
      }
    }


4. 결론

관련된 테스트코드는 개인 github에 업로드해둔 상태이다.

연계 솔루션을 운영하다보니 별의별 에러케이스를 만나게되는 것 같다….