Hi,
today I have coded a test case for my application, to see how transactions behave. And I have found that nothing works the way I thought it does.
I have a Spring-based application using Hibernate as JPA provider, backed by MySQL. I have DAO objects, extending Spring's JpaDaoSupport. These are covered by Spring's transaction management.
The test case I've created works like this: 1) An entity is created, with some counter set to 0. 2) Then two threads are created, which both call a DAO method incrementCounter() in a loop.
I thought that when DAO methods are covered with transaction, then only one thread will be inside it (i.e., Spring will take care of synchronization). But this has proven to be false assumption.
After (temporary) solving this by adding synchronized
to the DAO method, I found out that Hibernate does not store the changes made by the DAO method, and the other thread, when find()ing the entity, has old data. Only explicit call of this.getJpaTemplate().flush();
helped.
I also thought that entity manager would give me the same entity instance from the cache of the persistence context, but this is also false. I have checked hashCode() and equals(), and they are fine - based on entity's bussines key.
Any comments welcome, as it seems I miss some basic concepts of how JPA / Spring works with transactions.
- Should DAO methods be
synchronized
? - Should I call flush() at the end of every DAO method?
- Is spring responsible for making the calls to DAO methods behave transactionally? (i.e. not letting two threads work with the same entity at the same time)
- If not, how do I achieve this?
Note that in my test case, I use a single DAO object, but that should be OK as Spring's beans are singletons - right?
Thanks for any help.
public class EntityDaoImpl extends JpaDaoSupport implements EntityDao {
public synchronized void incrementCounter( String znacka )
{
String threadName = Thread.currentThread().getName();
log.info(threadName + " entering do incrementCounter().");
Entity ent = this.getJpaTemplate().find( Entity.class, znacka );
log.info("Found an entity "+ent.getZnacka()+"/"+ent.hashCode()+" - " + ObjectUtils.identityToString( ent ) );
log.info(threadName + ": Actual count: "+ent.getCount() );
ent.setCount( ent.getCount() + 5 );
int sleepTime = threadName.endsWith("A") ? 700 : 50;
try { Thread.sleep( sleepTime ); }
catch( InterruptedException ex ) { }
ent.setCount( ent.getCount() + 5 );
this.getJpaTemplate().flush();
log.info(threadName + " leaving incrementCounter().");
}
}
Without synchronized
and flush()
, this was giving me output like
Thread A: Actual count: 220
...
Thread B: Actual count: 220
...
Thread A: Actual count: 240
...
Thread B: Actual count: 250
...
Thread A: Actual count: 250
...etc, meaning that one thread has overwriten the changes from the other.