Почему последующее создание временной таблицы выдает ошибку при использовании Statement с DBCP2? - PullRequest
0 голосов
/ 03 апреля 2019

Я пытаюсь создать временную таблицу - #EMPLOYEE_TABLE в базе данных MSSQL, используя Spring и пул соединений DBCP2. Я использую java.sql.Statement для выполнения выполнения на БД. Я закрываю соединение после выполнения, чтобы отправить его обратно в пул соединений. Этот процесс создания отлично работает для первого выполнения. Но для последующего выполнения выдает следующую ошибку:

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, версия DBCP2 - 2.6.0, и я запускаю ее на Java 1.8

Однако, если я изменю реализацию execute с 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, чтобы увидеть, была ли проблема в освобождении ресурсов, и получил тот же результат.

Я что-то упускаю? Может кто-нибудь помочь мне разобраться в этом поведении?

1 Ответ

0 голосов
/ 03 апреля 2019

Когда вы используете пул соединений, соединение может быть повторно использовано для другого запроса. Локальные временные таблицы SQL Server действительны в течение всего времени сеанса. Если не предпринято конкретное действие, которое вызывает sp_reset_connection, продолжительность сеанса равна времени жизни соединения. В этой ситуации время жизни временной таблицы - это время жизни соединения.

DBCP ничего не знает об особенностях SQL Server, поэтому он не знает, что должен вызвать sp_reset_connection, чтобы начать новый сеанс на существующем соединении. Поскольку sp_reset_connection не вызывается, локальные временные таблицы будут существовать до тех пор, пока не будут явно удалены или не будет закрыто соединение.

Вам нужно либо удалить временную таблицу, когда вы закончите, либо удалить ее условно перед созданием, или создать, только если таблица еще не существует.

В качестве альтернативы, вам нужно убедиться, что DBCP выполнит sp_reset_connection перед раздачей соединения. Может быть возможно использовать (или злоупотреблять) validationQuery с testOnReturn или testOnBorrow. Обычно я не использую DBCP или SQL Server, поэтому я не могу дать рабочее решение, как, но вам нужно будет выполнить требование о том, что запрос проверки ведет себя как выбор (т. Е. Возвращает по нескольким строкам или нет, количество обновлений не учитывается). ).

...