【问题标题】:Reusing a PreparedStatement multiple times多次重用 PreparedStatement
【发布时间】:2011-01-28 20:32:25
【问题描述】:

在将 PreparedStatement 与没有任何池的单个公共连接一起使用的情况下,我可以为每个 dml/sql 操作重新创建一个实例,以保持预准备语句的功能吗?

我的意思是:

for (int i=0; i<1000; i++) {
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
    preparedStatement.close();
}

代替:

PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i=0; i<1000; i++) {
    preparedStatement.clearParameters();
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
}
preparedStatement.close();

我的问题是我想把这段代码放到多线程环境中,你能给我一些建议吗?谢谢

【问题讨论】:

  • 所以您的查询 sql 不会在循环中更改?如果该查询对于循环的每次迭代都没有改变,那么为什么要为每次迭代创建一个新的PreparedStatement(在第一个代码 sn-p 中)?这样做有什么理由吗?
  • 让我们说如果查询正在改变,那么第二种方法仍然更好,对吧?有什么缺点吗?

标签: java jdbc prepared-statement


【解决方案1】:

第二种方法效率更高一些,但更好的方法是批量执行它们:

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
        }

        statement.executeBatch();
    }
}

但是,您一次可以执行多少个批处理取决于 JDBC 驱动程序的实现。例如,您可能希望每 1000 个批次执行一次:

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        int i = 0;

        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
            i++;

            if (i % 1000 == 0 || i == entities.size()) {
                statement.executeBatch(); // Execute every 1000 items.
            }
        }
    }
}

对于多线程环境,如果按照正常的JDBC习语,在同一方法块内尽可能短的范围内获取和关闭连接和语句,则无需担心使用try-with-resources 语句如上sn-ps所示。

如果这些批处理是事务性的,那么您希望关闭连接的自动提交,并且仅在所有批处理完成后才提交事务。否则可能会导致前一批批成功后一批批不成功的时候数据库脏了。

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (Connection connection = dataSource.getConnection()) {
        connection.setAutoCommit(false);

        try (PreparedStatement statement = connection.prepareStatement(SQL)) {
            // ...

            try {
                connection.commit();
            } catch (SQLException e) {
                connection.rollback();
                throw e;
            }
        }
    }
}

【讨论】:

  • inside the same method block - 你的意思是每个线程都有自己的堆栈,这些连接和语句从一侧位于堆栈中,来自另一个数据源的每个新调用都将给予 executeFunction(==every线程)单独的连接实例。我理解你的意思吗?”
  • 我正在使用第一种方法,但是在 SQL 探查器中进行监视时,我看到重复的多个准备好的语句而不是一个。我无法弄清楚为什么它显示多个语句。需要帮助。
  • 如果查询在循环中没有改变,你的答案很好。如果查询正在改变,例如在我的查询改变的情况下怎么办..我认为第二种方法更好。请验证
【解决方案2】:

你的代码中的循环只是一个过于简单的例子,对吧?

最好只创建一次PreparedStatement,然后在循环中一遍又一遍地重复使用它。

在不可能的情况下(因为它使程序流程过于复杂),使用PreparedStatement 仍然是有益的,即使您只使用一次,因为工作的服务器端(解析SQL 和缓存执行计划),仍然会减少。

为了解决你想重用Java端PreparedStatement的情况,一些JDBC驱动(比如Oracle)有一个缓存功能:如果你在同一个连接上为同一个SQL创建一个PreparedStatement ,它将为您提供相同的(缓存的)实例。

关于多线程:我不认为 JDBC 连接可以在多个线程之间共享(即由多个线程同时使用)。每个线程都应该从池中获取自己的连接,使用它,然后再次将其返回到池中。

【讨论】:

  • 事实上,连接有它的专属线程,每条语句都在其中执行,但我通过暴露的准备好的语句堆栈访问该线程。所以其他并发线程最初只传递构建所有准备好的语句所需的参数,但随后它们可以同时修改参数
猜你喜欢
  • 2010-11-04
  • 1970-01-01
  • 2015-08-10
  • 2015-01-08
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
  • 2016-01-28
  • 2021-11-03
相关资源
最近更新 更多