Reputation: 790
I am using spring cache abstraction using Ehcache as a cache provider. I am trying to attach cache operations to spring JPA transactions, but not able to do so.
Even though transaction fails/rollback cache put happens.
Configuration,
@Bean
public EhCacheManagerFactoryBean cacheManagerUsingSpringApi() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
// provide xml file for ehcache configuration/
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("spring-cache-abs-ehcache.xml"));
return ehCacheManagerFactoryBean;
}
@Bean
public org.springframework.cache.CacheManager ehCacheCacheManager() {
final EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager(cacheManagerUsingSpringApi().getObject());
ehCacheCacheManager.setTransactionAware(true); // Setting transaction aware
return ehCacheCacheManager;
}
spring-cache-abs-ehcache.xml,
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true"
monitoring="autodetect"
dynamicConfig="true">
<cache name="EmployeeCache"
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="300" timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
<persistence strategy="localTempSwap" />
</cache>
</ehcache>
EmployeeRepository,
public interface EmployeeRepository extends JpaRepository<Employee, Long>, CustomEmployeeRepository {
}
Transactional Method,
@Repository
public class EmployeeRepositoryImpl implements CustomEmployeeRepository {
@PersistenceContext
private EntityManager entityManager;
@Autowired
private CacheManager cacheManager;
// THIS METHOD SHOULD NOT PUT INTO CACHE WITH NEW NAME
@Override
@Transactional
@Cacheable(cacheNames = "EmployeeCache", key = "#a0.id")
public Employee customUpdate(Employee employee) {
employee.setFirstName(UUID.randomUUID().toString());
entityManager.merge(employee);
// rolling back transaction
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return employee;
}
}
Test case(caller),
@Test
public void testCustomUpdate() {
// GIVEN
Employee employee = new Employee();
employee.setFirstName(UUID.randomUUID().toString());
employee.setLastName(UUID.randomUUID().toString());
final Employee savedEmployee = employeeRepository.save(employee);
// WHEN
final Employee updatedEmployee = employeeRepository.customUpdate(savedEmployee);
// THEN
final Cache employeeCache = cacheManager.getCache("EmployeeCache");
final Cache.ValueWrapper object = employeeCache.get(updatedEmployee.getId());
assertNull(object);
}
Test Should succeed i.e spring should not PUT data into cache in method employeeRepository.customUpdate
if transaction was rollback in that method.
But, spring puts data into cache even if transactions fails.
NOTE: Weird part is, if entry already exists in cache , then @CachePut does not updates entry in cache if transaction fails.
So, if I annotate employeeRepository.save
with @CachePut(cacheNames = "EmployeeCache", key = "#result.id")
then cache is not updated in update call.
What is missing here?
Upvotes: 2
Views: 3545
Reputation: 21
Refer to: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-ordering
The below declaration will cause the transactional advice to be executed first and then the cacheable advice.
<tx:annotation-driven order="0"/>
<cache:annotation-driven cache-manager="ehCacheManager" order="1"/>
You will see the code working if you call the @Cacheable annotated method from another method which is annotated with @Transactional. Both annotations i.e. @Cacheable and @Transaction on the same method won't help your cause unless you have advice ordering implemented. Please see below a crude implementation of your logic.
@Override
@Transactional
public Employee customUpdate(Employee employee) {
Employee mergedEmployee = updateHelper(employee);
// rolling back transaction
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return mergedEmployee;
}
@Cacheable(cacheNames = "EmployeeCache", key = "#a0.id")
public Employee updateHelper(Employee employee)) {
employee.setFirstName(UUID.randomUUID().toString());
//On a merge(...) call on the entityManager, the returned item is the managed
//instance
return entityManager.merge(employee);
}
Upvotes: 2