简体   繁体   English

发生异常时如何使Hibernate不回滚

[英]How to make Hibernate not to rollback when an exception occurs

The following SQL if run in MSSQL will insert the 1st and 3rd rows successfully:如果在 MSSQL 中运行以下 SQL 将成功插入第 1 行和第 3 行:

BEGIN TRAN
INSERT ... -- valid data
INSERT ... -- invalid data (e.g. over column width)
INSERT ... -- valid data
COMMIT

Even though the second row fails within the transaction, you can still see the two rows with some valid data after the commit in the table.即使第二行在事务中失败,您仍然可以在表中看到提交后具有一些有效数据的两行。

However, when trying something similar in Hibernate, it rollbacks the whole transaction.但是,当在 Hibernate 中尝试类似的操作时,它会回滚整个事务。 Is there a way to tell Hibernate not to rollback on failed rows and commit the rest as same as how MSSQL does it?有没有办法告诉 Hibernate 不要回滚失败的行并像 MSSQL 一样提交 rest?

eg例如

EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(new MyEntity("good"));
em.persist(new MyEntity("too long"));
em.persist(new MyEntity("good"));
transaction.commit();

The way i did it is to divide your logic into diferent functions, and open the transaction inside the persisting function instead of the main one.我这样做的方式是将您的逻辑划分为不同的功能,并在持久化的 function 而不是主要的内部打开事务。

The main problem I see in your code is that you're defining a block transaction insead of opening a transaction for each operation.我在您的代码中看到的主要问题是您正在定义一个块交易而不是为每个操作打开交易。

Here's my snippet:这是我的片段:

persistEntity(new MyEntity("good"));
persistEntity(new MyEntity("bad"));
persistEntity(new MyEntity("good"));

...

private void persistEntity(MyEntity entity){
    EntityTransaction transaction = em.getTransaction();
    transaction.begin();
    em.persist(entity);
    transaction.commit();    
}

This way it will rollback just for the bad entity and keep going with the other.这样,它只会为坏实体回滚并继续与另一个实体一起使用。 You can also add a try catch inside the persistEntity method, if you want to log the exception.如果要记录异常,还可以在 persistEntity 方法中添加 try catch。

Fun fact, If you're using Spring you could create another @Component for the persist operations and only add @Transactional to the persisting method, this way you don't have to manage the transactions yourself.有趣的事实是,如果您使用的是 Spring,您可以为持久化操作创建另一个 @Component 并且只将 @Transactional 添加到持久化方法中,这样您就不必自己管理事务。

This is not possible within the same transaction .在同一个事务中是不可能的。 Hibernate simply doesn't allow this. Hibernate 根本不允许这样做。 An error in a statement leads to an exception, which Hibernate cannot recover from.语句中的错误会导致异常,Hibernate 无法从中恢复。 From the manual: 从手册:

If the JPA EntityManager or the Hibernate-specific Session throws an exception, including any JDBC SQLException, you have to immediately rollback the database transaction and close the current EntityManager or Session. If the JPA EntityManager or the Hibernate-specific Session throws an exception, including any JDBC SQLException, you have to immediately rollback the database transaction and close the current EntityManager or Session.

Certain methods of the JPA EntityManager or the Hibernate Session will not leave the Persistence Context in a consistent state. JPA EntityManager 或 Hibernate Session 的某些方法不会将 Persistence Context 留在一致的 Z69ED39E28A693A251 中As a rule of thumb, no exception thrown by Hibernate can be treated as recoverable.根据经验,Hibernate 抛出的任何异常都不能被视为可恢复的。 Ensure that the Session will be closed by calling the close() method in a finally block.通过在 finally 块中调用 close() 方法来确保 Session 将被关闭。

Now this is a restriction (design decision) of Hibernate and not of the underlying JDBC or database stack.现在这是 Hibernate 的限制(设计决策),而不是基础 JDBC 或数据库堆栈的限制(设计决策)。 So what you want is perfectly possible using JDBC directly.所以你想要的完全可以直接使用 JDBC 。 If it is really important for you to get that behaviour, you might consider using JDBC calls for this section of the code.如果获得这种行为对您来说真的很重要,您可以考虑对这部分代码使用 JDBC 调用。 There you can do it exactly like in the SQL client: open transaction, issue statements, catching any exceptions manually and "ignoring" them, and at the end committing the transaction.在那里,您可以像在 SQL 客户端中一样执行此操作:打开事务、发出语句、手动捕获任何异常并“忽略”它们,最后提交事务。

Example code:示例代码:

Session session = em.unwrap(Session.class);
session.doWork(connection -> {
    // manual commit mode
    connection.setAutoCommit(false);

    executeInsertIgnoringError(connection, new Object[]{123, null, "abc"});
    executeInsertIgnoringError(connection, new Object[]{....});
    ...

    connection.commit();
});



private void executeInsertIgnoringError(Connection connection, Object[] values) {
    try (PreparedStatement stmt = 
           connection.prepareStatement("INSERT INTO MY_ENTITY VALUES (?, ?, ?, ...)")) {
        for (int i = 0; i < values.length; i++) {
            // PreparedStatement is indexed from 1
            stmt.setObject(i+1, values[i]);
        }
        stmt.executeUpdate();
    } catch (Exception e) {
        log.warn("Error occurred, continuing.");
    }
}

Don't do so, that is idiomatically wrong, at first just review the real scope of your transactions.不要这样做,这是惯用错误的,首先只需查看您交易的真实 scope。

You could write the code to run one statement at a time with autocommit on and not use @Transactional... Then perhaps catch any exceptions and throw them away as you go.您可以编写代码以一次运行一条语句并启用自动提交而不使用@Transactional ...然后可能会捕获任何异常并将它们丢弃,就像您 go 一样。 But pretty much everything in that sentence is troublesome to even think about as a responsible developer and it would affect your entire app.但是,这句话中的几乎所有内容都很难作为一个负责任的开发人员来考虑,并且会影响你的整个应用程序。 Flavius's post would be a little more granular in doing something similar with explicitly smaller transactions and is a good way to go about it too. Flavius 的帖子会更细化地做一些与明确较小的交易类似的事情,并且也是 go 关于它的好方法。

As others have been commenting it's not a long term great plan and goes against so many ways to write programs correctly and the benefits and purpose of transactions.正如其他人所评论的那样,这不是一个长期的好计划,并且与正确编写程序的许多方法以及事务的好处和目的背道而驰。 Perhaps if you plan to only use this as a one off data ingestion plan you could but again be very wary of using these patterns in a production grade app.也许如果您打算仅将其用作一次性数据摄取计划,您可以再次对在生产级应用程序中使用这些模式非常谨慎。

Having been sufficiently alarmed, you can read more about auto commit here and also be sure to read through the post links on why you probably shouldn't use it.已经足够警觉了,您可以在此处阅读有关自动提交的更多信息,并确保通读帖子链接,了解您可能不应该使用它的原因。

Spring JPA - No transaction set autocommit 'true' Spring JPA - 没有事务集自动提交“真”

You can do that by adding below property in hibernate config xml file您可以通过在 hibernate 配置 xml 文件中添加以下属性来做到这一点

<property name="hibernate.connection.autocommit" value="true"/>

If you could use @Transactional annotation then如果您可以使用@Transactional 注释,那么

@Transactional(dontRollbackOn={SQLException.class, NOResultException.class})

Then I would suggest one some change in your code.然后我会建议对您的代码进行一些更改。 It's better if you add your entities in a loop and catch exception on each transaction.最好在循环中添加实体并在每个事务中捕获异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM