为什么在使用带DBCP2的Statement时后续的临时表创建会引发错误?

问题描述 投票:0回答:1

我正在尝试使用Spring和DBCP2连接池在MSSQL数据库上创建一个临时表 - #EMPLOYEE_TABLE。我正在使用java.sql.Statement在DB上执行执行。我在执行后关闭连接,将其发送回连接池。此创建过程适用于第一次执行。但是对于后续执行,它会抛出以下错误:

com.microsoft.sqlserver.jdbc.SQLServerException: There is already an object named '#EMPLOYEE_TABLE' in the database.12:26:37.852 [34mINFO [0;39m [35mcom.lex.dbcp.dao.TestDaoImpl[0;39m - Connection :>>org.apache.commons.dbcp2.PoolingDataSource$PoolGuardConnectionWrapper@1807f5a7 
    at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:215)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1635)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.doExecuteStatement(SQLServerStatement.java:865)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement$StmtExecCmd.doExecute(SQLServerStatement.java:762)
    at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:5846)
    at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:1719)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:184)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:159)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.execute(SQLServerStatement.java:735)
    at org.apache.commons.dbcp2.DelegatingStatement.execute(DelegatingStatement.java:175)
    at org.apache.commons.dbcp2.DelegatingStatement.execute(DelegatingStatement.java:175)
    at org.apache.commons.dbcp2.DelegatingStatement.execute(DelegatingStatement.java:175)
    at com.lex.dbcp.dao.TestDaoImpl.createTempTableUsingStatement(TestDaoImpl.java:131)
    at com.lex.dbcp.common.App.main(App.java:25)

它是MSSQL 2014 DB,DBCP2版本是2.6.0,我在java 1.8上运行它

但是,如果我将执行实现从java.sql.Statement更改为java.sql.PreparedStatement,则执行对于任何数量的启动都可以正常工作。

使用Statement接口的示例代码:

    public void createTempTableUsingStatement() {

        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;

        String sqlQuery = "SELECT * INTO #EMPLOYEE_TABLE FROM EMPLOYEE WHERE 1=2";

        try {
            conn = jdbcTemplate.getDataSource().getConnection();

            st = conn.createStatement();
            st.execute(sqlQuery);

            LOGGER.info("Table created");

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {

            if (null != st) {
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (null != rs) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if (null != conn) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

    }

使用PreparedStatement的示例代码

public void createTempTableUsingPreparedStatement() {

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        String sqlQuery = "SELECT * INTO #EMPLOYEE_TABLE FROM EMPLOYEE WHERE 1=2";

        try {
            conn = jdbcTemplate.getDataSource().getConnection();

            ps = conn.prepareStatement(sqlQuery);
            ps.execute();

            LOGGER.info("Table created");

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {

            if (null != ps) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (null != rs) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if (null != conn) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

表结构

CREATE TABLE EMPLOYEE(
                emp_id int identity,
                emp_name varchar(255)
);

我留下了源项目的副本,以供参考StandAlone

现在我想要理解的是,为什么在java.sql.PreparedStatement的情况下多次触发的临时表创建工作而不是在java.sql.Statement的情况下。我也使用过try with resources来查看资源发布是否有问题并得到相同的结果。

我错过了什么吗?有人可以帮我理解这种行为吗?

java sql-server jdbc apache-commons-dbcp mssql-jdbc
1个回答
0
投票

使用连接池时,可以将该连接重用于其他请求。 SQL Server本地临时表在会话的生命周期内有效。除非采取特定操作,即调用sp_reset_connection,否则会话的长度等于连接的生命周期。在这种情况下,临时表的生存期是连接的生命周期。

DBCP对SQL Server的详细信息一无所知,因此它不知道它应该调用sp_reset_connection来在现有连接上启动新会话。由于未调用sp_reset_connection,因此本地临时表将继续存在,直到显式删除或连接关闭为止。

您可能需要在完成后删除临时表,在创建之前有条件地删除,或者仅在表尚不存在时创建。

或者,您需要确保DBCP在分发连接之前执行sp_reset_connection。有可能使用(或滥用)validationQuerytestOnReturntestOnBorrow。我通常不使用DBCP也不使用SQL Server,所以我不能给出一个有效的解决方案,但是你需要满足验证查询的行为选择(即:返回或更多行,没有更新计数) )。

© www.soinside.com 2019 - 2024. All rights reserved.