Forcing a transaction to rollback on validation errors in Seam - java

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.
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 )
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
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.
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.
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).
The edit functionality's edit.page.xml file contains a simple re-write pattern for a RESTful URL and two navigation rules:
If result was successful edit, redirect the user to the corresponding view page to see the updated info.
If the user hit the cancel button, redirect the user to the corresponding view page.
The edit.xhtml is pretty basic with fields for all of the parts of a user that can be edited.
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;
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;
}
Our GuaranteedRollbackException looks like:
#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'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:
Caused by org.hibernate.LazyInitializationException with message: "could not initialize proxy - no Session"

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.
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.
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.
And if something failed, you dont flush. You just return null or "error" with some message. Let me show you with some pseudo code.
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";
}
}
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";
}
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.
Update
You can try this:
#PersistenceContext(type=EXTENDED)
EntityManager em;
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);

I think validation should be done before the transaction is ever initiated.

I have faced this situation lately in a variety of guises.
The best I found is to treat the object you have as a value object (which it basically is after the rollback). To remove it from the database, find its 'attached' twin using a find by its id (which will not go to the database as it is almost certainly cached) and then remove the object that was returned.
Similar for updates : get a fresh copy and update it.
It is a bit of a hassle, but it avoids long transactions and all the evil locking issues related to that.

Related

How to implement atomic "save if not exists" logic by using Spring data JPA crud methods?

I have users registration controller in my Spring Boot project with spring-boot-starter-data-jpa and spring-boot-starter-web dependencies, which implements the following logic, where usersRepository is an instance of standard CrudRepository:
#PostMapping
public String processRegistrationForm(#Valid #ModelAttribute("registrationForm") UserForm form,
Errors errors, Model model) {
if (!errors.hasErrors()) {
UserEntity user = usersRepository.findByUsername(form.getUsername());
if (user != null) {
errors.rejectValue("username", "registration.username.not.unique");
} else {
usersRepository.save(form.toUserEntity(passwordEncoder));
model.addAttribute("isRegistrationComplete", true);
}
}
return "registration";
}
The method first checks whether the user with the given username exists, and if not - saves it into the database. The problem here is that this check-then-act behavior may result in DataIntegrityViolationException (with the underlying unique username constraint violation) if someone intervenes in between findByUsername() and save() calls and manages to save the user with the same username into the database. How can I avoid this? And would making the whole method #Transactional solve this problem?
It seems you want to create an entity but not overwrite it, and in an atomic operation you cannot test for existence first.
You could, however put a unique key on your resource and then simply go for the create option. If that entity (with that specific key) already exists you should receive an exception telling you about duplicate data. Now you can still decide whether you want to error out or simply update the existing entry.
Edit: Reading the other comments: your unique key is probably the user name, and you want to error out saying that the chosen user name is already in use.
Edit2: So you mention that my suggestion is what you had implemented but you were not happy. I think you did not suffer from performance but did not like the code (parsing - see my comment) or the user behaviour.
A user just fills in a form to register and while being delayed by a captcha or some bad password pattern all of a sudden that user name is taken by someone else. Not a nice situation.
You will only resolve it by acting as soon as a user tries to register with a name. Upon the first such check (and when you return the status that the user is still available) create the entity with an attribute that this is just a placeholder. While the user still fills in the registration form other users already can see the name is taken.
For all cases where a registration is not finished and thus names are blocked for nothing, have a garbage collector job that removes all placeholders after some time. So if a placeholder has not completed to a full user account within one hour, just remove that entry from the DB and another user is free to reuse the name.

Hibernate persist and error handling workflow

