1. 概述

本文将深入探讨Spring JPA提供的flush()方法。首先了解核心抽象概念(包括EntityManager和刷新模式),接着通过CustomerCustomerAddress实体构建示例场景。然后编写集成测试,观察两种刷新模式下flush()的工作机制。最后总结显式调用flush()的优势和注意事项。

2. 什么是flush()?

flush()本质上是JPA中EntityManager接口的核心方法EntityManager用于与JPA持久化上下文交互,提供管理实体生命周期、查询实体和执行CRUD操作的能力。

flush()方法的作用是将持久化上下文中对实体的变更同步到底层数据库。当调用EntityManager.flush()时,JPA提供者会立即执行必要的SQL语句来持久化或更新数据库中的实体

在深入探讨前,先了解与flush()密切相关的刷新模式概念。JPA的刷新模式决定了持久化上下文中实体的变更何时与数据库同步,主要包含两种模式:

  • AUTO(默认模式):在必要时自动同步变更(如事务提交或执行需要最新数据的查询)
  • COMMIT:延迟同步直到事务提交,变更对其他事务不可见

3. 示例搭建

使用CustomerCustomerAddress两个实体构建示例,其中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()的典型场景

CustomerAddresscustomer_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模式下的CustomerCustomerAddress创建场景:

@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仓库获取。


原始标题:Correct Use of flush() in JPA | Baeldung