java - Tomcat/SQL Server/Spring - 当 setStructured 参数由 JNDI 配置引起时,使用 SQLServerCallableStatement 抛出异常

标签 java sql-server spring tomcat jndi

技术堆栈:

  • Tomcat 7.0.47
  • Spring 4.3.8
  • SQL Server 2012

我正在尝试在我的 Java 网络应用程序中实现一个数据库连接池,虽然我已经实现了,但我在执行 SQL 语句时检测到一些功能缺失。

我已经尝试了两种不同的方法,但都没有成功,希望您能提供任何线索:

1° - Tomcat JNDI 资源

尽管我的连接池可以正常工作,但在 callableStatement 中使用结构作为参数时,我失去了功能,从 Proxy$14 向 SQLServerCallableStatement 抛出强制转换异常,这是常规数据源不存在的问题。我注意到了

jdbcInterceptors="ConnectionState;StatementFinalizer;SlowQueryReport(threshold=1500);" - 发生转换异常并且 JNDI 池连接工作正常。

jdbcInterceptors="ConnectionState;StatementFinalizer;" - 没有强制转换异常,但结构参数被忽略,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°- 使用 Tomcat 的 DBCP jar 的 Spring DataSource

通过这种方式,我的连接池连接工作很奇怪,虽然没有发生 Cast Exceptions,但我还没有解决的是连接没有释放到池中,而是在我的应用程序用完连接之前它们肯定会关闭。 (禁用 tomcat 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

此代码在每个数据库调用的 finally try block 中执行。

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

最佳答案

您从池中获取的Connection 对象不是连接到数据库的原始连接对象。它不会是通过调用 DriverManager.getConnection 生成的对象,而且它的类型也不是 SQLServerCallableStatement。相反,您将获得一个允许池正确管理它的包装器对象。

很明显,在池连接上调用 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 接口(interface),这一直是可能的)。如今,您不必做这种事情,因为 API 应该允许您在不向下转换的情况下做任何事情。

关于java - Tomcat/SQL Server/Spring - 当 setStructured 参数由 JNDI 配置引起时,使用 SQLServerCallableStatement 抛出异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53792619/

相关文章:

java - 如何检查对象中的所有非原始元素是否为空

sql - 无法启动 T-SQL 调试。无法连接到计算机("*****")

sql - SELECT DISTINCT 结果仅返回 COUNT > 1

java - 当斐波那契数列 > 2 时,Iteral fibonacci java 代码返回 0

java - 在Java中更改Windows通知区域中的图标名称

java - 解析 log4j 输出

c# - 获取从 SQL Server 返回的字符串值

java - Spring Maven 和 Tomcat 的 Hibernate 问题

javascript - 无法从使用 spring RedisTemplate 存储的 javascript 中获取 redis 嵌套哈希键值

java - 卡在 spring 教程中