I have a batch job that consistently throws stale object exceptions on a dynamic finder. Ideally I would not be running an ORM solution for this job, but I have no choice. The exception occurs in FormulaTagService, which is being called from FormulaBatchPopulatorService. This application is being run on two servers using one database. One server simply performs batch processing.
My questions are: a) Why would a simple select statement, resulting in a domain object instance where no changes are being made to the object during a given transaction would eventually be persisted in a session, resulting in a stale object exception? b) Is it possible that the sorting being done on formula.tags is being persisted at the end of the transaction, thus causing a staleobjectexception if someone else is modifying the formula on a different server?
Note, I did change the service to be read-only, and I am still get the stale object exception. Any help would be greatly appreciated.
Formula Tag Service
@Cacheable("formulaJob")
def getFormulaByTeacherTagsOrDefaultBatchJob(Long evaluationTemplateId, List teacherTags) {
Long formulaByTagsId = existsFormulaWithSameTagsBatchJob(evaluationTemplateId, teacherTags)
if (DefaultFormulaForEvaluationTemplate.get(evaluationTemplateId) == null && formulaByTagsId ==
null) {
return null;
}
Long defaultFormulaId = DefaultFormulaForEvaluationTemplate.get(evaluationTemplateId).formulaId
return formulaByTagsId ?: defaultFormulaId
}
def existsFormulaWithSameTagsBatchJob(Long evaluationTemplateId, List tags){
// LINE BELOW THROWING STALE OBJECT EXCEPTIONS
def formulas = Formula.findAllByExtEvaluationTemplateIdAndIsActive(evaluationTemplateId, true)
for (Formula formula: formulas) {
def formulaTags = formula.tags
if (existsTagMatchIgnoringBlankTags(tags, formulaTags)) {
def id = formula.id
formula.discard()
return id
}
}
}
@CacheEvict(value='formulaJob', allEntries=true)
def resetTags(){
}
def existsTagMatchIgnoringBlankTags(List tagsToCompare, List tagsExisting) {
if (!tagsToCompare || !tagsExisting) {
return false
}
else {
return tagsToCompare?.sort() == tagsExisting?.sort()
}
}
FormulaBatchPopulatorService Snippet
//Doing this below to improve performance of batch processing
if(index%250==0){
cleanUpGorm()
formulaTagService.resetTags() //cache-evict in formulatagservice
}
def cleanUpGorm(){
def session = sessionFactory.currentSession
session.flush()
session.clear()
propertyInstanceMap.get().clear()
}
I believe your answer is correct:
If you sort the tags and it's a list, I believe Groovy does this in place i.e. sorts the original list and returns it. This list would then be persisted at one of the following times:
It's persisted as the index field has likely changed so is considered dirty by GORM / hibernate.
I have a similar issue, resulting in the same problem. I came across this while verifying it to see if others faced it.
Not sure what's going on with the read only part! Is your service transactional?