|
I do not consider myself a Hibernate expert but I think I have done enough research to be sure that the issue I am about to describe is rooted in how Hibernate is designed. I hope there is a simple workaround because, I believe, this issue serious.
Hibernate is used by several web frameworks, all 'manage' hibernate sessions and typically a single session is used for the scope of the request. I will use Grails as example. Please, assume no 2nd level cache is configured.
My understanding: (A) Based on my testing the following is true: if results of a query contain some objects that are already attached to the session, the attached objects are returned and these objects ARE NOT REFRESHED. (I would really appreciate an answer why are they not refreshed since at this point Hibernate should know if the data has changed). To my knowledge there is no way to ask query to refresh the objects in L1 cache.
Is my understanding correct? All tests I have done seem to indicate that it is.
This creates a very serious concurrency issue, which I will describe next:
Consider some type of logical assert about data. As an example I will use something like this:
If I query User object for all objects with a nickname 'Bob' all results should have nickname 'Bob'.
(many other asserts would do, for example: 'if I configure uniqueness constraint on DB, then results returned from a query should have unique names' would be another example). Application code is likely to rely on such asserts. Unfortunatelly these are not guaranteed to hold because of (A).
So, let me assume that I have defined entity User which has, among other things, a properties called nickName and userName. Here is a how the above assertion breaks under concurrent use (shown in Groovy code to simplify the assert):
def q0 = mysession.createQuery("select u from User u where fullName = :userName")
q0.setParameter('userName', 'rpeszek')
q0.list()
... concurrent activity changes nickName for rpeszek from 'Troublemaker' to 'Bob'
def q = mysession.createQuery("select u from User u where nickName = :nickName")
q.setParameter('nickName ', 'Bob')
def users = q.list()
assert users.every{ it.branch == 'Bob'}
All of this maybe more readable with using GORM nicities:
User.findByUserName('rpeszek')
... concurrent activity changes nickName for rpeszek from 'Troublemaker' to 'Bob'
User.findAllByNickName('Bob').each{ it.nickName == 'Bob'}
The problem could be worked-around if Hibernate added ability to configure Query with an refresh option (so the objects already on the cache are refreshed with the new data obtained from that query. I would not expect associtations themselves to get refreshed. Without such refresh option the result of the query can be very wrong, the example I have just shown is only one of many such examples.
Am I wrong about any of this? Is there any Query or Session setter for refreshing L1 cache when querying? CacheMode is for L2 cache, I tried every option I could find in JavaDoc.
Can such option be added, please! I am very much open to solutions. The only approach I can think of is to reprogram everything using StatelessSession which is not a good option.
References: I described the issue in countless places and have got no meaningful replies. Current set of replies suggest that the community is completely unaware of this issue. I believe this issue is not unique to Grails and the impact is big.
http://jira.grails.org/browse/GRAILS-11645 http://stackoverflow.com/questions/25106636/strategies-for-dealing-with-concurrency-issues-caused-by-stale-domain-objects-g https://groups.google.com/forum/#!topic/grails-dev-discuss/wzekMGC0ibE
|