简体   繁体   English

防止JBoss + Hibernate中的事务回滚

[英]Prevent transaction rollback in JBoss + Hibernate

We have a Java application running on JBoss 5.1 and in some cases we need to prevent a transaction from being closed in case a JDBCException is thrown by some underlying method. 我们有一个在JBoss 5.1上运行的Java应用程序,在某些情况下,我们需要防止在某些基础方法抛出JDBCException情况下关闭事务。

We have an EJB method that looks like the following one 我们有一个类似于下面的EJB方法

@PersistenceContext(unitName = "bar")
public EntityManager em;

public Object foo() {
  try {
    insert(stuff);
    return stuff;
  } (catch PersistenceException p) {
    Object t = load(id);
    if (t != null) {
      find(t);
      return t;
    }
  }
}

If insert fails because of a PersistenceException (which wraps a JDBCException caused by a constraint violation), we want to continue execution with load within the same transaction. 如果insertPersistenceException (包装因违反约束而导致的JDBCException而失败,我们希望在同一事务中继续执行load

We are unable to do it right now because the transaction is closed by the container. 我们现在无法执行此操作,因为事务已由容器关闭。 Here's what we see in the logs: 这是我们在日志中看到的内容:

org.hibernate.exception.GenericJDBCException: Cannot open connection
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Cannot open connection
    at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614)

   ...

Caused by: javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000101:85fe:4f04679d:182 status: ActionStatus.ABORT_ONLY >

The EJB class is marked with the following annotations EJB类标有以下注释

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

Is there any proper way to prevent the transaction from rolling back in just this specific case? 是否有任何正确的方法可以阻止事务在这种特定情况下回滚?

You really should not try to do that. 你真的不应该试着这样做。 As mentioned in another answer, and quoting Hibernate Docs, no exception thrown by Hibernate should be treated as recoverable. 正如另一个答案中所提到的,并且引用了Hibernate Docs,Hibernate抛出的异常应该被视为可恢复的。 This could lead you to some hard to find/debug problems, specially with hibernate automatic dirty checking. 这可能会导致一些难以找到/调试的问题,尤其是使用hibernate自动脏检查。

A clean way to solve this problem is to check for those constraints before inserting the object. 解决此问题的一种干净方法是在插入对象之前检查这些约束。 Use a query for checking if a database constraint is being violated. 使用查询来检查是否违反了数据库约束。

public Object foo() {
    if (!objectExists()) {
        insertStuff();
        return stuff();
    }
    // Code for loading object...
}

I know this seems a little painful, but that's the only way you'll know for sure which constraint was violated (you can't get that information from Hibernate exceptions). 我知道这看起来有点痛苦,但这是你能确定哪个约束被违反的唯一方法(你无法从Hibernate例外中获取这些信息)。 I believe this is the cleanest solution (safest, at least). 我相信这是最干净的解决方案(至少最安全)。


If you still want to recover from the exception, you'd have to make some modifications to your code. 如果您仍想从异常中恢复,则必须对代码进行一些修改。

As mentioned, you could manage the transactions manually, but I don't recommend that. 如上所述,您可以手动管理事务,但我不建议这样做。 The JTA API is really cumbersome. JTA API非常麻烦。 Besides, if you use Bean Managed Transaction (BMT), you'd have to manually create the transactions for every method in your EJB, it's all or nothing. 此外,如果您使用Bean管理事务(BMT),则必须为EJB中的每个方法手动创建事务,它是全部或全部。

On the other side, you could refactor your methods so the container would use a different transaction for your query. 另一方面,您可以重构您的方法,以便容器为您的查询使用不同的事务。 Something like this: 像这样的东西:

@Stateless
public class Foo {
    ...
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public Object foo() {
        try {
            entityManager.insert(stuff);
            return stuff;
        } catch (PersistenceException e) {
            if (e.getCause() instanceof ConstraintViolationException) {
                // At this point the transaction has been rolled-backed.
                // Return null or use some other way to indicate a constrain
                // violation
                return null;
            }
            throw e;
        }
    }

    // Method extracted from foo() for loading the object.
    public Object load() {
        ...
    }
}

// On another EJB
@EJB
private Foo fooBean;

public Object doSomething() {
    Object foo = fooBean.insert();
    if (foo == null) {
        return fooBean.load();
    }

    return foo;
}

When you call foo(), the current transaction (T1) will be suspended and the container will create a new one (T2). 当你调用foo()时,当前事务(T1)将被挂起,容器将创建一个新事务(T2)。 When the error occurs, T2 will be rolled-backed, and T1 will be restored. 发生错误时,将回滚T2,并恢复T1。 When load() is called, it will use T1 (which is still active). 调用load()时,它将使用T1(仍处于活动状态)。

Hope this helps! 希望这可以帮助!

I don't think it's possible. 我不认为这是可能的。

In may depend on your JPA provider, but, for example, Hibernate explicitly states that any exception leaves session in inconsistent state and thus shouldn't be treated as recoverable ( 13.2.3. Exception handling ). In可能依赖于您的JPA提供程序,但是,例如,Hibernate明确声明任何异常都会使会话处于不一致状态,因此不应被视为可恢复( 13.2.3。异常处理 )。

I guess the best thing you can do is to disable automatic transaction management for this method and create a new transaction after exception manually (using UserTransaction , as far as I remember). 我想你能做的最好的事情就是禁用这种方法的自动事务管理,并在手动异常后创建一个新的事务(据我记忆,使用UserTransaction )。

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

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