From afab46fefa44d8910eaf8893154bff115fe58bc2 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Tue, 19 Jul 2016 18:00:29 +0300 Subject: [PATCH] HHH-10971 - Document flush operation order --- .../userguide/chapters/flushing/Flushing.adoc | 48 +++++++++- .../extras/flushing-order-example.sql | 5 ++ .../userguide/flush/FlushOrderTest.java | 89 +++++++++++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/flushing/extras/flushing-order-example.sql create mode 100644 documentation/src/test/java/org/hibernate/userguide/flush/FlushOrderTest.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc index 94e4176e10..ed8dac467d 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc @@ -234,4 +234,50 @@ The `INSERT` statement was not executed because the persistence context because [NOTE] ==== This mode is useful when using multi-request logical transactions and only the last request should flush the persistence context. -==== \ No newline at end of file +==== + +[[flushing-order]] +=== Flush operation order + +From a database perspective, a row state can be altered using either an `INSERT`, an `UPDATE` or a `DELETE` statement. +Because https://vladmihalcea.com/2014/07/30/a-beginners-guide-to-jpahibernate-entity-state-transitions/[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 `INSERT` statement is generated either by the `EntityInsertAction` or `EntityIdentityInsertAction`. These actions are scheduled by the `persist` operation, either explicitly or through cascading the `PersistEvent` from a parent to a child entity. +`DELETE`:: The `DELETE` statement is generated by the `EntityDeleteAction` or `OrphanRemovalAction`. +`UPDATE`:: The `UPDATE` statement is generated by `EntityUpdateAction` during flushing if the managed entity has been marked modified. The https://vladmihalcea.com/2014/08/21/the-anatomy-of-hibernate-dirty-checking/[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: + +[[flushing-order-example]] +.Flush operation order +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/FlushOrderTest.java[tags=flushing-order-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/flushing-order-example.sql[] +---- +==== + +Even if we removed the first entity and then persist a new one, Hibernate is going to execute the `DELETE` statement after the `INSERT`. + +[TIP] +==== +The order in which SQL statements are executed is given by the `ActionQueue` and not by the order in which entity state operations have been previously defined. +==== + +The `ActionQueue` executes all operations in the following order: + +. `OrphanRemovalAction` +. `EntityInsertAction` or `EntityIdentityInsertAction` +. `EntityUpdateAction` +. `CollectionRemoveAction` +. `CollectionUpdateAction` +. `CollectionRecreateAction` +. `EntityDeleteAction` + diff --git a/documentation/src/main/asciidoc/userguide/chapters/flushing/extras/flushing-order-example.sql b/documentation/src/main/asciidoc/userguide/chapters/flushing/extras/flushing-order-example.sql new file mode 100644 index 0000000000..0aaa1af6e6 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/flushing/extras/flushing-order-example.sql @@ -0,0 +1,5 @@ +INSERT INTO Person (name, id) +VALUES ('John Doe', 2L) + +DELETE FROM Person WHERE id = 1 + diff --git a/documentation/src/test/java/org/hibernate/userguide/flush/FlushOrderTest.java b/documentation/src/test/java/org/hibernate/userguide/flush/FlushOrderTest.java new file mode 100644 index 0000000000..eb5f2b583d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/flush/FlushOrderTest.java @@ -0,0 +1,89 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.flush; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.userguide.util.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class FlushOrderTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( FlushOrderTest.class); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Person.class + }; + } + + @Test + public void testOrder() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery("delete from Person").executeUpdate(); + }); + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person("John Doe"); + person.id = 1L; + entityManager.persist(person); + }); + doInJPA( this::entityManagerFactory, entityManager -> { + log.info("testFlushSQL"); + //tag::flushing-order-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 ); + //end::flushing-order-example[] + }); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private String name; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +}