Funny - I was just about to start a new thread on this exact topic.
I am having the exact same problem. I am using Hibernate's versioning feature in the standard way. (I'm using Hibernate 3.0.5)
Here is a mapping:
Code:
<class name="mycompany.Opportunity" lazy="false"
table="cc_opportunity">
<id name="id" type="long" unsaved-value="null">
<column name="id" sql-type="int" not-null="true" />
<generator class="identity" />
</id>
<version name="version" type="long"></version>
<many-to-one name="submittor" />
<property name="name" />
</class>
Pretty simple. Everything works correctly with detached objects.
However, I'm not using detached objects and Hibernate is NOT throwing a StaleObjectException when I expect it to.
The StrategyMy current approach to application transactions: short sessions with no detached objectsI would call my transaction and concurrency strategy
Short Session and Automatic Versioning (I think it is a legitimate approach, especially for web applications, and is different from all the strategies discussed in the Hibernate documentation.)
Here's how it works...
To be clear: This is a web application and I don't put anything in the HttpSession. In other words, there is nowhere to put a detached object or a long Hibernate session.The scenario: User submits a form post to update Opportunity:1 My approach (without detached objects)
- Get the Opportunity id from the servlet request (it was sent via a hidden field)
- load Opportunity:1 from Hibernate
- Populate Opportunity:1 with values from the servlet request
- call session.update(opportunity)
This differs from the common approach in that the common approach seems to be to handle the request like this:
- Construct a new opportunity
- Set its id to 1
- call session.update(opportunity)
The first approach (mine) seems better, because
- you are modifying the actual object, rather than a newly constructed mock version of the object.
- as such you have the ability to lazy-load and modify entities that are related to it.
- and Hibernate will perform dirty-checking, only executing an update if something has actually changed.
You couldn't do that with a newly constructed object, and this is the only way that I can see a web application being able to take advantage of Hibernate's dirty checking.
This approach has worked
spectacularly well in our web applications.
The problem...The only kink is that the versioning check doesn't happen.Sajid - I think I can tell you why this is happening, but I can't tell you how to address it. I have some ideas and I'll post if I come up with anything. I'm hoping that if we can establish this as a legitimate concern, someone from the Hibernate team might help us think through it and suggest a workaround.
In DefaultFlushEntityEventListener.onFlushEntity, it creates a new EntityUpdateAction, and for the version argument it passes
Code:
entry.getVersion()
where entry is the 1st level cached EntityEntry, and NOT the actual instance that is being updated.
The net effect is that it doesn't matter what is stored in the version field of your object if Hibernate has already loaded it and stored it in the session cache.I wrote a junit test case that illustrates the problem
Code:
public void testVersionChecking() throws Exception {
try {
Session session = openSession();
Opportunity opp = (Opportunity) session.load(Opportunity.class,
new Long(1));
Long origVersion = opp.getVersion();
// modify the name so it is dirty
opp.setName(opp.getName() + opp.getVersion() + 1);
session.saveOrUpdate(opp);
session.flush();
// Make sure versioning increment worked
assertTrue(opp.getVersion().intValue() == origVersion.intValue() + 1);
// Try to set version back and update
opp.setVersion(origVersion);
opp.setName("Incorrect Version - should not get saved");
session.saveOrUpdate(opp);
session.flush();
fail("Should have thrown a StaleObjectException exception!");
}
catch (StaleObjectStateException e) {
log.debug("successfully threw exception: " + e,e);
}
}
This test fails. A StaleObjectStateException is never thrown and the "Incorrect Version" update succeeds.
There are several ways to make it work but they all involve detaching the object.
- Before updating, I can call
Code:
session.evict(opp);
- I can create a new session and use it to perform the update
So as Sajid said, there seems to be no real way to enforce version checking on
objects within the same session.
One thing that I've thought about trying is extending DefaultFlushEntityEventListener and overriding it so that it uses the version number from the object itself rather than the session cached instance. However, I'm not as smart as the Hibernate guys and I have no idea how that would impact the rest of my application.