【发布时间】:2010-05-12 21:01:59
【问题描述】:
快速版本: 我们正在寻找一种在支持 bean 上执行方法期间发生特定情况时强制事务回滚的方法,但我们希望回滚发生而不必向用户显示一般的 500 错误页面。相反,我们希望用户看到她刚刚提交的表单和指示问题所在的 FacesMessage。
长版: 我们有一些支持 bean,它们使用组件在数据库中执行一些相关操作(使用 JPA/Hibernate)。在此过程中,在某些数据库操作发生后可能会发生错误。这可能是由于几个不同的原因,但对于这个问题,我们假设在发生某些 DB 写入后检测到验证错误,而在写入发生之前无法检测到该错误。发生这种情况时,我们希望确保到此为止的所有数据库更改都将被回滚。 Seam 可以处理这个问题,因为如果您从当前 FacesRequest 中抛出 RuntimeException,Seam 将回滚当前事务。
这样做的问题是向用户显示了一个通用错误页面。在我们的案例中,我们实际上希望向用户显示她所在的页面,其中包含有关问题的描述性消息,并有机会更正导致问题的错误输入。我们提出的解决方案是从发现注释验证问题的组件中抛出一个异常:
@ApplicationException( rollback = true )
然后我们的支持 bean 可以捕获这个异常,假设抛出它的组件已经发布了适当的 FacesMessage,并简单地返回 null 以将用户带回到显示错误的输入页面。 ApplicationException 注释告诉 Seam 回滚事务,我们不会向用户显示一般错误页面。
这在我们第一次使用它时效果很好,它恰好只用于插入。第二个我们尝试使用它的地方,我们必须在这个过程中删除一些东西。在第二种情况下,如果没有验证错误,一切正常。如果确实发生了验证错误,则会抛出回滚异常并将事务标记为回滚。即使没有发生数据库修改被回滚,当用户修复坏数据并重新提交页面时,我们得到:
java.lang.IllegalArgumentException: Removing a detached instance
分离的实例是从另一个对象延迟加载的(存在多对一关系)。实例化支持 bean 时加载该父对象。因为验证错误后事务被回滚,所以对象现在已分离。
我们的下一步是将此页面从对话范围更改为页面范围。当我们这样做时,Seam 甚至无法在验证错误后渲染页面,因为我们的页面必须命中 DB 才能渲染,并且事务已被标记为回滚。
所以我的问题是:其他人如何干净利落地处理错误并同时正确管理事务?更好的是,如果有人能发现我做错的事情并且相对容易修复,我希望能够使用我们现在拥有的一切。
我已经阅读了 Unified error page and exception handling 上的 Seam 框架文章,但这更适合您的应用程序可能遇到的更一般的错误。
更新:这里是一些伪代码和页面流程的详细信息。
在这种情况下,假设我们正在编辑一些用户的信息(在这种情况下,我们实际上并没有与用户打交道,但我不会发布实际代码)。
编辑功能的 edit.page.xml 文件包含一个简单的 RESTful URL 重写模式和两个导航规则:
- 如果结果编辑成功,将用户重定向到相应的视图页面以查看更新的信息。
- 如果用户点击取消按钮,将用户重定向到相应的查看页面。
edit.xhtml 非常基本,包含用户所有可编辑部分的字段。
backing bean 有以下注解:
@Name( "editUser" )
@Scope( ScopeType.PAGE )
有一些注入的组件,比如用户:
@In
@Out( scope = ScopeType.CONVERSATION ) // outjected so the view page knows what to display
protected User user;
我们在 backing bean 上有一个 save 方法,它为用户 save 委派工作:
public String save()
{
try
{
userManager.modifyUser( user, newFName, newLName, newType, newOrgName );
}
catch ( GuaranteedRollbackException grbe )
{
log.debug( "Got GuaranteedRollbackException while modifying a user." );
return null;
}
return USER_EDITED;
}
我们的 GuaranteedRollbackException 看起来像:
@ApplicationException( rollback = true )
public class GuaranteedRollbackException extends RuntimeException
{
public GuaranteedRollbackException(String message) {
super(message);
}
}
UserManager.modifyUser 看起来像这样:
public void modifyUser( User user, String newFName, String newLName, String type, String newOrgName )
{
// change the user - org relationship
modifyUser.modifyOrg( user, newOrgName );
modifyUser.modifyUser( user, newFName, newLName, type );
}
ModifyUser.modifyOrg 做了类似的事情
public void modifyOrg( User user, String newOrgName )
{
if (!userValidator.validateUserOrg( user, newOrgName ))
{
// maybe the org doesn't exist something. we don't care, the validator
// will add the appropriate error message for us
throw new GauaranteedRollbackException( "couldn't validate org" );
}
// do stuff here to change the user stuff
...
}
ModifyUser.modifyUser 类似于 modifyOrg。
现在(你将不得不和我一起迈出这一步,因为这听起来不一定是这个用户场景的问题,但它是我们正在做的事情)假设更改组织会导致 modifyUser验证失败,但无法提前验证此失败。我们已经将组织更新写入当前 txn 中的数据库,但由于用户修改无法验证,因此 GuaranteedRollbackException 会将事务标记为回滚。使用此实现,当我们再次渲染编辑页面以显示验证器添加的错误消息时,我们无法在当前范围内使用数据库。在渲染时,我们点击 db 以在页面上显示一些内容,但这是不可能的,因为 Session 无效:
由 org.hibernate.LazyInitializationException 引起,消息为:“无法初始化代理 - 无会话”
【问题讨论】:
-
我可以帮你,但是你能用纯代码显示吗 第一个场景和第二个场景:组件、你的页面、导航规则和异常处理程序
-
@Arthur Ronald F D Garcia,我添加了更多信息以及一些代码片段。这需要太多代码来发布整个内容,但希望这能让您了解您需要了解的内容。
标签: java hibernate jpa transactions seam