From 90b7f9e07cc2c9537087185f4b576aa9abefff37 Mon Sep 17 00:00:00 2001 From: vladmihalcea Date: Tue, 19 Jan 2016 15:42:44 +0200 Subject: [PATCH] Add Flushing chapter and JPQL section for batch updates --- .../userguide/Hibernate_User_Guide.adoc | 1 + .../userguide/chapters/batch/Batching.adoc | 37 ++- .../chapters/batch/extras/hql_delete.java | 5 +- .../chapters/batch/extras/hql_insert.java | 3 +- .../{executeUpdate.java => hql_update.java} | 7 +- .../chapters/batch/extras/jpql_delete.java | 11 + .../chapters/batch/extras/jpql_update.java | 12 + .../chapters/domain/basic_types.adoc | 4 +- .../userguide/chapters/flushing/Flushing.adoc | 238 +++++++++++++++++- .../{hql-insert.java => hql_insert.java} | 0 .../OneToManyUnidirectionalTest.java | 1 + .../test/userguide/flush/AlwaysFlushTest.java | 165 ++++++++++++ .../test/userguide/flush/AutoFlushTest.java | 214 ++++++++++++++++ .../test/userguide/flush/CommitFlushTest.java | 183 ++++++++++++++ .../test/userguide/flush/ManualFlushTest.java | 171 +++++++++++++ .../flushing-always-flush-sql-example.sql | 3 + .../flushing-auto-flush-commit-example.sql | 2 + .../flushing-auto-flush-jpql-example.sql | 9 + ...ushing-auto-flush-jpql-overlap-example.sql | 5 + .../flushing-commit-flush-jpql-example.sql | 9 + .../flushing-commit-flush-sql-example.sql | 3 + .../flush/flushing-manual-flush-example.sql | 5 + .../jpa/test/userguide/ql/BulkTest.java | 107 ++++++++ 23 files changed, 1169 insertions(+), 26 deletions(-) rename documentation/src/main/asciidoc/userguide/chapters/batch/extras/{executeUpdate.java => hql_update.java} (76%) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/batch/extras/jpql_delete.java create mode 100644 documentation/src/main/asciidoc/userguide/chapters/batch/extras/jpql_update.java rename documentation/src/main/docbook/devguide-old/en-US/extras/{hql-insert.java => hql_insert.java} (100%) create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/AlwaysFlushTest.java create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/AutoFlushTest.java create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/CommitFlushTest.java create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/ManualFlushTest.java create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-always-flush-sql-example.sql create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-commit-example.sql create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-jpql-example.sql create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-jpql-overlap-example.sql create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-commit-flush-jpql-example.sql create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-commit-flush-sql-example.sql create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-manual-flush-example.sql create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/ql/BulkTest.java diff --git a/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc b/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc index 2c58511df2..903c2e24a2 100644 --- a/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc +++ b/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc @@ -10,6 +10,7 @@ include::chapters/architecture/Architecture.adoc[] include::chapters/domain/DomainModel.adoc[] include::chapters/bootstrap/Bootstrap.adoc[] include::chapters/pc/PersistenceContext.adoc[] +include::chapters/flushing/Flushing.adoc[] include::chapters/jdbc/Database_Access.adoc[] include::chapters/transactions/Transactions.adoc[] include::chapters/jndi/JNDI.adoc[] diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc index 341b3c8e06..34f27c24da 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc @@ -122,7 +122,9 @@ They have different semantics from the `save()`, `saveOrUpdate()`, and `delete() DML, or Data Manipulation Language, refers to SQL statements such as `INSERT`, `UPDATE`, and `DELETE`. Hibernate provides methods for bulk SQL-style DML statement execution, in the form of Hibernate Query Language (HQL). -==== HQL for UPDATE and DELETE +==== HQL/JPQL for UPDATE and DELETE + +Both the Hibernate native Query Language and JPQL (Java Persistence Query Language) provide support for bulk UPDATE and DELETE. .Psuedo-syntax for UPDATE and DELETE statements using HQL ==== @@ -146,15 +148,23 @@ If the entity name is not aliased, then it is illegal for any property reference Joins, either implicit or explicit, are prohibited in a bulk HQL query. You can use sub-queries in the `WHERE` clause, and the sub-queries themselves can contain joins. -.Executing an HQL UPDATE, using the `Query.executeUpdate()` +.Executing a JPQL `UPDATE`, using the `Query.executeUpdate()` ==== [source,java] ---- -include::{sourcedir}/executeUpdate.java[] +include::{sourcedir}/jpql_update.java[] ---- ==== -In keeping with the EJB3 specification, HQL UPDATE statements, by default, do not effect the version or the timestamp property values for the affected entities. +.Executing an HQL `UPDATE`, using the `Query.executeUpdate()` +==== +[source,java] +---- +include::{sourcedir}/hql_update.java[] +---- +==== + +In keeping with the EJB3 specification, HQL `UPDATE` statements, by default, do not effect the version or the timestamp property values for the affected entities. You can use a versioned update to force Hibernate to reset the version or timestamp property values, by adding the `VERSIONED` keyword after the `UPDATE` keyword. .Updating the version of timestamp @@ -168,6 +178,16 @@ include::{sourcedir}/updating_version.java[] [NOTE] ==== If you use the `VERSIONED` statement, you cannot use custom version types, which use class `org.hibernate.usertype.UserVersionType`. + +This feature is only available in HQL since it's not standardized by JPA. +==== + +.A JPQL `DELETE` statement +==== +[source,java] +---- +include::{sourcedir}/jpql_delete.java[] +---- ==== .An HQL `DELETE` statement @@ -180,7 +200,7 @@ include::{sourcedir}/hql_delete.java[] Method `Query.executeUpdate()` returns an `int` value, which indicates the number of entities effected by the operation. This may or may not correlate to the number of rows effected in the database. -An HQL bulk operation might result in multiple SQL statements being executed, such as for joined-subclass. +An JPQL/HQL bulk operation might result in multiple SQL statements being executed, such as for joined-subclass. In the example of joined-subclass, a `DELETE` against one of the subclasses may actually result in deletes in the tables underlying the join, or further down the inheritance hierarchy. ==== HQL syntax for INSERT @@ -226,9 +246,4 @@ include::{sourcedir}/hql_insert.java[] ---- ==== -This section is only a brief overview of HQL. For more information, see <>. - -[[batch-bulk-jpql]] -=== Java Persistence Query Language for DML - -TODO +This section is only a brief overview of HQL. For more information, see <>. \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_delete.java b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_delete.java index 87c2971706..56fe95a570 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_delete.java +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_delete.java @@ -4,7 +4,8 @@ Transaction tx = session.beginTransaction(); String hqlDelete = "delete Customer c where c.name = :oldName"; // or String hqlDelete = "delete Customer where name = :oldName"; int deletedEntities = session.createQuery( hqlDelete ) - .setString( "oldName", oldName ) - .executeUpdate(); + .setString( "oldName", oldName ) + .executeUpdate(); + tx.commit(); session.close(); diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_insert.java b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_insert.java index 9505919c3a..c68b5b81ce 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_insert.java +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_insert.java @@ -3,6 +3,7 @@ Transaction tx = session.beginTransaction(); String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ..."; int createdEntities = session.createQuery( hqlInsert ) - .executeUpdate(); + .executeUpdate(); + tx.commit(); session.close(); diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/extras/executeUpdate.java b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_update.java similarity index 76% rename from documentation/src/main/asciidoc/userguide/chapters/batch/extras/executeUpdate.java rename to documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_update.java index ce05fc09a2..decb4ace65 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/extras/executeUpdate.java +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/hql_update.java @@ -4,8 +4,9 @@ Transaction tx = session.beginTransaction(); String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName"; // or String hqlUpdate = "update Customer set name = :newName where name = :oldName"; int updatedEntities = session.createQuery( hqlUpdate ) - .setString( "newName", newName ) - .setString( "oldName", oldName ) - .executeUpdate(); + .setString( "newName", newName ) + .setString( "oldName", oldName ) + .executeUpdate(); + tx.commit(); session.close(); diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/extras/jpql_delete.java b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/jpql_delete.java new file mode 100644 index 0000000000..8c26152587 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/jpql_delete.java @@ -0,0 +1,11 @@ +EntityManager entityManager = entityManagerFactory.createEntityManager(); +EntityTransaction tx = entityManager.getTransaction(); + +String jpqlDelete = "delete Customer c where c.name = :oldName"; +// or String jpqlDelete = "delete Customer where name = :oldName"; +int updatedEntities = entityManager.createQuery( jpqlDelete ) + .setParameter( "oldName", oldName ) + .executeUpdate(); + +tx.commit(); +entityManager.close(); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/extras/jpql_update.java b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/jpql_update.java new file mode 100644 index 0000000000..54f63afbb2 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/extras/jpql_update.java @@ -0,0 +1,12 @@ +EntityManager entityManager = entityManagerFactory.createEntityManager(); +EntityTransaction tx = entityManager.getTransaction(); + +String jpqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName"; +// or String query = "update Customer set name = :newName where name = :oldName"; +int updatedEntities = entityManager.createQuery( jpqlUpdate ) + .setParameter( "oldName", oldName ) + .setParameter( "newName", newName ) + .executeUpdate(); + +tx.commit(); +entityManager.close(); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc index 718f95bca4..6b3300a6f3 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc @@ -524,8 +524,8 @@ Note that this can cause difficulty as the driver chooses to map many different ==== UUID as identifier -Hibernate supports using UUID values as identifiers, and they can even be generated on user'sbehalf. -For details see the discussion of generators in <> +Hibernate supports using UUID values as identifiers, and they can even be generated on user's behalf. +For details, see the discussion of generators in <>. [[basic-datetime]] ==== Mapping Date/Time Values diff --git a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc index a0f12edc71..1364e965ac 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc @@ -1,12 +1,236 @@ [[flushing]] == Flushing +:sourcedir: ../../../../../test/java/org/hibernate/jpa/test/userguide/flush -* <> -* <> -* link:#criteria[???] -* link:#querynative[???] +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. -Flushing is the process of synchronizing the state of the persistence -context to the database. +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. -TODO +[NOTE] +==== +Because DML statements are grouped together, Hibernate can apply batching transparently. +See the <> for more information. +==== + +The flushing strategy is given by the http://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/Session.html#getFlushMode--[`flushMode`] of the current running Hibernate `Session`. +Although JPA defines only two flushing strategies (https://docs.oracle.com/javaee/7/api/javax/persistence/FlushModeType.html#AUTO[`AUTO`] and https://docs.oracle.com/javaee/7/api/javax/persistence/FlushModeType.html#COMMIT[`COMMIT`]), +Hibernate has a much broader spectrum of flush types: + +ALWAYS:: Flushes the `Session` before every query. +AUTO:: This is the default mode and it flushes the `Session` only if necessary. +COMMIT:: The `Session` tries to delay the flush until the current `Transaction` is committed, although it might flush prematurely too. +MANUAL:: The `Session` flushing is delegated to the application, which must call `Session.flush()` explicitly in order to apply the persistence context changes. + +[[flushing-auto]] +== `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. + +[[flushing-auto-flush-commit-example]] +.Automatic flushing on commit +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/AutoFlushTest.java[tags=flushing-auto-flush-commit-example] +---- + +[source, SQL, indent=0] +---- +include::{sourcedir}/flushing-auto-flush-commit-example.sql[] +---- +==== + +Hibernate logs the message prior to inserting the entity because the flush only occurred during transaction commit. + +[NOTE] +==== +This is valid for the `SEQUENCE` and `TABLE` identifier generators. +The `IDENTITY` generator must execute the insert right after calling `persist()`. +For details, see the discussion of generators in <>. +==== + +=== `AUTO` flush on JPQL/HQL query + +A flush may also be triggered when executing an entity query. + +[[flushing-auto-flush-jpql-example]] +.Automatic flushing on JPQL/HQL +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/AutoFlushTest.java[tags=flushing-auto-flush-jpql-example] +---- + +[source, SQL, indent=0] +---- +include::{sourcedir}/flushing-auto-flush-jpql-example.sql[] +---- +==== + +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: + +[[flushing-auto-flush-jpql-entity-example]] +.Automatic flushing on JPQL/HQL entities +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/AutoFlushTest.java[tags=flushing-auto-flush-jpql-entity-example] +---- +==== + +When querying for a `Person` entity, the flush is triggered prior to executing the entity query. + +[[flushing-auto-flush-jpql-overlap-example]] +.Automatic flushing on JPQL/HQL +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/AutoFlushTest.java[tags=flushing-auto-flush-jpql-overlap-example] +---- + +[source, SQL, indent=0] +---- +include::{sourcedir}/flushing-auto-flush-jpql-overlap-example.sql[] +---- +==== + +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. + +[[flushing-auto-flush-sql-example]] +.Automatic flushing on native SQL using `EntityManager` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/AutoFlushTest.java[tags=flushing-auto-flush-sql-example] +---- +==== + +The `Session` API doesn't trigger an `AUTO` flush when executing a native query + +[[flushing-auto-flush-sql-native-example]] +.Automatic flushing on native SQL using `Session` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/AutoFlushTest.java[tags=flushing-auto-flush-sql-native-example] +---- +==== + +To flush the `Session`, the query must use a synchronization: + +[[flushing-auto-flush-sql-synchronization-example]] +.Automatic flushing on native SQL with `Session` synchronization +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/AutoFlushTest.java[tags=flushing-auto-flush-sql-synchronization-example] +---- +==== + +[[flushing-commit]] +=== `COMMIT` flush + +JPA also defines a COMMIT flush mode, which is described as follows: + +[quote, Section 3.10.8 of the JPA 2.1 Specification] +____ +If `FlushModeType.COMMIT` is 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. + +[[flushing-commit-flush-jpql-example]] +.`COMMIT` flushing on JPQL +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CommitFlushTest.java[tags=flushing-commit-flush-jpql-example] +---- + +[source, SQL, indent=0] +---- +include::{sourcedir}/flushing-commit-flush-jpql-example.sql[] +---- +==== + +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. + +[[flushing-commit-flush-sql-example]] +.`COMMIT` flushing on SQL +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CommitFlushTest.java[tags=flushing-commit-flush-sql-example] +---- + +[source, SQL, indent=0] +---- +include::{sourcedir}/flushing-commit-flush-sql-example.sql[] +---- +==== + +[[flushing-always]] +=== `ALWAYS` flush + +[NOTE] +==== +The `ALWAYS` is only available with the native `Session` API. +==== + +The `ALWAYS` flush mode triggers a persistence context flush even when executing a native SQL query against the `Session` API. + +[[flushing-always-flush-sql-example]] +.`COMMIT` flushing on SQL +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/AlwaysFlushTest.java[tags=flushing-always-flush-sql-example] +---- + +[source, SQL, indent=0] +---- +include::{sourcedir}/flushing-always-flush-sql-example.sql[] +---- +==== + +[[flushing-manual]] +=== `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. + +[[flushing-manual-flush-example]] +.`MANUAL` flushing +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/ManualFlushTest.java[tags=flushing-manual-flush-example] +---- + +[source, SQL, indent=0] +---- +include::{sourcedir}/flushing-manual-flush-example.sql[] +---- +==== + +The `INSERT` statement was not executed because the persistence context because there was no manual `flush()` call. + +[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 diff --git a/documentation/src/main/docbook/devguide-old/en-US/extras/hql-insert.java b/documentation/src/main/docbook/devguide-old/en-US/extras/hql_insert.java similarity index 100% rename from documentation/src/main/docbook/devguide-old/en-US/extras/hql-insert.java rename to documentation/src/main/docbook/devguide-old/en-US/extras/hql_insert.java diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/associations/OneToManyUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/associations/OneToManyUnidirectionalTest.java index b69d8d1164..749e63f95f 100644 --- a/documentation/src/test/java/org/hibernate/jpa/test/userguide/associations/OneToManyUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/associations/OneToManyUnidirectionalTest.java @@ -25,6 +25,7 @@ import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA; */ public class OneToManyUnidirectionalTest extends BaseEntityManagerFunctionalTestCase { + @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/AlwaysFlushTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/AlwaysFlushTest.java new file mode 100644 index 0000000000..2d76710dde --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/AlwaysFlushTest.java @@ -0,0 +1,165 @@ +/* + * 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.jpa.test.userguide.flush; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.FlushMode; +import org.hibernate.Session; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA; +import static org.junit.Assert.assertTrue; + +/** + * AlwaysFlushTest - Always Flush Test + * + * @author Vlad Mihalcea + */ +public class AlwaysFlushTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( AlwaysFlushTest.class); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class, + Advertisement.class + }; + } + + @Test + public void testFlushSQL() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery("delete from Person").executeUpdate(); + }); + doInJPA( this::entityManagerFactory, entityManager -> { + log.info("testFlushSQL"); + //tag::flushing-always-flush-sql-example[] + Person 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); + //end::flushing-always-flush-sql-example[] + }); + } + + @Entity(name = "Person") + public static class Person { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) + private List phones = new ArrayList<>(); + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public List getPhones() { + return phones; + } + + public void addPhone(Phone phone) { + phones.add(phone); + phone.setPerson(this); + } + } + + @Entity(name = "Phone") + public static class Phone { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne + private Person person; + + private String number; + + public Phone() { + } + + public Phone(String number) { + this.number = number; + } + + public Long getId() { + return id; + } + + public String getNumber() { + return number; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } + + @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; + } + } +} diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/AutoFlushTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/AutoFlushTest.java new file mode 100644 index 0000000000..bf1a5d7418 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/AutoFlushTest.java @@ -0,0 +1,214 @@ +/* + * 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.jpa.test.userguide.flush; + +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA; +import static org.junit.Assert.assertTrue; + +/** + * AlwaysFlushTest - Always Flush Test + * + * @author Vlad Mihalcea + */ +public class AutoFlushTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( AutoFlushTest.class ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Advertisement.class + }; + } + + @Test + public void testFlushAutoCommit() { + EntityManager entityManager = null; + EntityTransaction txn = null; + try { + //tag::flushing-auto-flush-commit-example[] + 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(); + //end::flushing-auto-flush-commit-example[] + } catch (RuntimeException e) { + if ( txn != null && txn.isActive()) txn.rollback(); + throw e; + } finally { + if (entityManager != null) { + entityManager.close(); + } + } + } + + @Test + public void testFlushAutoJPQL() { + doInJPA( this::entityManagerFactory, entityManager -> { + log.info( "testFlushAutoJPQL" ); + //tag::flushing-auto-flush-jpql-example[] + 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(); + //end::flushing-auto-flush-jpql-example[] + } ); + } + + @Test + public void testFlushAutoJPQLOverlap() { + doInJPA( this::entityManagerFactory, entityManager -> { + log.info( "testFlushAutoJPQLOverlap" ); + //tag::flushing-auto-flush-jpql-overlap-example[] + Person person = new Person( "John Doe" ); + entityManager.persist( person ); + entityManager.createQuery( "select p from Person p" ).getResultList(); + //end::flushing-auto-flush-jpql-overlap-example[] + } ); + } + + @Test + public void testFlushAutoSQL() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( "delete from Person" ).executeUpdate();; + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + log.info( "testFlushAutoSQL" ); + //tag::flushing-auto-flush-sql-example[] + assertTrue(((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 ); + //end::flushing-auto-flush-sql-example[] + } ); + } + + @Test + public void testFlushAutoSQLNativeSession() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( "delete from Person" ).executeUpdate();; + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + log.info( "testFlushAutoSQLNativeSession" ); + //tag::flushing-auto-flush-sql-native-example[] + assertTrue(((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 ); + //end::flushing-auto-flush-sql-native-example[] + } ); + } + + @Test + public void testFlushAutoSQLSynchronization() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( "delete from Person" ).executeUpdate();; + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + log.info( "testFlushAutoSQLSynchronization" ); + //tag::flushing-auto-flush-sql-synchronization-example[] + assertTrue(((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 ); + //end::flushing-auto-flush-sql-synchronization-example[] + } ); + } + + //tag::flushing-auto-flush-jpql-entity-example[] + @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; + } + } + //end::flushing-auto-flush-jpql-entity-example[] +} diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/CommitFlushTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/CommitFlushTest.java new file mode 100644 index 0000000000..72dbeccf8f --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/CommitFlushTest.java @@ -0,0 +1,183 @@ +/* + * 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.jpa.test.userguide.flush; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FlushModeType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.junit.Assert.assertTrue; + +import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA; + +/** + * CommitFlushTest - Commit Flush Test + * + * @author Vlad Mihalcea + */ +public class CommitFlushTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( CommitFlushTest.class); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Person.class, + Phone.class, + Advertisement.class, + }; + } + + @Test + public void testFlushJPQL() { + doInJPA( this::entityManagerFactory, entityManager -> { + log.info("testFlushJPQL"); + //tag::flushing-commit-flush-jpql-example[] + Person 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(); + //end::flushing-commit-flush-jpql-example[] + }); + } + + @Test + public void testFlushSQL() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery("delete from Person") + .executeUpdate(); + }); + doInJPA( this::entityManagerFactory, entityManager -> { + log.info("testFlushSQL"); + //tag::flushing-commit-flush-sql-example[] + Person person = new Person("John Doe"); + entityManager.persist(person); + + assertTrue(((Number) entityManager + .createNativeQuery("select count(*) from Person") + .getSingleResult()).intValue() == 1); + //end::flushing-commit-flush-sql-example[] + }); + } + + @Entity(name = "Person") + public static class Person { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) + private List phones = new ArrayList<>(); + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public List getPhones() { + return phones; + } + + public void addPhone(Phone phone) { + phones.add(phone); + phone.setPerson(this); + } + } + + @Entity(name = "Phone") + public static class Phone { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne + private Person person; + + private String number; + + public Phone() { + } + + public Phone(String number) { + this.number = number; + } + + public Long getId() { + return id; + } + + public String getNumber() { + return number; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } + + @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; + } + } +} diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/ManualFlushTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/ManualFlushTest.java new file mode 100644 index 0000000000..2a294e4eca --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/ManualFlushTest.java @@ -0,0 +1,171 @@ +/* + * 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.jpa.test.userguide.flush; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.FlushMode; +import org.hibernate.Session; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA; + +import static org.junit.Assert.assertTrue; + +/** + * ManualFlushTest - Manual Flush Test + * + * @author Vlad Mihalcea + */ +public class ManualFlushTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( ManualFlushTest.class); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Person.class, + Phone.class, + Advertisement.class, + }; + } + + @Test + public void testFlushSQL() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery("delete from Person").executeUpdate(); + }); + doInJPA( this::entityManagerFactory, entityManager -> { + log.info("testFlushSQL"); + //tag::flushing-manual-flush-example[] + Person 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); + //end::flushing-manual-flush-example[] + }); + } + + @Entity(name = "Person") + public static class Person { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) + private List phones = new ArrayList<>(); + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public List getPhones() { + return phones; + } + + public void addPhone(Phone phone) { + phones.add(phone); + phone.setPerson(this); + } + } + + @Entity(name = "Phone") + public static class Phone { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne + private Person person; + + private String number; + + public Phone() { + } + + public Phone(String number) { + this.number = number; + } + + public Long getId() { + return id; + } + + public String getNumber() { + return number; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } + + @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; + } + } +} diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-always-flush-sql-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-always-flush-sql-example.sql new file mode 100644 index 0000000000..9491eaeb63 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-always-flush-sql-example.sql @@ -0,0 +1,3 @@ +INSERT INTO Person (name, id) VALUES ('John Doe', 1) + +SELECT COUNT(*) FROM Person \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-commit-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-commit-example.sql new file mode 100644 index 0000000000..5bc96b1d3a --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-commit-example.sql @@ -0,0 +1,2 @@ +--INFO: Entity is in persisted state +INSERT INTO Person (name, id) VALUES ('John Doe', 1) \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-jpql-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-jpql-example.sql new file mode 100644 index 0000000000..3830616412 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-jpql-example.sql @@ -0,0 +1,9 @@ +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 \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-jpql-overlap-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-jpql-overlap-example.sql new file mode 100644 index 0000000000..1bf994de84 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-auto-flush-jpql-overlap-example.sql @@ -0,0 +1,5 @@ +INSERT INTO Person (name, id) VALUES ('John Doe', 1) + +SELECT p.id AS id1_1_ , + p.name AS name2_1_ +FROM Person p \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-commit-flush-jpql-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-commit-flush-jpql-example.sql new file mode 100644 index 0000000000..d2779b790d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-commit-flush-jpql-example.sql @@ -0,0 +1,9 @@ +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) diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-commit-flush-sql-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-commit-flush-sql-example.sql new file mode 100644 index 0000000000..9491eaeb63 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-commit-flush-sql-example.sql @@ -0,0 +1,3 @@ +INSERT INTO Person (name, id) VALUES ('John Doe', 1) + +SELECT COUNT(*) FROM Person \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-manual-flush-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-manual-flush-example.sql new file mode 100644 index 0000000000..c8fb32ebc8 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/flush/flushing-manual-flush-example.sql @@ -0,0 +1,5 @@ +SELECT COUNT(p.id) AS col_0_0_ +FROM Person p + +SELECT COUNT(*) +FROM Person \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/ql/BulkTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/ql/BulkTest.java new file mode 100644 index 0000000000..3b0d39b32b --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/ql/BulkTest.java @@ -0,0 +1,107 @@ +package org.hibernate.jpa.test.userguide.ql; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * BulkTest - Bulk JPQL Test + * + * @author Vlad Mihalcea + */ +public class BulkTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class + }; + } + + @Test + public void testUpdate() { + final Calendar calendar = new GregorianCalendar(); + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( new Customer( "Vlad" ) ); + entityManager.persist( new Customer( "Mihalcea" ) ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + String oldName = "Vlad"; + String newName = "Mr. Vlad"; + + String jpqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName"; + int updatedEntities = entityManager.createQuery( jpqlUpdate ) + .setParameter( "oldName", oldName ) + .setParameter( "newName", newName ) + .executeUpdate(); + assertEquals(1, updatedEntities); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + String oldName = "Mr. Vlad"; + String newName = "Vlad"; + + + String jpqlUpdate = "update Customer set name = :newName where name = :oldName"; + int updatedEntities = entityManager.createQuery( jpqlUpdate ) + .setParameter( "oldName", oldName ) + .setParameter( "newName", newName ) + .executeUpdate(); + assertEquals(1, updatedEntities); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + String oldName = "Vlad"; + + String jpqlDelete = "delete Customer c where c.name = :oldName"; + int updatedEntities = entityManager.createQuery( jpqlDelete ) + .setParameter( "oldName", oldName ) + .executeUpdate(); + assertEquals(1, updatedEntities); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + String oldName = "Mihalcea"; + + String jpqlDelete = "delete Customer where name = :oldName"; + int updatedEntities = entityManager.createQuery( jpqlDelete ) + .setParameter( "oldName", oldName ) + .executeUpdate(); + assertEquals(1, updatedEntities); + } ); + } + + @Entity(name = "Customer") + public static class Customer { + + @Id + @GeneratedValue + private Long id; + + private String name; + + public Customer() { + } + + public Customer(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } +}