在一个@Transaction方法内,循环调用一个数据库读取操作。代码类似如下:
业务处理类中
@Transactional(rollbackFor = Exception.class)
public void proc(List<Info> infoList) {
//在infoList中,有很多供应商Id,Id存在很多重复的现像
for(Info info:infoList){
//对infoList循环,根据供应商Id取相应的供应商信息
supplierService.getById(info.getSupplierId());
//这里的读取效率很低,在这个方法签名上加@Cacheable,使用@Cacheable管理Redis缓存
//其它处理省略...
}
}
Service类中:
@Cacheable(value = "supplier", cacheManager = "myRedisCacheManager", key = "#id", unless = "#result == null")
public Supplier getById(Long id) {
log.debug("读数据库,id:{}",id);
//数据库查询代码省略
}
期望的结果:当一个supplierId被查询后,放入Redis缓存中,以后再次访问这个方法时,将直接从Redis中读取,,不会进入Service内的方法体。
测试发现的问题:
同一个supplierId,在循环中调用加了@Cacheable的方法时,再次访问时并没有走Redis,日志中发现同一个SupplierId在不停的重复访问数据库,但查看Redis内容中,却发现了Supplier信息已经被缓存了。
虽然通过改变业务处理逻辑,可以避过这个问题,但是我仍然希望知道产生这种现象的原因。
思路:由于是在SpringBoot框架下,使用了Redis, SpringBoot的Cache接口在Redis下的默认实现类是RedisCache。找到这个类,打开它,在其中搜索Put,找到了public void put(Object key, @Nullable Object value)方法,在这个方法下加上断点。
经断点发现:RedisCache中的put方法,是在循环都执行完毕后,才调用的。这就解释了为什么反复调用@Cacheable方法时,不走Redis的原因,是这个时候,Redis中根本就没有缓存返回的内容。
这又是什么原因导致循环完毕后@Cacheable方法才执行Put操作呢?
难道不是在调用@Cacheable方法时同步处理Redis缓存吗?
继续观察,发现在Put方法断点时的方法调用栈中,有一个方法是afterCommit,看到这个方法名,马上就能联想到可能是在事务提交后才执行了put方法。
看来是在加@Transaction的方法内,调用@Cacheable方法时,put操作会在事务提交后才执行的。
又观察断点,发现put方法是通过一个TransactionAwareCacheDecorator的类来调用的。搜索一下这个类名,找到了这个类的Javadoc文档,内容如下:
Cache decorator which synchronizes its put(java.lang.Object, java.lang.Object), evict(java.lang.Object) and clear() operations with Spring-managed transactions (through Spring's TransactionSynchronizationManager, performing the actual cache put/evict/clear operation only in the after-commit phase of a successful transaction. If no transaction is active, put(java.lang.Object, java.lang.Object), evict(java.lang.Object) and clear() operations will be performed immediately, as usual.
Note: Use of immediate operations such as putIfAbsent(java.lang.Object, java.lang.Object) and evictIfPresent(java.lang.Object) cannot be deferred to the after-commit phase of a running transaction. Use these with care in a transactional environment.
Spring Boot缓存优化:@Transaction与@Cacheable的事务延迟问题
在Spring Boot中,当@Transaction方法内循环调用@Cacheable方法时,缓存操作会在事务成功提交后执行,导致在事务期间无法从Redis获取数据。这个问题可以通过调整业务逻辑或理解TransactionAwareCacheDecorator的工作原理来解决。
629

被折叠的 条评论
为什么被折叠?



