Hibernate lock causes dirty collection exception - java

I'm trying to reattach to the session an object from the HTTP session that was originally retrieved from the DB, I do this by calling session.lock(object, LockMode.None) and even though lock does not cascade this works all right for me because it does not push updates to the DB like merge does (the lock is required to open a detail view in a pop-up and the actual saving would occur later on the main window). Now to my surprise I have found that if my entity has a one to many relation any changes on that collection would cause a HibernateException "Reassociated object has dirty collection".
How am I supposed to reattach objects to the session without updating the DB or discarding the changes on the object then?
Here is the situation as code
EntityA t = createAnEntityA();
Session sess = factory.openSession();
sess.beginTransaction();
sess.save(t);
sess.getTransaction().commit();
sess.close();
// t is now saved on the DB but in dettached state
// change a simple property
sess = factory.openSession();
sess.beginTransaction();
t.setPropertyB("B");
sess.lock(t, LockMode.NONE);
// t is attached again, you won't get LazyInitializationException
// by calling its properties, although you have to be careful
// because the reattachment does not cascade to children
sess.getTransaction().commit();
sess.close();
// no updates went to the DB because setPropertyB was called
// when t was still dettached
// now change a collection
EntityC c = createAnEntityC();
t.getCollectionPropertyC().add(c);
sess = factory.openSession();
sess.beginTransaction();
sess.lock(t, LockMode.NONE);
// Exception is thrown :-(
sess.getTransaction().commit();
sess.close();

I'm afraid currently it's not possible without hitting DB.
Apparently, the exception is happening because of lock command.
There's a Jira reporting this behavior.
https://hibernate.atlassian.net/browse/HHH-511
It has 2 patches to fix the problem. You could try those patches.
But if you problem is only Lazy loading of collection, you could consider using Open Session In View pattern. (maybe not the best pattern, but it can work for your case)

Related

Should I commit hibernate transaction before calling session.evict(obj)

When should I commit a transaction in hibernate after saving it. Is it before or after I call session.evict(obj). Currently my code looks like this(only required parts).
Session session = connector.getSession();
Transaction tx = session.beginTransaction();
try {
Criteria crit = session.createCriteria(ST_CODE_SETTINGS_STORE.class).add(Restrictions.eq("TYPE", "issueno"));
List<ST_CODE_SETTINGS_STORE> ls = crit.list();
if (ls.size() < 1) {
session.save(st_code_settings_store);
session.evict(st_code_settings_store);
msg = "insert";
}
else {
Long Id = null;
ST_CODE_SETTINGS_STORE st_code_settings_store1 = ls.get(0);
Id = st_code_settings_store1.getCODE_ID();
Object o = session.get(ST_CODE_SETTINGS_STORE.class, Id);
ST_CODE_SETTINGS_STORE update = (ST_CODE_SETTINGS_STORE) o;
session.update(update);
}
tx.commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
System.out.println("Error: " + e.getMessage());
connector.printStack(e);
throw e;
} finally {
session.close();
}
Sometimes if I commit after evicting, the data does not get saved in the database. Is it the right way to code??
The method evict() removes a single object from Session cache. So
before you call evict() the object should be there in the Session
cache. Therefore if you save the object first time, you have to save
the object via Session.save(object). Subsequent update calls should
follow through session.saveOrUpdate(object) or session.update(object)
before calling evict() to remove the loaded object from the cache.(reference )
From Hibernate Docs
Ending a Session usually involves four distinct phases:
flush the session
commit the transaction
close the session
handle exceptions
Do not use the anti-patterns session-per-user-session or
session-per-application (there are, however, rare exceptions to this
rule). Some of the following issues might also arise within the
recommended patterns, so ensure that you understand the implications
before making a design decision:
A Session is not thread-safe. Things that work concurrently, like HTTP
requests, session beans, or Swing workers, will cause race conditions
if a Session instance is shared. If you keep your Hibernate Session in
your HttpSession (this is discussed later in the chapter), you should
consider synchronizing access to your Http session. Otherwise, a user
that clicks reload fast enough can use the same Session in two
concurrently running threads.
An exception thrown by Hibernate means
you have to rollback your database transaction and close the Session
immediately (this is discussed in more detail later in the chapter).
If your Session is bound to the application, you have to stop the
application. Rolling back the database transaction does not put your
business objects back into the state they were at the start of the
transaction. This means that the database state and the business
objects will be out of sync. Usually this is not a problem, because
exceptions are not recoverable and you will have to start over after
rollback anyway.
The Session caches every object that is in a
persistent state (watched and checked for dirty state by Hibernate).
If you keep it open for a long time or simply load too much data, it
will grow endlessly until you get an OutOfMemoryException. One
solution is to call clear() and evict() to manage the Session cache,
but you should consider a Stored Procedure if you need mass data
operations.

