Thank you all for raising this and for contributing to this thread, we can confirm that the solution suggested by @fabian.schwarzkopf is the correct one. Please keep reading for more details on the issue itself, our investigation and the approach to address it.
Background & changes in Confluence
Atlassian app developers started noticing “ org.hibernate.StaleObjectStateException
“ in our mutual customer logs using recent versions of Confluence.
Atlassian investigated the changes in the recent Confluence versions and concluded that the fix for the high-impact bug report caused the behaviour to start to manifest: CONFSERVER-55928.
However, we believe that the issue is not with the Confluence code and has to be be addressed by partners.
See below for more details on the issue itself and the Atlassian validated solution to prevent it in customer installations.
Understand the Issue
The partners report that something similar to this code works for them before.
// [...]
attachmentManager.saveAttachment(...);
// [...]
Page page = pageManager.getPage(pageId);
Page originalPage = (Page) page.clone();
String content = page.getBodyAsString();
//modify content
page.setBodyAsString(content);
pageManager.saveContentEntity(page, originalPage, null);
The reason it works without specify outer transaction is that Confluence automatically provides transaction wrapper for mostly all manager layer method call. So the code above will become:
// [...]
attachmentManager.saveAttachment(...); // ===> Transaction 1
// [...]
Page page = pageManager.getPage(pageId); // ===> Transaction 2
Page originalPage = (Page) page.clone();
String content = page.getBodyAsString();
//modify content
page.setBodyAsString(content);
pageManager.saveContentEntity(page, originalPage, null); // ===> Transaction 3
And it works because there no relation between the call.
Now with the change from Unknowns Attachment Fix (CONFSERVER-55928). When Collaborative Editing is enabled, Confluence will try to trigger content reconciliation after attachment change. And this happen right after current transaction is completed. With the help from this code:
synchronizationManager.runOnSuccessfulCommit(() -> {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager, new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRES_NEW));
transactionTemplate.execute(status -> {
eventPublisher.publish(createContentUpdatedEvent(content, updateTrigger));
return null;
});
});
Apply to the code above, mean that right after “Transaction 1“ the page will be updated. Then page
object from line number 4 is stale. That is why vendor got org.hibernate.StaleObjectStateException
whenever they try to update that object.
How to address it in the app
To address the issue above, please update the app code in the following way:
transactionTemplate.execute(() -> { // outter Transaction
// [...]
attachmentManager.saveAttachment(...); // outter Transaction
// [...]
Page page = pageManager.getPage(pageId); // outter Transaction
Page originalPage = (Page) page.clone();
String content = page.getBodyAsString();
//modify content
page.setBodyAsString(content);
pageManager.saveContentEntity(page, originalPage, null); // outter Transaction
// [...]
return null;
}); // page won't be updated by Reconciliation process until this line
By wrapping all the code within one transaction, we makes sure all manager method call using same Transaction (as the default propagation level is PROPAGATION_REQUIRED
). And the stage of Hibernate object (
page` in this case) will consistent across method call.
Because the return value of manager’s method call could be a Hibernate object. From the best practice to work with Hibernate object is all of them must belong to single Unit of Work (section 11.1.1). Confluence provides transaction wrapper in manager’s method call to make it easier to use our API but partners should change their implementation to make it work more resilient and follow the best practices. More information of how to work with transaction in Confluence could be found in here