Donald Jackson
Donald Jackson

Reputation: 521

Can't solve HibernateOptimisticLockingFailureException in Grails 2.4.5

I have been trying for many hours to figure out why I cannot resolve this issue to no avail.

Basically, I have a domain class (PrizeSchedule) which is used in my app for scheduling when prizes are to be given away. When a request comes in and it is time for a prize to be given (scheduled time is less than now) it will then allocate that prize and mark it as processed.

What this means is, if enough time passes between entries there could be 2 or more prizes ready to be given away. When I test my application with 10 (or more) requests directly after each other, in this scenario I always get the following issue on a few of the requests:

optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

I've tried using prizeScheduleInstance.merge and prizeScheduleInstance.lock but the same errors pop up. After much time I've figured out that the PrizeSchedule.find() call is not producing the correct results. It appears though that even though the previous request has done the following:

prizeSchedule.setProcessed(true);
prizeSchedule.setReferenceNumber(prizeSchedule.generateToken());           
prizeSchedule.save(flush:true);

The next request is still able to find the processed PrizeSchedule instance, you can see here in logs below

Request 1:

2015-07-13 22:05:28,915 [http-bio-8080-exec-7] DEBUG PrizeService  - Session ID: 603 won prize ID: 103314

Request 2:

2015-07-13 22:05:30,136 [http-bio-8080-exec-12] DEBUG PrizeService  - Session ID: 604 won prize ID: 103314

As you can see, the second request still somehow found the PrizeSchedule instance even though the processed flag has been marked as true.

I have tried variations of this query to find the PrizeSchedule instance and they all produce the same issue, here below are the ones I have tried

PrizeSchedule ps = PrizeSchedule.findByCampaignAndDateScheduledLessThanAndProcessed(campaign, new Date(), false, [cache: false])

and

PrizeSchedule ps = ps = PrizeSchedule.find("FROM PrizeSchedule ps WHERE ps.campaign = :campaign AND ps.processed = :processed AND ps.dateScheduled < :now ORDER BY ps.dateScheduled ASC",
        [campaign: campaign, processed: false, now: new Date()], [cache: false]
    )

In case it matters - the controller I have calls a service to process the requests, which in turn calls other groovy/src code which makes use of the prizeService through

def prizeService = Holders.grailsApplication.mainContext.getBean 'prizeService'

This is because of generic interfaces and abstract classes used in those areas.

I hope someone can help, and thanks in advance for taking the time to read this!

Upvotes: 1

Views: 1287

Answers (2)

codedabbler
codedabbler

Reputation: 1261

I hope you understand that when you do concurrent updates with optimistic locking, there is a chance that the StateObjectStateException will be thrown. It will become more common as the concurrency increases in your model.

Since you mention this is part of a scheduled job, you could have code that retries the prize awarding in a new session, for e.g., scheduling another job to run immediately again.

Upvotes: 0

Donald Jackson
Donald Jackson

Reputation: 521

OK I'm posting this as the solution to my problem, just in case anyone else comes across this in future. This may be obvious but as a relative newcomer to Grails I found this quite difficult to solve/find information about.

Basically my controller was annotated with

@Transactional(readOnly = true)

And the method doing the actual processing was annotated with

@Transactional

Finally, the service itself was also annotated with

@Transactional

My assumption is that having these annotations somehow mix the transactions that are used when accessing and storing data in the Hibernate session. After removing the annotations from my controller (and adjusting code) and just having my service as the @Transactional component - everything started working as I expected.

I hope this helps someone out there suffering like me!

Upvotes: 1

Related Questions