Hibernate persist() method

Is the below statement a valid one?
persist() also guarantees that it will not execute an INSERT statement if it is called outside of transaction boundaries
When I try the below code using persist; then the row is getting inserted without any transaction (It is commented out).
SessionFactory sessionFactory = new Configuration().configure("student.cfg.xml").buildSessionFactory();
Session session = sessionFactory.openSession();
//Transaction tran = session.beginTransaction();
/*
* Persist is working without transaction boundaries ===> why?
*/
Student student = new Student();
student.setFirstName("xxx");
student.setLastName("yyy");
student.setCity("zzz");
student.setState("ppp");
student.setCountry("###");
student.setId("123456");
session.persist(student);
//tran.commit();
session.flush();
session.close();
persist() also guarantees that it will not execute an INSERT statement if it is called outside of transaction boundaries
This statement is correct. When control returns from persist() back to your code, no INSERT statements have been executed. These statements are guaranteed to be deferred until session flushing. Note that persist() would be a pointless method if no insert happened ever.
AFAIK data is saving because of session.flush(), try after removing this, mostly you will get an error.
Hibernate persist
Diff. save & persist

hibernate multiple threads prevent multiple save(), JTA necessary?

I'm using a hibernate session per request model for my web application. My jdbc transaction begins at the beginning of each web request and commited at the end.
// Non-managed environment idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
}
finally {
sess.close();
}
I'm faced with the problem where I am testing for existence of an entity (A) based on several parameters and doing an insert only if it doesn't exist.
public synchronized myMethod(param1, param2) {
MyEntityA entity = MyEntityADAO.findEntity(param1, param2)
if (entity == null) {
entity = .../create entity
MyEntityADAO.save(entity);
}
}
the problem is that synchronization does not help because the call to MyEntityADAO.save() does not actually write to the database when the currently running thread exits the method and releases the lock, the write to the database occurs after the transaction is commited which is generally what I need for my application except for a few scenarios. The code above causes multiple records saved with same parameters in a multithreaded environment.
I've tried to execute the save code in its own new session and transaction:
public synchronized myMethod(param1, param2) {
MyEntityA entity = MyEntityADAO.findEntity(param1, param2)
if (entity == null) {
entity = .../create entity
Session session = HibernateUtil.createSession();
MyEntityADAO.save(entity);
Transaction t = session.beginTransaction();
}
}
the above causes problems with 2 open sessions loading the same collection with hibernate in some instances.
Should I enclose every DAO call in its own transaction and use transaction propagation with JTA? Is there a way to avoid JTA? Is it alright to commit transaction associated with the main session after the call to MyEntityADAO.save() and call beginTransaction on the main session right after and have the transaction commited at the end of the request as it does now?
The coherence of the data in database should not be compromised by doing only some part of an atomic change in its own transaction. And although some synchronization might work on your environment, if you need to cluster your app, or if several applications acces the database, it won't solve the problem.
What you should do is to put a unique constraint in the database on [param1 - param2]. That will cause one of the two transactions to rollback if there is a race condition.
If you choose to still isolate the check/insert code in its own transaction (because it's not a problem if that succeeds and the outer transaction fails), I don't see how JTA would be a problem. Supposing you're using EJBs or Spring, just put this method in its own EJB/bean, and mark the method as transactional, with the REQUIRES_NEW propagation.
The code would thus look like this:
// some code
Long id = myBean.checkIfExistOrCreate(param1, param2); // this methos call starts a new transaction
// now we're sure that the entity exists. Load it in the current session.
MyEntity e = em.find(MyEntity.class, id);
If you can't synchronize checkIfExistOrCreate, then try calling it, catch any exception that it could throw, and retry calling it:
Long id = null;
try {
id = myBean.checkIfExistOrCreate(param1, param2);
}
catch (Exception e) { // a well-defined exception would be better
// the transaction roled back: retry
id = myBean.checkIfExistOrCreate(param1, param2);
}
// now we're sure that the entity exists. Load it in the current session.
MyEntity e = em.find(MyEntity.class, id);
The solution that worked for me and my particular app requirements trying to avoid JTA and nested transactions:
Using ManagedSessionContext because org.hibernate.context.ThreadLocalSessionContext will close and create a new session for each transaction. You will run into problems with entities that have collections associated if you load those entities in multiple open sessions (when you will create multiple transactions for one request).
I open a hibernate session and bind it to the context in the beginning of my web request
Any service layer method that needs test for existence prior to insert is marked synchronized, the global transaction is commited with the insert statement and a new transaction is started
At the end the request the transaction bound to the session is commited
public synchronized myMethod(param1, param2) {
MyEntityA entity = MyEntityADAO.findEntity(param1, param2)
if (entity == null) {
entity = .../create entity
MyEntityADAO.save(entity);
HibernateUtil.getCurrentSession().getTransaction().commit();
HibernateUtil.getCurrentSession().getTransaction().begin();
}
}
I know its ugly and will not work for everybody in every scenerio, but after doing a very intense search on transaction management, isolation levels, locking, versioning that is the only solution I have found that worked for me. I am not using Spring, and I'm not using a Java EE container, using Tomcat 6.

Difference between Hibernate update by Session.update and HibernateTemplate.merge

I saw to types of update operation:
First:
getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) {
session.flush();
session.setCacheMode(CacheMode.IGNORE);
SomeObject ss = (SomeObject) session.get(SomeObject.class, id);
long next = ss.getAndIncrement();
session.update(ss);
session.flush();
return null;
}
});
and secondly
SomeObject ss = loadSomeObject();
long next = ss.getAndIncrement();
getHibernateTemplate.merge(ss);
These two method do the same. I want to know which one is better and safe and why. Thank you.
In the first operation the object ss is attached to the session. where as in the second operation its detached. So if you have an attached objects you can use update. If you have a detached objects then use merge which first attaches the object to the session then will do an update.
EDIT: For your information on attached(persistent) and detached objects :
Hibernate defines and supports the following object states:
Transient - an object is transient if it has just been instantiated using the new operator, and it is not associated with a Hibernate Session. It has no persistent representation in the database and no identifier value has been assigned. Transient instances will be destroyed by the garbage collector if the application does not hold a reference anymore. Use the Hibernate Session to make an object persistent (and let Hibernate take care of the SQL statements that need to be executed for this transition).
Persistent - a persistent instance has a representation in the database and an identifier value. It might just have been saved or loaded, however, it is by definition in the scope of a Session. Hibernate will detect any changes made to an object in persistent state and synchronize the state with the database when the unit of work completes. Developers do not execute manual UPDATE statements, or DELETE statements when an object should be made transient.
Detached - a detached instance is an object that has been persistent, but its Session has been closed. The reference to the object is still valid, of course, and the detached instance might even be modified in this state. A detached instance can be reattached to a new Session at a later point in time, making it (and all the modifications) persistent again. This feature enables a programming model for long running units of work that require user think-time. We call them application transactions, i.e., a unit of work from the point of view of the user.
Whats an API without any code examples?
SessionFactory sf = ctx.getBean("hibernateSessionFactory",SessionFactory.class);
Session session = sf.openSession();
Transaction t = session.beginTransaction();
try {
Session s2 = sf.openSession();
Organization org = (Organization)s2.get(Organization.class,100624l);//1
org.setOrgName("org");
s2.close();//2
Organization org1 = (Organization)session.get(Organization.class,100624l);//3
org.setOrgName("testOrg");
org1.setOrgName("org");//a
session.merge(org);//4
System.out.println(org == org1);//b
t.commit();
} catch (HibernateException e) {
t.rollback();
throw e;
}finally{
session.close();
}
1st instance is loaded and made persistent.
The instance is detached.
Another instance is loaded
At the point of executing this operation, there are 2 instances of the same object
in the session(org and org1) – org is detached and org1 is persistent
If we do an update() or saveOrUpdate() there, we get the below exception:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.spring.model.Organization#100624]
a. We do a merge here which:
I. Merges the state of the detached object + persistent object.
II. In case of conflict, the object which is merged wins, like in this case, the value saved will be : testOrg
III. Had we merged on org1, we would have got org.
b. This will always return false, meaning post merge, org was still in DETACHED state
I hope the diff. is clear now.
Summary :
saveOrUpdate() or update() will throw an exception if there are 2 instances of the same object in the session(one detached and one persistent)
merge() will not throw the exception, but will save the object while merging the changes.
Merge Does Following
Merge has intelligence. It has lot of pre-checks before it go actual merge(if required)
if Object is transient, It simply fires INSERT query makes object persistent(attached to session)
if Object is detached, fires select query to check whether data modified or not
if modified, fires UPDATE query otherwise just ignore merge task.
where as session.update
throws exception if object is transient.
if Object is detached, it simply fires UPDATE query irrespective of data changes to object.
session.merge is expensive than update
The basic difference is:
Merge() is not concerned about sessions whether persistent or detached...it will just update without considering the sessions.
In case of update() it will throw an exception like org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session.
It's explained here with a good example:
http://www.java4developer.com/difference-between-update-and-merge-in-hibernate/
Both update() and merge() methods in hibernate are used to convert the object which is in detached state into persistence state. But there is little difference. Let us see which method will be used in what situation.
Let Us Take An Example
SessionFactory factory = cfg.buildSessionFactory();
Session session1 = factory.openSession();
Employee s1 = null;
Object o = session1.get(Employee.class, new Integer(101));
s1 = (Student)o;
session1.close();
s1.setSSN(97);
Session session2 = factory.openSession();
Employee s2 = null;
Object o1 = session2.get(Employee.class, new Integer(101));
s2 = (Student)o1;
Transaction tx=session2.beginTransaction();
session2.merge(s1);
SessionFactory factory = cfg.buildSessionFactory();
Session session1 = factory.openSession();
Employee s1 = null;
Object o = session1.get(Employee.class, new Integer(101));
s1 = (Employee)o;
session1.close();
s1.setMarks(97);
Session session2 = factory.openSession();
Employee s2 = null;
Object o1 = session2.get(Employee.class, new Integer(101));
s2 = (Employee)o1;
Transaction tx=session2.beginTransaction();
session2.merge(s1);
Hope you are clear…, actually update and merge methods will come into picture when ever we loaded the same object again and again into the database, like above.