I'm prety new to hibernate and I have a question about the error handling and persistance workflow. I have the following piece of legacy code.
public void doPersist(Contact out){
contactValidator.validationOne(out);
entityManager.persist(out);
contactValidator.validationTwo(out);
contactValidator.validationThree(out);
}
ContactValidator is a class used to validate the Contact, it throws a multiple business exceptions in every validation method. I don't like how the method doPersist is constructed. Why it first calls entityManager.persist and then validate the object? In case of exception in some of the validation methods the data should be rollbacked. How Hibernate will rollback the data when persist is already called?
I personally don't care for how the doPersist method is written either because I can see several cleaner alternatives that avoid having such a superfluous method.
To answer your specific question, the magic of rollback happens because of how transactions work. A transaction is nothing more than a series of operations that are performed as a single unit of work that must adhere to being atomic, consistent, isolated, and durable (e.g. ACID).
While the transaction is active and has yet to be committed, if an exception is thrown, then the exception handling tells the transaction to forget about what it was told to do.
Session session = sessionFactory.openSession();
try {
session.getTransaction().begin();
// do whatever work you want to do here
session.getTransaction().commit();
}
catch ( Throwable t ) {
if ( session.getTransaction().isActive() ) {
session.getTransaction().rollback();
}
throw t;
}
finally {
session.close();
}
So in this code, even if the transaction is attempting to commit and an exception is thrown, the catch clause sees that it is active and rolls the transaction back, thus telling the database to throw away all the work it was just asked to perform.
Now I want to touch on your ContactValidator.
My guess is that your ContactValidator closely aligned with how Bean Validation. It basically looks at the bean's state and makes sure that there aren't any inconsistent expectations and if so, assert with an exception.
When using hibernate-validator in conjunction with hibernate-core, you get bean validation automatically for free because Hibernate will plug into the validator framework and perform validate operations for the following events
PrePersistEvent
PreUpdateEvent
PreRemoveEvent
As you can see, there isn't any post event support out of the box. That makes sense because you generally want to satisfy constraints before you actually save or update a database row. This is why I find your second and third contact validation methods strange.
That aside, if you really need some post-insert or post-update validation, you can easily tie into the existing bean validator listener for these operations too with a custom Hibernate listener which you register on the PostInsertEvent and PostUpdateEvent groups to call bean validation.

How to write correct/reliable transactional code with JAX-RS and Spring

