问题
在使用SpringJPA进行java开发,有时需记录某张数据表的数据变更,于是我先调用JPA的findById方法获取修改前的数据,接着调用@Query 注解(含@Modifying注解)的方法修改数据表的数据,最后再次调用JPA的findById方法获取修改后的数据,结果悲催的发现获得的修改前和修改后数据是一样的,并没有体现出数据的修改。起初我以为是表数据没有修改成功,可查看数据库发现表数据是被修改了的,后来经过网上查询才知道了造成这个问题的原因,特此记录一下。
原因
这主要是JPA的缓存机制造成了上面的问题:
- 当JPA进行find操作时,在EntityManager中缓存了find生成的对象,当再次find时会直接返回该对象。
- JPA中@Query和find、save系列方法是两套不同的体系,@Query引起的数据库变更EntityManager并不能发现,更进一步说,使用其它工具或者其它框架修改数据库中的数据,也不能及时反应到JPA的find系列方法上来。
解决方法
- 避免使用@Query的方法进行数据库更新,在需要时显式清理EntityManager的缓存,或使用save的方法进行数据库更新。
- JPA 提供了另外一种方式则是@Modifying(clearAutomatically = true),@Modifying的clearAutomatically属性为true时,执行完modifying query之后就会清理缓存,从而在下次调用find时就可以读取到数据库中的最新值。
不过这会带来一个新问题:clear 操作清理的缓存中,还包括提交后未 flush 的数据,例如调用 save 而不是 saveAndFlush 就有可能不会立即将修改内容更新到数据库中,在 save 之后 flush 之前调用 @Modifying(clearAutomatically = true) 修饰的方法就有可能导致修改丢失。如果再要解决这个问题,还可以再加上另外一个属性 @Modifying(clearAutomatically = true, flushAutomatically = true),@Modifying 的flushAutomatically属性为true时,执行 modifying query 之前会先调用 flush 操作,从而避免数据丢失问题。在实际运行中,clear 和 flush 操作都可能需要消耗一定的时间,要根据系统实际情况可以选择使用其中的一个或两个属性,以保证系统的正确性。