多次重用预准备语句

2022-08-31 09:56:53

在将ReadyStatement与没有任何池的单个公共连接一起使用的情况下,我是否可以为每个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();

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


答案 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语句(如上面的片段所示)在同一方法块内的最短范围内获取并关闭连接和语句,则无需担心这一点。

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

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

答案 2

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

最好创建一次,并在循环中一遍又一遍地重用它。PreparedStatement

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

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

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


推荐