1. 概述
本文将深入探讨Spring JPA提供的flush()
方法。首先了解核心抽象概念(包括EntityManager
和刷新模式),接着通过Customer
和CustomerAddress
实体构建示例场景。然后编写集成测试,观察两种刷新模式下flush()
的工作机制。最后总结显式调用flush()
的优势和注意事项。
2. 什么是flush()?
flush()
本质上是JPA中EntityManager
接口的核心方法。EntityManager
用于与JPA持久化上下文交互,提供管理实体生命周期、查询实体和执行CRUD操作的能力。
flush()
方法的作用是将持久化上下文中对实体的变更同步到底层数据库。当调用EntityManager.flush()
时,JPA提供者会立即执行必要的SQL语句来持久化或更新数据库中的实体。
在深入探讨前,先了解与flush()
密切相关的刷新模式概念。JPA的刷新模式决定了持久化上下文中实体的变更何时与数据库同步,主要包含两种模式:
AUTO
(默认模式):在必要时自动同步变更(如事务提交或执行需要最新数据的查询)COMMIT
:延迟同步直到事务提交,变更对其他事务不可见
3. 示例搭建
使用Customer
和CustomerAddress
两个实体构建示例,其中CustomerAddress
包含customer_id
字段。首先定义Customer
实体:
@Entity
public class Customer {
private String name;
private int age;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// getters and setters
}
接着定义CustomerAddress
实体:
@Entity
public class CustomerAddress {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
private String city;
private long customer_id;
// ... getters and setters
}
后续将用这两个类测试flush()
的各种应用场景。
3.1. 配置EntityManager
配置EntityManager
前需先获取EntityManagerFactory
实现类实例:
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource);
emf.setPackagesToScan("com.baeldung.flush");
emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
emf.setJpaProperties(getHibernateProperties());
return emf;
}
为配置EntityManagerFactory
,需提供数据源和JPA属性。使用H2嵌入式数据库作为示例:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
.build();
配置JPA属性:
Properties getHibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
return properties;
}
后续将用此EntityManagerFactory
创建EntityManager
实例,在事务中测试有无flush()
的场景。
4. FlushModeType.COMMIT模式下的flush()
先设置刷新模式为COMMIT
:
entityManager.setFlushMode(FlushModeType.COMMIT);
如前所述,FlushModeType.COMMIT
会延迟变更刷新直到事务提交。这能减少事务期间的SQL语句数量提升性能,但若其他事务修改相同数据会增加数据不一致风险。
为理解flush()
与EntityManager.persist()
的配合,先测试不调用flush()
时创建和查询Customer
:
@Test
void givenANewCustomer_whenPersistAndNoFlush_thenDatabaseNotSynchronizedWithPersistentContextUsingCommitFlushMode() {
entityManager.setFlushMode(FlushModeType.COMMIT);
EntityTransaction transaction = getTransaction();
Customer customer = saveCustomerInPersistentContext("Alice", 30);
Customer customerInContext = entityManager.find(Customer.class, customer.getId());
assertDataInPersitentContext(customerInContext);
TypedQuery<Customer> retrievedCustomer = entityManager.createQuery("SELECT c FROM Customer c WHERE c.name = 'Alice'", Customer.class);
List<Customer> resultList = retrievedCustomer.getResultList();
assertThat(resultList).isEmpty();
transaction.rollback();
}
通过配置的EntityManager
获取事务实例:
EntityTransaction getTransaction() {
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
return transaction;
}
创建并持久化Customer
:
Customer saveCustomerInPersistentContext(String name, int age) {
Customer customer = new Customer();
customer.setName(name);
customer.setAge(age);
entityManager.persist(customer);
return customer;
}
查询数据库发现结果为空列表,说明事务未提交时持久化上下文未同步到数据库。
保持FlushModeType.COMMIT
,在persist()
后显式调用flush()
:
@Test
void givenANewCustomer_whenPersistAndFlush_thenDatabaseSynchronizedWithPersistentContextUsingCommitFlushMode() {
entityManager.setFlushMode(FlushModeType.COMMIT);
EntityTransaction transaction = getTransaction();
Customer customer = saveCustomerInPersistentContext("Alice", 30);
entityManager.flush();
Long generatedCustomerID = customer.getId();
Customer customerInContext = entityManager.find(Customer.class, generatedCustomerID);
assertDataInPersitentContext(customerInContext);
TypedQuery<Customer> retrievedCustomer = entityManager.createQuery("SELECT c FROM Customer c WHERE c.name = 'Alice'", Customer.class);
Customer result = retrievedCustomer.getSingleResult();
assertThat(result).isEqualTo(EXPECTED_CUSTOMER);
transaction.rollback();
}
添加flush()
后,数据库立即同步持久化上下文变更。即使事务未提交且刷新模式为COMMIT
,新Customer
实体已保存到数据库。
4.1. 跨实体查询
扩展示例加入CustomerAddress
实体。观察涉及多表查询时显式调用flush()
的典型场景:
CustomerAddress
的customer_id
依赖Customer
实体生成。保存Customer
后需查询数据库获取ID,再用于创建CustomerAddress
:
@Test
public void givenANewCustomer_whenPersistAndFlush_thenCustomerIdGeneratedToBeAddedInAddress() {
entityManager.setFlushMode(FlushModeType.COMMIT);
EntityTransaction transaction = getTransaction();
saveCustomerInPersistentContext("John", 25);
entityManager.flush();
Customer retrievedCustomer = entityManager.createQuery("SELECT c FROM Customer c WHERE c.name = 'John'", Customer.class)
.getSingleResult();
Long customerId = retrievedCustomer.getId();
CustomerAddress address = new CustomerAddress();
address.setCustomer_id(customerId);
entityManager.persist(address);
entityManager.flush();
CustomerAddress customerAddress = entityManager.createQuery("SELECT a FROM CustomerAddress a WHERE a.customer_id = :customerID", CustomerAddress.class)
.setParameter("customerID", customerId)
.getSingleResult();
assertThat(customerAddress).isNotNull();
transaction.rollback();
}
最后说明rollback()
与flush()
的关系:调用rollback()
会撤销事务内所有变更,恢复数据库到事务前状态。所有测试用例最后都调用rollback()
,防止部分变更提交导致数据不一致。
5. FlushModeType.AUTO模式下的flush()
测试FlushModeType.AUTO
模式下的flush()
行为。先设置刷新模式:
entityManager.setFlushMode(FlushModeType.AUTO);
JPA自动检测刷新需求并在查询前执行刷新,确保查询结果最新且持久化对象的变更已写入数据库。无需显式调用flush()
:
@Test
void givenANewCustomer_whenPersistAndNoFlush_thenDBIsSynchronizedWithThePersistentContextWithAutoFlushMode() {
entityManager.setFlushMode(FlushModeType.AUTO);
EntityTransaction transaction = getTransaction();
Customer customer = saveCustomerInPersistentContext("Alice", 30);
Customer customerInContext = entityManager.find(Customer.class, customer.getId());
assertDataInPersitentContext(customerInContext);
TypedQuery<Customer> retrievedCustomer = entityManager.createQuery("SELECT c FROM Customer c WHERE c.name = 'Alice'", Customer.class);
Customer result = retrievedCustomer.getSingleResult();
assertThat(result).isEqualTo(EXPECTED_CUSTOMER);
transaction.rollback();
}
测试显示未调用flush()
,查询Customer
表仍能返回持久化上下文中的实体对象。**FlushModeType.AUTO
模式下无需显式flush()
即可同步实体与数据库**。
再次测试AUTO
模式下的Customer
和CustomerAddress
创建场景:
@Test
public void givenFlushModeAutoAndNewCustomer_whenPersistAndNoFlush_thenCustomerIdGeneratedToBeAddedInAddress() {
entityManager.setFlushMode(FlushModeType.AUTO);
EntityTransaction transaction = getTransaction();
saveCustomerInPersistentContext("John", 25);
Customer singleResult = entityManager.createQuery("SELECT c FROM Customer c WHERE c.name = 'John'", Customer.class)
.getSingleResult();
Long customerId = singleResult.getId();
CustomerAddress address = new CustomerAddress();
address.setCustomer_id(customerId);
entityManager.persist(address);
CustomerAddress customerAddress = entityManager.createQuery("SELECT a FROM CustomerAddress a WHERE a.customer_id = :customerID", CustomerAddress.class)
.setParameter("customerID", customerId)
.getSingleResult();
assertThat(customerAddress).isNotNull();
transaction.rollback();
}
AUTO
模式下同样无需flush()
,事务未提交时仍能查询到数据库中的Customer
记录。
6. 显式flush()的优势
显式调用flush()
的主要优势:
✅ 数据一致性:调用flush()
后立即确保数据库与持久化上下文同步,实现实时一致性
✅ 性能优化:合理使用时可将相关变更批量处理,减少SQL语句数量,尤其适用于同一对象集的大量修改
✅ 即时反馈:变更立即写入数据库,提供操作成功/失败的即时反馈,便于调试
✅ 资源管理:及时刷新变更可减少内存占用,避免事务结束前堆积大量待处理变更
7. 显式flush()的潜在问题
不当或频繁调用flush()
可能引发的问题:
⚠️ 数据库争用:多事务访问相同数据时可能导致锁、死锁等问题
⚠️ 内存消耗:频繁调用会增加内存使用,尤其修改同一对象集大量数据时可能引发OOM
⚠️ 连接效率低下:每次flush()
都需要数据库往返,可能导致连接池耗尽
⚠️ 数据完整性风险:若不注意变更顺序可能导致数据不一致,需谨慎处理关联关系
踩坑提示:应按需使用
flush()
,权衡性能、内存和数据完整性影响。通过批量处理变更和最小化SQL语句优化使用。
8. 总结
本文通过两种刷新模式探讨了flush()
的正确使用方法,编写多个集成测试验证不同场景下的行为,最后分析了显式调用flush()
的利弊。掌握这些机制能帮助开发者更精准地控制JPA持久化行为,避免常见的数据一致性问题。
示例代码可在GitHub仓库获取。