Quick version: We are looking for a way to force a transaction to be canceled if there are special situations during the execution of the bean-based method, but we would like the rollback to occur without having to show the user a 500 common error page. Instead, we would like the user to see the just presented a form and FacesMessage that indicates what the problem is.
Long version: We have several supporting beans that use components to perform several related operations in the database (using JPA / Hibernate). During the process, an error may occur after performing some database operations. This may be for several reasons, but for this question, let's say that a validation error occurred that was detected after some database entries occurred that were not detected before the entry occurred. When this happens, we would like to make sure that all changes to db up to this point will be undone. A seam can handle this because if you throw a RuntimeException from the current FacesRequest, Seam will cancel the current transaction.
The problem is that the user is shown a common error page. In our case, we would like the user to be shown the page on which it was, with a descriptive message about what went wrong, and you have the opportunity to fix the bad login that caused this problem. The solution we came up with is to throw an exception from the component that detects the validation problem using the annotation:
@ApplicationException( rollback = true )
Then our bean support can catch this exception, suppose the component that threw it posted the appropriate FacesMessage and simply returns null to return the user to the input page with the error displayed. The ApplicationException annotation tells Seam to roll back the transaction, and we do not show the user a common error page.
This worked well for the first place we used it only when we did the inserts. The second place that we tried to use, we must remove something during the process. In this second case, everything works if there is no validation error. If a validation error occurs, the rollback exception is discarded and the transaction is marked for rollback. Even if the database changes have not been undone when the user corrects the bad data and resends the page, we get:
java.lang.IllegalArgumentException: Removing a detached instance
A disconnected instance is lazily loaded from another object (there are many relationships). This parent is loaded when the bean is instantiated. Since the transaction was discarded after a validation error, the object is now detached.
Our next step was to change this page from the talk area to the page area. When we did this, Seam could not even display the page after a validation error, because our page must hit the database for rendering, and the transaction was marked for rollback.
So my question is: how do other error handling people cleanly and correctly manage transactions at the same time? Even better, I would like to use everything that we have now, if someone can notice what I'm doing wrong, which would be relatively easy to fix.
I read the Seam Framework article on the Unified Error Page and Exception Handling , but this is more related to the more common errors your application may require.
Update : here are some psudo code and page flow information.
In this case, suppose we are editing some user information (we are not really dealing with the user in this case, but I will not post the actual code).
The edit function edit.page.xml file of the editing function contains a simple re-entry template for a RESTful URL and two navigation rules:
- If the result was successful, redirect the user to the appropriate viewing page to view updated information.
- If the user clicked the cancel button, redirect the user to the corresponding viewing page.
The edit.xhtml file is quite simple with fields for all parts of the user that can be edited.
Bean support has the following annotations:
@Name( "editUser" ) @Scope( ScopeType.PAGE )
There are some components introduced, such as User:
@In @Out( scope = ScopeType.CONVERSATION )
We have a bean-based save method that delegates the work to save the user:
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; }
Our GuaranteedRollbackException property is as follows:
@ApplicationException( rollback = true ) public class GuaranteedRollbackException extends RuntimeException { public GuaranteedRollbackException(String message) { super(message); } }
UserManager.modifyUser looks something like this:
public void modifyUser( User user, String newFName, String newLName, String type, String newOrgName ) {
ModifyUser.modifyOrg does something like
public void modifyOrg( User user, String newOrgName ) { if (!userValidator.validateUserOrg( user, newOrgName )) {
ModifyUser.modifyUser is similar to modifyOrg.
Now (you will need to take this jump with me because it does not necessarily sound like a problem with this user script, but this is for what we are doing) we assume that the change in org causes changeUser not to check, but that it is impossible to check this failure ahead of time. We have already recorded the org update in db in our current txn, but since the user modifier does not check, the Expression GuaranteedRollbackException will mark the transaction to be rolled back. With this implementation, we cannot use the database in the current area when we again show the edit page to display the error message added by the validator. During rendering, we end up in db to get something displayed on the page, and this is not possible because the session is invalid:
An org.hibernate.LazyInitializationException is thrown with the message: "proxy cannot be initialized - no session"