diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java index ba757586d7..7ff63c97ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java @@ -34,13 +34,13 @@ import org.jboss.logging.Logger; */ class EventListenerGroupImpl implements EventListenerGroup { private static final Logger log = Logger.getLogger( EventListenerGroupImpl.class ); + private static final Set DEFAULT_DUPLICATION_STRATEGIES = Collections.unmodifiableSet( makeDefaultDuplicationStrategy() ); private final EventType eventType; private final CallbackRegistry callbackRegistry; private final boolean isJpaBootstrap; - private final Set duplicationStrategies = new LinkedHashSet<>(); - + private Set duplicationStrategies = DEFAULT_DUPLICATION_STRATEGIES; private T[] listeners = null; public EventListenerGroupImpl( @@ -50,21 +50,6 @@ class EventListenerGroupImpl implements EventListenerGroup { this.eventType = eventType; this.callbackRegistry = callbackRegistry; this.isJpaBootstrap = isJpaBootstrap; - - duplicationStrategies.add( - // At minimum make sure we do not register the same exact listener class multiple times. - new DuplicationStrategy() { - @Override - public boolean areMatch(Object listener, Object original) { - return listener.getClass().equals( original.getClass() ); - } - - @Override - public Action getAction() { - return Action.ERROR; - } - } - ); } @Override @@ -85,7 +70,13 @@ class EventListenerGroupImpl implements EventListenerGroup { @Override public void clear() { - duplicationStrategies.clear(); + //Odd semantics: we're expected (for backwards compatibility) to also clear the default DuplicationStrategy. + duplicationStrategies = new LinkedHashSet<>();; + listeners = null; + } + + @Override + public void clearListeners() { listeners = null; } @@ -125,6 +116,9 @@ class EventListenerGroupImpl implements EventListenerGroup { @Override public void addDuplicationStrategy(DuplicationStrategy strategy) { + if ( duplicationStrategies == DEFAULT_DUPLICATION_STRATEGIES ) { + duplicationStrategies = makeDefaultDuplicationStrategy(); + } duplicationStrategies.add( strategy ); } @@ -308,4 +302,24 @@ class EventListenerGroupImpl implements EventListenerGroup { return Arrays.asList( listeners ); } + + private static Set makeDefaultDuplicationStrategy() { + final Set duplicationStrategies = new LinkedHashSet<>(); + duplicationStrategies.add( + // At minimum make sure we do not register the same exact listener class multiple times. + new DuplicationStrategy() { + @Override + public boolean areMatch(Object listener, Object original) { + return listener.getClass().equals( original.getClass() ); + } + + @Override + public Action getAction() { + return Action.ERROR; + } + } + ); + return duplicationStrategies; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerGroup.java b/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerGroup.java index af1cc38465..1c1561dc30 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerGroup.java @@ -61,8 +61,20 @@ public interface EventListenerGroup extends Serializable { public void prependListener(T listener); public void prependListeners(T... listeners); + /** + * Clears both the list of event listeners and all DuplicationStrategy, + * including the default duplication strategy. + * @deprecated likely want to use {@link #clearListeners()} instead, which doesn't + * also reset the registered DuplicationStrategy(ies). + */ + @Deprecated public void clear(); + /** + * Removes all registered listeners + */ + public void clearListeners(); + /** * Fires an event on each registered event listener of this group. * diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index 4a609a376a..57e8ec4251 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -194,6 +194,7 @@ public class SessionFactoryImpl implements SessionFactoryImplementor { private final transient FastSessionServices fastSessionServices; private final transient SessionBuilder defaultSessionOpenOptions; private final transient SessionBuilder temporarySessionOpenOptions; + private final transient StatelessSessionBuilder defaultStatelessOptions; public SessionFactoryImpl( final MetadataImplementor bootMetamodel, @@ -364,8 +365,9 @@ public class SessionFactoryImpl implements SessionFactoryImplementor { fetchProfiles.put( fetchProfile.getName(), fetchProfile ); } - this.defaultSessionOpenOptions = withOptions(); - this.temporarySessionOpenOptions = buildTemporarySessionOpenOptions(); + this.defaultSessionOpenOptions = createDefaultSessionOpenOptionsIfPossible(); + this.temporarySessionOpenOptions = this.defaultSessionOpenOptions == null ? null : buildTemporarySessionOpenOptions(); + this.defaultStatelessOptions = this.defaultSessionOpenOptions == null ? null : withStatelessOptions(); this.fastSessionServices = new FastSessionServices( this ); this.observer.sessionFactoryCreated( this ); @@ -392,6 +394,17 @@ public class SessionFactoryImpl implements SessionFactoryImplementor { } } + private SessionBuilder createDefaultSessionOpenOptionsIfPossible() { + final CurrentTenantIdentifierResolver currentTenantIdentifierResolver = getCurrentTenantIdentifierResolver(); + if ( currentTenantIdentifierResolver == null ) { + return withOptions(); + } + else { + //Don't store a default SessionBuilder when a CurrentTenantIdentifierResolver is provided + return null; + } + } + private SessionBuilder buildTemporarySessionOpenOptions() { return withOptions() .autoClose( false ) @@ -502,25 +515,23 @@ public class SessionFactoryImpl implements SessionFactoryImplementor { } public Session openSession() throws HibernateException { - final CurrentTenantIdentifierResolver currentTenantIdentifierResolver = getCurrentTenantIdentifierResolver(); - //We can only reuse the defaultSessionOpenOptions as a constant when there is no TenantIdentifierResolver - if ( currentTenantIdentifierResolver != null ) { - return this.withOptions().openSession(); + //The defaultSessionOpenOptions can't be used in some cases; for example when using a TenantIdentifierResolver. + if ( this.defaultSessionOpenOptions != null ) { + return this.defaultSessionOpenOptions.openSession(); } else { - return this.defaultSessionOpenOptions.openSession(); + return this.withOptions().openSession(); } } public Session openTemporarySession() throws HibernateException { - final CurrentTenantIdentifierResolver currentTenantIdentifierResolver = getCurrentTenantIdentifierResolver(); - //We can only reuse the defaultSessionOpenOptions as a constant when there is no TenantIdentifierResolver - if ( currentTenantIdentifierResolver != null ) { - return buildTemporarySessionOpenOptions() - .openSession(); + //The temporarySessionOpenOptions can't be used in some cases; for example when using a TenantIdentifierResolver. + if ( this.temporarySessionOpenOptions != null ) { + return this.temporarySessionOpenOptions.openSession(); } else { - return this.temporarySessionOpenOptions.openSession(); + return buildTemporarySessionOpenOptions() + .openSession(); } } @@ -542,7 +553,12 @@ public class SessionFactoryImpl implements SessionFactoryImplementor { } public StatelessSession openStatelessSession() { - return withStatelessOptions().openStatelessSession(); + if ( this.defaultStatelessOptions != null ) { + return this.defaultStatelessOptions.openStatelessSession(); + } + else { + return withStatelessOptions().openStatelessSession(); + } } public StatelessSession openStatelessSession(Connection connection) { diff --git a/hibernate-core/src/test/java/org/hibernate/event/service/internal/EventListenerDuplicationStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/event/service/internal/EventListenerDuplicationStrategyTest.java index e87364228f..b423363dc7 100644 --- a/hibernate-core/src/test/java/org/hibernate/event/service/internal/EventListenerDuplicationStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/event/service/internal/EventListenerDuplicationStrategyTest.java @@ -184,6 +184,22 @@ public class EventListenerDuplicationStrategyTest { listenerGroup.appendListener( new ExpectedListener( tracker ) ); } + @Test + public void testDuplicationStrategyRemovedOnClear() { + listenerGroup.clear(); + //As side-effect, it's now allowed to register the same event twice: + listenerGroup.appendListener( new OriginalListener( tracker ) ); + listenerGroup.appendListener( new OriginalListener( tracker ) ); + } + + @Test + public void testDefaultDuplicationStrategy() { + thrown.expect( EventListenerRegistrationException.class ); + //By default, it's not allowed to register the same type of listener twice: + listenerGroup.appendListener( new OriginalListener( tracker ) ); + listenerGroup.appendListener( new OriginalListener( tracker ) ); + } + /** * Keep track of which listener is called and how many listeners are called. */ diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest2.java b/hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest2.java new file mode 100644 index 0000000000..ff60f8009f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest2.java @@ -0,0 +1,562 @@ +/* + * 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.test.hql.size; + +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.boot.SessionFactoryBuilder; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-13944") +public class ManyToManySizeTest2 extends BaseNonConfigCoreFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + @Before + public void setUp() { + inTransaction( session -> { + Skill mathSkill = new Skill(); + Skill frenchSkill = new Skill(); + + session.persist( mathSkill ); + session.persist( frenchSkill ); + + Teacher teacherWithNoSkills = new Teacher(); + + Teacher teacherWithOneSkill = new Teacher(); + teacherWithOneSkill.addSkill( mathSkill ); + + Teacher teacherWithTwoSkills = new Teacher(); + teacherWithTwoSkills.addSkill( mathSkill ); + teacherWithTwoSkills.addSkill( frenchSkill ); + + session.persist( teacherWithNoSkills ); + session.persist( teacherWithOneSkill ); + session.persist( teacherWithTwoSkills ); + + Student studentWithTeacherWithNoSkills = new Student(); + studentWithTeacherWithNoSkills.setTeacher( teacherWithNoSkills ); + session.persist( studentWithTeacherWithNoSkills ); + + Student studentWithTeacherWithOneSkill = new Student(); + studentWithTeacherWithOneSkill.setTeacher( teacherWithOneSkill ); + session.persist( studentWithTeacherWithOneSkill ); + + Student studentWithTeacherWithTwoSkills = new Student(); + studentWithTeacherWithTwoSkills.setTeacher( teacherWithTwoSkills ); + session.persist( studentWithTeacherWithTwoSkills ); + } ); + sqlStatementInterceptor.clear(); + } + + @After + public void tearDown() { + inTransaction( session -> { + session.createQuery( "delete from Student" ).executeUpdate(); + session.createQuery( "delete from Teacher" ).executeUpdate(); + session.createQuery( "delete from Skill" ).executeUpdate(); + } ); + } + + @Test + public void testSize() { + inTransaction( session -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // Using "join" (instead of "left join") removes the teacher with no skills from the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // Using "left join" includes the teacher with no skills in the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join teacher.skills skills where size(skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + } ); + } + + @Test + public void testSizeAddFetch() { + inTransaction( session -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + + + // Using "join" (instead of "left join") removes the teacher with no skills from the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + + // Using "left join" includes the teacher with no skills in the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills left join teacher.skills skills where size(skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getSkills() ) ); + } ); + } + + @Test + public void testSizeWithoutNestedPath() { + inTransaction( session -> { + List teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.skills) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.skills) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.skills) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // If there is no "join", then results include the teacher with no skills + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + } ); + } + + @Test + public void testSizeWithoutNestedPathAddFetchDistinct() { + inTransaction( session -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > 2", + Teacher.class + ).getResultList(); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 2 ); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getSkills() ) ); + } ); + } + + @Test + public void testSizeWithNestedPathAndImplicitJoin() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select student from Student student where size(student.teacher.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0, students.size() ); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); + + students = em.createQuery( + "select student from Student student where size(student.teacher.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1, students.size() ); + + students = em.createQuery( + "select student from Student student where size(student.teacher.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2, students.size() ); + + // If there is no "join" in the query, then results include the student with the teacher with no skills + students = em.createQuery( + "select student from Student student where size(student.teacher.skills) > -1", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + } ); + } + + @Test + public void testSizeWithNestedPathAndImplicitJoinAddFetchDistinct() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0, students.size() ); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 3 ); + + students = em.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + + students = em.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + + students = em.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > -1", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getSkills() ) ); + } ); + } + + @Test + public void testSizeWithNestedPath() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.skills) > -1", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1L, students.size() ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } ); + } + + @Test + public void testSizeWithNestedPathAddFetchDistinct() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > -1", + Student.class + ).getResultList(); + // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. + // A possible optimization would be to reuse this INNER JOIN. + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 4 ); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getSkills() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } ); + } + + @Test + public void testSizeWithNestedPath2() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select student from Student student join student.teacher t where size(t.skills) > -1", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(t.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(t.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1L, students.size() ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(t.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } ); + } + + @Test + public void testSizeWithNestedPath2AddFetchDistinct() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > -1", + Student.class + ).getResultList(); + // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. + // A possible optimization would be to reuse this INNER JOIN. + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 4 ); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getSkills() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Skill.class, Teacher.class, Student.class }; + } + + @Entity(name = "Skill") + public static class Skill { + + private Integer id; + + private Set teachers = new HashSet<>(); + + @Id + @Column(name = "skill_id") + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToMany(mappedBy = "skills") + public Set getTeachers() { + return teachers; + } + + public void setTeachers(Set teachers) { + this.teachers = teachers; + } + + public void addTeacher(Teacher teacher) { + teachers.add( teacher ); + } + } + + @Entity(name = "Teacher") + public static class Teacher { + + private Integer id; + + private Set students = new HashSet<>(); + + private Set skills = new HashSet<>(); + + @Id + @Column(name = "teacher_id") + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @OneToMany(mappedBy = "teacher") + public Set getStudents() { + return students; + } + + public void setStudents(Set students) { + this.students = students; + } + + public void addStudent(Student student) { + students.add( student ); + } + + @ManyToMany + public Set getSkills() { + return skills; + } + + public void addSkill(Skill skill) { + skills.add( skill ); + skill.addTeacher( this ); + } + + public void setSkills(Set skills) { + this.skills = skills; + } + } + + @Entity(name = "Student") + public static class Student { + + private Integer id; + + private Teacher teacher; + + @Id + @Column(name = "student_id") + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne(optional = false) + @JoinColumn(name = "teacher_fk_id") + public Teacher getTeacher() { + return teacher; + } + + public void setTeacher(Teacher teacher) { + this.teacher = teacher; + teacher.addStudent( this ); + } + } + + private static int countNumberOfJoins(String query) { + return query.toLowerCase( Locale.ROOT ).split( " join ", -1 ).length - 1; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest2.java b/hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest2.java new file mode 100644 index 0000000000..5e29b99fde --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest2.java @@ -0,0 +1,559 @@ +/* + * 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.test.hql.size; + +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.boot.SessionFactoryBuilder; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-13944") +public class OneToManySizeTest2 extends BaseNonConfigCoreFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + @Before + public void setUp() { + inTransaction( session -> { + Student mathStudent = new Student(); + Student frenchStudent = new Student(); + Student scienceStudent = new Student(); + + Teacher teacherWithNoStudents = new Teacher(); + Teacher teacherWithOneStudent = new Teacher(); + Teacher teacherWithTwoStudents = new Teacher(); + + session.persist( teacherWithNoStudents ); + session.persist( teacherWithOneStudent ); + session.persist( teacherWithTwoStudents ); + + mathStudent.setTeacher( teacherWithOneStudent ); + teacherWithOneStudent.addStudent( mathStudent ); + + frenchStudent.setTeacher( teacherWithTwoStudents ); + teacherWithTwoStudents.addStudent( frenchStudent ); + + scienceStudent.setTeacher( teacherWithTwoStudents ); + teacherWithTwoStudents.addStudent( scienceStudent ); + + session.persist( mathStudent ); + session.persist( frenchStudent ); + session.persist( scienceStudent ); + } ); + sqlStatementInterceptor.clear(); + } + + @After + public void tearDown() { + inTransaction( session -> { + session.createQuery( "delete from Student" ).executeUpdate(); + session.createQuery( "delete from Teacher" ).executeUpdate(); + session.createQuery( "delete from Skill" ).executeUpdate(); + } ); + } + + @Test + public void testSize() { + inTransaction( session -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.students students where size(students) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.students students where size(students) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.students students where size(students) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.students students where size(students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // Using "left join" includes the teacher with no students in the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join teacher.students students where size(students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + } ); + } + + @Test + public void testSizeAddFetch() { + inTransaction( session -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + + + // Using "join" (instead of "left join") removes the teacher with no students from the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + + // Using "left join" includes the teacher with no students in the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students left join teacher.students students where size(students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getStudents() ) ); + } ); + } + + @Test + public void testSizeWithoutNestedPath() { + inTransaction( session -> { + List teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.students) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.students) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.students) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // If there is no "join", then results include the teacher with no students + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + } ); + } + + @Test + public void testSizeWithoutNestedPathAddFetchDistinct() { + inTransaction( session -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > 2", + Teacher.class + ).getResultList(); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getStudents() ) ); + } ); + } + + @Test + public void testSizeWithNestedPathAndImplicitJoin() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select student from Student student where size(student.teacher.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0, students.size() ); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); + + students = em.createQuery( + "select student from Student student where size(student.teacher.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2, students.size() ); + + students = em.createQuery( + "select student from Student student where size(student.teacher.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + + students = em.createQuery( + "select student from Student student where size(student.teacher.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + } ); + } + + @Test + public void testSizeWithNestedPathAndImplicitJoinAddFetchDistinct() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.students where size(student.teacher.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0, students.size() ); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 2 ); + + students = em.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.students where size(student.teacher.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + + students = em.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.students where size(student.teacher.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = em.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.students where size(student.teacher.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + } ); + } + + @Test + public void testSizeWithNestedPath() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } ); + } + + @Test + public void testSizeWithNestedPathAddFetchDistinct() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.students where size(student.teacher.students) > -1", + Student.class + ).getResultList(); + // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. + // A possible optimization would be to reuse this INNER JOIN. + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 3 ); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.students where size(student.teacher.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.students where size(student.teacher.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.students where size(student.teacher.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } ); + } + + @Test + public void testSizeWithNestedPath2() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select student from Student student join student.teacher t where size(t.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(t.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(t.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + + students = em.createQuery( + "select student from Student student join student.teacher t where size(t.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } ); + } + + @Test + public void testSizeWithNestedPath2AddFetchDistinct() { + doInJPA( this::sessionFactory, em -> { + List students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > -1", + Student.class + ).getResultList(); + // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. + // A possible optimization would be to reuse this INNER JOIN. + assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 3 ); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + + students = em.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Skill.class, Teacher.class, Student.class }; + } + + @Entity(name = "Skill") + public static class Skill { + + private Integer id; + + private Set teachers = new HashSet<>(); + + @Id + @Column(name = "skill_id") + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToMany(mappedBy = "skills") + public Set getTeachers() { + return teachers; + } + + public void setTeachers(Set teachers) { + this.teachers = teachers; + } + + public void addTeacher(Teacher teacher) { + teachers.add( teacher ); + } + } + + @Entity(name = "Teacher") + public static class Teacher { + + private Integer id; + + private Set students = new HashSet<>(); + + private Set skills = new HashSet<>(); + + @Id + @Column(name = "teacher_id") + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @OneToMany(mappedBy = "teacher") + public Set getStudents() { + return students; + } + + public void setStudents(Set students) { + this.students = students; + } + + public void addStudent(Student student) { + students.add( student ); + } + + @ManyToMany + public Set getSkills() { + return skills; + } + + public void addSkill(Skill skill) { + skills.add( skill ); + skill.addTeacher( this ); + } + + public void setSkills(Set skills) { + this.skills = skills; + } + } + + @Entity(name = "Student") + public static class Student { + + private Integer id; + + private Teacher teacher; + + @Id + @Column(name = "student_id") + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne(optional = false) + @JoinColumn(name = "teacher_fk_id") + public Teacher getTeacher() { + return teacher; + } + + public void setTeacher(Teacher teacher) { + this.teacher = teacher; + } + } + + private static int countNumberOfJoins(String query) { + return query.toLowerCase( Locale.ROOT ).split( " join ", -1 ).length - 1; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/multitenancy/discriminator/DiscriminatorMultiTenancyTest.java b/hibernate-core/src/test/java/org/hibernate/test/multitenancy/discriminator/DiscriminatorMultiTenancyTest.java index f15e825257..6600182eb2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/multitenancy/discriminator/DiscriminatorMultiTenancyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/multitenancy/discriminator/DiscriminatorMultiTenancyTest.java @@ -9,6 +9,7 @@ package org.hibernate.test.multitenancy.discriminator; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import org.hibernate.MultiTenancyStrategy; @@ -108,6 +109,7 @@ public class DiscriminatorMultiTenancyTest extends BaseUnitTestCase { final SessionFactoryBuilder sfb = metadata.getSessionFactoryBuilder(); sessionFactory = (SessionFactoryImplementor) sfb.build(); + currentTenantResolver.setHibernateBooted(); } @After @@ -181,9 +183,19 @@ public class DiscriminatorMultiTenancyTest extends BaseUnitTestCase { private static class TestCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver { private String currentTenantIdentifier; + private final AtomicBoolean postBoot = new AtomicBoolean(false); + + public void setHibernateBooted() { + postBoot.set( true ); + } @Override public String resolveCurrentTenantIdentifier() { + if ( postBoot.get() == false ) { + //Check to prevent any optimisation which might want to cache the tenantId too early during bootstrap: + //it's a common use case to want to provide the tenantId, for example, via a ThreadLocal. + throw new IllegalStateException( "Not booted yet: illegal to try reading the tenant Id at this point!" ); + } return currentTenantIdentifier; }