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에 업로드해둔 상태이다.
연계 솔루션을 운영하다보니 별의별 에러케이스를 만나게되는 것 같다….