在 JBoss + Hibernate 中防止事务回滚

8

我们有一个在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;
    }
  }
}

如果由于约束违规导致PersistenceException(包装了由JDBCException引起的异常)而导致insert失败,我们希望能在同一事务中使用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个回答

5

你真的不应该尝试这样做。正如另一个答案中提到的,并引用Hibernate文档,Hibernate抛出的任何异常都不应被视为可恢复的。这可能会导致一些难以找到/调试的问题,特别是在使用Hibernate自动脏检查时。

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

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

我知道这看起来有点麻烦,但这是你唯一能确定哪个约束被违反的方法(你无法从Hibernate异常中获取该信息)。我认为这是最干净的解决方案(至少是最安全的)。


如果你仍然想从异常中恢复,那么你需要对代码进行一些修改。

正如之前提到的,你可以手动管理事务,但我不建议这样做。JTA API非常麻烦。此外,如果你使用Bean Managed Transaction(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。

希望这可以帮助您!


2
我认为这是不可能的。
这可能取决于您的JPA提供程序,但例如,Hibernate明确表示任何异常都会使会话处于不一致状态,因此不应将其视为可恢复的(参见13.2.3. 异常处理)。
我想你最好的做法是禁用该方法的自动事务管理,并在异常后手动创建一个新事务(使用UserTransaction,就我所知)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接