Envers auditing with Hibernate versioning
skomarla Dec 18, 2009 3:18 PMHas anyone seen odd behavior with
using envers and the session cache? I know Adam will ask me for a unit test, but this is a hard one
Object A is versioned (I mean hibernate optimistic locking versioning) and envers Audited
User 1 edits instance a of A via a webpage, and changes a primitive value and saves it
instance a is saved to db using hibernate generic dao. Data in DB is updated (row_version goes from 1 -> 2)
User 2 in a different browser session also edits instance a (retrieves version 2) and saves it
instance a is saved to db using hibernate generic dao. Data in DB is updated (row_version goes from 2 -> 3)
User 1 then retrieves the object and makes another change. The problem occurs here. When the user submits the page, and a dao is used to load the object via the pk, hibernate retrieves version 2 of the object (not version 3). A subsequent update and save yields a "optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect):"
Has anyone seen this? If I take out the envers revision listener, this does not happen. I have tried it many many times. Now with the envers listener in play, if I explicitly retrieve the object and force an evict from the session, and retrieve it again, it loads version 3
This is running in JBoss with JTA and Spring 2.5.6 and envers 1.2.2.GA, but I have seen this with envers 1.2.1.GA as well.
some code snippets.
SpringMVC controller method:
@RequestMapping("/networkNodes/viewRoutingInfo.do") public void viewRoutingObjectVersion(ServletRequest req, ServletResponse resp, @RequestParam("routingId") Integer routingId) throws IOException { Routing r = routingService.findRoutingDetails(routingId); try { resp.getOutputStream().print(r.getObjectVersion() + "<br>"); // modified routingService.testRoutingUpdate(routingId); r = routingService.findRoutingDetails(routingId); resp.getOutputStream().print("after first mod " + r.getObjectVersion() + "<br>"); routingService.testRoutingUpdate(routingId); r = routingService.findRoutingDetails(routingId); resp.getOutputStream().print("after second mod " + r.getObjectVersion() + "<br>"); } catch (Exception e) { resp.getOutputStream().print(e.getMessage()); } }
routingService is a Spring bean that is Transactional via @Transactional annotation.
public void testRoutingUpdate(int routingId) { Routing r = routingDao.get(routingId); r.setDataQuality(r.getDataQuality()+1); routingDao.update(r); }
Some misc code though I don't believe them to be relevant:
RoutingDao is
public interface RoutingDao extends GenericDao<Routing, Integer> {
GenericDao is
public interface GenericDao<T, PK extends Serializable> {
GenericDaoImpl is
public class GenericDaoImpl<T, PK extends Serializable> extends HibernateDaoSupport implements GenericDao<T, PK>, FinderExecutor<T> {
Some output (the URL i'm hitting with browser)
http://skomarla:8080/ifn-springmvc-web/networkNodes/viewRoutingInfo.do?routingId=8
User 1 req 1:
156
after first mod 157
after second mod 158
User 2 req 1:
158
after first mod 159
after second mod 160
User 1 req 2:
158
Object of class [com.sterling.ifn.model.Routing] with identifier [8]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.sterling.ifn.model.Routing#8]
Notice how User 1's second req retrieved version 156