Flushing
Flushing is the process of synchronizing the state of the persistence context with the underlying database.
The EntityManager and the Hibernate Session expose a set of methods, through which the application developer can change the persistent state of an entity.
The persistence context acts as a transactional write-behind cache, queuing any entity state change.
Like any write-behind cache, changes are first applied in-memory and synchronized with the database during flush time.
The flush operation takes every entity state change and translates it to an INSERT, UPDATE or DELETE statement.
|
Because DML statements are grouped together, Hibernate can apply batching transparently. See the Batching chapter for more information. |
The flushing strategy is given by the flushMode of the current running Hibernate Session.
Although JPA defines only two flushing strategies (AUTO and COMMIT),
Hibernate has a much broader spectrum of flush types:
- ALWAYS
-
Flushes the
Sessionbefore every query. - AUTO
-
This is the default mode and it flushes the
Sessiononly if necessary. - COMMIT
-
The
Sessiontries to delay the flush until the currentTransactionis committed, although it might flush prematurely too. - MANUAL
-
The
Sessionflushing is delegated to the application, which must callSession.flush()explicitly in order to apply the persistence context changes.
AUTO flush
By default, Hibernate uses the AUTO flush mode which triggers a flush in the following circumstances:
-
prior to committing a
Transaction -
prior to executing a JPQL/HQL query that overlaps with the queued entity actions
-
before executing any native SQL query that has no registered synchronization
AUTO flush on commit
In the following example, an entity is persisted and then the transaction is committed.
entityManager = entityManagerFactory().createEntityManager();
txn = entityManager.getTransaction();
txn.begin();
Person person = new Person( "John Doe" );
entityManager.persist( person );
log.info( "Entity is in persisted state" );
txn.commit();
--INFO: Entity is in persisted state
INSERT INTO Person (name, id) VALUES ('John Doe', 1)
Hibernate logs the message prior to inserting the entity because the flush only occurred during transaction commit.
|
This is valid for the |
AUTO flush on JPQL/HQL query
A flush may also be triggered when executing an entity query.
Person person = new Person( "John Doe" );
entityManager.persist( person );
entityManager.createQuery( "select p from Advertisement p" ).getResultList();
entityManager.createQuery( "select p from Person p" ).getResultList();
SELECT a.id AS id1_0_ ,
a.title AS title2_0_
FROM Advertisement a
INSERT INTO Person (name, id) VALUES ('John Doe', 1)
SELECT p.id AS id1_1_ ,
p.name AS name2_1_
FROM Person p
The reason why the Advertisement entity query didn’t trigger a flush is because there’s no overlapping between the Advertisement and the Person tables:
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
@Entity(name = "Advertisement")
public static class Advertisement {
@Id
@GeneratedValue
private Long id;
private String title;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
When querying for a Person entity, the flush is triggered prior to executing the entity query.
Person person = new Person( "John Doe" );
entityManager.persist( person );
entityManager.createQuery( "select p from Person p" ).getResultList();
INSERT INTO Person (name, id) VALUES ('John Doe', 1)
SELECT p.id AS id1_1_ ,
p.name AS name2_1_
FROM Person p
This time, the flush was triggered by a JPQL query because the pending entity persist action overlaps with the query being executed.
AUTO flush on native SQL query
When executing a native SQL query, a flush is always triggered when using the EntityManager API.
EntityManagerassertTrue(((Number) entityManager
.createNativeQuery( "select count(*) from Person")
.getSingleResult()).intValue() == 0 );
Person person = new Person( "John Doe" );
entityManager.persist( person );
assertTrue(((Number) entityManager
.createNativeQuery( "select count(*) from Person")
.getSingleResult()).intValue() == 1 );
The Session API doesn’t trigger an AUTO flush when executing a native query
SessionassertTrue(((Number) entityManager
.createNativeQuery( "select count(*) from Person")
.getSingleResult()).intValue() == 0 );
Person person = new Person( "John Doe" );
entityManager.persist( person );
Session session = entityManager.unwrap(Session.class);
assertTrue(((Number) session
.createSQLQuery( "select count(*) from Person")
.uniqueResult()).intValue() == 0 );
To flush the Session, the query must use a synchronization:
Session synchronizationassertTrue(((Number) entityManager
.createNativeQuery( "select count(*) from Person")
.getSingleResult()).intValue() == 0 );
Person person = new Person( "John Doe" );
entityManager.persist( person );
Session session = entityManager.unwrap( Session.class );
assertTrue(((Number) session
.createSQLQuery( "select count(*) from Person")
.addSynchronizedEntityClass( Person.class )
.uniqueResult()).intValue() == 1 );
COMMIT flush
JPA also defines a COMMIT flush mode, which is described as follows:
If
FlushModeType.COMMITis set, the effect of updates made to entities in the persistence context upon queries is unspecified.
When executing a JPQL query, the persistence context is only flushed when the current running transaction is committed.
COMMIT flushing on JPQLPerson person = new Person("John Doe");
entityManager.persist(person);
entityManager.createQuery("select p from Advertisement p")
.setFlushMode( FlushModeType.COMMIT)
.getResultList();
entityManager.createQuery("select p from Person p")
.setFlushMode( FlushModeType.COMMIT)
.getResultList();
SELECT a.id AS id1_0_ ,
a.title AS title2_0_
FROM Advertisement a
SELECT p.id AS id1_1_ ,
p.name AS name2_1_
FROM Person p
INSERT INTO Person (name, id) VALUES ('John Doe', 1)
Because the JPA doesn’t impose a strict rule on delaying flushing, when executing a native SQL query, the persistence context is going to be flushed.
COMMIT flushing on SQLPerson person = new Person("John Doe");
entityManager.persist(person);
assertTrue(((Number) entityManager
.createNativeQuery("select count(*) from Person")
.getSingleResult()).intValue() == 1);
INSERT INTO Person (name, id) VALUES ('John Doe', 1)
SELECT COUNT(*) FROM Person
ALWAYS flush
|
The |
The ALWAYS flush mode triggers a persistence context flush even when executing a native SQL query against the Session API.
COMMIT flushing on SQLPerson person = new Person("John Doe");
entityManager.persist(person);
Session session = entityManager.unwrap( Session.class);
assertTrue(((Number) session
.createSQLQuery("select count(*) from Person")
.setFlushMode( FlushMode.ALWAYS)
.uniqueResult()).intValue() == 1);
INSERT INTO Person (name, id) VALUES ('John Doe', 1)
SELECT COUNT(*) FROM Person
MANUAL flush
Both the EntityManager and the Hibernate Session define a flush() method that, when called, triggers a manual flush.
Hibernate also defines a MANUAL flush mode so the persistence context can only be flushed manually.
MANUAL flushingPerson person = new Person("John Doe");
entityManager.persist(person);
Session session = entityManager.unwrap( Session.class);
session.setFlushMode( FlushMode.MANUAL);
assertTrue(((Number) entityManager
.createQuery("select count(id) from Person")
.getSingleResult()).intValue() == 0);
assertTrue(((Number) session
.createSQLQuery("select count(*) from Person")
.uniqueResult()).intValue() == 0);
SELECT COUNT(p.id) AS col_0_0_
FROM Person p
SELECT COUNT(*)
FROM Person
The INSERT statement was not executed because the persistence context because there was no manual flush() call.
|
This mode is useful when using multi-request logical transactions and only the last request should flush the persistence context. |
Flush operation order
From a database perspective, a row state can be altered using either an INSERT, an UPDATE or a DELETE statement.
Because entity state changes are automatically converted to SQL statements, it’s important to know which entity actions are associated to a given SQL statement.
INSERT-
The
INSERTstatement is generated either by theEntityInsertActionorEntityIdentityInsertAction. These actions are scheduled by thepersistoperation, either explicitly or through cascading thePersistEventfrom a parent to a child entity. DELETE-
The
DELETEstatement is generated by theEntityDeleteActionorOrphanRemovalAction. UPDATE-
The
UPDATEstatement is generated byEntityUpdateActionduring flushing if the managed entity has been marked modified. The dirty checking mechanism is responsible for determining if a managed entity has been modified since it was first loaded.
Hibernate does not execute the SQL statements in the order of their associated entity state operations.
To visualize how this works, consider the following example:
Person person = entityManager.find( Person.class, 1L);
entityManager.remove(person);
Person newPerson = new Person( );
newPerson.setId( 2L );
newPerson.setName( "John Doe" );
entityManager.persist( newPerson );
INSERT INTO Person (name, id)
VALUES ('John Doe', 2L)
DELETE FROM Person WHERE id = 1
Even if we removed the first entity and then persist a new one, Hibernate is going to execute the DELETE statement after the INSERT.
|
The order in which SQL statements are executed is given by the |
The ActionQueue executes all operations in the following order:
-
OrphanRemovalAction -
EntityInsertActionorEntityIdentityInsertAction -
EntityUpdateAction -
CollectionRemoveAction -
CollectionUpdateAction -
CollectionRecreateAction -
EntityDeleteAction