locking
schachi Sep 27, 2005 8:59 AMi created a sessionbean, which generates unique filenames. the sessionbean is called within different threads in a short time,
so i have to synchronize the processing
annotation: it would have been to "complicated" to write down the code of the filename-generation, so in this example i generate unique integers
instead of filenames (its easier to understand).
Variante A)
Code Sessionbean:
...
public synchronized String getNextFilename() throws Exception {
Collection col = em.createQuery("from FATable where id = :id")
.setParameter("id", 1)
.getResultList();
faData = (FAData) col.iterator().next();
faData.setCurrentNr(faData.getCurrentNr()++);
return (Integer.toString(faData.getCurrentNr)); // ...
}
...
DB at start:
-------------- !id !currentNr! -------------- ! 1 ! 15 ! --------------
Processing:
thread A: call method sessionbean.getNextFileName
thread B: call method sessionbean.getNextFileName
thread A: entry method getNextFileName
thread A: em.createQuery("from FATable where id = :id").setParameter("id", 1).getResultList()
--> container runs query: select id, currentNr from FATable where id = 1
thread A: faData.setCurrentNr(faData.getCurrentNr()+1) (15+1=16)
thread A: leave method getNextFileName
thread B: entry method getNextFileName
thread B: em.createQuery("from FATable where id =
:id").setParameter("id", 1).getResultList()
--> container runs query: select id, currentNr from FATable where id = 1
thread A: container runs updatequery: update FATable currentNr = 16 where id = 1
thread A: commit
thread B: faData.setCurrentNr(faData.getCurrentNr()+1) (15+1=16)!
thread B: leave method getNextFileName
thread B: container runs updatequery: update FATable currentNr = 16 where id = 1
thread B: commit
DB at end:
-------------- !id !currentNr! -------------- ! 1 ! 16 ! --------------
Problem:
The method was called twice, so the currentNr should be 17 instead of 16!
Reason for this "error": the isolation-level of the db-connection is commited read, and Thread B enters the method, before Thread A transmit the commit!
Variante B)
i tried to lock the row
Code Sessionbean:
...
public synchronized String getNextFilename() throws Exception {
Collection col = em.createQuery("from FAData where id = :id")
.setParameter("id", 1)
.getResultList();
faData = (FAData) col.iterator().next();
HibernateSession hs = (HibernateSession)em;
Session session = hs.getHibernateSession();
session.lock(faData, LockMode.UPGRADE);
faData.setCurrentNr(faData.getCurrentNr()++);
return (Integer.toString(faData.getCurrentNr)); // ...
}
...
DB at start:
-------------- !id !currentNr! -------------- ! 1 ! 15 ! --------------
Processing:
(Prosa)
thread A: call method sessionbean.getNextFileName thread B: call method sessionbean.getNextFileName thread A: entry method getNextFilename thread A: em.createQuery(from FATable where id = 1).getResultList() --> container runs query: select id, currentNr from FATable where id = 1 thread A: session.lock(faData, LockMode.UPGRADE) --> container runs query: select id, currentNr from FATable where id = 1 for update thread A: faData.setCurrentNr(faData.getCurrentNr()+1) (15+1=16) thread A: exit method getNextFileName thread B: entry method getNextFilename thread B: em.createQuery(from FATable where id = 1) --> container runs query: select id, currentNr from FATable where id = 1 thread B: session.lock(faData, LockMode.UPGRADE) --> container runs query: select id, currentNr from FATable where id = 1 for update (db locks, until thread A released this row) thread A: container runs updatequery: update FATable currentNr = 16 where id = 1 thread A: commit (release the lock) thread B: faData.setCurrentNr(faData.getCurrentNr()+1) (15+1=16)! thread B: exit method getNextFilename thread B: container runs updatequery: update FATable currentNr = 16 where id = 1 thread B: commit
DB at end:
-------------- !id !currentNr! -------------- ! 1 ! 16 ! --------------
Problem:
The method was called twice, so currentNr should be 17 instead of 16
Reason for this "error":
a) the isolation-level of the db-connection is commited read, and Thread B enters the method, before Thread A transmit the commit
b) DB locks thread B not until the .lock-statement (of Thread B) (i need the lock allready on the query-statement)
Variante C)
i made some experiments with the version-attribute, but this "only" (of course) prevents from overwriting data (exception will be thrown).
How can i solve this problem in a J2EE environment?
I need a safe solution, to synchronize the threads (thread B is locked until thread A transmit the commit).
Or is it better to delegate the lock to the DB? (then i would need a solution, where i can query the database und lock the resultset in one step
(simular to the Hibernate-query: createQuery(....).setLockMode(....).getResultlist)
how do you handle this problem in your application?
thank you for help
(i hope, you understand my english)
marc