如何使用 spring 数据 jpa 处理缓存实体的更新?
How to handle updates on cached entites with spring data jpa?
我在大多数 spring 数据存储库上使用 springs @Cacheable
注释。这包括 findById(...)
方法。结果每次我想编辑和保存一个实体时,我都会得到一个异常:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
这是我的存储库:
public interface ProductRepository extends Repository<Product, Long> {
@Cacheable(cacheNames = "products", key = "#id")
Optional<Product> findById(Long id);
@Caching(
put = {
@CachePut(cacheNames = "products", key = "#result.id"),
@CachePut(cacheNames = "productByIsbn", key = "#entity.isbn", condition = "#entity.isbn != null")
})
<S extends Product> S save(S entity);
@Override
@Caching(evict = {
@CacheEvict(cacheNames = "products", key = "#entity.id"),
@CacheEvict(cacheNames = "productByIsbn", key = "#entity.isbn", condition = "#entity.isbn != null")
})
void delete(AbstractProductEntity entity);
@Cacheable(cacheNames = "productByIsbn", condition = "#isbn != null")
Optional<Product> findOneByIsbn(String isbn);
}
据我了解,我的缓存实体与持久化上下文分离,无法再次保存。
解决这个问题的最佳方法是什么?
我知道我可以添加第二个 findByUncached 方法,该方法仅在内部使用并绕过缓存。但对我来说,这似乎是一个糟糕的解决方案,因为我必须对代码段中使用的所有方法执行此操作,其中实体从存储库中读取,然后再次持久化。
如果我相信我的缓存是最新的,我可以重新附加缓存的对象,但我如何使用 spring-data 来做到这一点?普通版本库界面没有merge方法。
EDIT1:感谢 Jens Schauder 指出我的版本问题。
结果是所有缓存实体中的版本 属性 在调用 save(...) 后未在缓存中正确更新。虽然我不完全确定为什么会这样,但是用 @CacheEvict
替换 @CachePut
注释并将缓存更新留给下一次读取解决了这个问题。我现在正在研究 @CachePut
应该如何工作。
这里的问题不在于实体分离。
Spring 如果您为分离的实体调用 save
,Data JPA 将执行合并。
问题是该实体是“陈旧的”,即它的版本属性不再与数据库中的版本匹配,因为其他一些事务更新了该实体,从而也更新了版本。
一般来说,我建议不要将第三方缓存解决方案与 JPA 一起使用。 JPA 有自己的两个缓存层。
一级缓存是 EntityManager
本身。
对您来说更有趣的可能是 2nd level cache and possibly the query cache,它是 Hibernate 特定的 afaik。
我在大多数 spring 数据存储库上使用 springs @Cacheable
注释。这包括 findById(...)
方法。结果每次我想编辑和保存一个实体时,我都会得到一个异常:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
这是我的存储库:
public interface ProductRepository extends Repository<Product, Long> {
@Cacheable(cacheNames = "products", key = "#id")
Optional<Product> findById(Long id);
@Caching(
put = {
@CachePut(cacheNames = "products", key = "#result.id"),
@CachePut(cacheNames = "productByIsbn", key = "#entity.isbn", condition = "#entity.isbn != null")
})
<S extends Product> S save(S entity);
@Override
@Caching(evict = {
@CacheEvict(cacheNames = "products", key = "#entity.id"),
@CacheEvict(cacheNames = "productByIsbn", key = "#entity.isbn", condition = "#entity.isbn != null")
})
void delete(AbstractProductEntity entity);
@Cacheable(cacheNames = "productByIsbn", condition = "#isbn != null")
Optional<Product> findOneByIsbn(String isbn);
}
据我了解,我的缓存实体与持久化上下文分离,无法再次保存。
解决这个问题的最佳方法是什么?
我知道我可以添加第二个 findByUncached 方法,该方法仅在内部使用并绕过缓存。但对我来说,这似乎是一个糟糕的解决方案,因为我必须对代码段中使用的所有方法执行此操作,其中实体从存储库中读取,然后再次持久化。
如果我相信我的缓存是最新的,我可以重新附加缓存的对象,但我如何使用 spring-data 来做到这一点?普通版本库界面没有merge方法。
EDIT1:感谢 Jens Schauder 指出我的版本问题。
结果是所有缓存实体中的版本 属性 在调用 save(...) 后未在缓存中正确更新。虽然我不完全确定为什么会这样,但是用 @CacheEvict
替换 @CachePut
注释并将缓存更新留给下一次读取解决了这个问题。我现在正在研究 @CachePut
应该如何工作。
这里的问题不在于实体分离。
Spring 如果您为分离的实体调用 save
,Data JPA 将执行合并。
问题是该实体是“陈旧的”,即它的版本属性不再与数据库中的版本匹配,因为其他一些事务更新了该实体,从而也更新了版本。
一般来说,我建议不要将第三方缓存解决方案与 JPA 一起使用。 JPA 有自己的两个缓存层。
一级缓存是 EntityManager
本身。
对您来说更有趣的可能是 2nd level cache and possibly the query cache,它是 Hibernate 特定的 afaik。