强制事务回滚 Seam 中的验证错误 [英] Forcing a transaction to rollback on validation errors in Seam

查看:24
本文介绍了强制事务回滚 Seam 中的验证错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

快速版本:我们正在寻找一种在支持 bean 上执行方法期间发生特定情况时强制事务回滚的方法,但我们希望回滚发生而不必向用户显示通用的 500 错误页面.相反,我们希望用户看到她刚刚提交的表单和表明问题所在的 FacesMessage.

Quick version: We're looking for a way to force a transaction to rollback when specific situations occur during the execution of a method on a backing bean but we'd like the rollback to happen without having to show the user a generic 500 error page. Instead, we'd like the user to see the form she just submitted and a FacesMessage that indicates what the problem was.

长版:我们有一些支持 bean,它们使用组件在数据库中执行一些相关的操作(使用 JPA/Hibernate).在此过程中,某些数据库操作发生后可能会发生错误.这可能是出于几个不同的原因,但对于这个问题,让我们假设在某些 DB 写入发生后检测到验证错误,而在写入发生之前无法检测到.发生这种情况时,我们希望确保到目前为止的所有数据库更改都将回滚.Seam 可以解决这个问题,因为如果您从当前 FacesRequest 中抛出 RuntimeException,Seam 将回滚当前事务.

Long version: We've got a few backing beans that use components to perform a few related operations in the database (using JPA/Hibernate). During the process, an error can occur after some of the database operations have happened. This could be for a few different reasons but for this question, let's assume there's been a validation error that is detected after some DB writes have happened that weren't detectible before the writes occurred. When this happens, we'd like to make sure all of the db changes up to this point will be rolled back. Seam can deal with this because if you throw a RuntimeException out of the current FacesRequest, Seam will rollback the current transaction.

这样做的问题是向用户显示了一个通用错误页面.在我们的例子中,我们实际上希望向用户显示她所在的页面,并显示有关出错的描述性消息,并有机会纠正导致问题的错误输入.我们提出的解决方案是从发现注解验证问题的组件抛出异常:

The problem with this is that the user is shown a generic error page. In our case, we'd actually like the user to be shown the page she was on with a descriptive message about what went wrong, and have the opportunity to correct the bad input that caused the problem. The solution we've come up with is to throw an Exception from the component that discovers the validation problem with the annotation:

@ApplicationException( rollback = true )

然后我们的后台 bean 可以捕获这个异常,假设抛出它的组件已经发布了适当的 FacesMessage,并且简单地返回 null 以将用户带回显示错误的输入页面.ApplicationException 注释告诉 Seam 回滚事务,我们不会向用户显示通用错误页面.

Then our backing bean can catch this exception, assume the component that threw it has published the appropriate FacesMessage, and simply return null to take the user back to the input page with the error displayed. The ApplicationException annotation tells Seam to rollback the transaction and we're not showing the user a generic error page.

这对于我们第一次使用它的地方效果很好,碰巧只用于插入.第二个我们尝试使用它的地方,我们必须在这个过程中删除一些东西.在第二种情况下,如果没有验证错误,一切正常.如果确实发生了验证错误,则会抛出回滚异常并将事务标记为回滚.即使没有数据库修改发生回滚,当用户修复坏数据并重新提交页面时,我们得到:

This worked well for the first place we used it that happened to only be doing inserts. The second place we tried to use it, we have to delete something during the process. In this second case, everything works if there's no validation error. If a validation error does happen, the rollback Exception is thrown and the transaction is marked for rollback. Even if no database modifications have happened to be rolled back, when the user fixes the bad data and re-submits the page, we're getting:

java.lang.IllegalArgumentException: Removing a detached instance 

分离的实例是从另一个对象延迟加载的(存在多对一关系).在实例化支持 bean 时加载该父对象.因为事务在验证错误后回滚,对象现在被分离.

