From a623bd453ff5d0aa0ec2828f102d70618d75b19f Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 14 Jan 2025 15:59:20 +0100 Subject: [PATCH] add systematic tests for before native query flushing in JPA and plain Hibernate --- .../jpa/autoflush/HibernateAutoflushTest.java | 125 ++++++++++++++++++ .../test/jpa/autoflush/JpaAutoflushTest.java | 110 +++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/autoflush/HibernateAutoflushTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/autoflush/JpaAutoflushTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/autoflush/HibernateAutoflushTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/autoflush/HibernateAutoflushTest.java new file mode 100644 index 000000000000..6360f6f9ea4d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/autoflush/HibernateAutoflushTest.java @@ -0,0 +1,125 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.autoflush; + +import jakarta.persistence.Entity; +import jakarta.persistence.FlushModeType; +import jakarta.persistence.Id; +import org.hibernate.query.QueryFlushMode; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SessionFactory +@DomainModel(annotatedClasses = HibernateAutoflushTest.Thing.class) +public class HibernateAutoflushTest { + + @Test void test1(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.persist( new Thing( "Widget" ) ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .getResultList(); + // Hibernate does NOT autoflush before a native query + assertEquals( 0, resultList.size() ); + } ); + } + + @Test void test2(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select typeofthing from thing", String.class ) + .getResultList(); + // Hibernate does NOT autoflush before a native query + assertEquals(0, resultList.size()); + } ); + } + + @Test void test3(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inSession( em -> { + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .getResultList(); + // spec says we must NOT flush before native query outside tx + assertEquals(0, resultList.size()); + } ); + } + + @Test void test4(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.setFlushMode( FlushModeType.COMMIT ); + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .getResultList(); + // spec says we must NOT flush before native query with FMT.COMMIT + assertEquals(0, resultList.size()); + } ); + } + + @Test void test5(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .addSynchronizedQuerySpace( "Thing" ) + .getResultList(); + // we should not flush because user specified that the query touches the table + assertEquals(1, resultList.size()); + } ); + } + + @Test void test6(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .addSynchronizedQuerySpace( "XXX" ) + .getResultList(); + // we should not flush because user specified that the query doesn't touch the table + assertEquals(0, resultList.size()); + } ); + } + + @Test void test7(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.persist( new Thing( "Widget" ) ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .setQueryFlushMode( QueryFlushMode.FLUSH ) + .getResultList(); + // we should flush because of the QueryFlushMode + assertEquals( 1, resultList.size() ); + } ); + } + + @Entity(name="Thing") + public static class Thing { + @Id + long id; + String typeOfThing; + + public Thing(String typeOfThing) { + this.typeOfThing = typeOfThing; + } + + public Thing() { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/autoflush/JpaAutoflushTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/autoflush/JpaAutoflushTest.java new file mode 100644 index 000000000000..bf355cf490b3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/autoflush/JpaAutoflushTest.java @@ -0,0 +1,110 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.autoflush; + +import jakarta.persistence.Entity; +import jakarta.persistence.FlushModeType; +import jakarta.persistence.Id; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_SPACES; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Jpa(annotatedClasses = JpaAutoflushTest.Thing.class) +public class JpaAutoflushTest { + + @Test void test1(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.persist( new Thing( "Widget" ) ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .getResultList(); + // spec says we must flush before native query in tx + assertEquals( 1, resultList.size() ); + } ); + } + + @Test void test2(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select typeofthing from thing", String.class ) + .getResultList(); + // spec says we must flush before native query in tx + assertEquals(1, resultList.size()); + } ); + } + + @Test void test3(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + scope.inEntityManager( em -> { + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .getResultList(); + // spec says we must NOT flush before native query outside tx + assertEquals(0, resultList.size()); + } ); + } + + @Test void test4(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.setFlushMode( FlushModeType.COMMIT ); + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .getResultList(); + // spec says we must NOT flush before native query with FMT.COMMIT + assertEquals(0, resultList.size()); + } ); + } + + @Test void test5(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .setHint( HINT_NATIVE_SPACES, "Thing" ) + .getResultList(); + // we should not flush because user specified that the query touches the table + assertEquals(1, resultList.size()); + } ); + } + + @Test void test6(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + scope.inTransaction( em -> { + em.persist( new Thing("Widget") ); + List resultList = + em.createNativeQuery( "select * from thing", Thing.class ) + .setHint( HINT_NATIVE_SPACES, "XXX" ) + .getResultList(); + // we should not flush because user specified that the query doesn't touch the table + assertEquals(0, resultList.size()); + } ); + } + + @Entity(name="Thing") + public static class Thing { + @Id + long id; + String type; + + public Thing(String type) { + this.type = type; + } + + public Thing() { + } + } +}