Issue
I have a a concurrency issue I tried to solve with a while loop that attempts to save an entity multiple times until it hits some max retry count. I'd like to avoid talking about whether or not there are other ways to solve this problem. I have other Stackoverflow posts about that. :) Long story short: there's a unique constraint on a column that is derived and includes a numeric portion that keeps incrementing to avoid collisions. In a loop, I:
- select max(some_value)
- increment the result
- attempt to save new object with this new result
- explicitly flush the entity and, if that fails because of the unique index, I catch a DataAccessException.
All of this seems to work except when the loop goes back to step 1 and tries to select, I get:
17:20:46,111 INFO [org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl] (http-localhost/127.0.0.1:8080-3) HHH000010: On release of batch it still contained JDBC statements
17:20:46,111 INFO [my.Class] (http-localhost/127.0.0.1:8080-3) MESSAGE="Failed to save to database. Will retry (retry count now at: 9) Exception: could not execute statement; SQL [n/a]; constraint [SCHEMA_NAME.UNIQUE_CONSTRAINT_NAME]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement"
And a new Exception is caught. It seems like the first flush that causes the unique constraint violation and throws the DataAccessException
doesn't clear the entity manager's batch. What's the appropriate way to deal with this? I'm using Spring with JPA and don't have direct access to the entity manager. I guess I could inject it if I need it but that's a painful solution to this problem.
Solution
You cannot do that - once you flush something and it fails and an exception is thrown, the transaction will be marked as roll back. That means it doesnt matter that you catch the exception and proceed, you'll end up with a rollback.
Actually it doesnt matter at all what exception was thrown - by default Spring's transaction manager will rollback on every unchecked exception.
You can overcome it by specifically defining a noRollbackFor
on the @Transactional
annotation (providing you are using annotation driver transactions)
Edit - it also won't help you in case of this constraint violation, since the transaction will be probably marked as rollback at the database level.
Answered By - paranoidAndroid
Answer Checked By - Timothy Miller (JavaFixing Admin)