Tomcat / SQL Server / Spring - исключение приведения с SQLServerCallableStatement, когда параметр setStructured вызван конфигурацией JNDI - PullRequest
0 голосов
/ 15 декабря 2018

Технический стек:

  • Tomcat 7.0.47
  • Spring 4.3.8
  • SQL Server 2012

Я пытаюсьреализовать пул соединений с БД в моем веб-приложении на Java, и хотя я этого добился, при выполнении операторов SQL я обнаружил некоторые несоответствия.

Я попробовал 2 разных подхода, но безуспешно, надеюсь, вы сможете обеспечитьлюбая подсказка:

1 ° - ресурс JNDI Tomcat

Несмотря на то, что мой пул соединений работает, я потерял функциональность, когда использовал структуры в качестве параметров в callableStatement, выбрасывая исключение приведения из Proxy $ 14 в SQLServerCallableStatement, проблема, которая не возникаетпредставить с обычным источником данных.Я заметил, что

jdbcInterceptors="ConnectionState;StatementFinalizer;SlowQueryReport(threshold=1500);" - Произошло исключение Cast и пул JNDI работает нормально.

jdbcInterceptors="ConnectionState;StatementFinalizer;" - Исключение не Cast, но параметр Structure игнорируется, и пул JNDI работает нормально.

jdbcInterceptors=" - Параметр структуры работает нормально, но в подключении к пулу JNDI исчерпаны доступные подключения.

это моя конфигурация:

Tomcat server.xml

<Resource name="jdbc/TomcatDS"
    global="jdbc/TomcatDS"
    factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
    auth="Container"
    type="javax.sql.DataSource"
    username="user"
    password="pass"
    driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
    description="SQLServer DB DS"
    url="jdbc:sqlserver://<host>:1433;DatabaseName=<DB>;schema=dbo;encrypt=true;trustServerCertificate=true;"
    maxActive="50"
    maxTotal="50"
    maxIdle="50"
    minIdle="10"
    maxWait="15000"
    reomoveAbandoned="true"
    removeAbandonedTimeout="3000"
    defaultAutoCommit="true" jdbcInterceptors="ConnectionState;StatementFinalizer;SlowQueryReport(threshold=1500);"/>

web.xml

<resource-ref>
    <description>DB Connection</description>
    <res-ref-name>jdbc/TomcatDS</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref>

context.xml

<Context path="/">
    <ResourceLink name="jdbc/TomcatDS" global="jdbc/TomcatDS" type="javax.sql.DataSource"/>
</Context>

spring-db-config.xml

<bean id="sqlServerDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/TomcatDS"/>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" abstract="true">
    <property name="dataSource">
        <ref bean="sqlServerDataSource" />
    </property>
</bean>

2 ° - Spring DataSource с использованием Tomcat DBCP jar

При таком способе соединение с пулом соединений работает странно, и хотя исключений не происходитчто я еще не решил, так это то, что соединения не освобождаются в пул, вместо этого они однозначно закрываются, пока в моем приложении не закончатся соединения.(Отключенный JNDI)

Моя конфигурация:

spring-db-config.xml

<bean id="sqlServerPoolDataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
        <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
        <property name="username" value="user"/>
        <property name="password" value="pass"/>
        <property name="url" value="jdbc:sqlserver://<host>:1433;DatabaseName=<DB>;schema=dbo;encrypt=true;trustServerCertificate=true;"/>
        <property name="maxActive" value="50"/>
        <property name="maxIdle" value="50"/>
        <property name="minIdle" value="10"/>
        <property name="maxWait" value="15000"/>
        <property name="removeAbandoned" value="true"/>
        <property name="removeAbandonedTimeout" value="3000"/>
        <property name="defaultAutoCommit" value="true"/>
        <property name="jdbcInterceptors" value="ConnectionState;StatementFinalizer;"/>     
    </bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" abstract="true">
    <property name="dataSource">
        <ref bean="sqlServerPoolDataSource" />
    </property>
</bean>

DAO.java

Этот код выполняется при окончательной попытке блока каждого вызова БД.

