使用带有 autocommit=true 的 jdbc 时回滚批处理执行

Posted

技术标签:

【中文标题】使用带有 autocommit=true 的 jdbc 时回滚批处理执行【英文标题】:Rollback batch execution when using jdbc with autocommit=true 【发布时间】:2013-01-31 12:04:29 【问题描述】:

我正在使用 JDBC,自动提交=true。在其中一项操作中,我正在使用准备好的语句进行批量插入。

public void executeBatchInsert(String query, List<Object[]> entityList)  
        try 
            pstmt = conn.prepareStatement(query);
            for(int i=0; i<entityList.size(); i++) 
                int j=1;
                for(Object o: entityList.get(i)) 
                    pstmt.setObject(j++, formatColumnValue(o));
                

                pstmt.addBatch();
                if((i+1)%1000 == 0) 
                    pstmt.executeBatch();
                
            
            pstmt.executeBatch();
         catch (SQLException e) 
        
    

如果我在执行时遇到异常,当我关闭这个连接时,是否会释放所有的锁并进行回滚?

-- B. 光辉。

【问题讨论】:

使用autoCommit=true 进行批处理的行为(明确地!)未在 JDBC 规范中定义。一般来说,您根本不应该在不禁用 autoCommit 的情况下使用批处理执行。 仅供参考...关于Comment by Mark Rotteveel,这一点记录在JDBC™ 4.3 Specification 的第14.1.1 节中: executeBatch 的提交行为总是在发生错误时由实现定义并且自动提交是true 【参考方案1】:

您的问题的直接答案是:不。如果发生异常,您必须手动调用rollback 方法。在这样做之前,您必须 setAutoCommitfalse。默认情况下,自动提交设置为true。将自动提交设置为 true 时,您无法执行 rollbackexception 会告诉您这一点。

稍后不要忘记将autoCommit 设置回true,否则您可能会使用其他方法获得不可预期的结果。

以下是有关如何实现此功能的示例。这只是草图,您可能应该多注意如何处理connectionprepared statmentexception 等。

public void insertAndRollback(Connection connection) 
    try 
        final ArrayList parameters = new ArrayList();

        // Add your parameters to the arraylist
        parameters.add("John");
        parameters.add("Lee");
        parameters.add("Mary");
        parameters.add("Peter");
        parameters.add("Lewis");
        parameters.add("Patrick");

        final String parameterizedQuery = "insert into person (name) values (?)";

        final int batchSize = 5; // Set your batch size here
        int count = 0;
        int aux = 0;

        // Get the total number of '?' in the query
        int totalQueryParameters = Utils.countCharOccurrences(parameterizedQuery, '?');
        final int auxTotalQueryParameters = totalQueryParameters;

        final PreparedStatement preparedStatement = connection.prepareStatement(parameterizedQuery);

        // Auto Commit must be set to false
        connection.setAutoCommit(false);

        for(int i = 0; i < parameters.size(); i++)
        
            Object obj = parameters.get(i);

            aux++;
            preparedStatement.setObject(aux, obj);

            if(totalQueryParameters == i + 1)  // Because the ArrayList starts from zero.
                // First query "parsed" - > Add to batch
                preparedStatement.addBatch();
                // One query has been added to the batch. Re-adapt the cycle.
                totalQueryParameters = totalQueryParameters + auxTotalQueryParameters;
                aux = 0;
            

            if(++count % batchSize == 0) 
                preparedStatement.executeBatch();
            
        

        preparedStatement.executeBatch(); // insert remaining queries
        preparedStatement.close();
        connection.setAutoCommit(true); // Make it back to default.
     catch (SQLException ex) 
        // Do the rollback
        doRollback(connection);

        try 
            // Make it back to default.
            connection.setAutoCommit(true);
         catch (SQLException ex1) 
            ex1.printStackTrace();
        

        // Dont forget to close the preparedStatement and the connection
        // if you don't need the connection open any more.

        ex.printStackTrace();
    



private void doRollback(Connection c) 
    try 
        c.rollback();
     catch (SQLException ex) 
        ex.printStackTrace();
    

【讨论】:

感谢您提供这个完整的示例。但是,我认为您的 catch 子句中可能有错误:// Make it back to default. connection.setAutoCommit(false); 我相信您也想在此处将其设置为 true【参考方案2】:

事实上 PreparedStatement.executeBatch 并没有澄清问题,也许在其他地方,但我确信它不是原子操作,因为 SQL 没有批处理操作,因此 executeBatch 在数据库级别分别执行每个语句。我在 mysql 上测试过:

t1 是一个空表,有 n1 个 INT(11) Not Null 列,autocommit = true

    ResultSet rs1 = conn.createStatement().executeQuery("select count(*) from t1");
    rs1.next();
    System.out.println(rs1.getInt(1));

    String query = "insert into t1 (n1) values(?)";
    PreparedStatement ps = conn.prepareStatement(query);
    ps.setObject(1, 1);
    ps.addBatch();
    ps.setObject(1, null);
    ps.addBatch();
    try 
        ps.executeBatch();
     catch (Exception e) 
        System.out.println(e);
    

    ResultSet rs2 = conn.createStatement().executeQuery("select count(*) from t1");
    rs2.next();
    System.out.println(rs2.getInt(1));

打印出来

0
java.sql.BatchUpdateException: Column 'n1' cannot be null
1

即批次中有2个插入;第一次成功,第二次失败,仍然 t1 有 1 行

【讨论】:

有趣.. 所以,假设有两个线程 t1 和 t2 更新表中的记录。如果 t1 正在更新 1-100 条记录,而 t2 正在更新 50-150 条记录。当两个线程都尝试执行“executeBatch”时,t2 将在 t1 上被阻塞,因为 t1 锁定了这些记录。如果 t2 提交失败(由于某种原因),是否会提交记录(部分)并释放锁50-150?还是我们应该明确地对其执行回滚? 但是你说 autocommit = true。主要的一点是,executeBatch 带有 2 个(或更多)插入/更新语句的行为与执行 2 个单独的 executeUpdate 语句相同。 知道了。所以,在这种情况下不会有任何回滚。如果批处理有 1-10,如果它在 5 处出现异常,则将提交 1-4,而不会执行 5-10。仪式? 当然可以,但我建议您模拟和测试您有疑问的任何情况【参考方案3】:

棘手的一个, autocommit=true 强烈不推荐,执行批处理时。

话虽如此,我建议使用getUpdateCount() 并构建逻辑以执行剩余的操作。

终于commit

【讨论】:

以上是关于使用带有 autocommit=true 的 jdbc 时回滚批处理执行的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 Hibernate 中不推荐“hibernate.connection.autocommit = true”?

无法将 AutoCommit 设置为 true - Weblogic 12 - SQL Server

JDBCTemplate在AutoCommit为True时手动控制事务

can't call rollback when autocommit=true配置文件my.ini怎么修改

明明已经设置了setAutoCommit(false);为啥还是报错AutoCommit 模式设置为"true"时,无法调用回滚操作.

有没有办法在 MySQL 中获取 autocommit 的默认值?