我正在尝试使用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
来查看资源发布是否有问题并得到相同的结果。
我错过了什么吗?有人可以帮我理解这种行为吗?
使用连接池时,可以将该连接重用于其他请求。 SQL Server本地临时表在会话的生命周期内有效。除非采取特定操作,即调用sp_reset_connection
,否则会话的长度等于连接的生命周期。在这种情况下,临时表的生存期是连接的生命周期。
DBCP对SQL Server的详细信息一无所知,因此它不知道它应该调用sp_reset_connection
来在现有连接上启动新会话。由于未调用sp_reset_connection
,因此本地临时表将继续存在,直到显式删除或连接关闭为止。
您可能需要在完成后删除临时表,在创建之前有条件地删除,或者仅在表尚不存在时创建。
或者,您需要确保DBCP在分发连接之前执行sp_reset_connection
。有可能使用(或滥用)validationQuery
与testOnReturn
或testOnBorrow
。我通常不使用DBCP也不使用SQL Server,所以我不能给出一个有效的解决方案,但是你需要满足验证查询的行为选择(即:返回或更多行,没有更新计数) )。