springoracle-databasetomcatspring-jdbctomcat-dbcp

Tomcat 9 w/ Apache DBCP + Spring 5 + Oracle 12c + SqlArrayValue


We have existing SpringMVC J2EE application that runs on Orale WebLogic which we are attempting to migrate to Apache Tomcat. Everything seems to work except we are having issues binding array values through JDBC into the Database. Here is how that was being done successfully on WebLogic.

SqlParameterSource in = new MapSqlParameterSource()
                .addValue("i_username", user.getUsername())
                .addValue("i_statuses",
                        new SqlArrayValue<String>(statuses,
                                "VARCHAR_TABLE_T"));

Map<String, Object> out = myDatabaseProc.execute(in);

The statuses variable is a String array and VARCHAR_TABLE_T is an Oracle DB type of the same. SqlArrayValue is a Spring class specifically for Oracle array handling in JDBC.

When the code is executed we get the following error (again, this is only broken on Tomcat).

java.lang.ClassCastException: org.apache.tomcat.dbcp.dbcp2.PoolingDataSource$PoolGuardConnectionWrapper cannot be cast to oracle.jdbc.OracleConnection
        at oracle.sql.TypeDescriptor.setPhysicalConnectionOf(TypeDescriptor.java:803) ~[ojdbc8.jar:12.2.0.1.0]
        at oracle.sql.TypeDescriptor.<init>(TypeDescriptor.java:585) ~[ojdbc8.jar:12.2.0.1.0]
        at oracle.sql.ArrayDescriptor.<init>(ArrayDescriptor.java:258) ~[ojdbc8.jar:12.2.0.1.0]
        at org.springframework.data.jdbc.support.oracle.SqlArrayValue.createTypeValue(SqlArrayValue.java:90) ~[spring-data-oracle-1.2.1.RELEASE.jar:?]
        at org.springframework.jdbc.core.support.AbstractSqlTypeValue.setTypeValue(AbstractSqlTypeValue.java:60) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
        at org.springframework.jdbc.core.StatementCreatorUtils.setValue(StatementCreatorUtils.java:293) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
        at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValueInternal(StatementCreatorUtils.java:232) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
        at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValue(StatementCreatorUtils.java:147) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
        at org.springframework.jdbc.core.CallableStatementCreatorFactory$CallableStatementCreatorImpl.createCallableStatement(CallableStatementCreatorFactory.java:209) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
        at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:1090) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
        at org.springframework.jdbc.core.JdbcTemplate.call(JdbcTemplate.java:1147) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
        at org.springframework.jdbc.core.simple.AbstractJdbcCall.executeCallInternal(AbstractJdbcCall.java:412) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
        at org.springframework.jdbc.core.simple.AbstractJdbcCall.doExecute(AbstractJdbcCall.java:372) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
        at org.springframework.jdbc.core.simple.SimpleJdbcCall.execute(SimpleJdbcCall.java:198) ~[spring-jdbc-5.2.3.RELEASE.jar:5.2.3.RELEASE]

I found a bunch online about unwrapping the connection to be able to perform vendor specific actions, however it seems that Spring should be doing that for me? I tried setting accessToUnderlyingConnectionAllowed to true in Tomcat's server.xml (something I had found online) where we have the connection resource setup, but that did not affect the behavior.

Here is the resource from server.xml.

<Resource name="jdbc/datasource" auth="Container"
          type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver"
          url="jdbc:oracle:thin:@//db.domain.com:port/sid"
          username="user" password="password"
          maxTotal="15" maxIdle="3" maxWaitMillis="-1"
          accessToUnderlyingConnectionAllowed="true" />

Solution

  • It appears that spring-data-jdbc-ext is EOL. The SqlArrayValue class simply isn't unwrapping the connection. I ended up jettisoning the JDBC extensions and rolling my own very similar class which handles unwrapping the connection. This now works on WebLogic and Tomcat (with accessToUnderlyingConnectionAllowed set to true) DBCP and Tomcat's native connection pool (no accessToUnderlyingConnectionAllowed required).

    import java.sql.Connection;
    import java.sql.SQLException;
    
    import org.springframework.dao.InvalidDataAccessApiUsageException;
    import org.springframework.jdbc.core.support.AbstractSqlTypeValue;
    
    import oracle.jdbc.OracleConnection;
    
    public class OracleSqlArrayValue<T> extends AbstractSqlTypeValue {
        private T[] values;
        private String defaultTypeName;
    
        public OracleSqlArrayValue(T[] values) {
            this.values = values;
        }
    
        public OracleSqlArrayValue(T[] values, String defaultTypeName) {
            this.values = values;
            this.defaultTypeName = defaultTypeName;
        }
    
        @Override
        protected Object createTypeValue(Connection conn, int sqlType,
                String typeName) throws SQLException {
    
            if (typeName == null && defaultTypeName == null) {
                throw new InvalidDataAccessApiUsageException(
                        "No type named defined. Instantiate class with default type name.");
            }
    
            if (!conn.isWrapperFor(OracleConnection.class)) {
                throw new InvalidDataAccessApiUsageException(
                        "Unable to unwrap OracleConnection. Ensure you are connecting to an Oracle DB.");
            }
    
            return conn.unwrap(OracleConnection.class).createOracleArray(
                    typeName != null ? typeName : defaultTypeName, values);
        }
    }