Issue
After installing Liferay DXP 7.2 Service Pack 1 or Fix Pack 2 a StaleObjectStateException is generated when a customization uses a Liferay service to update or modify data:
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.liferay.portal.model.impl.ContactImpl#8367818] at org.hibernate.event.def.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:485) at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:255) at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:84) at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:867) at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:851) at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:855) at com.liferay.portal.dao.orm.hibernate.SessionImpl.merge(SessionImpl.java:241) ... 208 more
Resolution
A StaleObjectStateException is commonly thrown by Hibernate when stale data is detected, such as when conflicting modifications are encountered.
General Pattern: if you update a model, but don’t re-capture the return value, future updates to that same model, even within the same thread, may result in a StaleObjectStateException. To resolve this, make sure to store the return value of any methods that update the object:
Incorrect:
_fooLocalService.update(foo);
Correct:
foo = _fooLocalService.update(foo);
Example #1: if you fetch a variable and then update it multiple times within different methods, this may also result in a StaleObjectStateException. This pattern often spans multiple classes (something updated in one class’s instance method is never returned, so another class never knows about the update). To resolve this, make sure that any method that updates a model object also returns that model object:
Incorrect:
public void setLastAccessDate(Foo foo) {
foo.setLastAccessDate(new Date());
_fooLocalService.update(foo);
}
public void setLastAccessIPAddress(Foo foo) {
foo.setLastAccessIPAddress("127.0.0.1");
_fooLocalService.update(foo);
}
public void service(
HttpServletRequest request, HttpServletResponse response) {
Foo foo = _fooLocalService.getFoo(fooId);
setLastAccessDate(foo);
setLastAccessIPAddress(foo);
}
Correct:
public Foo setLastAccessDate(Foo foo) {
foo.setLastAccessDate(new Date());
return _fooLocalService.update(foo);
}
public Foo setLastAccessIPAddress(Foo foo) {
foo.setLastAccessIPAddress("127.0.0.1");
return _fooLocalService.update(foo);
}
public void service(
HttpServletRequest request, HttpServletResponse response) {
Foo foo = _fooLocalService.getFoo(fooId);
foo = setLastAccessDate(foo);
foo = setLastAccessIPAddress(foo);
}
Example #2: if you fetch a variable and then store it in a shared memory location (such as the HttpSession or an internal cache of frequently accessed objects), this shared memory location may not know about any updates, which may also cause a StaleObjectStateException. To resolve this, you would make sure to reset the value in the shared location any time an update occurs:
Incorrect:
public void setLastAccessDate(Foo foo) {
foo.setLastAccessDate(new Date());
_fooLocalService.update(foo);
}
public void service(
HttpServletRequest request, HttpServletResponse response) {
Foo foo = _fooLocalService.getFoo(fooId);
session.setAttribute("FOO", foo);
setLastAccessDate(foo);
}
Correct:
public Foo setLastAccessDate(Foo foo) {
foo.setLastAccessDate(new Date());
return _fooLocalService.update(foo);
}
public void service(
HttpServletRequest request, HttpServletResponse response) {
Foo foo = _fooLocalService.getFoo(fooId);
session.setAttribute("FOO", foo);
foo = setLastAccessDate(foo);
session.setAttribute("FOO", foo);
}
Additional Examples from Liferay source: LPS-86385 and LPS-86695
Additional Information
As of Liferay DXP 7.2 Service Pack 1 or Fix Pack 2, additional Liferay services now have MVCC enabled.
Liferay's MVCC implementation relies on Hibernate's optimistic locking system. With this mechanism in place, modifications that Hibernate identifies as conflicting will be revealed.
Below is a list of additional Liferay services that now have MVCC enabled.
Additional Liferay Services with MVCC Enabled
| Components | Services | Tables |
|
Account |
AccountEntry |
AccountEntry |
|
Asset |
AssetEntryUsage |
AssetEntryUsage |
|
Asset Auto Tagger |
AssetAutoTaggerEntry |
AssetAutoTaggerEntry |
|
Asset Category Property |
AssetCategoryProperty |
AssetCategoryProperty |
|
Asset Display Page |
AssetDisplayPageEntry |
AssetDisplayPageEntry |
|
Asset Entry Rel |
AssetEntryAssetCategoryRel |
AssetEntryAssetCategoryRel |
|
Asset List |
AssetListEntry |
AssetListEntry |
|
Blogs |
BlogsEntry |
BlogsEntry |
|
Bookmarks |
BookmarksEntry |
BookmarksEntry |
|
Calendar |
Calendar |
Calendar |
|
Document Library Content |
DLContent |
DLContent |
|
Dynamic Data Mapping |
DDMContent |
DDMContent |
|
Fragment |
FragmentCollection |
FragmentCollection |
|
Journal |
JournalArticle |
JournalArticle |
|
Knowledge Base |
KBArticle |
KBArticle |
|
Layout Page Template |
LayoutPageTemplateCollection |
LayoutPageTemplateCollection |
|
Mobile Device Rules |
MDRAction |
MDRAction |
|
Segments |
SegmentsEntry |
SegmentsEntry |
|
Site |
SiteFriendlyURL |
SiteFriendlyURL |
|
Site Navigation |
SiteNavigationMenu |
SiteNavigationMenu |
|
Trash |
TrashEntry |
TrashEntry |
|
Wiki |
WikiNode |
WikiNode |