JPA2 unique constraint: do I really need to flush? - java

I have a DAO where I need to catch an unique constraint exception. To do this, the only working solution is to flush my EntityManager after the persist. Only then I come into a catch block where I have to filter out the exception. And, my DAO method needs to be wrapped in a transaction (REQUIRES_NEW) otherwise I have the RollBackException.
Am I doing something wrong?
try {
em.persist(myObject);
em.flush();
} catch (PersistenceException ex) {
if (ex.getCause() != null) {
String cause = ex.getCause().toString();
if (cause != null) {
if (cause.contains("org.hibernate.exception.ConstraintViolationException")) {
logger
.error("org.hibernate.exception.ConstraintViolationException: possible unique constraint failure on name");
throw ex;
}
}
}
}

Am I doing something wrong?
EntityManager#persist() won't trigger an immediate insert (unless you are using an IDENTITY strategy). So if you want to actually write the in memory changes to the database and get a chance to catch a constraint violation, you have to flush() "manually" (although even doing so doesn't strictly guarantee anything, your database could be configured to use deferred constraints).
In other words, what you're doing is IMO just the right way to go.
I have a service method with a #Transactional on it (REQUIRED) called addNewObject(). In this method some stuff happens that might throw an exception (and thus rollback the transaction). One of these calls is a call to the DAO method addObject. This one might fail because of the unique constraint. Now, if I do flush, the object will be persisted if there is no unique violation. However, within the service there might still be something else that causes the method to throw an exception.
I think that you are confusing flush and commit. If something goes wrong outside addObject and throws an unrecoverable exception (but inside the same transaction), the whole transaction will be rolled back, including the INSERT statements corresponding to the persist() calls. To sum up, flush != commit.

Related

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.

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

Am I need to clear Hibernate exception somehow?

I am doing exception check while saving data in the following way:
try {
session.beginTransaction();
session.update(person);
session.getTransaction().commit();
}
catch(Exception e) {
session.getTransaction().rollback();
log.error("Error saving person to database", e);
}
nevertheless I have my application terminated with an exception somewhere later.
(I did this check in order to avoid of Data truncation errors. May be there is a way to check data truncation without causing an exception in Hibernate?)
When a Hibernate exception is thrown, you cannot use the session, which caused the exception, any more. A rollback is done automatically.
If you want to continue after the exception, you have two possibilities:
Throw away the old session and create a new one.
Use a StatelessSession instead of a session. A StatelessSession can be used after an exception. In a StatelessSession you have to do the rollback manually.
Normally you'll do solution 1.
Solution 2. is useful if you intentionally provoke an exception and you have a special way to react on it. (For example an index violation exception in an insert operation, and after the exception you do an update instead of an insert.)

Persisting an object in Hibernate while having a known primary key.

My problem is with detached objects...
I am currently using Spring with Hibernate.
I have a mapped object that has a primary key as a String (I know it sucks... but refactoring the code would take months), and I wish to persist it. (I have simplified the object with just two attributes)
#Id
private String id;
private String pattern;
So for example I want to add something like:
["id":"myFirstPattern","pattern":".*"]
Notice that my primary key is already set. The problem with that is that whenever I try to persist, Hibernate will try to link this object with any object within the context (because of the primary key) and will fail to do so, since there are none. Throwing a detached object error.
I've done some research and came to the conclusion that merge() would suffice my needs, since it persists and updates even if the object is not available. However I found this a rather dirty workaround and wanted to check if there are any other solutions to this problem.
Take into account that we have a Helper layer, so Services layer will not work directly with the HibernateDao layer. So I can "mask" this by adding 'persist' and 'update' methods that will invoke the same merge DAO method.
Thanks,
Flavio.
Have you tried saveOrUpdate?
Session sess = factory.openSession();
Transaction tx;
try {
tx = sess.beginTransaction();
session.saveOrUpdate( yourObjectHere );
tx.commit();
}
catch (Exception e) {
if (tx!=null) tx.rollback();
throw e;
}
finally {
sess.close();
}
I tried using some different approach after I tried the idea Mauricio gave to me. Since SaveOrUpdate was using the cached entities to verify if it should update or save an object I thought about making a clear just before saving my object.
so, here is my piece of code:
try {
getHibernateTemplate().clear();
getHibernateTemplate().save(entity);
} catch (DataAccessException e) {
if (e.getCause() instanceof ConstraintViolationException)
throw new HibernateDaoException("Entity could not be persisted. Constraint violation.");
throw new HibernateDaoException(e);
}
For now, it is working as expected, even though it seems that it will kill my cached database reason to exist... However this update feature will be used sparingly, as the main reason for the component is returning info, matching patterns and returning the best results.
Anyway I will return soon if I find any flaws.
Any comments, please feel free to post them :)
I'm not certain, and I can't try it myself right now, but doesn't setting the #Id property to use the "assigned" generator do exactly that?

handling persistence exceptions

I got a minor problem with catching exceptions. I've got code like this:
Role r=new Role("default");
r.setId(Role.DEFAULT_ID);
u.getRoles().add(r); // u is instance of entity which is in relation manytomany with r
try{
em.persist(u);
}catch(Exception e){
System.out.println(e.getClass().getName()+" - default role not found, creating...");
em.persist(r);
em.persist(u);
}
Hope the point of this is clear. If the default role does not yet exist an exception is supposed to be catched, the role is created and then it's given another shot. However I can't catch any exception.
The error log of first two exceptions thrown is:
[org.hibernate.util.JDBCExceptionReporter] (http-127.0.0.1-8080-5) could not insert collection: [Comic.model.User.roles#5] [insert into USER_ACCOUNT_ROLE (USER_ACCOUNT_uid, roles_rid) values (?, ?)]
java.sql.SQLIntegrityConstraintViolationException: ...blabla you dont follow constraints
.
ERROR [org.hibernate.event.def.AbstractFlushingEventListener] (http-127.0.0.1-8080-5) Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: could not insert collection: [Comic.model.User.roles#5]
I guess I can't catch any exception since it's thrown outside my try block right? Any suggestions what could I do about this?
For your User entity I think your relationship to Role can/should be ManyToMany.
You should not need to manually be managing persisting the object graph, as you are doing in the catch block, if you place a CascadeType.PERSIST on that relationship as well.
If em.persist() throws and Exception. Then it seems strange that you would call the method again (twice) in your catch statement.
Either you need to add another try catch, inside your catch statement to deal with a second exception.
Or you need to change your logic to check for the need to persist the role first rather than handling it with Exception catching.

Categories

Resources