Forcing a transaction to rollback when checking errors in Seam

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 ) // outjected so the view page knows what to display protected User user; 

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 ) { // change the user - org relationship modifyUser.modifyOrg( user, newOrgName ); modifyUser.modifyUser( user, newFName, newLName, type ); } 

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

+6
java hibernate jpa seam transactions
source share
3 answers

I have to agree with @duffymo about approval before the transaction. It is difficult to deal with database exceptions and present them to the user.

The reason you get the thrown exception is most likely because you think you wrote something in the database, and then you cause the object to be deleted or updated, and then try to write something.

Instead, you need to create a long-running conversation with flushMode set to MANUAL . Then you begin to persevere, and then you can perform your test, and if everything is in order, you persist again. After you are done and everything will be fine, you call entityManager.flush() . That will save everything in the database.

And if something doesn’t work, you don’t rinse it. You just return null or "error" with some message. Let me show you some pseudo code.

Suppose you have a Person and Organization object. Now you need to save the Personality before you can put a person in the 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"; } } 

Please note that now we save the person, but we did not clear the transaction. However, it will check the restrictions that you have set in your entity. (@NotNull, @NotEmpty, etc.). Thus, it will only simulate conservation.

Now you save the organization for the 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"; } 

Here you can even put the material in a try catch and only return success if there is no exception so that you do not get to the error page.

Update

You can try the following:

 @PersistenceContext(type=EXTENDED) EntityManager em; 

This will cause the stateful bean to have an extended EJB3 persistence context. Messages received in the request remain in a controlled state as long as the bean exists, so any subsequent method call in the stateful bean can update them without requiring an explicit call to the EntityManager. This may throw an exception to your LazyInitializationException. Now you can use em.refresh(user);

+1
source share

I think the check should be done before the transaction begins.

0
source share

Recently, I came across this situation in various guises.

The best I have found is to process the object that you have as the value object (which is mostly located after the rollback). To remove it from the database, find its “attached” double using find by its identifier (which will not go to the database, since it is almost certainly cached), and then removes the returned object.

Similar updates: get a new copy and update it.

This is a bit of a hassle, but it avoids lengthy transactions and all the associated locking issues.

0
source share

All Articles