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

原文 标签 java sql-server spring tomcat jndi

技术栈:


Tomcat 7.0.47
春季4.3.8
SQL Server 2012


我正在尝试在Java Web应用程序中实现数据库连接池,尽管我已经实现了它,但是在执行SQL语句时发现了一些功能失误。

我尝试了2种不同的方法,但是没有运气,希望您能提供任何线索:

1°-Tomcat JNDI资源

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

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

jdbcInterceptors="ConnectionState;StatementFinalizer;"-没有强制转换异常,但是忽略了Structure参数,并且JNDI池连接工作正常。

jdbcInterceptors="-结构参数工作正常,但JNDI池连接用尽了可用的连接。

这是我的配置:

Tomcat服务器.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

用这种方法,我的连接池连接工作很奇怪,尽管没有发生强制转换异常,但我还没有解决的问题是连接没有释放到池中,相反,它们肯定是关闭的,直到我的应用程序用尽连接为止。 (禁用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

该代码在每个数据库调用的最后try块中执行。

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接口,一直以来都是可能的)。这些天来,您不必做这种事情,因为API应该允许您做所有事情而无需向下转换。

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

相关文章:

sql-server - 与 SQL Server 2000 相比,SQL Server 2005/2008 的主要优点是什么?

sql - 查询分组和总和的表

Spring SimpleJdbcInsert 与 JdbcTemplate

java - 如何从Thymeleaf的下拉菜单中获取所选值?

java - 在PHP中实现以下随机生成器

java - 动态Web模块3.0需要Java 1.6或更高版本

sql-server - 如何在UDF标量函数中将表名作为参数传递?

java - 对于 Multi-Tenancy 环境,如何将jpa存储库初始化从启动移动到 session 创建时间

javascript - 找不到Js Spring MVC

java - 在2种不同的方法之间传递字符串值