2012-01-04 60 views
7

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

我们有一个看起来像下面的一个

@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执行EJB方法同样的交易。

我们现在无法做到这一点,因为交易是由容器关闭的。下面是我们在日志中看到:

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) 

,以防止有交易的回滚在短短这个特定的情况下,任何适当的方式?

回答

5

你真的不应该这样做。正如另一个答案中提到的,并且引用Hibernate Docs,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. Exception handling)。

我想你可以做的最好的事情是禁用这种方法的自动事务管理,并手动创建异常后的新事务(使用UserTransaction,据我记忆)。