1. Overview
In this tutorial, we’ll take a quick look at the flush() method provided by Spring JPA.
First, we’ll learn the key abstractions involved including EntityManager and flush modes. Next, we’ll set up a sample example using Customer and CustomerAddress entities. Then, we’ll write the integration test to see how flush() works with the two flush modes. We’ll conclude by looking at some of the key benefits as well as some considerations while using explicit flush().
2. What Is flush()?
Essentially the flush() method is a part of the EntityManager interface in JPA. EntityManager can be used to interact with the persistence context in JPA. It provides methods for managing entity lifecycle, querying entities, and performing CRUD operations on the database.
The flush() method is used to synchronize any changes made to entities managed by persistence context with the underlying database. When we invoke flush() on the EntityManager, the JPA provider in turn executes any SQL statements required to persist or update the entities in the database.
Before delving further into the correct use of this method, let’s take a look at a concept that is closely related to the working of flush() and that is the various flush modes provided by JPA.
The flush modes in JPA determine when the changes made to entities in the persistence context are synchronized with the database. The two main flush modes provided by JPA are AUTO and COMMIT.
AUTO is the default flush mode. It means that changes made to entities are automatically synchronized with the database when necessary, such as when a transaction is committed, or when a query is executed that requires up-to-date data.
On the other hand COMMIT mode delays synchronization until the transaction is committed. This means that changes made to entities will not be visible to other transactions until the current transaction is committed.
3. Example Setup
Let’s consider a simple example of two entities, Customer and CustomerAddress. The CustomerAddress entity contains the customer_id as long. Let’s define the Customer entity first:
@Entity
public class Customer {
private String name;
private int age;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// getters and setters
}
Next, let’s define the Address entity:
@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
}
Later on, we’ll use these two classes to test various scenarios where flush() may be required.
3.1. Setting Up the EntityManager
Before we set up an EntityManager, we need to get an instance of a class that implements the EntityManagerFactory interface:
@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;
}
For the EntityManagerFactory to be set up, we need to provide the data source and JPA properties.
Let’s use the H2-embedded database as our sample database:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
.build();
Let’s set up the properties next:
Properties getHibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
return properties;
}
In the subsequent sections, we’ll use this EntityManagerFactory to create an instance of EntityManager. We’ll use this EntityManager to test various scenarios within a transaction with and without flush().
4. flush() With FlushModeType.COMMIT
Let’s start by setting the FlushModeType to COMMIT:
entityManager.setFlushMode(FlushModeType.COMMIT);
As we mentioned at the start, FlushModeType.COMMIT delays the flushing of changes to the database until the transaction is committed. This can improve performance by reducing the number of SQL statements sent to the database during a transaction, but it also increases the risk of data inconsistency if other transactions modify the same data.
To understand the need for flush() in combination with persist() of EntityManager class, let’s first test our Customer creation and attempt to query the database for the newly created Customer without calling flush():
@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();
}
Essentially, here we use the bean of type EntityManager configured earlier to get an instance of a Transaction:
EntityTransaction getTransaction() {
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
return transaction;
We then create the Customer and persist it using entityManager.persist(customer):
Customer saveCustomerInPersistentContext(String name, int age) {
Customer customer = new Customer();
customer.setName(name);
customer.setAge(age);
entityManager.persist(customer);
return customer;
}
Next, we query the database to retrieve the newly created Customer object.
We find that the result of the query converted to List
Next, keeping the FlushModeType to be COMMIT, let’s make sure we call flush() after calling persist() on the entity manager:
@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();
}
Here, we can see that by adding the call to flush() after calling persist(), the database is synchronized with the transactions in the persistent context. We’ll query the database and get the result back which is equal to the newly created customer entity. Importantly, the new Customer entity has been saved already in the database even though FlushModeType is COMMIT and the transaction itself is not yet committed.
4.1. Query Across Two Entities
At this point, let’s expand our simple example to include the CustomerAddress entity as well. We want to see the potential cases where we want to explicitly call flush() involving queries across multiple tables of the database.
CustomerAddress contains a field called customer_id, which is auto-generated once the customer entity is created. We can see here that we want to save a Customer, query the database for the id and use the auto-generated id in the CustomerAddress entity:
@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();
}
In the end, let’s quickly touch upon the use of rollback() in relation to the flush() method. Essentially, when we call rollback(), any changes made within a transaction are undone. The database is restored to its state before the transaction began. In all the scenarios we’ve been testing above, we have called rollback() at the end**.** This prevents any partial changes from being committed to the database, which could lead to data inconsistencies or corruption.
5. flush() With FlushModeType.AUTO
Next, let’s see what happens when we use flush() with the FlushModeType set to AUTO.
We’ll set the flush mode to AUTO first:
entityManager.setFlushMode(FlushModeType.AUTO);
Here the JPA automatically detects when a flush is needed and performs it before executing the query. This ensures that the query results are up-to-date and that any changes made to persistent objects are persisted in the database. Therefore there’s no need to explicitly call flush() here:
@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();
}
The test shows that we haven’t called any flush() after persist(). Still, we can see that querying the Customer table returns the TypedQuery
There’s no need for explicit flush() to synchronize the entity in the persistent context with the database since the FlushModeType is AUTO.
Let’s again test the Customer and CustomerAddress creation scenario when the mode is AUTO:
@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();
}
As expected, here as well, with the AUTO mode, the flush() is not needed and we can query the Customer in the database before the CustomerAddress is created while the transaction is still ongoing**.**
Next, let’s look at some of the key benefits followed by potential issues in using flush() explicitly.
6. Benefits of Explicit flush()
Here are some of the benefits of explicit flush():
- Consistency: We can be sure that the database is synchronised with the persistent context as soon as flush() is called and therefore have real-time consistency across the two components.
- Improved performance: When used properly, we can reduce the number of SQL statements sent to the database by grouping related changes into a single batch. This can lead to improved performance, particularly if we are making many changes to the same set of objects.
- Immediate feedback: Any pending changes are written to the database immediately when we call flush(). This can provide immediate feedback on the success or failure of the operation, which will be useful for debugging and troubleshooting.
- Better resource management: By flushing changes to the database as soon as they are made, we can reduce the memory and other resources required to hold pending changes until the end of the transaction.
7. Potential Issues of Explicit flush()
However, we must be aware of potential issues that can be caused by improper or frequent calls to flush():
- Database contention: when using flush() we can potentially face database contention. This is most probable if multiple transactions are trying to access the same set of data. This can lead to locks, deadlocks, and other database-related issues.
- Increased memory usage: When called too frequently, it can result in increased memory usage, particularly if we are making many changes to the same set of objects. This can lead to out-of-memory errors and other memory-related issues.
- Inefficient use of database connections: When called too frequently, flush() can result in inefficient use of database connections. Each call to flush() requires a new round trip to the database, which can lead to connection pool exhaustion and other connection-related issues.
- Data integrity issues: flush() can result in data integrity issues, particularly if we are not careful about the order in which changes are made. This can lead to inconsistencies and errors in the data stored in the database.
Overall, it’s important to use flush() judiciously and only when necessary, taking into account the potential impact on performance, memory usage, and data integrity. It’s also important to optimize our use of flush() by grouping related changes and minimizing the number of SQL statements sent to the database.
8. Conclusion
In this article, we looked at the correct use of flush() using two flush modes. We created various integration tests to see how flush() can be used correctly under various scenarios. Lastly, we discussed the pros and cons of using the method.
As usual, the sample code is available over on GitHub.