【问题标题】:Is it Possible to Insert Row by Row from a Java PreparedStatement?是否可以从 Java PreparedStatement 中逐行插入?
【发布时间】:2015-09-25 18:16:50
【问题描述】:

我有一个应用程序,我正在使用准备好的语句将 1000 行批量写入 SQL Server 数据库。现在,如果这些插入中的任何一个失败,我希望能够将有问题的行写入日志文件,这样数据就不会完全丢失。如果可能的话,我宁愿只写一行失败的日志,而不是全部 1000 行,所以我的问题是:

如果在插入表时有一行失败,比如发生主键冲突,是阻止插入整个批次,还是只有一行失败?如果批处理失败,我是否可以对准备好的语句进行某种循环,一个一个地执行每个查询,这样我就可以找出失败的行,并只记录该行?还是我实现这一点的唯一方法是为每个插入语句保留一个单独的数组,并在批处理失败时循环遍历该数组?

更多细节:

数据库类型:SQL Server 2008
连接库:java.sql
SQL 语句:

Insert into Table(Column1,Column2) Values (Value1, Value2)  

Java 代码:

PreparedStatement prpStmt = dbConnection.prepareStatement(insertQuery.toString());  
for (List lst : listOfValues){
    prpStmt.setString(1,lst[0]);  
    prpStmt.setString(2,lst[1]);  
    prpStmt.addBatch();  
    dbCount++;  
    if (dbCount == DB_COUNT_LIMIT){  
        try {
            prpStmt.executeBatch();
            dbConnection.commit();  
            dbCount = 0;  
            prpStmt.clearBatch();
        } catch (Exception e){
            for (PreparedStatement ps : prpStmt.getBatches()){
            if (!logToDBIndividually())
                logToFile();
            }
        }
    }  
}

【问题讨论】:

  • 您知道在任何给定时间内将处理多少条记录吗?
  • 这取决于许多因素,其中包括所涉及的数据库、数据库配置、JDBC 驱动程序及其配置、JDBC 连接配置、您准备语句的 SQL 以及用于执行工作的 Java 代码。如何命名 DB 并呈现至少模型 Java 代码代表 SQL?
  • 如果执行方式是逐语句的,批处理的概念将无济于事。在批处理的情况下,语句被推到一起。您可以执行列表中的语句来完成此操作。
  • @john-bollinger 添加了一些编辑以显示我希望实现的代码和数据库详细信息。 blahfunk 记录可能数以万计。 james-jithin 是的,我知道。我的想法是我将理想地执行大批量以节省时间。但是,如果某些事情失败了,我会逐个声明地找到失败的特定语句。

标签: java exception prepared-statement


【解决方案1】:

如果在插入表时有一行失败,比如发生主键冲突,是阻止插入整个批次,还是只有一行失败?

在发生错误之前执行的语句仍然被执行。批处理中稍后的语句可能已执行,也可能未执行,由驾驶员自行决定。假设您已经关闭了语句连接的自动提交,正如您在使用 executeBatch() 时应该做的那样,并且看起来您已经完成了,是否提交或回滚成功进行的更改由您自行决定。

驱动程序是否继续通过失败的语句,如果一个确实失败,那么您可以通过检查由getUpdateCounts() 方法返回的数组来确定哪些批处理语句成功执行结果BatchUpdateException。详情请参阅该方法的文档或Statement.executeBatch() 的文档。

还请注意,您提供的示例代码存在严重缺陷。如果您的一个更新失败,例如引发异常,重要的是提交或回滚事务,并清除您不想在下一次迭代中重新执行的任何批处理语句环形。此外,捕获普通的Exception 很少适合,在这里尤其不适合,因为您希望以不同于其他异常的方式处理BatchUpdateException。这样的事情可能会更好:

PreparedStatement prpStmt = dbConnection.prepareStatement(insertQuery.toString());  
for (List lst : listOfValues){
    prpStmt.setString(1,lst[0]);  
    prpStmt.setString(2,lst[1]);  
    prpStmt.addBatch();  
    dbCount++;  
    if (dbCount >= DB_COUNT_LIMIT) {  // should not be >, but no harm in being safe
        try {
            prpStmt.executeBatch();
            dbConnection.commit();  
            dbCount = 0;  
            prpStmt.clearBatch();
        } catch (BatchUpdateException bue){
            int[] updateCounts = bue.getUpdateCounts();

            if (updateCounts.length < dbCount) {
                /*
                 * The first updateCounts.length statements (only) were
                 * executed successfully.  The next one failed, and no more
                 * were attempted.
                 */
            } else {
                /*
                 * The failed statements can be identified by having
                 * updateCounts[i] == Statement.EXECUTE_FAILED
                 */
            }

            // Presumably you want to:
            dbConnection.commit();

            // Maybe you want to:
            dbCount = 0;  
            prpStmt.clearBatch();
            // Otherwise you need to do some other kind of cleanup / retry
        }

        /*
         * no need to catch any other exception, including SQLException, in
         * this scope, as it's unlikely that the overall bulk insertion can be
         * continued after such an exception.
         */
    }  
}

如果批处理失败,我是否可以对准备好的语句进行某种循环,逐个执行每个查询,这样我就可以找出失败的行,并只记录该行?

您可以确定失败的语句,如上所示。这将允许您记录失败。但是,您不能删除询问Statement 对象的当前批处理,或者删除除通过clearBatch() 之外的行,因此如果驱动程序恰好是在第一个错误后停止处理批处理的类型,那么从此类故障中恢复可能不像你想的那样直截了当。但是,所需的信息就在那里;使用带索引的for 循环来迭代列表而不是增强的for 循环可能更容易。

或者是我实现此目的的唯一方法是为每个插入语句保留一个单独的数组,并在批处理发生失败时循环遍历该数组?

不,这不是唯一的方法,但我可以想象可以干净地实施的变体。但是,使用带索引的 for 循环,您确实可以非常干净地恢复。

【讨论】:

  • 完美,感谢您详细介绍。我完全忘记了在异常情况下清除批次,我敢肯定这会让我发疯一段时间,试图找出问题所在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-05
  • 1970-01-01
  • 2021-11-03
  • 1970-01-01
  • 2017-09-03
  • 1970-01-01
相关资源
最近更新 更多