是否可以从Java PreparedStatement逐行插入?

时间:2015-09-25 18:16:51

标签: java exception prepared-statement

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

如果一行在插入表格时失败,说发生主键违规,整个批次是否被阻止插入,或只有一行失败?如果批处理失败,我是否可以在预准备语句上执行某种循环,逐个执行每个查询,这样我就可以找出失败的行,并仅记录该行?或者是我实现此目的的唯一方法是保留每个insert语句的单独数组,并在批处理失败时循环执行该操作?

更多细节:

  

数据库类型: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();
            }
        }
    }  
}

1 个答案:

答案 0 :(得分: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循环可能会更容易。

  

或者是我实现此目的的唯一方法是保留每个insert语句的单独数组,并在批处理失败时循环执行该操作?

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