我正在努力解决一个管理非平凡数据模型的EJB3类的问题。当我的容器管理的事务性方法提交时,我会抛出约束验证异常。我希望防止它们被包装在EJBException
中,而是抛出调用者可以处理的合理的应用程序异常。
要将它包装在合适的应用程序异常中,我必须能够捕获它。大多数情况下,一个简单的try/catch就可以完成这项工作,因为验证异常是从我进行的EntityManager
调用中抛出的。
不幸的是,某些约束仅在提交时检查。例如,只有当容器管理的事务提交时,映射集合上的@Size(min=1)
冲突才会被捕获,一旦它在我的事务方法末尾离开我的控制。我无法捕获验证失败时抛出的异常并对其进行包装,因此容器将其包装在javax.transaction.RollbackException
中,并将其包装在受诅咒的EJBException
中。调用者必须捕获所有的EJBException
并深入到原因链中,试图找出这是否是一个验证问题,这真的很不好。
我正在使用容器管理的事务,所以我的EJB看起来像这样:
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER
class TheEJB {
@Inject private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public methodOfInterest() throws AppValidationException {
try {
// For demonstration's sake create a situation that'll cause validation to
// fail at commit-time here, like
someEntity.getCollectionWithMinSize1().removeAll();
em.merge(someEntity);
} catch (ValidationException ex) {
// Won't catch violations of @Size on collections or other
// commit-time only validation exceptions
throw new AppValidationException(ex);
}
}
}
..。其中,AppValidationException
是已检查异常或未检查异常,注释为@ApplicationException
,因此不会被EJB3包装。
有时,我可以使用EntityManager.flush()
触发早期的约束冲突并捕获它,但并不总是如此。即使这样,我也希望能够捕获延迟约束检查在提交时抛出的数据库级约束冲突,而这些冲突只有在JTA提交时才会出现。
帮助?
已经尝试过的:
通过允许我在我控制的代码中触发提交,Bean managed transactions将解决我的问题。不幸的是,它们不是一种选择,因为bean管理的事务不提供任何与TransactionAttributeType.REQUIRES_NEW
等价的东西--没有办法使用BMT挂起事务。JTA的一个恼人的疏忽。
请参见:
..。但有关警告和详细信息,请参阅答案。
javax.validation.ValidationException
是一个JDK异常;我不能修改它来添加一个批注来防止包装。我不能子类化它来添加注释;它是由EclpiseLink抛出的,而不是我的代码。我不确定将其标记为@ApplicationException
是否会阻止Arjuna (AS7的JTA impl)将其包装在RollbackException
中。
我尝试这样使用EJB3拦截器:
@AroundInvoke
protected Object exceptionFilter(InvocationContext ctx) throws Exception {
try {
return ctx.proceed();
} catch (ValidationException ex) {
throw new SomeAppException(ex);
}
}
..。但是似乎拦截器在JTA内部触发(这是合理的,而且通常是可取的),所以我想捕获的异常还没有被抛出。
我想我想要的是能够定义一个异常过滤器,在 JTA完成它的事情之后应用。有什么想法吗?
我正在使用JBoss AS 7.1.1.Final和EclipseLink 2.4.0。根据these instructions,EclipseLink是作为JBoss模块安装的,但这对手头的问题并不重要。
更新:在对这个问题进行了更多的思考之后,我意识到除了JSR330验证异常之外,我还真的需要能够捕获来自DB和deadlock or serialization failure rollbacks with SQLSTATE 40P01 and 40001 respectively的SQLIntegrityConstraintViolationException。这就是为什么仅仅试图确保提交永远不会抛出的方法不能很好地工作。检查过的应用程序异常不能通过JTA提交抛出,因为JTA接口自然不会声明它们,但未检查的@ApplicationException
注释异常应该能够这样做。
似乎在任何我可以有效地捕获应用程序异常的地方,我也可以捕获EJBException并在其中深入研究JTA异常和底层验证或JDBC异常,然后基于此进行决策。如果JTA中没有异常过滤功能,我可能不得不这么做。
发布于 2012-08-03 09:01:27
我还没试过这个。但我猜这应该能行得通。
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
class TheEJB {
@Inject
private TheEJB self;
@Inject private EntityManager em;
public methodOfInterest() throws AppValidationException {
try {
self.methodOfInterestImpl();
} catch (ValidationException ex) {
throw new AppValidationException(ex);
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public methodOfInterestImpl() throws AppValidationException {
someEntity.getCollectionWithMinSize1().removeAll();
em.merge(someEntity);
}
}
容器需要启动一个新的事务并在 methodOfInterest
中提交,因此您应该能够捕获包装器方法中的异常。
Ps:答案是基于@LairdNelson提供的优雅想法而更新的……
发布于 2012-08-06 12:26:30
对于我在原始问题中所说的REQUIRES_NEW
和BMT,有一个警告。
请参阅容器责任中的EJB3.1规范,13.6.1节spec管理的事务界定。它写道:
容器必须管理客户端对具有bean管理的事务划分的企业bean实例的调用,如下所示。当客户端通过企业bean的客户端视图之一调用业务方法时,容器将挂起可能与客户端请求相关联的任何事务。如果存在与该实例相关联的事务(如果有状态会话bean实例在以前的某个业务方法中启动了该事务,则会发生这种情况),容器会将该方法的执行与该事务相关联。如果有与bean实例相关联的拦截器方法,则在调用拦截器方法之前执行这些操作。
(斜体我的)。这一点很重要,因为这意味着BMT EJB不会继承具有关联容器托管tx的调用方的JTA tx。任何当前的tx都被挂起,因此如果BMT EJB创建了一个tx,那么它就是一个新事务,当它提交时,它只提交它的事务。
这意味着您可以使用BMT方法开始和提交事务,就好像它是有效的REQUIRES_NEW
一样,并执行以下操作:
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
class TheEJB {
@Inject private EntityManager em;
@Resource private UserTransaction tx;
// Note: Any current container managed tx gets suspended at the entry
// point to this method; it's effectively `REQUIRES_NEW`.
//
public methodOfInterest() throws AppValidationException, SomeOtherAppException {
try {
tx.begin();
// For demonstration's sake create a situation that'll cause validation to
// fail at commit-time here, like
someEntity.getCollectionWithMinSize1().removeAll();
em.merge(someEntity);
tx.commit();
} catch (ValidationException ex) {
throw new AppValidationException(ex);
} catch (PersistenceException ex) {
// Go grubbing in the exception for useful nested exceptions
if (isConstraintViolation(ex)) {
throw new AppValidationException(ex);
} else {
throw new SomeOtherAppException(ex);
}
}
}
}
这使提交处于我的控制之下。当我不需要一个事务跨越多个不同EJB的多个调用时,这允许我在代码中处理所有错误,包括提交时的错误。
Java EE 6 tutorial page on bean managed transactions没有提到这一点,也没有提到BMT是如何被调用的。
关于BMT不能在我链接的博客中模拟REQUIRES_NEW
的说法是正确的,只是不清楚。如果您有一个bean管理的事务性EJB,那么您不能为了开始另一个事务而挂起一个已经开始的事务。调用单独的helper EJB可能会挂起您的tx,并为您提供等同于REQUIRES_NEW
的服务,但我还没有测试过。
问题的另一半-我需要容器管理事务的情况,因为我有必须跨几个不同的EJB和EJB方法完成的工作-通过防御性编码解决。
实体管理器的早期紧急刷新允许我捕获我的JSR330验证规则可以发现的任何验证错误,所以我只需要确保它们是完整和全面的,这样我就不会在提交时从数据库获得任何检查约束或完整性冲突错误。我不能干净利落地处理它们,所以我真的需要防御性地避免它们。
防御性编码的一部分是:
javax.validation
注释,以及在不是集合上的javax.validation
约束的情况下使用@AssertTrue
验证方法,我很高兴看到这些都得到了支持。例如,我有一个具有B
集合的实体A
。A
必须至少有一个B
,所以我在A
.B
验证器中定义的JSR330集合中添加了一个@Size(min=1)
约束。我已经为澳大利亚商业编号( an )等内容添加了几个自定义验证器,以确保我不会尝试将无效的验证器发送到数据库并触发DB级验证错误。EntityManager.flush()
提前刷新实体管理器。这会强制验证在代码控制下进行,而不是在JTA去提交EJB外观中的transaction.REQUIRES_NEW
方法强制提前提交,并允许我处理EJB中的故障,适当地重试等。这有时需要一个helper EJB来解决自调用业务方法的问题。我仍然不能像在Swing中直接使用JDBC时那样优雅地处理和重试序列化失败或死锁,因此来自容器的所有这些“帮助”使我在某些方面倒退了几步。不过,它在其他地方节省了大量繁琐的代码和逻辑。
在发生这些错误的地方,我添加了一个UI框架级别的异常过滤器。它看到EJBException
包装JTA RollbackException
包装PersistenceException
包装特定于EclipseLink的异常包装PSQLException
,检查SQLState,并基于此做出决策。这很荒谬,但却很管用
发布于 2012-08-06 12:51:32
将eclipselink.exception-handler
属性设置为指向ExceptionHandler
的实现看起来很有希望,但却行不通。
ExceptionHandler
的JavaDoc是...坏的..。因此,您需要查看test implementation和使用它的测试(1、2)。还有一些更有用的文档here。
似乎很难使用异常筛选器来处理一些特定的情况,而不影响其他所有情况。我想捕获PSQLException
,检查SQLSTATE 23514 (违反CHECK
约束),抛出一个有用的异常,否则不会改变任何东西。这看起来不切实际。
最后,我放弃了这个想法,在可能的情况下转而使用bean管理的事务(现在我正确地理解了它们是如何工作的),并采用了一种防御性的方法来防止在使用JTA容器管理的事务时出现不必要的异常。
https://stackoverflow.com/questions/11774722
复制相似问题