多次重用PreparedStatement


96

在使用带有单个公共连接且没有任何池的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为每个迭代(在第一个代码段中)创建一个新的?有什么理由吗?
萨比尔·汗

可以说如果查询正在更改,那么第二种方法还是更好的吧?有什么缺点吗?
万人迷

Answers:


144

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

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.
            }
        }
    }
}

对于多线程环境,如果您使用try-with-resources语句按照常规的JDBC习惯在同一方法块内获取并关闭连接和语句,并且在最短范围内执行该语句,则无需担心此问题,如下所示:以上摘要。

如果这些批次是事务性的,那么您想关闭连接的自动提交,仅在所有批次完成后才提交事务。否则,当第一批批次成功而第二批批次失败时,则可能导致数据库变脏。

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(==每个线程)调用中提供单独的连接实例。我能理解你的意思吗?”
Pavel_K,2015年

我正在执行第一种方法,但是在SQL事件探查器中进行监视时,我看到重复的多个准备好的语句而不是一个。我不知道为什么它显示多个语句。需要协助。
流氓小伙子

如果查询没有在循环中更改,您的回答就很好。.如果查询更改了,例如在我更改查询的情况下怎么办..我认为第二种方法还是更好。请验证
Stunner

13

您代码中的循环只是一个过度简化的示例,对吧?

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

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

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

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


1
实际上,连接具有其专用线程,并且每个语句都在其中执行,但是我通过暴露的准备好的语句堆栈访问该线程。所以,其他并发线程最初只传递PARAMS需要建立所有准备好的声明,但他们可以同时修改PARAMS
钢羽
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.