快速版本:
我们正在寻找一种在后备bean上执行方法期间发生特定情况时强制事务回滚的方法,但我们希望进行回滚而不必向用户显示通用的500错误页面。相反,我们希望用户看到她刚提交的表单以及一个FacesMessage来指示问题所在。
长版:
我们有一些支持Bean,它们使用组件来执行数据库中的一些相关操作(使用JPA/Hibernate)。在此过程中,某些数据库操作已发生,然后可能会发生错误。这可能是出于几种不同的原因,但是出于这个问题,我们假设在发生某些DB写入之后检测到验证错误,而该写入写入之前是无法检测到的。发生这种情况时,我们希望确保到目前为止的所有数据库更改都将被回滚。 Seam可以处理此问题,因为如果您从当前FacesRequest中抛出RuntimeException,Seam将回滚当前事务。
问题是向用户显示了一个通用错误页面。在我们的案例中,我们实际上希望向用户显示她所在的页面,并附上描述错误原因的描述性消息,并有机会纠正导致问题的错误输入。我们想出的解决方案是从组件中抛出异常,该异常会发现带有批注的验证问题:
@ApplicationException( rollback = true )
然后,我们的后备bean可以捕获此异常,并假设抛出该异常的组件已经发布了适当的FacesMessage,然后简单地返回null即可将用户带回显示错误的输入页面。 ApplicationException注释告诉Seam回滚事务,我们没有向用户显示通用错误页面。
这在我们第一次使用它时恰好只是在做插入操作的地方效果很好。我们尝试使用它的第二个地方,我们必须在此过程中删除某些内容。在第二种情况下,如果没有验证错误,则一切正常。如果确实发生验证错误,则会引发rollback Exception并将该事务标记为回滚。即使没有发生任何数据库修改都回滚的情况,当用户修复不良数据并重新提交页面时,我们也会得到:
java.lang.IllegalArgumentException: Removing a detached instance
分离的实例是从另一个对象延迟加载的(存在多对一关系)。实例化后备bean时,将加载该父对象。由于验证错误后事务已回滚,因此该对象现在已分离。
我们的下一步是将该页面从对话范围更改为页面范围。当我们这样做时,Seam甚至无法在验证错误后呈现页面,因为我们的页面必须命中数据库才能呈现,并且该事务已被标记为可回滚。
所以我的问题是:其他人如何干净地处理错误并同时正确地管理事务?更好的是,如果有人可以发现我做错的事情相对容易修复,那么我希望能够使用我们现在拥有的所有功能。
我已经阅读了有关Unified error page and exception handling的Seam Framework文章,但这更适合于您的应用程序可能遇到的更常见的错误。
更新:这是一些伪代码和页面流的详细信息。
在这种情况下,假设我们正在编辑某些用户的信息(在这种情况下,我们实际上并未与用户打交道,但我不会发布实际的代码)。
编辑功能的edit.page.xml文件包含一个用于RESTful URL的简单重写模式和两个导航规则:
edit.xhtml非常基本,其中包含可以编辑的用户所有部分的字段。
支持bean具有以下注释:
@Name( "editUser" )
@Scope( ScopeType.PAGE )
有一些注入(inject)的组件,例如User:
@In
@Out( scope = ScopeType.CONVERSATION ) // outjected so the view page knows what to display
protected User user;
我们在支持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相似。
现在(您将不得不接受这一飞跃,因为这不一定听起来像是此User场景的问题,但这是针对我们正在做的事情)假设更改组织会导致ModifyUser失败,验证,但无法提前验证此失败。我们已经在当前的txn中将组织更新写入数据库,但是由于用户修改无法通过验证,因此GuaranteedRollbackException将标记事务回滚。通过此实现,当我们再次呈现编辑页面以显示 validator 添加的错误消息时,我们将无法在当前范围内使用数据库。渲染时,我们点击db以使某些内容显示在页面上,这是不可能的,因为Session无效:
由org.hibernate.LazyInitializationException引起,并带有消息:“无法初始化代理-没有 session ”
最佳答案
我必须同意@duffymo关于在启动事务之前进行验证的信息。处理数据库异常并将其呈现给用户非常困难。
出现分离异常的原因很可能是因为您认为已向数据库中写入了一些内容,然后调用了对象的remove或refresh,然后尝试再次写入一些内容。
您需要做的是创建一个long-running conversation
,并将flushMode
设置为MANUAL
。
然后,您开始保存内容,然后可以执行验证,如果可以,则再次保存。完成后,一切都会顺利进行,您可以调用entityManager.flush()
。它将所有内容保存到数据库。
如果发生故障,则不要冲洗。您只需使用一些消息return null
或"error"
。让我向您展示一些伪代码。
假设您有一个“个人和组织”实体。
现在,您需要先存储人员,然后才能将人员放入组织。
private Person person;
private Organization org;
@Begin(join=true,FlushMode=MANUAL) //yes syntax is wrong, but you get the point
public String savePerson() {
//Inside some save method, and person contains some data that user has filled through a form
//Now you want to save person if they have name filled in (yes I know this example should be done from the view, but this is only an example
try {
if("".equals(person.getName()) {
StatusMessages.instance().add("User needs name");
return "error"; //or null
}
entityManager.save(person);
return "success";
} catch(Exception ex) {
//handle error
return "failure";
}
}
请注意,我们现在保存了人员,但尚未刷新交易。但是,它将检查您在entitybean上设置的约束。 (@ NotNull,@ NotEmpty等)。因此,它将仅模拟保存。
现在,您可以为人保存组织。
@End(FlushMode=MANUAL) //yes syntax is wrong, but you get the point
public String saveOrganization() {
//Inside some save method, and organization contains some data that user has filled through a form, or chosen from combobox
org.setPerson(person); //Yes this is only demonstration and should have been collection (OneToMany)
//Do some constraint or validation check
entityManager.save(org);
//Simulate saving org
//if everything went ok
entityManager.flush() //Now person and organization is finally stored in the database
return "success";
}
在这里,您甚至可以将内容放入
try catch
中,并且仅在没有异常发生时返回成功,这样您才不会被抛出错误页面。更新
您可以尝试以下方法:
@PersistenceContext(type=EXTENDED)
EntityManager em;
这将使有状态Bean具有EJB3扩展的持久性上下文。只要bean存在,在查询中检索到的消息就保持受管状态,因此对有状态bean的任何后续方法调用都可以更新它们,而无需对EntityManager进行任何显式调用。这可以避免您的LazyInitializationException。
您现在可以使用
em.refresh(user);
关于java - 强制事务回退Seam中的验证错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2822746/