Basically, I am trying to understand how to write correct (or "to correctly write"?) transactional code, when developing REST service with Jax-RS and Spring. Also, we're using JOOQ for data-access. But that shouldn't be very relevant...
Consider simple model, where we have some organisations, that have these fields: "id", "name", "code". All of which must be unique. Also there's a status field.
Organization might be removed at some point. But we don't want to remove the data altogether, because we want to save it for analytical/maintenance purposes. So we just set organization 'status' field to 'REMOVED'.
Because we don't delete the organization row from the table, we can't simply put the unique constraint on the "name" column, because, we might delete organization and then create a new one with the same name. But let's assume that codes has to be unique globally, so we DO have a unique constraint on the code column.
So with that, let's see this simple example, that creates organization, performing some checks along the way.
Resource:
#Component
#Path("/api/organizations/{organizationId: [0-9]+}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaTypeEx.APPLICATION_JSON_UTF_8)
public class OrganizationResource {
#Autowired
private OrganizationService organizationService;
#Autowired
private DtoConverter dtoConverter;
#POST
public OrganizationResponse createOrganization(#Auth Person person, CreateOrganizationRequest request) {
if (organizationService.checkOrganizationWithNameExists(request.name())) {
// this throws special Exception which is intercepted and translated to response with 409 status code
throw Responses.abortConflict("organization.nameExist", ImmutableMap.of("name", request.name()));
}
if (organizationService.checkOrganizationWithCodeExists(request.code())) {
throw Responses.abortConflict("organization.codeExists", ImmutableMap.of("code", request.code()));
}
long organizationId = organizationService.create(person.user().id(), request.name(), request.code());
return dtoConverter.from(organization.findById(organizationId));
}
}
DAO service looks like that:
#Transactional(DBConstants.SOME_TRANSACTION_MANAGER)
public class OrganizationServiceImpl implements OrganizationService {
#Autowired
#Qualifier(DBConstants.SOME_DSL)
protected DSLContext context;
#Override
public long create(long userId, String name, String code) {
Organization organization = new Organization(null, userId, name, code, OrganizationStatus.ACTIVE);
OrganizationRecord organizationRecord = JooqUtil.insert(context, organization, ORGANIZATION);
return organizationRecord.getId();
}
#Override
public boolean checkOrganizationWithNameExists(String name) {
return checkOrganizationExists(Tables.ORGANIZATION.NAME, name);
}
#Override
public boolean checkOrganizationWithCodeExists(String code) {
return checkOrganizationExists(Tables.ORGANIZATION.CODE, code);
}
private boolean checkOrganizationExists(TableField<OrganizationRecord, String> checkField, String checkValue) {
return context.selectCount()
.from(Tables.ORGANIZATION)
.where(checkField.eq(checkValue))
.and(Tables.ORGANIZATION.ORGANIZATION_STATUS.ne(OrganizationStatus.REMOVED))
.fetchOne(DSL.count()) > 0;
}
}
This brings some questions:
Should I put #Transactional annotation on Resource's createOrganization method? Or should I create one more service that talks to DAO and put #Transactional annotation to it's method? Something else?
What would happen if two users concurrently send request with the same "code" field. Before first transaction is commited the checks are successfully passed, so no 409 respones will be sent. Than first transaction will be committed properly, but the second one will violate DB constraint. This will throw SQLException. How to gracefully handle that? I mean I still want to show nice error message on the client side, saying that name is already used. But I can't really parse SQLException or smth.. can I?
Similar to the previous one, but this time "name" is not unique. In this case, second transaction will not violate any constraints, which leads to having two organization with the same name, that violates our buisness constraints.
Where can I see/learn tutorials/code/etc., that you consider great examples on how to write correct/reliable REST+DB code with complicated buisness logic. Github/books/blogs, whatever. I've tried to find something like that myselft, but most examples just focus on the plumbing - add these libs to maven, use these annotations, there is your simple CRUD, the end. They don't contain any transactional considirations at all. I.e.
UPDATE:
I know about isolation level and the usual error/isolation matrix (dirty reads, etc..). The problem I have is finding some "production-ready" sample to learn from. Or a good book on a subject. I still don't really get how to handle all the errors properly.. I guess I need to retry a couple of times, if transaction failed.. and than just throw some generic error and implement client, that handles that.. But do I really have to use SERIALIZABLE mode, whenever I use range queries? Because it will affect performance greatly. But otherwise how can I garantee that transaction will fail..
Anyway I've decided that for now I need more time to learn about transactions and db management in general to tackle this problem...
Generally, without talking about transactionality, endpoint should only grab parameters from the request and call the Service. It shouldn't do business logic.
It seems your checkXXX methods are part of the business logic, because they throw errors about domains-specific conflicts. Why not put them into the Service into one method, which is by the way transactional?
//service code
public Organization createOrganization(String userId, String name, String code) {
if (this.checkOrganizationWithNameExists(request.name())) {
throw ...
}
if (this.checkOrganizationWithCodeExists(code)) {
throw ...
}
long organizationId = this.create(userId, name, code);
return dao.findById(organizationId);
}
I took as your parameters are Strings, but they can be anything. I'm not sure you want to throw Responses.abortConflict in the service layer because it seems to be a REST concept, but you can define your own exception types for it if you want.
Endpoint code should look like this, however, it might contain additional try-catch block which converts the thrown exceptions to Error responses:
//endpoint code
#POST
public OrganizationResponse createOrganization(#Auth Person person, CreateOrganizationRequest request) {
String code = request.code();
String name = request.name();
String userId = person.user().id();
return dtoConverter.from(organizationService.createOrganization(userId, name, code));
}
As for question 2 and 3, transaction isolation levels are your friends. Put isolation level high enough. I think 'repeatable read' is the suitable one in your case. Your checkXXX methods will detect if some other transaction commits entities with the same name or code and it's guaranteeed that the situations stays by the time 'create' method is executed. One more useful read regarding Spring and transaction isolation levels.
As per my understanding the best way to handle DB level transaction you must use Spring's Isolation trnsaction in effective way in the dao layer. Below is sample industry standard codde in your case...
public interface OrganizationService {
#Retryable(maxAttempts=3,value=DataAccessResourceFailureException.class,backoff=#Backoff(delay = 1000))
public boolean checkOrganizationWithNameExists(String name);
}
#Repository
#EnableRetry
public class OrganizationServiceImpl implements OrganizationService {
#Transactional(isolation = Isolation.READ_COMMITTED)
#Override
public boolean checkOrganizationWithNameExists(String name){
//your code
return true;
}
}
Please pinch me if I'm wrong in here
Separation of concern :
Jax-rs resource (endpoint) layer : just handle the request, invoke the service and wrap the potential exception in appropriate response code (just catch and wrap manually or use exception mapper).
Service / business layer : expose a transactional method for each unit of work, business error must be handled as checked exception, operational ones as unchecked (subclasses of RuntimeException).
Data access layer: just handle the data access stuff (i.e. get db context, executes query and eventually map the result).
I insist on one thing, the good place to have transaction boundaries is the place where your business methods are defined. A transaction scope must be a business unit of work.
Regarding the concurrency issue, there is 2 way to handle this kind of concurrency problem : pessimistic or optimistic locking.
Pessimistic :
Lock
do your stuff
Update
Release lock
Optimistic :
check version
do your stuff
update if version is same, fail otherwise
Pessimistic is an issue regarding scalability and performance, optimistic problem is that you sometimes end by sending an operating error to the end-user.
I would personally go with optimistic locking in your case, JOOQ support it
First off the DAO layer should not even know it's being fronted by a REST webservice. Be sure to separate responsibilities.
Keep the #Transactional on the DAO. If you are issuing only a single statement than you need to decide if you are OK with dirty reads. Basically, figure out what the lowest Isolation Level is for your application. Every method will start a new Transaction (unless called from another method that already had one started) and if any Exceptions are thrown it will rollback any calls. You can setup a custom ExceptionHandler in your Controller to handle SQLDataIntegrityExceptions (like you're "code" insert example).
Use an Aggregate Primary Key that covers (id, name, code, status) so you can have an org with the same name but one will be "CURRENT" and one will be "REMOVED"

