SpringJpa踩坑之自动更新

前提:使用框架为springboot 1.5.9 数据库为mysql

改动项目的时候深深感觉JPA自带的API太不灵活了,于是使用自定义的更新方法

写完更新完方法之后,单元测试跑通当时还没出现问题,接着部署环境测试时遇到问题了。明明只想修改指定的几行数据,却发现其他数据也被修改,代码如下:

spring data jpa query 批量更新 spring jpa 部分更新_hibernate


SQL打印如下:

spring data jpa query 批量更新 spring jpa 部分更新_jpa_02


顿时感觉坑爹,JPA居然会自动帮你执行更新语句,这要是直接放到线上环境后果不堪设想。接着我在网上查找资料时才了解到,锅主要不是JPA的,本身JPA就是封装Hibernate,自动更新是Hibernate的特性。

1.Jpa会保管pojo对象的状态:

1)临时(新建)状态:对象被创建时的状态,数据库里面没有与之对应的记录!也就是没有保存到数据库之前的状态。

2) 持久(托管)状态:处于session的管理中,并且数据库里面存在与之对应的记录!

3) 游离(脱管)状态:对象不处于session的管理中,但是数据库里面存在与之对应的记录!

4) 删除状态:数据库中没有,Session缓存中没有。

spring data jpa query 批量更新 spring jpa 部分更新_jpa_03


2.自动更新原因

因为当实体对象属于托管状态下时,往这个对象里面的某个属性set新的值,这个新的值会被自动更新到数据表中去。(JPA自带的特性)

如何判断对象是否处于托管状态:

使用EntityManager.contains(entity)方法可以得知某个实体对象是否处于托管状态,也就是说是否处于persistence context中。

EntityManager:是JPA中用于增删改查的接口,它的作用相当于一座桥梁,连接内存中的java对象和数据库的数据存储

persistence context:是由一组受托管的实体对象实例所构成的集合。它受entity manager 的管理。Entity manager追踪persistence context中所有对象的修改和更新情况,并根据指定的flush模式将这些修改保存到数据库中。一旦persistence context被关闭,所有实体对象实例都会脱离EntityManager而成为非托管对象。对象一旦从persistence context中脱离,就不再受entity manager管理了,任何对此对象的状态变更也将不会被同步到数据库。

关于自动更新的EntityManager的三个关键方法:

1.merge
通过entityManager将一个存在的实体“同步到”persistenceContext中。
实体的状态将从其单独的状态转换为受persistenceContext管理的状态。
如果Entity是新创建的,则这个方法类似于persist()这个方法。
如果Entity已经存在的,则只作为更新操作。

2.Flush
将PersistenceContext的信息同步到数据库中。
当触发Flush这个动作的时候,所有的实体都将会被insert/update/remove到数据库中。会强制发送sql更新(update)语句,数据由内存到数据库
flush触发的时间:当对缓存中的数据进行过修改,在提交事务时,会调用flush方法刷新数据库

3.Refresh
Refresh的作用是从数据库中将Entity的状态进行更新操作。如果Entity和数据库中的数据不一致,将更新数据库中的数据到Entity中。数据由数据库到缓存
但在commit或flush之前调用Refresh,那么缓存中的数据又变成了和数据库中的数据一样的了,你原先修改的数据白费了

3.如何避免自动更新
1、事务提交之前调用Refresh(不推荐)

改变后的内存中的PO在Refresh之后又变得和数据库一模一样,然后再在事务提交之后调用flush方法,将数据从内存中更新到数据库(好像这样做没多大意义)。

2、set 属性前,将其状态改为游离状态。

将处于persistence context范围中的托管对象变为游离对象,这时重置属性值不会更新到数据表中去。

persistence context 有两种类型。一种称为 transaction-scope persistence context,在这种状态下 persistence context 是与事务相关的,也就是说在事务范围内托管对象所有的更改都会被更新到数据表中去,当事务提交后,这个 persistence context 也就销毁了,之后的更改不会被更新到数据表中去。另一种为 extended persistence context

一般使用的 persistence context 都是默认的 transaction-scoped,extended 的很少用到。

将对象置为游离态的方法:

close 方法:关闭 session 对象,但是 session 关闭会使得后续在进行相关操作可能会报错,视情况使用。
语法:session.close ()

clear 方法:将 session 中的所有的对象全部清除出缓存,这个方法比较容易一刀切。
语法:session.clear ()

evict 方法:将指定的某个对象清除出缓存 session,这个相对来说灵活一些
语法:session.evict(Object obj)

3.避免直接修改查询出的PO对象,通过转换PO对象,来避开直接操作JPA映射对象

4.总结
Hibernate这个自动更新在不了解这个特性的情况下使用,确实容易酿成大祸,这个功能还是得看实际业务逻辑是否使用。