public void terminateDBCall(ResultSet rs, CallableStatement cstmt) {
            if (cstmt != null) {
                try {                
                    if (cstmt.getConnection() != null && !cstmt.getConnection().isClosed()){
                        cstmt.getConnection().close();                  
                    }
                    cstmt.close();
                    cstmt = null;
                } catch (SQLException ex) {
                    logHandler.registerSystemEvent(this.getClass(), Constants.SystemLogEvent.ERROR, "La session de base de datos no pudo ser cerrada", ex);
                }
            } 
            if (rs != null) {
                try {
                    rs.close();
                    rs = null;
                } catch (SQLException ex) {
                    logHandler.registerSystemEvent(this.getClass(), Constants.SystemLogEvent.ERROR, "El ResultSet no pudo ser cerrado", ex);
                }
            }               
        }

Код, который вызывает исключение приведения

@Override
    public boolean updateUser(User userVO) {
        boolean resultFlag = false;
        CallableStatement cstmt = null;
        ResultSet rs = null;
        try {
            SQLServerDataTable sourceDataTable = new SQLServerDataTable(); 
            sourceDataTable.addColumnMetadata("plantaId", java.sql.Types.INTEGER);
            for (FactoryValueObject factoryVO : userVO.getFactoryList()) {
                sourceDataTable.addRow(factoryVO.getFactoryId());
            }

            cstmt = getCallableStatement("UPDATE_USER");
            cstmt.setInt("UsuarioId", userVO.getUserId());
            cstmt.setNString("Usuario", userVO.getUserName());
            cstmt.setNString("Password", userVO.getUserPassword());
            cstmt.setInt("RolId", userVO.getRoleId());
            cstmt.setNString("Email", userVO.getEmail());
            cstmt.setInt("Activo", (userVO.isActiveFlag() ? 1 : 0));
            cstmt.setNString("ModificadoPor", userVO.getUpdatedBy());
=====>          ((SQLServerCallableStatement) cstmt).setStructured("ListaPlantaIds", "dbo.ID_PLANTAS_ASIGNADAS", sourceDataTable);
            cstmt.execute();
            rs = cstmt.getResultSet();
            rs.next();
            resultFlag = rs.getInt("resultFlag") == 1;
            if(!resultFlag)
                logDataBaseError(rs);
            logHandler.registerSystemEvent(this.getClass(), Constants.SystemLogEvent.DEBUG, "The user ID: " + userVO.getUserId() + " has been updated!");
        } catch (Exception ex) {
            logHandler.registerSystemEvent(this.getClass(), Constants.SystemLogEvent.ERROR, ex.getMessage(), ex);
        } finally {
            terminateDBCall(rs, cstmt);
        }   
        return resultFlag;
    }

java.lang.ClassCastException: com.sun.proxy.$Proxy14 cannot be cast to com.microsoft.sqlserver.jdbc.SQLServerCallableStatement

1 Ответ

0 голосов
/ 18 декабря 2018

Объект Connection, который вы получаете из пула, не является необработанным объектом подключения, который подключен к базе данных.Это не будет объект, созданный путем вызова DriverManager.getConnection, и не будет иметь тип SQLServerCallableStatement.Вместо этого вы получите объект wrapper , который позволяет пулу правильно им управлять.

Очевидно, что вызов close в подключении из пула в значительной степени нарушит пул, так чтообъект, который вы вызываете close, должен вместо этого возвращать соединение с пулом, а не закрывать базовое соединение.

Чтобы получить ссылку на «реальный» объект соединения, вам нужно развернуть соединение, какэто:

if(cstmt.isWrapperFor(SQLServerCallableStatement.class)) {
    SQLServerCallableStatement raw = cstmt.unwrap(SQLServerCallableStatement.class);
    raw.setStructured("ListaPlantaIds", "dbo.ID_PLANTAS_ASIGNADAS", sourceDataTable);
} else {
    // Do whatever you need to ; maybe throw an exception?
}

Мне интересно, если вам даже нужно получить ссылку на необработанный тип здесь.Разве вы не можете использовать существующие вызовы JDBC для работы со «структурированными» данными?Я считаю, что тип java.sql.Ref - это то, как вы будете делать это независимо от поставщика.Использование «простого» JDBC будет чище и менее хрупким, чем перебор с базовыми типами данных, создаваемыми драйвером.

До JDBC 1.6 программистам часто приходилось делать это при попытке создать новый Clobобъекты, и будет прибегать к понижению их соединений, чтобы добраться до предоставленных вендором методов для создания новых объектов Clob (вместо простой реализации интерфейса Clob, который всегда был возможен).В наши дни вам не нужно делать такие вещи, потому что API должны позволять вам делать все без уныния.

...