concurrency (stale data) problem in JPA

Let's say I have methods with following signature
Object getData(int id) {
//create a entity manager
//get data frm db
//return data
}
updateData() {
Object obj = getData(id)
//get entity manager
//start transcation tx
//update
//commit tx
}
Now will it cause concurrency issue? Can data be stale in worst case? E.g.
if I getData and by the time I update, if someone updates the data will my updateData will have stale data?
Now can i use following:Will i solve the problem?
Object getData(int id,Entitymanager em) {
//get data frm db using em
//return data
}
updateData() {
Object obj = getData(id)
//get entity manager em
//start transcation tx
//getdata using getData(id,em)
//commit tx
}
Yes, that can happen.
If you get an entity (version 1), someone else modifies it (creating version 2), then you modify version 1 and save it, any changes in version 2 will be lost.
To stop that from happening, use optimistic concurrency by adding a #Version attribute to your entity. If a commit has occurred between your get and update, an exception will be thrown. At the point you can choose your best option to deal with it.
There can also be a problem in multi-threaded environment, if multiple access the same piece of code at the same time, it can create lock, for this you can use row level locking technique, that will help you.check out the below link.
http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/consist.htm
If two separate requests access updateData() concurrently you may get stale data. You may handle the staleness by locking the fetched data in updateData(). If you're using Hibernate as your JPA provider you can lock the data as follows:
updateData() {
Object obj = getData(id);
Session session = (Session) em.getDelegate();
session.refresh(obj, LockMode.UPGRADE);
}
The refresh is necessary because it may happen that between the fetching and the locking of the data another transaction completes in updateData.
Please keep in mind that the entity manager used in getData and updateData must be the same.

Categories

Resources