The detached instance is lazily loaded from another object (there's a many to one relationship). That parent object is loaded when the backing bean is instantiated. Because the transaction was rolled back after the validation error, the object is now detached.

我们的下一步是将此页面从对话范围更改为页面范围.当我们这样做时,Seam 甚至无法在验证错误后呈现页面,因为我们的页面必须命中 DB 才能呈现并且事务已被标记为回滚.

Our next step was to change this page from conversation scope to page scope. When we did this, Seam can't even render the page after the validation error because our page has to hit the DB to render and the transaction has been marked for rollback.

所以我的问题是:其他人如何同时干净利落地处理错误并正确管理事务?更好的是,如果有人能发现我做错的事情并且相对容易修复,我希望能够使用我们现在拥有的一切.

So my question is: how are other people dealing with handling errors cleanly and properly managing transactions at the same time? Better yet, I'd love to be able to use everything we have now if someone can spot something I'm doing wrong that would be relatively easy to fix.

我已经阅读了关于统一错误页面和异常处理的Seam框架文章,但这是针对的更倾向于您的应用程序可能遇到的更常见的错误.

I've read the Seam Framework article on Unified error page and exception handling but this is geared more towards more generic errors your application might encounter.

更新:这是页面流程的一些伪代码和详细信息.

Update: here's some psudo-code and details of the page flow.

在这种情况下,假设我们正在编辑一些用户的信息(在这种情况下,我们实际上并未与用户打交道,但我不会发布实际代码).

In this case, assume we're editing some user's information (we're not actually dealing with a user in this case but I'm not going to post the actual code).

编辑功能的 edit.page.xml 文件包含一个简单的 RESTful URL 重写模式和两个导航规则:

The edit functionality's edit.page.xml file contains a simple re-write pattern for a RESTful URL and two navigation rules:

  1. 如果结果编辑成功,则将用户重定向到相应的查看页面以查看更新的信息.
  2. 如果用户点击取消按钮,则将用户重定向到相应的查看页面.

edit.xhtml 非常基本,其中包含可编辑用户所有部分的字段.

The edit.xhtml is pretty basic with fields for all of the parts of a user that can be edited.

支持 bean 具有以下注释:

The backing bean has the following annotations:

@Name( "editUser" )
@Scope( ScopeType.PAGE )

有一些注入的组件,如用户:

There are some injected components like the User:

@In
@Out( scope = ScopeType.CONVERSATION ) // outjected so the view page knows what to display
protected User user;

我们在支持 bean 上有一个保存方法,它为用户保存委托工作:

We have a save method on the backing bean that delegates the work for the user 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 看起来像:

Our GuaranteedRollbackException looks like:

@ApplicationException( rollback = true )
public class GuaranteedRollbackException extends RuntimeException
{
    public GuaranteedRollbackException(String message) {
        super(message);
    }
}

UserManager.modifyUser 看起来像这样:

UserManager.modifyUser looks something like this:

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 做类似的事情

ModifyUser.modifyOrg does something like

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.modifyUser is similar to modifyOrg.

现在(你将不得不和我一起进行这个飞跃,因为这听起来不一定是这个用户场景的问题,但它是我们正在做的事情)假设更改组织会导致修改用户无法验证,但不可能提前验证此失败.我们已经将组织更新写入当前 txn 中的数据库,但由于用户修改无法验证,GuaranteedRollbackException 将标记要回滚的事务.使用此实现,当我们再次呈现编辑页面以显示验证器添加的错误消息时,我们无法在当前范围内使用数据库.在渲染时,我们点击了 db 以获取要在页面上显示的内容,这是不可能的,因为 Session 无效:

Now (you're going to have to take this leap with me because it doesn't necessarily sound like it's a problem with this User scenario but it is for the stuff we're doing) assume changing the org causes the modifyUser to fail to validate but that it's impossible to validate this failure ahead of time. We've already written the org update to the db in our current txn but since the user modify fails to validate, the GuaranteedRollbackException will mark the transaction to be rolled back. With this implementation, we're not able to use the DB in the current scope when we're rendering the edit page again to display the error message added by the validator. While rendering, we hit the db to get something to display on the page and that isn't possible because the Session is invalid:

由 org.hibernate.LazyInitializationException 引起,消息为:无法初始化代理 - 无会话"

Caused by org.hibernate.LazyInitializationException with message: "could not initialize proxy - no Session"

推荐答案

我必须同意 @duffymo 关于在启动事务之前进行验证的意见.处理数据库异常并将其呈现给用户是非常困难的.

I must agree with @duffymo about validating before the transaction is initiated. It is quite difficult to handle database exceptions and presenting those to the user.

您收到分离异常的原因很可能是因为您认为您已将某些内容写入数据库,然后您对该对象调用了 remove on 或 refresh,然后您又尝试再次写入内容.

The reason you get the detached exception is most likely because you think you have written something to the database, and then you call remove on or refresh on the object, and then you try to write something again.

您需要做的是创建一个长时间运行的对话,并将flushMode 设置为MANUAL.然后你开始坚持一些东西,然后你可以执行你的验证,如果没问题,你再次坚持.在您完成并且一切顺利之后,您调用 entityManager.flush().这会将所有内容保存到数据库中.

What you need to do instead is create a long-running conversation with flushMode set to MANUAL. Then you start persisting stuff, and then you can perform your validation, and if that is ok you persist again. After you are done and everything is good to go, you call entityManager.flush(). Which will save everything to the database.

如果出现故障,您就不会刷新.您只需 return null"error" 和一些消息.让我给你看一些伪代码.

And if something failed, you dont flush. You just return null or "error" with some message. Let me show you with some pseudo code.

假设您有一个 Person 和 Organization 实体.现在,您需要先存储 Person,然后才能将 person 放入 Organization.

Lets say you have a Person and Organization entity. Now you need to store Person before you can put person to Organization.

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";
}
}

请注意,我们现在保存了 person,但我们还没有刷新交易.但是,它会检查您在实体bean 上设置的约束.(@NotNull、@NotEmpty 等).所以它只会模拟保存.

Note that we now save person, but we have not flushed the transaction. However, it will check constraints that you have set on your entitybean. (@NotNull, @NotEmpty and so on). So it will only simulate a save.

现在您可以为个人保存组织.

Now you save organization for person.

@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中,只有在没有异常发生时才返回成功,这样你就不会被抛出到错误页面.

Here you can even put stuff in try catch and only return success if no exception occurred, so that you don't get thrown to error page.

更新

你可以试试这个:

@PersistenceContext(type=EXTENDED)
EntityManager em;

这将使有状态 bean 具有 EJB3 扩展持久性上下文.只要 bean 存在,查询中检索到的消息就保持在托管状态,因此对有状态 bean 的任何后续方法调用都可以更新它们,而无需对 EntityManager 进行任何显式调用.这可能会避免您的 LazyInitializationException.您现在可以使用em.refresh(user);

This will make a stateful bean has an EJB3 extended persistence context. The messages retrieved in the query remain in the managed state as long as the bean exists, so any subsequent method calls to the stateful bean can update them without needing to make any explicit call to the EntityManager. This might avoid your LazyInitializationException. You can now use em.refresh(user);

这篇关于强制事务回滚 Seam 中的验证错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