引言
问题起源于对新我的项目-数字外围的代码审查,在审阅账户模块后,发现补录、更新等接口没有调用JPA
的仓库save
办法进行数据长久化,但更新仍然失效,随查阅材料文献,开启了对本议题的探索。
指标:没有调用save
办法,更新是怎么失效的?
试一试
在查阅大量材料后,理解到与JPA
长久化上下文Persistence Context
无关,一起试试吧。
试验筹备
初始化spring-boot
我的项目,依赖spring-data-jpa
,并开启spring-data
的show-sql
配置以便调试。
spring: jpa: show-sql: true
建设客户信息实体:
/** * 客户信息表 */@Entity@Table(name = "CUSTOMER")public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /** * 客户姓名 */ private String name; /** * 客户手机号 */ private String phone; @Override public String toString() { return "Customer{" + "id=" + id + ", name='" + name + '\'' + ", phone='" + phone + '\'' + '}'; }}
配置DataJpaTest
启用JPA
测试环境,不启动整个spring-context
,能够缩小单元测试执行耗时。
/** * 客户信息仓库测试 */@DataJpaTest // 主动配置JPA测试@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // 不启用内嵌数据库代替public class CustomerRepositoryTest { private static final Logger logger = LoggerFactory.getLogger(CustomerRepositoryTest.class);}
复现更新场景
更新办法如下,构建一条Hello Kitty!
测试数据,并对保留后的实体信息进行批改,但不调用save
。
/** * 数据更新办法 */public void update() { logger.info("----------更新测试用例开始----------"); Customer customer = new Customer(); customer.setName("Hello Kitty!"); customer.setPhone("17712345678"); this.customerRepository.save(customer); logger.info("构建测试数据: {}", customer); Long id = customer.getId(); this.customerRepository.findById(id).ifPresent(entity -> { entity.setName("Hello 冬泳怪鸽!"); entity.setPhone("18888888888"); logger.info("更新测试数据: {}", entity); }); logger.info("----------更新测试用例完结----------");}
开启事务,设置事务不回滚,调用上文的update
办法。
@Test@Transactional // 开启事务@Rollback(value = false) // 事务不回滚public void updateCustomerInTransaction() { this.update();}
查看数据库,更新胜利。
查看日志,在updateCustomerInTransaction
办法执行完后,Hibernate
执行了update CUSTOMER set name=?, phone=? where id=?
更新,自动更新胜利。
2022-01-15 09:42:34.461 INFO 8206 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------更新测试用例开始----------Hibernate: insert into CUSTOMER (name, phone) values (?, ?)2022-01-15 09:42:34.537 INFO 8206 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 构建测试数据: Customer{id=1, name='Hello Kitty!', phone='17712345678'}2022-01-15 09:42:34.559 INFO 8206 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 更新测试数据: Customer{id=1, name='Hello 冬泳怪鸽!', phone='18888888888'}2022-01-15 09:42:34.559 INFO 8206 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------更新测试用例完结----------Hibernate: update CUSTOMER set name=?, phone=? where id=?
敞开事务,比照试验。
@Test@Transactional(propagation = Propagation.NOT_SUPPORTED) // 挂起/敞开事务public void updateCustomerWithoutTransaction() { this.update();}
查看数据库,数据没有更新。
查看日志,Hibernate
没有执行update
自动更新。
2022-01-15 10:26:20.866 INFO 8897 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------更新测试用例开始----------Hibernate: insert into CUSTOMER (name, phone) values (?, ?)2022-01-15 10:26:20.996 INFO 8897 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 构建测试数据: Customer{id=2, name='Hello Kitty!', phone='17712345678'}Hibernate: select customer0_.id as id1_0_0_, customer0_.name as name2_0_0_, customer0_.phone as phone3_0_0_ from CUSTOMER customer0_ where customer0_.id=?2022-01-15 10:26:21.054 INFO 8897 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 更新测试数据: Customer{id=2, name='Hello 冬泳怪鸽!', phone='18888888888'}2022-01-15 10:26:21.054 INFO 8897 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------更新测试用例完结----------
比照试验后果:事务开启的前提下,对实体的改变会主动长久化到数据库;当事务敞开时,则不失效。
长久化上下文
咱们先来理解下JPA
长久化上下文:
The persistence context is the first-level cache where all the entities are fetched from the database or saved to the database. It sits between our application and persistent storage.
Persistence context keeps track of any changes made into a managed entity. If anything changes during a transaction, then the entity is marked as dirty. When the transaction completes, these changes are flushed into persistent storage.
If every change made in the entity makes a call to persistent storage, we can imagine how many calls will be made. This will lead to a performance impact because persistent storage calls are expensive.长久化上下文是一级缓存,缓存中所有实体都是从数据库中
fetch
或save
到数据库中的,它位于应用程序和长久存储之间。
长久化上下文跟踪所治理实体的所有更改,如果在事务中产生扭转,实体会被标记为dirty
,事务实现后,所有改变会同步到长久存储。
如果实体做的每一次改变都要调用存储,能够设想须要将调用很屡次,这会引起性能问题,因为长久存储调用很低廉。
具体分析下事务状态下试验的日志:
①2022-01-15 09:42:34.461 INFO 8206 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------更新测试用例开始----------②Hibernate: insert into CUSTOMER (name, phone) values (?, ?)③2022-01-15 09:42:34.537 INFO 8206 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 构建测试数据: Customer{id=1, name='Hello Kitty!', phone='17712345678'}④2022-01-15 09:42:34.559 INFO 8206 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 更新测试数据: Customer{id=1, name='Hello 冬泳怪鸽!', phone='18888888888'}⑤2022-01-15 09:42:34.559 INFO 8206 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------更新测试用例完结----------⑥Hibernate: update CUSTOMER set name=?, phone=? where id=?
- ① 办法开始
- ②
CUSTOMER
表插入数据,并将该实体保留到长久化上下文 - ③ 打印长久化后的数据
- ④ 更新实体数据,这里没有执行
select
语句,因为长久化上下文存在该实体,findById
间接从长久化上下文中获取 - ⑤ 办法完结
- ⑥ 事务提交前,查看实体为
Dirty
,改变同步到数据库
强制更新试验
如果在JPA
长久化上下文中强制调用save
会产生什么?
批改更新办法为强制更新,在批改entity
后手动调用save
办法更新。
/** * 数据强制更新办法 */public void forceUpdate() { logger.info("----------强制更新测试用例开始----------"); Customer customer = new Customer(); customer.setName("Hello Kitty!"); customer.setPhone("17712345678"); this.customerRepository.save(customer); logger.info("构建测试数据: {}", customer); Long id = customer.getId(); this.customerRepository.findById(id).ifPresent(entity -> { entity.setName("Hello 冬泳怪鸽!"); entity.setPhone("18888888888"); this.customerRepository.save(entity); logger.info("更新测试数据: {}", entity); }); logger.info("----------强制更新测试用例完结----------");}
开启事务,设置事务不回滚,调用上文的forceUpdate
办法。
@Test@Transactional // 开启事务@Rollback(value = false) // 事务不回滚public void forceUpdateCustomerInTransaction() { this.forceUpdate();}
日志执行后果如下,强制调用了save
办法,但非立刻执行,最终的update
语句仍在办法完结后执行。
2022-01-15 11:38:52.810 INFO 9512 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------强制更新测试用例开始----------Hibernate: insert into CUSTOMER (name, phone) values (?, ?)2022-01-15 11:38:52.914 INFO 9512 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 构建测试数据: Customer{id=3, name='Hello Kitty!', phone='17712345678'}2022-01-15 11:38:52.943 INFO 9512 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 更新测试数据: Customer{id=3, name='Hello 冬泳怪鸽!', phone='18888888888'}2022-01-15 11:38:52.943 INFO 9512 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------强制更新测试用例完结----------Hibernate: update CUSTOMER set name=?, phone=? where id=?
敞开事务,比照试验。
@Test@Transactional(propagation = Propagation.NOT_SUPPORTED) // 挂起/敞开事务public void forceUpdateCustomerWithoutTransaction() { this.forceUpdate();}
日志如下,多执行了一个select + update
,猜想JPA
不开启事务的状况下,先查问以后实体信息和数据库记录是否有变动,有变动则进行更新。
2022-01-15 12:29:27.616 INFO 9977 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------强制更新测试用例开始----------Hibernate: insert into CUSTOMER (name, phone) values (?, ?)2022-01-15 12:29:27.721 INFO 9977 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 构建测试数据: Customer{id=4, name='Hello Kitty!', phone='17712345678'}Hibernate: select customer0_.id as id1_0_0_, customer0_.name as name2_0_0_, customer0_.phone as phone3_0_0_ from CUSTOMER customer0_ where customer0_.id=?Hibernate: select customer0_.id as id1_0_0_, customer0_.name as name2_0_0_, customer0_.phone as phone3_0_0_ from CUSTOMER customer0_ where customer0_.id=?Hibernate: update CUSTOMER set name=?, phone=? where id=?2022-01-15 12:29:27.801 INFO 9977 --- [ main] c.s.q.s.r.CustomerRepositoryTest : 更新测试数据: Customer{id=4, name='Hello 冬泳怪鸽!', phone='18888888888'}2022-01-15 12:29:27.801 INFO 9977 --- [ main] c.s.q.s.r.CustomerRepositoryTest : ----------强制更新测试用例完结----------
总结
- 开启事务,
JPA
长久化上下文在事务提交时进行实体脏查看,并同步到数据库。 JPA
长久化上下文作为应用程序和数据库之前的一级缓存,缩小对存储的调用,晋升性能。