防止JBoss + Hibernate中的事务回滚

时间:2012-01-04 16:52:32

标签: java jpa transactions jboss ejb-3.0

我们有一个在JBoss 5.1上运行的Java应用程序,在某些情况下,我们需要防止在某些基础方法抛出JDBCException的情况下关闭事务。

我们有一个类似于以下方法的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;
    }
  }
}

如果insert由于PersistenceException(由约束违规导致包裹JDBCException)而失败,我们希望在同一事务中继续执行load

我们现在无法执行此操作,因为事务已由容器关闭。这是我们在日志中看到的内容:

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 >

EJB类标有以下注释

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

是否有任何正确的方法可以阻止事务在这种特定情况下回滚?

2 个答案:

答案 0 :(得分:5)

你真的不应该这样做。正如另一个答案中所提到的,并且引用了Hibernate Docs,Hibernate抛出的异常应该被视为可恢复的。这可能会导致一些难以找到/调试的问题,尤其是使用hibernate自动脏检查。

解决此问题的一种简洁方法是在插入对象之前检查这些约束。使用查询来检查是否违反了数据库约束。

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

我知道这看起来有点痛苦,但这是你能确定哪个约束被违反的唯一方法(你无法从Hibernate例外中获取这些信息)。我相信这是最干净的解决方案(至少最安全)。


如果您仍想从异常中恢复,则必须对代码进行一些修改。

如前所述,您可以手动管理交易,但我不建议这样做。 JTA API非常麻烦。此外,如果您使用Bean管理事务(BMT),则必须为EJB中的每个方法手动创建事务,它是全部或全部。

另一方面,您可以重构您的方法,以便容器为您的查询使用不同的事务。像这样:

@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;
}

当你调用foo()时,当前事务(T1)将被暂停,容器将创建一个新事务(T2)。发生错误时,将回滚T2,并恢复T1。当调用load()时,它将使用T1(仍处于活动状态)。

希望这有帮助!

答案 1 :(得分:2)

我不认为这是可能的。

In可能依赖于您的JPA提供程序,但是,例如,Hibernate明确声明任何异常都会使会话处于不一致状态,因此不应被视为可恢复(13.2.3. Exception handling)。

我想你能做的最好的事情就是禁用此方法的自动事务管理,并在手动异常后创建一个新的事务(据我所知,使用UserTransaction)。