Reputation: 302
I am working Spring and Hibernate. I have a requirement where I need to update a particular field by adding a number to it. Since multiple threads could execute it at the same time, while updating I check the field value with the old value. So if nothing was updated that means it was incremented by some other thread and we trigger a retry.
CompanyService
public Company getAndIncrementRequestId(final int companyId, int retry) throws Exception {
Optional<Company> companyOptional = companyRepository.findById(companyId);
if (!companyOptional.isPresent()) {
throw new EntityNotFoundException("Company not found for given id" + companyId);
}
Company company = companyOptional.get();
int oldRequestId = company.getRequestId();
int requestId;
if (oldRequestId == Integer.MAX_VALUE) {
requestId = 1;
} else {
requestId = oldRequestId + 1;
}
company.setRequestId(requestId); //--------------------------> PROBLEM
int result = companyRepository.updateRequestId(companyId, requestId, oldRequestId);
if (result == 0) {
if (retry < 0) {
throw new Exception("Unable to get requestId");
}
LOG.warn("Retrying since there was some update on requestId by some other thread");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
LOG.warn("Unexpected InterruptException occurred while trying to get requestId");
}
return getAndIncrementRequestId(companyId, retry - 1);
}
return company;
}
CompanyRepository
@Transactional
public interface CompanyRepository extends CrudRepository<Company, Integer> {
Optional<Company> findById(String id);
@Modifying(clearAutomatically = true)
@Query("update Company c set c.requestId = :requestId WHERE c.id = :companyId AND c.requestId = :oldRequestId")
int updateRequestId(@Param("companyId") Integer companyId, @Param("requestId") Integer requestId,@Param("oldRequestId") Integer oldRequestId);
}
But this above code in Service will trigger two hibernate updates one which set the requestId with lastest requestId and the other the actual update. Could observe two queries in the log after setting show-sql as true.
But if the line ,
company.setRequestId(requestId);
Is moved down after the companyRepository.updateRequestId() it works fine.
Working CompanyService
public Company getAndIncrementRequestId(final int companyId, int retry) throws Exception {
Optional<Company> companyOptional = companyRepository.findById(companyId);
if (!companyOptional.isPresent()) {
throw new EntityNotFoundException("Company not found for given id" + companyId);
}
Company company = companyOptional.get();
int oldRequestId = company.getRequestId();
int requestId;
if (oldRequestId == Integer.MAX_VALUE) {
requestId = 1;
} else {
requestId = oldRequestId + 1;
}
int result = companyRepository.updateRequestId(companyId, requestId, oldRequestId);
if (result == 0) {
if (retry < 0) {
throw new Exception("Unable to get requestId");
}
LOG.warn("Retrying since there was some update on requestId by some other thread");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
LOG.warn("Unexpected InterruptException occurred while trying to get requestId");
}
return getAndIncrementRequestId(companyId, retry - 1);
}
company.setRequestId(requestId); //--------------------------> PROBLEM DOES NOT EXISTS
return company;
}
Sp my question why are there two queries when I have not even passed the entity Company anywhere..?
Upvotes: 0
Views: 1323
Reputation: 2592
It is because when you do "companyRepository.findById(companyId);" the returned company is returned in managed state. So , when in case 1 you set the request id before invoking "companyRepository.updateRequestId(companyId, requestId, oldRequestId);", a transaction object is created in the company repository which executes all the pending updates of the managed entity plus the query of the method "updateRequestId" also gets fired. While in second case, since you have written set statement after invoking "companyRepository.updateRequestId(companyId, requestId, oldRequestId);", that is why the update on managed object never gets fired because it does not get any transaction
Upvotes: 1