Is there a way to force a transactional rollback when encountering an exception?

When the program encounter an exception, The RollBackOnly goes to True.
How can I "Set" this RollBack To False Even it is encountering an exception.
#Resource
protected SessionContext context;
Public void creerNewEntity(final Entity myEntity) {
try {
this.em.persist(myEntity);
this.em.flush();
} catch (final EntityExistsException e) {
System.out.println((this.context.getRollbackOnly())); // It s has a true value
throw new MyException("TODO BLABLA", e);
}
}
When the program throw this Exception "MyException", I change the object myEntity by setting for example a new Id then I call again creerNewEntity().
Unfortunately, it doesn't work, I got this exception "javax.persistence.PersistenceException: org.hibernate.HibernateException: proxy handle is no longer valid", I think because the RollBack has a true value, How can I change the rollback to make this works ?
Thanks.
There probably isn't a simple way to do this since the whole point of the EJB design is that you don't care about such things. The first error inside of a transaction makes it invalid -> rollback. That's the rule.
If you want something special, then get yourself a database connection from the session and use plain SQL instead of EJB to modify the data. That way, you can try to INSERT a new instance and handle all exceptions yourself. When the insert succeeds, you can use EJB to load the newly created object to add it to the session.
That said, I'm not sure what you're trying to achieve with the code above. Just ignoring when you can't create a new instance in the database feels like "I don't care about quality of my product." Maybe your attempt to work around an error is just a symptom of a bad design of your application. Take a step back and consider what you're doing and why. Maybe if you told us more about the reasons why you want to ignore all errors (even the really, really deadly ones), we would be able to point out a better solution.
EDIT So you get javax.persistence.EntityExistsException which means you tried to save the same entity twice. That can mean any number of things:
You loaded the bean in a different session and now you try to save it in a second one. Since the new session can't know if the bean exists, it tries to create it again.
Instead of loading the bean from the session like you should, you cheated and create a new instance manually. Of course, the session manager now thinks this is a new bean.
The correct solution depends on what you need to achieve. If you modified myEntity and need to save the changes, use em.merge(). The EM will then check if the object already exists and if it does, it will do an SQL UPDATE instead of an INSERT
If you just want to give some other class a valid entity, then you need to get it from the database. If the database returns null, you need to create a new instance and persist it and then return it.
See also: JPA EntityManager: Why use persist() over merge()?
EntityExistsException is PersistenceException
when jpa throws it, ejb CMT is marked for rollback
http://piotrnowicki.com/2013/03/jpa-and-cmt-why-catching-persistence-exception-is-not-enough/
as aaron suggested, you could use merge()
you can also contain transaction boundary by using RequiresNew
#TransactionAttribute(REQUIRES_NEW)
http://docs.oracle.com/javaee/6/tutorial/doc/bncij.html

Hibernate giving org.hibernate.NonUniqueObjectException and saving obsolete object to the database

I have below code in my service class. This code is called twice from 2 different methods as part of a functionality.
//Some code here...
LOG.info("###Inside customer interceptor");
try
{
customerDao.save(customer);
}
catch (final Exception exp)
{
//some code here too...
}
When it is first time called from first method and executed, I can see that there is a select SQL statement printed in tomcat console, but there is no update SQL statement as I expected(May be because of the fact that Hibernate doesn't issue an insert/update immediately).
Immediately When this code block is called again from second method, I see a SQL select statement and then a SQL update statement in tomcat console, and immediately after that I see a big fat org.hibernate.NonUniqueObjectException.
which as I can understand coming because the earlier entity was still attached to session and not committed to DB.
However when I go and see the database I find the values saved in the database were of the object from first call and not the second one as I expected. Is this normal or am I missing something here?
I am using Spring's annotation(#Transactional) driven transaction strategy.
I'd rather use .merge(customer) method or .saveOrUpdate(customer) method to perform this operation.
If you use .merge, you will not get the nonUnique exception as it overwrites the object that is in the session with the one you just passed.
Where as to perform saveOrUpdate, the session should not have the
same instance of the object you are trying to update or else you get this nonUniqueObject exception.
Because you are using spring #Transactional, the transaction should be committed after the complete method has been executed.

Categories

Resources