From 1d68b1a2d054dd5b396d7c1c440c0f5d45022bc1 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 9 Jul 2018 13:27:55 +0200 Subject: [PATCH 001/772] Revert "HHH-12670 - Allows native SQL queries that take a given resultClass to map the result set to the required type" This reverts commit 9fac6747ef22acff9c7350efca33286452a2c967. --- .../AbstractSharedSessionContract.java | 30 +------ .../query/ScalarResultNativeQueryTest.java | 81 +++++-------------- 2 files changed, 20 insertions(+), 91 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index ea3c5925e2f8..ac3d5da8acf5 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -10,7 +10,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.lang.reflect.Array; import java.sql.SQLException; import java.util.List; import java.util.TimeZone; @@ -81,7 +80,6 @@ import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; -import org.hibernate.transform.BasicTransformerAdapter; import org.hibernate.type.Type; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -934,38 +932,12 @@ public NativeQueryImplementor createNativeQuery(String sqlString, Class resultCl } private void handleNativeQueryResult(NativeQueryImplementor query, Class resultClass) { - boolean isObjectArray = Object[].class.equals( resultClass ); - if ( Tuple.class.equals( resultClass ) ) { query.setResultTransformer( new NativeQueryTupleTransformer() ); } - else if ( resultClass.isArray() && !isObjectArray ) { - Class elementClass = resultClass.getComponentType(); - - query.setResultTransformer( new BasicTransformerAdapter() { - @Override - public Object transformTuple(Object[] tuple, String[] aliases) { - Object[] result = (Object[]) Array.newInstance( elementClass, tuple.length ); - for ( int i = 0; i < tuple.length; i++ ) { - result[i] = elementClass.cast( tuple[i] ); - } - return result; - } - } ); - } - else if ( this.getFactory().getMetamodel().getEntities() - .stream() - .anyMatch( entityType -> entityType.getJavaType().isAssignableFrom( resultClass ) ) ) { + else { query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); } - else if ( !isObjectArray ) { - query.setResultTransformer( new BasicTransformerAdapter() { - @Override - public Object transformTuple(Object[] tuple, String[] aliases) { - return resultClass.cast( tuple[0] ); - } - } ); - } } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java index 8f4fb20fc9fd..6e7e18432a2a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java @@ -6,7 +6,6 @@ */ package org.hibernate.jpa.test.query; -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import java.util.List; @@ -22,9 +21,7 @@ import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; - import org.hibernate.testing.RequiresDialect; -import org.hibernate.testing.TestForIssue; import org.junit.Test; /** @@ -64,64 +61,24 @@ protected Class[] getAnnotatedClasses() { @Test public void shouldApplyConfiguredTypeForProjectionOfScalarValue() { - doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.persist( new Person( 1, 29 ) ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - List results = entityManager.createNamedQuery( "personAge", String.class ).getResultList(); - assertEquals( 1, results.size() ); - assertEquals( "29", results.get( 0 ) ); - } ); - } - - @Test - @TestForIssue( jiraKey = "HHH-12670" ) - public void testNativeSQLWithExplicitScalarMapping() { - doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.persist( new Person( 1, 29 ) ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - List results = entityManager.createNativeQuery( - "select p.age from person p", Integer.class ) - .getResultList(); - assertEquals( 1, results.size() ); - assertEquals( Integer.valueOf( 29 ), results.get( 0 ) ); - } ); - } - - @Test - @TestForIssue( jiraKey = "HHH-12670" ) - public void testNativeSQLWithExplicitTypedArrayMapping() { - doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.persist( new Person( 1, 29 ) ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - List results = entityManager.createNativeQuery( - "select p.id, p.age from person p", Integer[].class ) - .getResultList(); - assertEquals( 1, results.size() ); - assertEquals( Integer.valueOf( 1 ), results.get( 0 )[0] ); - assertEquals( Integer.valueOf( 29 ), results.get( 0 )[1] ); - } ); - } - - @Test - @TestForIssue( jiraKey = "HHH-12670" ) - public void testNativeSQLWithObjectArrayMapping() { - doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.persist( new Person( 1, 29 ) ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - List results = entityManager.createNativeQuery( - "select p.id, p.age from person p", Object[].class ) - .getResultList(); - assertEquals( 1, results.size() ); - assertEquals( Integer.valueOf( 1 ), results.get( 0 )[0] ); - assertEquals( Integer.valueOf( 29 ), results.get( 0 )[1] ); - } ); + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( new Person( 1, 29 ) ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + List results = em.createNamedQuery( "personAge", String.class ).getResultList(); + assertEquals( 1, results.size() ); + assertEquals( "29", results.get( 0 ) ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.createQuery( "delete from Person" ).executeUpdate(); + em.getTransaction().commit(); + em.close(); } } From 1e9f67fbc9d56378b0174bb7c7a8f8b7fb3aa128 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 9 Jul 2018 09:44:38 +0100 Subject: [PATCH 002/772] HHH-12776 Add test for issue --- .../EntityResultNativeQueryTest.java | 52 +++++++++++++++++++ .../nativequery/SecondSimpleEntity.java | 46 ++++++++++++++++ .../integration/nativequery/SimpleEntity.java | 46 ++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/EntityResultNativeQueryTest.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/SecondSimpleEntity.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/SimpleEntity.java diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/EntityResultNativeQueryTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/EntityResultNativeQueryTest.java new file mode 100644 index 000000000000..bb117086f4a4 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/EntityResultNativeQueryTest.java @@ -0,0 +1,52 @@ +/* + * 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.envers.test.integration.nativequery; + +import java.util.List; +import javax.persistence.Query; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +public class EntityResultNativeQueryTest extends BaseEnversJPAFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleEntity.class, SecondSimpleEntity.class }; + } + + @Test + @Priority(10) + public void initData() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( new SimpleEntity( "Hibernate" ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12776") + public void testNativeQueryResultHandling() { + doInJPA( this::entityManagerFactory, entityManager -> { + Query query = entityManager.createNativeQuery( "select * from SimpleEntity", SimpleEntity.class ); + List results = query.getResultList(); + SimpleEntity result = (SimpleEntity) results.get( 0 ); + assertThat( result.getStringField(), is( "Hibernate" ) ); + } ); + } + +} + diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/SecondSimpleEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/SecondSimpleEntity.java new file mode 100644 index 000000000000..3fd9f8249e05 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/SecondSimpleEntity.java @@ -0,0 +1,46 @@ +/* + * 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.envers.test.integration.nativequery; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.envers.Audited; + +/** + * @author Andrea Boriero + */ +@Audited +@Entity +public class SecondSimpleEntity { + @Id + @GeneratedValue + private Long id; + + private String stringField; + + public SecondSimpleEntity() { + } + + public SecondSimpleEntity(String stringField) { + this.stringField = stringField; + } + + public Long getId() { + return id; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(String stringField) { + this.stringField = stringField; + } +} + diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/SimpleEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/SimpleEntity.java new file mode 100644 index 000000000000..e8d3e79fea3b --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/nativequery/SimpleEntity.java @@ -0,0 +1,46 @@ +/* + * 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.envers.test.integration.nativequery; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.envers.Audited; + +/** + * @author Andrea Boriero + */ +@Entity(name = "SimpleEntity") +@Audited +public class SimpleEntity { + @Id + @GeneratedValue + private Long id; + + private String stringField; + + public SimpleEntity() { + } + + public SimpleEntity(String stringField) { + this.stringField = stringField; + } + + public Long getId() { + return id; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(String stringField) { + this.stringField = stringField; + } +} + From 147476038bbfe5655af09c3be0a55ece5131a086 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 9 Jul 2018 13:17:51 +0200 Subject: [PATCH 003/772] HHH-12778 Register the cause when an exception occurs creating a proxy --- .../bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index 5c3201f19c6a..600d375e4240 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -54,7 +54,7 @@ public Object getProxy() { return proxy; } catch (Throwable t) { - throw new HibernateException( "Unable to instantiate proxy instance" ); + throw new HibernateException( "Unable to instantiate proxy instance", t ); } } From 500edf4b8a8b320b43456db2c8e2230c815de074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 12:29:11 +0200 Subject: [PATCH 004/772] HHH-12720 Test proxy serialization with hibernate.enable_lazy_load_no_trans = true --- .../EntityProxySerializationTest.java | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java new file mode 100644 index 000000000000..afd39d9908b4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java @@ -0,0 +1,241 @@ +/* + * 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.serialization; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.internal.util.SerializationHelper; +import org.hibernate.proxy.AbstractLazyInitializer; +import org.hibernate.proxy.HibernateProxy; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Selaron + */ +public class EntityProxySerializationTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleEntity.class, ChildEntity.class }; + } + + @Override + protected void configure(final Configuration configuration) { + // enable LL without TX, which used to cause problems when serializing proxies (see HHH-12720) + configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, Boolean.TRUE.toString() ); + } + + /** + * Prepare and persist a {@link SimpleEntity} with two {@link ChildEntity}. + */ + @Before + public void prepare() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + + try { + final Number count = (Number) s.createQuery("SELECT count(ID) FROM SimpleEntity").getSingleResult(); + if (count.longValue() > 0L) { + // entity already added previously + return; + } + + final SimpleEntity entity = new SimpleEntity(); + entity.setId( 1L ); + entity.setName( "TheParent" ); + + final ChildEntity c1 = new ChildEntity(); + c1.setId( 1L ); + c1.setParent( entity ); + + final ChildEntity c2 = new ChildEntity(); + c2.setId( 2L ); + c2.setParent( entity ); + + s.save( entity ); + s.save( c1 ); + s.save( c2 ); + } + finally { + t.commit(); + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + public void testProxyInitializationWithoutTX() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + assertEquals( "TheParent", parent.getName() ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( parent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-12720") + @FailureExpected(jiraKey = "HHH-12720") + public void testProxyInitializationWithoutTXAfterDeserialization() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + // destroy AbstractLazyInitializer internal state + final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent ); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( deserializedParent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( deserializedParent ) ); + + assertEquals( "TheParent", deserializedParent.getName() ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( deserializedParent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + @Entity(name = "SimpleEntity") + static class SimpleEntity implements Serializable { + + private Long id; + + private String name; + + Set children = new HashSet<>(); + + @Id + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent") + @LazyCollection(LazyCollectionOption.EXTRA) + @Fetch(FetchMode.SELECT) + public Set getChildren() { + return children; + } + + public void setChildren(final Set children) { + this.children = children; + } + + } + + @Entity + static class ChildEntity { + private Long id; + + private SimpleEntity parent; + + @Id + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.PROXY) + public SimpleEntity getParent() { + return parent; + } + + public void setParent(final SimpleEntity parent) { + this.parent = parent; + } + + } +} \ No newline at end of file From 3336489e40af55c57558636d6dc465fa2f3610d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 13:06:06 +0200 Subject: [PATCH 005/772] HHH-12720 Allow lazy loading outside of a transaction after proxy deserialization if the proper settings were enabled --- .../proxy/AbstractLazyInitializer.java | 50 +++++++++++++++++-- .../proxy/AbstractSerializableProxy.java | 31 +++++++++++- .../proxy/pojo/BasicLazyInitializer.java | 1 + .../pojo/bytebuddy/ByteBuddyInterceptor.java | 9 +--- .../pojo/bytebuddy/SerializableProxy.java | 25 +++++++++- .../javassist/JavassistLazyInitializer.java | 2 + .../pojo/javassist/SerializableProxy.java | 25 +++++++++- .../EntityProxySerializationTest.java | 2 - 8 files changed, 126 insertions(+), 19 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java index 6a3db7de0a39..a0b4afe73cbf 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java @@ -13,6 +13,7 @@ import org.hibernate.LazyInitializationException; import org.hibernate.SessionException; import org.hibernate.TransientObjectException; +import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -232,6 +233,13 @@ else if ( session.isOpen() && session.isConnected() ) { } } + /** + * Initialize internal state based on the currently attached session, + * in order to be ready to load data even after the proxy is detached from the session. + * + * This method only has any effect if + * {@link SessionFactoryOptions#isInitializeLazyStateOutsideTransactionsEnabled()} is {@code true}. + */ protected void prepareForPossibleLoadingOutsideTransaction() { if ( session != null ) { allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); @@ -362,25 +370,57 @@ protected final Boolean isReadOnlyBeforeAttachedToSession() { } /** - * Set the read-only/modifiable setting that should be put in affect when it is - * attached to a session. - *

+ * Get whether the proxy can load data even + * if it's not attached to a session with an ongoing transaction. + * + * This method should only be called during serialization, + * and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}. + * + * @return {@code true} if out-of-transaction loads are allowed, {@code false} otherwise. + */ + protected boolean isAllowLoadOutsideTransaction() { + return allowLoadOutsideTransaction; + } + + /** + * Get the session factory UUID. + * + * This method should only be called during serialization, + * and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}. + * + * @return the session factory UUID. + */ + protected String getSessionFactoryUuid() { + return sessionFactoryUuid; + } + + /** + * Restore settings that are not passed to the constructor, + * but are still preserved during serialization. + * * This method should only be called during deserialization, before associating * the proxy with a session. * * @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when * associated with a session; null indicates that the default should be used. + * @param sessionFactoryUuid the session factory uuid, to be used if {@code allowLoadOutsideTransaction} is {@code true}. + * @param allowLoadOutsideTransaction whether the proxy can load data even + * if it's not attached to a session with an ongoing transaction. * * @throws IllegalStateException if isReadOnlySettingAvailable() == true */ /* package-private */ - final void setReadOnlyBeforeAttachedToSession(Boolean readOnlyBeforeAttachedToSession) { + final void afterDeserialization(Boolean readOnlyBeforeAttachedToSession, + String sessionFactoryUuid, boolean allowLoadOutsideTransaction) { if ( isReadOnlySettingAvailable() ) { throw new IllegalStateException( - "Cannot call setReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" + "Cannot call afterDeserialization when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" ); } this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession; + + this.sessionFactoryUuid = sessionFactoryUuid; + this.allowLoadOutsideTransaction = allowLoadOutsideTransaction; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java index be63fd87c424..527ea59bd2d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java @@ -5,6 +5,7 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.proxy; + import java.io.Serializable; /** @@ -16,6 +17,8 @@ public abstract class AbstractSerializableProxy implements Serializable { private String entityName; private Serializable id; private Boolean readOnly; + private String sessionFactoryUuid; + private boolean allowLoadOutsideTransaction; /** * For serialization @@ -23,10 +26,21 @@ public abstract class AbstractSerializableProxy implements Serializable { protected AbstractSerializableProxy() { } + /** + * @deprecated use {@link #AbstractSerializableProxy(String, Serializable, Boolean, String, boolean)} instead. + */ + @Deprecated protected AbstractSerializableProxy(String entityName, Serializable id, Boolean readOnly) { + this( entityName, id, readOnly, null, false ); + } + + protected AbstractSerializableProxy(String entityName, Serializable id, Boolean readOnly, + String sessionFactoryUuid, boolean allowLoadOutsideTransaction) { this.entityName = entityName; this.id = id; this.readOnly = readOnly; + this.sessionFactoryUuid = sessionFactoryUuid; + this.allowLoadOutsideTransaction = allowLoadOutsideTransaction; } protected String getEntityName() { @@ -46,8 +60,23 @@ protected Serializable getId() { * @param li the read-only/modifiable setting to use when * associated with a session; null indicates that the default should be used. * @throws IllegalStateException if isReadOnlySettingAvailable() == true + * + * @deprecated Use {@link #afterDeserialization(AbstractLazyInitializer)} instead. */ + @Deprecated protected void setReadOnlyBeforeAttachedToSession(AbstractLazyInitializer li) { - li.setReadOnlyBeforeAttachedToSession( readOnly ); + li.afterDeserialization( readOnly, null, false ); + } + + /** + * Initialize an {@link AbstractLazyInitializer} after deserialization. + * + * This method should only be called during deserialization, + * before associating the AbstractLazyInitializer with a session. + * + * @param li the {@link AbstractLazyInitializer} to initialize. + */ + protected void afterDeserialization(AbstractLazyInitializer li) { + li.afterDeserialization( readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java index 46eae5ff1cf5..3b066a1bf070 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java @@ -105,6 +105,7 @@ private Object getReplacement() { if ( isUninitialized() ) { if ( replacement == null ) { + prepareForPossibleLoadingOutsideTransaction(); replacement = serializableProxy(); } return replacement; diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java index 915ae46fff96..744761d6e5d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java @@ -17,13 +17,6 @@ import org.hibernate.proxy.pojo.BasicLazyInitializer; import org.hibernate.type.CompositeType; -import net.bytebuddy.implementation.bind.annotation.AllArguments; -import net.bytebuddy.implementation.bind.annotation.FieldValue; -import net.bytebuddy.implementation.bind.annotation.Origin; -import net.bytebuddy.implementation.bind.annotation.RuntimeType; -import net.bytebuddy.implementation.bind.annotation.StubValue; -import net.bytebuddy.implementation.bind.annotation.This; - import static org.hibernate.internal.CoreLogging.messageLogger; public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyConfiguration.Interceptor { @@ -94,6 +87,8 @@ protected Object serializableProxy() { interfaces, getIdentifier(), ( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ), + getSessionFactoryUuid(), + isAllowLoadOutsideTransaction(), getIdentifierMethod, setIdentifierMethod, componentIdType diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java index 493f777b03fa..0d9839af4d49 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java @@ -26,6 +26,10 @@ public final class SerializableProxy extends AbstractSerializableProxy { private final CompositeType componentIdType; + /** + * @deprecated use {@link #SerializableProxy(String, Class, Class[], Serializable, Boolean, String, boolean, Method, Method, CompositeType)} instead. + */ + @Deprecated public SerializableProxy( String entityName, Class persistentClass, @@ -35,7 +39,24 @@ public SerializableProxy( Method getIdentifierMethod, Method setIdentifierMethod, CompositeType componentIdType) { - super( entityName, id, readOnly ); + this( + entityName, persistentClass, interfaces, id, readOnly, null, false, + getIdentifierMethod, setIdentifierMethod, componentIdType + ); + } + + public SerializableProxy( + String entityName, + Class persistentClass, + Class[] interfaces, + Serializable id, + Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction, + Method getIdentifierMethod, + Method setIdentifierMethod, + CompositeType componentIdType) { + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); this.persistentClass = persistentClass; this.interfaces = interfaces; if ( getIdentifierMethod != null ) { @@ -105,7 +126,7 @@ protected CompositeType getComponentIdType() { private Object readResolve() { HibernateProxy proxy = ByteBuddyProxyFactory.deserializeProxy( this ); - setReadOnlyBeforeAttachedToSession( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); + afterDeserialization( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); return proxy; } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java index 5ae423c0a189..ce460ed151d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java @@ -125,6 +125,8 @@ protected Object serializableProxy() { interfaces, getIdentifier(), ( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ), + getSessionFactoryUuid(), + isAllowLoadOutsideTransaction(), getIdentifierMethod, setIdentifierMethod, componentIdType diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java index 9e9785336ebb..3ec55b8182e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java @@ -29,16 +29,37 @@ public final class SerializableProxy extends AbstractSerializableProxy { private final CompositeType componentIdType; + /** + * @deprecated use {@link #SerializableProxy(String, Class, Class[], Serializable, Boolean, String, boolean, Method, Method, CompositeType)} instead. + */ + @Deprecated + public SerializableProxy( + String entityName, + Class persistentClass, + Class[] interfaces, + Serializable id, + Boolean readOnly, + Method getIdentifierMethod, + Method setIdentifierMethod, + CompositeType componentIdType) { + this( + entityName, persistentClass, interfaces, id, readOnly, null, false, + getIdentifierMethod, setIdentifierMethod, componentIdType + ); + } + public SerializableProxy( String entityName, Class persistentClass, Class[] interfaces, Serializable id, Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction, Method getIdentifierMethod, Method setIdentifierMethod, CompositeType componentIdType) { - super( entityName, id, readOnly ); + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); this.persistentClass = persistentClass; this.interfaces = interfaces; if ( getIdentifierMethod != null ) { @@ -114,7 +135,7 @@ protected CompositeType getComponentIdType() { */ private Object readResolve() { HibernateProxy proxy = JavassistProxyFactory.deserializeProxy( this ); - setReadOnlyBeforeAttachedToSession( ( JavassistLazyInitializer ) proxy.getHibernateLazyInitializer() ); + afterDeserialization( ( JavassistLazyInitializer ) proxy.getHibernateLazyInitializer() ); return proxy; } } diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java index afd39d9908b4..e9139fd9812f 100644 --- a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java @@ -31,7 +31,6 @@ import org.hibernate.proxy.AbstractLazyInitializer; import org.hibernate.proxy.HibernateProxy; -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Before; @@ -139,7 +138,6 @@ public void testProxyInitializationWithoutTX() { @SuppressWarnings("unchecked") @Test @TestForIssue(jiraKey = "HHH-12720") - @FailureExpected(jiraKey = "HHH-12720") public void testProxyInitializationWithoutTXAfterDeserialization() { final Session s = openSession(); From e0900b17e2c77bc607fc0e3dcffd0fc63f30a400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 16:12:05 +0200 Subject: [PATCH 006/772] HHH-12720 Deprecate a useless constructor in AbstractSerializableProxy A no-arg constructor is only necessary for superclasses of serializable classes that are not themselves serializable. Here the class is serializable, so the constructor is useless. --- .../java/org/hibernate/proxy/AbstractSerializableProxy.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java index 527ea59bd2d1..ec7a0c01ef15 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java @@ -21,8 +21,10 @@ public abstract class AbstractSerializableProxy implements Serializable { private boolean allowLoadOutsideTransaction; /** - * For serialization + * @deprecated This constructor was initially intended for serialization only, and is not useful anymore. + * In any case it should not be relied on by user code. */ + @Deprecated protected AbstractSerializableProxy() { } From f2b4aedc037d8263a5a934b619dbee24e44d846b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 5 Jul 2018 09:41:04 +0200 Subject: [PATCH 007/772] HHH-7686 Clarify and test initialization code in the writeReplace() method in proxies If we copy the behavior of "traditional" (non-map) proxies to the "dynamic-map" proxies, we'd better know what this behavior is and be sure it works correctly. --- .../proxy/AbstractLazyInitializer.java | 20 +++++ .../proxy/pojo/BasicLazyInitializer.java | 17 ++-- .../EntityProxySerializationTest.java | 81 +++++++++++++++++++ 3 files changed, 106 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java index a0b4afe73cbf..5378447c8aef 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java @@ -233,6 +233,26 @@ else if ( session.isOpen() && session.isConnected() ) { } } + /** + * Attempt to initialize the proxy without loading anything from the database. + * + * This will only have any effect if the proxy is still attached to a session, + * and the entity being proxied has been loaded and added to the persistence context + * of that session since the proxy was created. + */ + protected final void initializeWithoutLoadIfPossible() { + if ( !initialized && session != null && session.isOpen() ) { + final EntityKey key = session.generateEntityKey( + getIdentifier(), + session.getFactory().getMetamodel().entityPersister( getEntityName() ) + ); + final Object entity = session.getPersistenceContext().getEntity( key ); + if ( entity != null ) { + setImplementation( entity ); + } + } + } + /** * Initialize internal state based on the currently attached session, * in order to be ready to load data even after the proxy is detached from the session. diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java index 3b066a1bf070..a8059fb2d7ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java @@ -9,7 +9,6 @@ import java.io.Serializable; import java.lang.reflect.Method; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.MarkerObject; import org.hibernate.proxy.AbstractLazyInitializer; @@ -91,17 +90,11 @@ else if ( method.equals( setIdentifierMethod ) ) { } private Object getReplacement() { - final SharedSessionContractImplementor session = getSession(); - if ( isUninitialized() && session != null && session.isOpen() ) { - final EntityKey key = session.generateEntityKey( - getIdentifier(), - session.getFactory().getMetamodel().entityPersister( getEntityName() ) - ); - final Object entity = session.getPersistenceContext().getEntity( key ); - if ( entity != null ) { - setImplementation( entity ); - } - } + /* + * If the target has already been loaded somewhere, just not set on the proxy, + * then use it to initialize the proxy so that we will serialize that instead of the proxy. + */ + initializeWithoutLoadIfPossible(); if ( isUninitialized() ) { if ( replacement == null ) { diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java index e9139fd9812f..0ca5621e9de2 100644 --- a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.HashSet; +import java.util.Map; import java.util.Set; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -94,6 +95,86 @@ public void prepare() { } } + /** + * Tests that serializing an initialized proxy will serialize the target instead. + */ + @SuppressWarnings("unchecked") + @Test + public void testInitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Initialize the proxy + parent.getName(); + assertTrue( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.getName() ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that serializing a proxy which is not initialized + * but whose target has been (separately) added to the persistence context + * will serialize the target instead. + */ + @SuppressWarnings("unchecked") + @Test + public void testUninitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Load the target of the proxy without the proxy being made aware of it + s.detach( parent ); + s.find( SimpleEntity.class, 1L ); + s.update( parent ); + + // assert we still have an uninitialized proxy + assertFalse( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.getName() ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + /** * Tests that lazy loading without transaction nor open session is generally * working. The magic is done by {@link AbstractLazyInitializer} who opens a From bc6c98254175e4e5d4cb5bd02b42c3dc02740890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 13:05:09 +0200 Subject: [PATCH 008/772] HHH-7686 Test dynamic map proxy serialization --- .../MapProxySerializationTest.java | 247 ++++++++++++++++++ .../serialization/DynamicMapMappings.hbm.xml | 33 +++ 2 files changed, 280 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java create mode 100644 hibernate-core/src/test/resources/org/hibernate/test/serialization/DynamicMapMappings.hbm.xml diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java new file mode 100644 index 000000000000..896ad7b2cdb1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java @@ -0,0 +1,247 @@ +/* + * 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.serialization; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.EntityMode; +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.internal.util.SerializationHelper; +import org.hibernate.proxy.AbstractLazyInitializer; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.map.MapProxy; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Selaron + */ +public class MapProxySerializationTest extends BaseCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "serialization/DynamicMapMappings.hbm.xml" }; + } + + @Override + protected void configure(final Configuration configuration) { + // enable LL without TX, which used to cause problems when serializing proxies (see HHH-12720) + configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, Boolean.TRUE.toString() ); + + // dynamic-map by default. + configuration.setProperty( AvailableSettings.DEFAULT_ENTITY_MODE, EntityMode.MAP.getExternalName() ); + } + + @Before + public void prepare() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + + try { + final Number count = (Number) s.createQuery("SELECT count(ID) FROM SimpleEntity").getSingleResult(); + if (count.longValue() > 0L) { + // entity already added previously + return; + } + + final Map entity = new HashMap<>(); + entity.put( "id", 1L ); + entity.put( "name", "TheParent" ); + + final Map c1 = new HashMap<>(); + c1.put( "id", 1L ); + c1.put( "parent", entity ); + + s.save( "SimpleEntity", entity ); + s.save( "ChildEntity", c1 ); + } + finally { + t.commit(); + s.close(); + } + } + + /** + * Tests that serializing an initialized proxy will serialize the target instead. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-7686") + @FailureExpected(jiraKey = "HHH-7686") + public void testInitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Initialize the proxy + parent.get( "name" ); + assertTrue( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final Map deserializedParent = + (Map) SerializationHelper.clone( (Serializable) parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.get( "name" ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that serializing a proxy which is not initialized + * but whose target has been (separately) added to the persistence context + * will serialized the target instead. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-7686") + @FailureExpected(jiraKey = "HHH-7686") + public void testUninitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Load the target of the proxy without the proxy being made aware of it + s.detach( parent ); + s.byId( "SimpleEntity" ).load( 1L ); + s.update( parent ); + + // assert we still have an uninitialized proxy + assertFalse( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final Map deserializedParent = + (Map) SerializationHelper.clone( (Serializable) parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.get( "name" ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + public void testProxyInitializationWithoutTX() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + assertEquals( "TheParent", parent.get( "name" ) ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( parent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-7686") + @FailureExpected(jiraKey = "HHH-7686") + public void testProxyInitializationWithoutTXAfterDeserialization() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + // destroy AbstractLazyInitializer internal state + final Map deserializedParent = + (Map) SerializationHelper.clone( (Serializable) parent ); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( deserializedParent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( deserializedParent ) ); + + assertEquals( "TheParent", deserializedParent.get( "name" ) ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( deserializedParent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/resources/org/hibernate/test/serialization/DynamicMapMappings.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/serialization/DynamicMapMappings.hbm.xml new file mode 100644 index 000000000000..dff71b2c79d6 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/serialization/DynamicMapMappings.hbm.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + From 782336faeddb36012309bcbe855b470cbe2299e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 13:05:45 +0200 Subject: [PATCH 009/772] HHH-7686 Allow lazy loading outside of a transaction after dynamic map proxy deserialization if the proper settings were enabled In theory, trying to deserialize MapLazyInitializer instances that were serialized before this patch should still work, although using such instances (i.e. trying to access any method on the proxy) would still fail, just like it used to before this patch. --- .../proxy/AbstractLazyInitializer.java | 15 ++++++-- .../proxy/AbstractSerializableProxy.java | 2 +- .../proxy/map/MapLazyInitializer.java | 16 +++++++++ .../org/hibernate/proxy/map/MapProxy.java | 36 ++++++++++++++++--- .../proxy/map/SerializableMapProxy.java | 34 ++++++++++++++++++ .../MapProxySerializationTest.java | 4 --- 6 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java index 5378447c8aef..be5b30b37173 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java @@ -45,8 +45,17 @@ public abstract class AbstractLazyInitializer implements LazyInitializer { private boolean allowLoadOutsideTransaction; /** - * For serialization from the non-pojo initializers (HHH-3309) + * @deprecated This constructor was initially intended for serialization only, and is not useful anymore. + * In any case it should not be relied on by user code. + * Subclasses should rather implement Serializable with an {@code Object writeReplace()} method returning + * a subclass of {@link AbstractSerializableProxy}, + * which in turn implements Serializable and an {@code Object readResolve()} method + * instantiating the {@link AbstractLazyInitializer} subclass + * and calling {@link AbstractSerializableProxy#afterDeserialization(AbstractLazyInitializer)} on it. + * See {@link org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor} and + * {@link org.hibernate.proxy.pojo.bytebuddy.SerializableProxy} for examples. */ + @Deprecated protected AbstractLazyInitializer() { } @@ -240,7 +249,7 @@ else if ( session.isOpen() && session.isConnected() ) { * and the entity being proxied has been loaded and added to the persistence context * of that session since the proxy was created. */ - protected final void initializeWithoutLoadIfPossible() { + public final void initializeWithoutLoadIfPossible() { if ( !initialized && session != null && session.isOpen() ) { final EntityKey key = session.generateEntityKey( getIdentifier(), @@ -380,7 +389,7 @@ public final void setReadOnly(boolean readOnly) { * * @throws IllegalStateException if isReadOnlySettingAvailable() == true */ - protected final Boolean isReadOnlyBeforeAttachedToSession() { + public final Boolean isReadOnlyBeforeAttachedToSession() { if ( isReadOnlySettingAvailable() ) { throw new IllegalStateException( "Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java index ec7a0c01ef15..95f0a6dfff3a 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** - * Convenience base class for SerializableProxy. + * Convenience base class for the serialized form of {@link AbstractLazyInitializer}. * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java index 7561579352f4..6c08dd18d013 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java @@ -31,4 +31,20 @@ public Class getPersistentClass() { throw new UnsupportedOperationException("dynamic-map entity representation"); } + // Expose the following methods to MapProxy by overriding them (so that classes in this package see them) + + @Override + protected void prepareForPossibleLoadingOutsideTransaction() { + super.prepareForPossibleLoadingOutsideTransaction(); + } + + @Override + protected boolean isAllowLoadOutsideTransaction() { + return super.isAllowLoadOutsideTransaction(); + } + + @Override + protected String getSessionFactoryUuid() { + return super.getSessionFactoryUuid(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java index 7585b2b44723..6416358ab2f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java @@ -22,14 +22,12 @@ public class MapProxy implements HibernateProxy, Map, Serializable { private MapLazyInitializer li; + private Object replacement; + MapProxy(MapLazyInitializer li) { this.li = li; } - public Object writeReplace() { - return this; - } - public LazyInitializer getHibernateLazyInitializer() { return li; } @@ -82,4 +80,34 @@ public Object put(Object key, Object value) { return li.getMap().put(key, value); } + @Override + public Object writeReplace() { + /* + * If the target has already been loaded somewhere, just not set on the proxy, + * then use it to initialize the proxy so that we will serialize that instead of the proxy. + */ + li.initializeWithoutLoadIfPossible(); + + if ( li.isUninitialized() ) { + if ( replacement == null ) { + li.prepareForPossibleLoadingOutsideTransaction(); + replacement = serializableProxy(); + } + return replacement; + } + else { + return li.getImplementation(); + } + } + + private Object serializableProxy() { + return new SerializableMapProxy( + li.getEntityName(), + li.getIdentifier(), + ( li.isReadOnlySettingAvailable() ? Boolean.valueOf( li.isReadOnly() ) : li.isReadOnlyBeforeAttachedToSession() ), + li.getSessionFactoryUuid(), + li.isAllowLoadOutsideTransaction() + ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java new file mode 100644 index 000000000000..189ae366eb32 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java @@ -0,0 +1,34 @@ +/* + * 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.proxy.map; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.hibernate.proxy.AbstractSerializableProxy; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory; +import org.hibernate.type.CompositeType; + +public final class SerializableMapProxy extends AbstractSerializableProxy { + + public SerializableMapProxy( + String entityName, + Serializable id, + Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction) { + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); + } + + private Object readResolve() { + MapLazyInitializer initializer = new MapLazyInitializer( getEntityName(), getId(), null ); + afterDeserialization( initializer ); + return new MapProxy( initializer ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java index 896ad7b2cdb1..0b8eb3993c15 100644 --- a/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java @@ -21,7 +21,6 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.map.MapProxy; -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Before; @@ -86,7 +85,6 @@ public void prepare() { @SuppressWarnings("unchecked") @Test @TestForIssue(jiraKey = "HHH-7686") - @FailureExpected(jiraKey = "HHH-7686") public void testInitializedProxySerializationIfTargetInPersistenceContext() { final Session s = openSession(); @@ -128,7 +126,6 @@ public void testInitializedProxySerializationIfTargetInPersistenceContext() { @SuppressWarnings("unchecked") @Test @TestForIssue(jiraKey = "HHH-7686") - @FailureExpected(jiraKey = "HHH-7686") public void testUninitializedProxySerializationIfTargetInPersistenceContext() { final Session s = openSession(); @@ -210,7 +207,6 @@ public void testProxyInitializationWithoutTX() { @SuppressWarnings("unchecked") @Test @TestForIssue(jiraKey = "HHH-7686") - @FailureExpected(jiraKey = "HHH-7686") public void testProxyInitializationWithoutTXAfterDeserialization() { final Session s = openSession(); From 7471aa158945825b4144f6613d43405a5e3168f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 5 Jul 2018 11:44:55 +0200 Subject: [PATCH 010/772] HHH-7686 Add missing @Overrides in MapProxy --- .../main/java/org/hibernate/proxy/map/MapProxy.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java index 6416358ab2f4..de6e2a2eb48a 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java @@ -28,54 +28,67 @@ public class MapProxy implements HibernateProxy, Map, Serializable { this.li = li; } + @Override public LazyInitializer getHibernateLazyInitializer() { return li; } + @Override public int size() { return li.getMap().size(); } + @Override public void clear() { li.getMap().clear(); } + @Override public boolean isEmpty() { return li.getMap().isEmpty(); } + @Override public boolean containsKey(Object key) { return li.getMap().containsKey(key); } + @Override public boolean containsValue(Object value) { return li.getMap().containsValue(value); } + @Override public Collection values() { return li.getMap().values(); } + @Override public void putAll(Map t) { li.getMap().putAll(t); } + @Override public Set entrySet() { return li.getMap().entrySet(); } + @Override public Set keySet() { return li.getMap().keySet(); } + @Override public Object get(Object key) { return li.getMap().get(key); } + @Override public Object remove(Object key) { return li.getMap().remove(key); } + @Override public Object put(Object key, Object value) { return li.getMap().put(key, value); } From b0f2d0fa5988dd5eec8074922f1fd21b25e8688b Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Mon, 9 Jul 2018 19:54:01 +0200 Subject: [PATCH 011/772] HHH-8805 - [SchemaUpdate] javax.persistence.ForeignKey doesn't respect ConstraintMode.NO_CONSTRAINT --- .../cfg/annotations/EntityBinder.java | 7 +- ...nColumnNoConstraintSecondaryTableTest.java | 106 +++++++++++++++++ ...ColumnNoConstraintSecondaryTablesTest.java | 106 +++++++++++++++++ ...chemaUpdateJoinColumnNoConstraintTest.java | 107 ++++++++++++++++++ 4 files changed, 322 insertions(+), 4 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintSecondaryTableTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintSecondaryTablesTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java index 19ede93efeb3..ad96518092ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java @@ -1006,12 +1006,11 @@ private SecondaryTable findMatchingSecondaryTable(Join join) { SecondaryTables secondaryTables = annotatedClass.getAnnotation( SecondaryTables.class ); if ( secondaryTables != null ) { - for ( SecondaryTable secondaryTable2 : secondaryTables.value() ) { - if ( secondaryTable != null && nameToMatch.equals( secondaryTable.name() ) ) { - return secondaryTable; + for ( SecondaryTable secondaryTablesEntry : secondaryTables.value() ) { + if ( secondaryTablesEntry != null && nameToMatch.equals( secondaryTablesEntry.name() ) ) { + return secondaryTablesEntry; } } - } return null; diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintSecondaryTableTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintSecondaryTableTest.java new file mode 100644 index 000000000000..fa4b0af2e164 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintSecondaryTableTest.java @@ -0,0 +1,106 @@ +/* + * 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.schemaupdate; + +import java.io.File; +import java.nio.file.Files; +import java.util.EnumSet; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SecondaryTable; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.tool.hbm2ddl.SchemaUpdate; +import org.hibernate.tool.schema.TargetType; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue(jiraKey = "HHH-8805") +public class SchemaUpdateJoinColumnNoConstraintSecondaryTableTest extends BaseUnitTestCase { + + private static final String EXPECTED_SCRIPT = + " create table Child ( " + + " id bigint not null, " + + " some_fk bigint, " + + " primary key (id) " + + " ); " + + " " + + " create table Parent ( " + + " id bigint not null, " + + " primary key (id) " + + " ); "; + private static final String DELIMITER = ";"; + + @Test + public void test() throws Exception { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( Environment.HBM2DDL_AUTO, "none" ) + .build(); + try { + File output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + + final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) + .addAnnotatedClass( Parent.class ) + .buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( DELIMITER ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.SCRIPT ), metadata ); + + String outputContent = new String(Files.readAllBytes(output.toPath())); + + assertFalse( outputContent.toLowerCase().contains( "foreign key" ) ); + + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Entity(name = "Parent") + @SecondaryTable( + name = "ParentDetails", + foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT) + ) + public static class Parent { + + @Id + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintSecondaryTablesTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintSecondaryTablesTest.java new file mode 100644 index 000000000000..f1e426c5d204 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintSecondaryTablesTest.java @@ -0,0 +1,106 @@ +/* + * 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.schemaupdate; + +import java.io.File; +import java.nio.file.Files; +import java.util.EnumSet; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.SecondaryTable; +import javax.persistence.SecondaryTables; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.tool.hbm2ddl.SchemaUpdate; +import org.hibernate.tool.schema.TargetType; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue(jiraKey = "HHH-8805") +public class SchemaUpdateJoinColumnNoConstraintSecondaryTablesTest extends BaseUnitTestCase { + + private static final String EXPECTED_SCRIPT = + " create table Child ( " + + " id bigint not null, " + + " some_fk bigint, " + + " primary key (id) " + + " ); " + + " " + + " create table Parent ( " + + " id bigint not null, " + + " primary key (id) " + + " ); "; + private static final String DELIMITER = ";"; + + @Test + public void test() throws Exception { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( Environment.HBM2DDL_AUTO, "none" ) + .build(); + try { + File output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + + final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) + .addAnnotatedClass( Parent.class ) + .buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( DELIMITER ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.SCRIPT ), metadata ); + + String outputContent = new String(Files.readAllBytes(output.toPath())); + + assertFalse( outputContent.toLowerCase().contains( "foreign key" ) ); + + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Entity(name = "Parent") + @SecondaryTables( + @SecondaryTable( + name = "ParentDetails", + foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT) + ) + ) + public static class Parent { + + @Id + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintTest.java new file mode 100644 index 000000000000..1cc8f0be3fcc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateJoinColumnNoConstraintTest.java @@ -0,0 +1,107 @@ +/* + * 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.schemaupdate; + +import java.io.File; +import java.nio.file.Files; +import java.util.EnumSet; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.tool.hbm2ddl.SchemaUpdate; +import org.hibernate.tool.schema.TargetType; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +@TestForIssue(jiraKey = "HHH-8805") +public class SchemaUpdateJoinColumnNoConstraintTest extends BaseUnitTestCase { + + private static final String DELIMITER = ";"; + + @Test + public void test() throws Exception { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( Environment.HBM2DDL_AUTO, "none" ) + .build(); + try { + File output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + + final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) + .addAnnotatedClass( Parent.class ) + .addAnnotatedClass( Child.class ) + .buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( DELIMITER ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.SCRIPT ), metadata ); + + String outputContent = new String(Files.readAllBytes(output.toPath())); + + assertFalse( outputContent.toLowerCase().contains( "foreign key" ) ); + + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + + @Entity(name = "Child") + public static class Child { + + @Id + private Long id; + + @ManyToOne + @JoinColumn( + name = "some_fk", + foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT) + ) + private Parent parent; + } + +} From 73bc468906ed46b8eb0bb27ff1caccf0610446da Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Thu, 5 Jul 2018 12:43:40 +0300 Subject: [PATCH 012/772] HHH-12200 - Docs mention outdated APIs --- .../asciidoc/quickstart/guides/obtaining.adoc | 2 - .../userguide/appendices/Configurations.adoc | 19 +- .../userguide/chapters/batch/Batching.adoc | 1 - .../userguide/chapters/caching/Caching.adoc | 400 +----------------- 4 files changed, 9 insertions(+), 413 deletions(-) diff --git a/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc b/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc index 8002e2a44d1f..605bc13cddd5 100644 --- a/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc +++ b/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc @@ -17,8 +17,6 @@ hibernate-proxool:: Integrates the http://proxool.sourceforge.net/[Proxool] conn hibernate-jcache:: Integrates the https://jcp.org/en/jsr/detail?id=107$$[JCache] caching specification into Hibernate, enabling any compliant implementation to become a second-level cache provider. hibernate-ehcache:: Integrates the http://ehcache.org/[Ehcache] caching library into Hibernate as a second-level cache provider. -hibernate-infinispan:: Integrates the http://infinispan.org/[Infinispan] caching library into Hibernate as a second-level cache provider. - === Release Bundle Downloads diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index 7dcd85451eaf..31f385ab7684 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -610,12 +610,12 @@ The default value of this setting is determined by the value for `hibernate.gene [[configurations-cache]] === Cache Properties -`*hibernate.cache.region.factory_class*` (e.g. `org.hibernate.cache.infinispan.InfinispanRegionFactory`):: -The fully-qualified name of the `RegionFactory` implementation class. +`*hibernate.cache.region.factory_class*` (e.g. `jcache`):: +Either a shortcut name (e.g. `jcache`, `ehcache`) or the fully-qualified name of the `RegionFactory` implementation class. `*hibernate.cache.default_cache_concurrency_strategy*`:: Setting used to give the name of the default https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/CacheConcurrencyStrategy.html[`CacheConcurrencyStrategy`] to use -when either `@javax.persistence.Cacheable` or `@org.hibernate.annotations.Cache`. `@org.hibernate.annotations.Cache` is used to override the global setting. +when either `@javax.persistence.Cacheable` or `@org.hibernate.annotations.Cache`. `@org.hibernate.annotations.Cache` is used to override the global setting. `*hibernate.cache.use_minimal_puts*` (e.g. `true` (default value) or `false`):: Optimizes second-level cache operation to minimize writes, at the cost of more frequent reads. This is most useful for clustered caches and is enabled by default for clustered cache implementations. @@ -650,17 +650,8 @@ Sets the associated collection cache concurrency strategy for the designated reg [[configurations-infinispan]] === Infinispan properties -`*hibernate.cache.infinispan.cfg*` (e.g. `org/hibernate/cache/infinispan/builder/infinispan-configs.xml`):: -Classpath or filesystem resource containing the Infinispan configuration settings. - -`*hibernate.cache.infinispan.statistics*`:: -Property name that controls whether Infinispan statistics are enabled. The property value is expected to be a boolean true or false, and it overrides statistic configuration in base Infinispan configuration, if provided. - -`*hibernate.cache.infinispan.use_synchronization*`:: -Deprecated setting because Infinispan is designed to always register a `Synchronization` for `TRANSACTIONAL` caches. - -`*hibernate.cache.infinispan.cachemanager*` (e.g. There is no default value, the user must specify the property.):: -Specifies the JNDI name under which the `EmbeddedCacheManager` is bound. +For more details about how to customize the Infinispan second-level cache provider, check out the +http://infinispan.org/docs/stable/user_guide/user_guide.html#configuration_properties[Infinispan User Guide] [[configurations-transactions]] === Transactions properties diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc index 443e3fa70e04..cb495bfbf040 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc @@ -280,7 +280,6 @@ Hibernate generates a value automatically. Automatic generation is only available if you use ID generators which operate on the database. Otherwise, Hibernate throws an exception during parsing. Available in-database generators are `org.hibernate.id.SequenceGenerator` and its subclasses, and objects which implement `org.hibernate.id.PostInsertIdentifierGenerator`. -The most notable exception is `org.hibernate.id.TableHiLoGenerator`, which does not expose a selectable way to get its values. For properties mapped as either version or timestamp, the insert statement gives you two options. You can either specify the property in the properties_list, in which case its value is taken from the corresponding select expressions, or omit it from the properties_list, diff --git a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc index c12af7803fb8..efb5d2402137 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc @@ -677,401 +677,9 @@ unless an appropriate `` entry is added to the Ehcache configurati [[caching-provider-infinispan]] === Infinispan -[NOTE] -==== -Use of the build-in integration for http://infinispan.org/[Infinispan] requires that the `hibernate-infinispan module` jar (and all of its dependencies) are on the classpath. -==== - -How to configure Infinispan to be the second level cache provider varies slightly depending on the deployment scenario: - -==== Single Node Local - -In standalone library mode, a JPA/Hibernate application runs inside a Java SE application or inside containers that don't offer Infinispan integration. - -Enabling Infinispan second level cache provider inside a JPA/Hibernate application that runs in single node is very straightforward. -First, make sure the Hibernate Infinispan cache provider (and its dependencies) are available in the classpath, then modify the persistence.xml to include these properties: - -==== -[source, XML, indent=0] ----- - - - - - ----- -==== - -Plugging in Infinispan as second-level cache provider requires at the bare minimum that `hibernate.cache.region.factory_class` is set to an Infinispan region factory implementation. -Normally, this is `org.hibernate.cache.infinispan.InfinispanRegionFactory` but other region factories are possible in alternative scenarios (see <> section for more info). - -By default, the Infinispan second-level cache provider uses an Infinispan configuration that's designed for clustered environments. -However, since this section is focused on running Infinispan second-level cache provider in a single node, an Infinispan configuration designed for local environments is recommended. -To enable that configuration, set `hibernate.cache.infinispan.cfg` to `org/hibernate/cache/infinispan/builder/infinispan-configs-local.xml` value. - -The next section focuses on analysing how the default local configuration works. -Changing Infinispan configuration options can be done following the instructions in <> section. - -===== Default Local Configuration - -Infinispan second-level cache provider comes with a configuration designed for local, single node, environments. -These are the characteristics of such configuration: - -* Entities, collections, queries and timestamps are stored in non-transactional local caches. - -* Entities and collections query caches are configured with the following eviction settings. -You can change these settings on a per entity or collection basis or per individual entity or collection type. -More information in the <> section below. - - Eviction wake up interval is 5 seconds. - - Max number of entries are 10,000 - - Max idle time before expiration is 100 seconds - - Default eviction algorithm is LRU, least recently used. - -* _No eviction/expiration is configured for timestamp caches_, nor it's allowed. - -===== Local Cache Strategies - -Before version 5.0, Infinispan only supported `transactional` and `read-only` strategies. - -Starting with version 5.0, all cache strategies are supported: `transactional`, `read-write`, `nonstrict-read-write` and `read-only`. - -==== Multi Node Cluster - -When running a JPA/Hibernate in a multi-node environment and enabling Infinispan second-level cache, it is necessary to cluster the second-level cache so that cache consistency can be guaranteed. -Clustering the Infinispan second-level cache provider is as simple as adding the following properties: - -==== -[source, XML, indent=0] ----- - - ----- -==== - -As with the standalone local mode, at the bare minimum the region factory has to be configured to point to an Infinispan region factory implementation. - -However, the default Infinispan configuration used by the second-level cache provider is already configured to work in a cluster environment, so no need to add any extra properties. - -The next section focuses on analysing how the default cluster configuration works. -Changing Infinispan configuration options can be done following the instructions in <> section. - -===== Default Cluster Configuration [[integrations:infinispan-2lc-cluster-configuration]] - -Infinispan second-level cache provider default configuration is designed for multi-node clustered environments. -The aim of this section is to explain the default settings for each of the different global data type caches (entity, collection, query and timestamps), why these were chosen and what are the available alternatives. -These are the characteristics of such configuration: - -* For all entities and collections, whenever a new _entity or collection is read from database_ and needs to be cached, _it's only cached locally_ in order to reduce intra-cluster traffic. -This option can be changed so that entities/collections are cached cluster wide, by switching the entity/collection cache to be replicated or distributed. -How to change this option is explained in the <> section. - -* All _entities and collections are configured to use a synchronous invalidation_ as clustering mode. -This means that when an entity is updated, the updated cache will send a message to the other members of the cluster telling them that the entity has been modified. -Upon receipt of this message, the other nodes will remove this data from their local cache, if it was stored there. -This option can be changed so that both local and remote nodes contain the updates by configuring entities or collections to use a replicated or distributed cache. -With replicated caches all nodes would contain the update, whereas with distributed caches only a subset of the nodes. -How to change this option is explained in the <> section. - -* All _entities and collections have initial state transfer disabled_ since there's no need for it. - -* Entities and collections are configured with the following eviction settings. -You can change these settings on a per entity or collection basis or per individual entity or collection type. -More information in the <> section below. - - Eviction wake up interval is 5 seconds. - - Max number of entries are 10,000 - - Max idle time before expiration is 100 seconds - - Default eviction algorithm is LRU, least recently used. - -* Assuming that query caching has been enabled for the persistence unit (see <>), the query cache is configured so that _queries are only cached locally_. -Alternatively, you can configure query caching to use replication by selecting the `replicated-query` as query cache name. -However, replication for query cache only makes sense if, and only if, all of this conditions are true: - - Performing the query is quite expensive. - - The same query is very likely to be repeatedly executed on different cluster nodes. - - The query is unlikely to be invalidated out of the cache (Note: Hibernate must aggressively invalidate query results from the cache any time any instance of one of the entity types targeted by the query. All such query results are invalidated, even if the change made to the specific entity instance would not have affected the query result) - -* _query cache_ uses the _same eviction/expiration settings as for entities/collections_. - -* _query cache has initial state transfer disabled_ . It is not recommended that this is enabled. - -* The _timestamps cache is configured with asynchronous replication_ as clustering mode. -Local or invalidated cluster modes are not allowed, since all cluster nodes must store all timestamps. -As a result, _no eviction/expiration is allowed for timestamp caches either_. - -[IMPORTANT] -==== -Asynchronous replication was selected as default for timestamps cache for performance reasons. -A side effect of this choice is that when an entity/collection is updated, for a very brief period of time stale queries might be returned. -It's important to note that due to how Infinispan deals with asynchronous replication, stale queries might be found even query is done right after an entity/collection update on same node. -The reason why asynchronous replication works this way is because there's a single node that's owner for a given key, and that enables changes to be applied in the same order in all nodes. -Without it, it could happen that an older value could replace a newer value in certain nodes. -==== - -[NOTE] -==== -Hibernate must aggressively invalidate query results from the cache any time any instance of one of the entity types is modified. All cached query results referencing given entity type are invalidated, even if the change made to the specific entity instance would not have affected the query result. -The timestamps cache plays here an important role - it contains last modification timestamp for each entity type. After a cached query results is loaded, its timestamp is compared to all timestamps of the entity types that are referenced in the query and if any of these is higher, the cached query result is discarded and the query is executed against DB. -==== - -===== Cluster Cache Strategies - -Before version 5.0, Infinispan only supported `transactional` and `read-only` strategies on top of _transactional invalidation_ caches. - -Since version 5.0, Infinispan currently supports all cache concurrency modes in cluster mode, although not all combinations of configurations are compatible: - -* _non-transactional invalidation_ caches are supported as well with `read-write` strategy. The actual setting of cache concurrency mode (`read-write` vs. `transactional`) is not honored, the appropriate strategy is selected based on the cache configuration (_non-transactional_ vs. _transactional_). -* `read-write` mode is supported on _non-transactional distributed/replicated_ caches, however, eviction should not be used in this configuration. Use of eviction can lead to consistency issues. Expiration (with reasonably long max-idle times) can be used. -* `nonstrict-read-write` mode is supported on _non-transactional distributed/replicated_ caches, but the eviction should be turned off as well. In addition to that, the entities must use versioning. This mode mildly relaxes the consistency - between DB commit and end of transaction commit a stale read (see <>) may occur in another transaction. However this strategy uses less RPCs and can be more performant than the other ones. -* `read-only` mode is supported on both _transactional_ and _non-transactional_ _invalidation_ caches and _non-transactional distributed/replicated_ caches, but use of this mode currently does not bring any performance gains. - -The available combinations are summarized in table below: - -[[caching-provider-infinispan-compatibility-table]] -.Cache concurrency strategy/cache mode compatibility table -[options="header"] -|=== -|Concurrency strategy|Cache transactions|Cache mode |Eviction -|transactional |transactional |invalidation |yes -|read-write |non-transactional |invalidation |yes -|read-write |non-transactional |distributed/replicated |no -|nonstrict-read-write|non-transactional |distributed/replicated |no -|=== - -Changing caches to behave different to the default behaviour explained in previous section is explained in <> section. - -[[caching-provider-infinispan-stale-read-example]] -.Stale read with `nonstrict-read-write` strategy -==== -[source, indent=0] ----- -A=0 (non-cached), B=0 (cached in 2LC) -TX1: write A = 1, write B = 1 -TX1: start commit -TX1: commit A, B in DB -TX2: read A = 1 (from DB), read B = 0 (from 2LC) // breaks transactional atomicity -TX1: update A, B in 2LC -TX1: end commit -Tx3: read A = 1, B = 1 // reads after TX1 commit completes are consistent again ----- -==== - -[[caching-provider-infinispan-region-factory]] -==== Alternative RegionFactory - -In standalone environments or managed environments with no Infinispan integration, `org.hibernate.cache.infinispan.InfinispanRegionFactory` should be the choice for region factory implementation. -However, it might be sometimes desirable for the Infinispan cache manager to be shared between different JPA/Hibernate applications, for example to share intra-cluster communications channels. -In this case, the Infinispan cache manager could be bound into JNDI and the JPA/Hibernate applications could use an alternative region factory implementation: - -[[caching-provider-infinispan-region-factory-jndi-example]] -.`JndiInfinispanRegionFactory` configuration -==== -[source, XML, indent=0] ----- - - - ----- -==== - -==== Inside Wildfly - -In WildFly, Infinispan is the default second level cache provider for JPA/Hibernate. -When using JPA in WildFly, region factory is automatically set upon configuring `hibernate.cache.use_second_level_cache=true` (by default second-level cache is not used). - -You can find details about its configuration in link:{wildflydocroot}/JPA%20Reference%20Guide[the JPA reference guide], in particular, in the link:{wildflydocroot}/JPA%20Reference%20Guide#JPAReferenceGuide-UsingtheInfinispansecondlevelcache[second level cache] section. - -The default second-level cache configurations used by Wildfly match the configurations explained above both for local and clustered environments. -So, an Infinispan based second-level cache should behave exactly the same standalone and within containers that provide Infinispan second-level cache as default for JPA/Hibernate. - -[IMPORTANT] -==== -Remember that if deploying to Wildfly or Application Server, the way some Infinispan second level cache provider configuration is defined changes slightly because the properties must include deployment and persistence information. -Check the <> section for more details. -==== - -[[caching-provider-infinispan-config]] -==== Configuration properties - -As explained above, Infinispan second-level cache provider comes with default configuration in `infinispan-config.xml` that is suited for clustered use. -If there's only single JVM accessing the DB, you can use more performant `infinispan-config-local.xml` by setting the `hibernate.cache.infinispan.cfg` property. -If you require further tuning of the cache, you can provide your own configuration. -Caches that are not specified in the provided configuration will default to `infinispan-config.xml` (if the provided configuration uses clustering) or `infinispan-config-local.xml`. - -[WARNING] -==== -It is not possible to specify the configuration this way in WildFly. -Cache configuration changes in Wildfly should be done either modifying the cache configurations inside the application server configuration, or creating new caches with the desired tweaks and plugging them accordingly. -See examples below on how entity/collection specific configurations can be applied. -==== - -[[caching-provider-infinispan-config-example]] -.Use custom Infinispan configuration -==== -[source, XML, indent=0] ----- - ----- -==== - -[NOTE] -==== -If the cache is configured as transactional, InfinispanRegionFactory automatically sets transaction manager so that the TM used by Infinispan is the same as TM used by Hibernate. -==== - -Cache configuration can differ for each type of data stored in the cache. In order to override the cache configuration template, use property `hibernate.cache.infinispan._data-type_.cfg` where `_data-type_` can be one of: - -`entity`:: Entities indexed by `@Id` or `@EmbeddedId` attribute. -`immutable-entity`:: Entities tagged with `@Immutable` annotation or set as `mutable=false` in mapping file. -`naturalid`:: Entities indexed by their `@NaturalId` attribute. -`collection`:: All collections. -`timestamps`:: Mapping _entity type_ -> _last modification timestamp_. Used for query caching. -`query`:: Mapping _query_ -> _query result_. -`pending-puts`:: Auxiliary caches for regions using invalidation mode caches. - -For specifying cache template for specific region, use region name instead of the `_data-type_`: - -[[caching-provider-infinispan-config-cache-example]] -.Use custom cache template -==== -[source, XML, indent=0] ----- - - - - ----- -==== - -.Use custom cache template in Wildfly -When applying entity/collection level changes inside JPA applications deployed in Wildfly, it is necessary to specify deployment name and persistence unit name: - -==== -[source, XML, indent=0] ----- - - ----- -==== - -[IMPORTANT] -==== -Cache configurations are used only as a template for the cache created for given region (usually each entity hierarchy or collection has its own region). It is not possible to use the same cache for different regions. -==== - -Some options in the cache configuration can also be overridden directly through properties. These are: - -`hibernate.cache.infinispan._something_.eviction.strategy`:: Available options are `NONE`, `LRU` and `LIRS`. -`hibernate.cache.infinispan._something_.eviction.max_entries`:: Maximum number of entries in the cache. -`hibernate.cache.infinispan._something_.expiration.lifespan`:: Lifespan of entry from insert into cache (in milliseconds) -`hibernate.cache.infinispan._something_.expiration.max_idle`:: Lifespan of entry from last read/modification (in milliseconds) -`hibernate.cache.infinispan._something_.expiration.wake_up_interval`:: Period of thread checking expired entries. -`hibernate.cache.infinispan.statistics`:: Globally enables/disable Infinispan statistics collection, and their exposure via JMX. - -Example: -==== -[source, XML, indent=0] ----- - - - - - ----- -==== - -With the above configuration, you're overriding whatever eviction/expiration settings were defined for the default entity cache name in the Infinispan cache configuration used, regardless of whether it's the default one or user defined. -More specifically, we're defining the following: - -* All entities to use LRU eviction strategy -* The eviction thread to wake up every 2 seconds (2000 milliseconds) -* The maximum number of entities for each entity type to be 5000 entries -* The lifespan of each entity instance to be 1 minute (600000 milliseconds). -* The maximum idle time for each entity instance to be 30 seconds (30000 milliseconds). - -You can also override eviction/expiration settings on a per entity/collection type basis in such way that the overriden settings only afftect that particular entity (i.e. `com.acme.Person`) or collection type (i.e. `com.acme.Person.addresses`). -Example: - -[source,xml] ----- - ----- -==== - -Inside of Wildfly, same as with the entity/collection configuration override, eviction/expiration settings would also require deployment name and persistence unit information: - -[source,xml] ----- - - ----- -==== - -[NOTE] -==== -In versions prior to 5.1, `hibernate.cache.infinispan._something_.expiration.wake_up_interval` was called `hibernate.cache.infinispan._something_.eviction.wake_up_interval`. -Eviction settings are checked upon each cache insert, it is expiration that needs to be triggered periodically. -The old property still works, but its use is deprecated. -==== - -[NOTE] -==== -Property `hibernate.cache.infinispan.use_synchronization` that allowed to register Infinispan as XA resource in the transaction has been deprecated in 5.0 and is not honored anymore. Infinispan 2LC must register as synchronizations on transactional caches. Also, non-transactional cache modes hook into the current JTA/JDBC transaction as synchronizations. -==== - -[[caching-provider-infinispan-remote]] -==== Remote Infinispan Caching - -Lately, several questions ( link:http://community.jboss.org/message/575814#575814[here] and link:http://community.jboss.org/message/585841#585841[here] ) have appeared in the Infinispan user forums asking whether it'd be possible to have an Infinispan second level cache that instead of living in the same JVM as the Hibernate code, it resides in a remote server, i.e. an Infinispan Hot Rod server. -It's important to understand that trying to set up second level cache in this way is generally not a good idea for the following reasons: - -* The purpose of a JPA/Hibernate second level cache is to store entities/collections recently retrieved from database or to maintain results of recent queries. -So, part of the aim of the second level cache is to have data accessible locally rather than having to go to the database to retrieve it everytime this is needed. -Hence, if you decide to set the second level cache to be remote as well, you're losing one of the key advantages of the second level cache: the fact that the cache is local to the code that requires it. -* Setting a remote second level cache can have a negative impact in the overall performance of your application because it means that cache misses require accessing a remote location to verify whether a particular entity/collection/query is cached. -With a local second level cache however, these misses are resolved locally and so they are much faster to execute than with a remote second level cache. - -There are however some edge cases where it might make sense to have a remote second level cache, for example: +Infinispan is a distributed in-memory key/value data store, available as a cache or data grid, which can be used as a Hibernate 2nd-level cache provider as well. -* You are having memory issues in the JVM where JPA/Hibernate code and the second level cache is running. -Off loading the second level cache to remote Hot Rod servers could be an interesting way to separate systems and allow you find the culprit of the memory issues more easily. -* Your application layer cannot be clustered but you still want to run multiple application layer nodes. -In this case, you can't have multiple local second level cache instances running because they won't be able to invalidate each other for example when data in the second level cache is updated. -In this case, having a remote second level cache could be a way out to make sure your second level cache is always in a consistent state, will all nodes in the application layer pointing to it. -* Rather than having the second level cache in a remote server, you want to simply keep the cache in a separate VM still within the same machine. -In this case you would still have the additional overhead of talking across to another JVM, but it wouldn't have the latency of across a network. -+ -The benefit of doing this is that: -+ -** Size the cache separate from the application, since the cache and the application server have very different memory profiles. -One has lots of short lived objects, and the other could have lots of long lived objects. -** To pin the cache and the application server onto different CPU cores (using _numactl_ ), and even pin them to different physically memory based on the NUMA nodes. +It supports advanced functionality such as transactions, events, querying, distributed processing, off-heap and geographical failover. +For more details, check out the +http://infinispan.org/docs/stable/user_guide/user_guide.html#jpa_hibernate_2l_cache[Infinispan User Guide]. From 0e1f3d5c942e682430ab7866ddb403a2cbe6057d Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 10 Jul 2018 16:24:34 -0700 Subject: [PATCH 013/772] HHH-12740 : Subselect fetching doesn't work when multiLoad was used (cherry picked from commit 579ee65fd3e242e0c92af1e9e08df4696840d394) --- .../DynamicBatchingEntityLoaderBuilder.java | 5 + .../MultiLoadSubSelectCollectionTest.java | 202 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/ops/multiLoad/MultiLoadSubSelectCollectionTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java index de7059dc3702..64c6737fbd45 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java @@ -434,6 +434,11 @@ protected boolean isSingleRowLoader() { return false; } + @Override + protected boolean isSubselectLoadingEnabled() { + return persister.hasSubselectLoadableCollections(); + } + public List doEntityBatchFetch( SharedSessionContractImplementor session, QueryParameters queryParameters, diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/multiLoad/MultiLoadSubSelectCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/ops/multiLoad/MultiLoadSubSelectCollectionTest.java new file mode 100644 index 000000000000..31bf5a1547cf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/multiLoad/MultiLoadSubSelectCollectionTest.java @@ -0,0 +1,202 @@ +/* + * 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.ops.multiLoad; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.CacheMode; +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static javax.persistence.GenerationType.AUTO; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + * @author Gail Badner + */ +public class MultiLoadSubSelectCollectionTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class }; + } + + protected void addSettings(Map settings) { + settings.put( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Before + public void before() { + Session session = sessionFactory().openSession(); + session.getTransaction().begin(); + session.setCacheMode( CacheMode.IGNORE ); + for ( int i = 1; i <= 60; i++ ) { + final Parent p = new Parent( i, "Entity #" + i ); + for ( int j = 0; j < i ; j++ ) { + Child child = new Child(); + child.setParent( p ); + p.getChildren().add( child ); + } + session.persist( p ); + } + session.getTransaction().commit(); + session.close(); + } + + @After + public void after() { + Session session = sessionFactory().openSession(); + session.getTransaction().begin(); + session.createQuery( "delete Child" ).executeUpdate(); + session.createQuery( "delete Parent" ).executeUpdate(); + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12740" ) + public void testSubselect() { + doInHibernate( + this::sessionFactory, session -> { + + + List list = session.byMultipleIds( Parent.class ).multiLoad( ids(56) ); + assertEquals( 56, list.size() ); + + // None of the collections should be loaded yet + for ( Parent p : list ) { + assertFalse( Hibernate.isInitialized( list.get( 0 ).children ) ); + } + + // When the first collection is loaded, the full batch of 50 collections + // should be loaded. + Hibernate.initialize( list.get( 0 ).children ); + + for ( int i = 0 ; i < 50 ; i++ ) { + assertTrue( Hibernate.isInitialized( list.get( i ).children ) ); + assertEquals( i + 1, list.get( i ).children.size() ); + } + + // The collections for the 51st through 56th entities should still be uninitialized + for (int i = 50 ; i < 56 ; i ++ ) { + assertFalse( Hibernate.isInitialized( list.get( i ).children ) ); + } + + // When the 51st collection gets initialized, the remaining collections should + // also be initialized. + Hibernate.initialize( list.get( 50 ).children ); + + for ( int i = 50 ; i < 56 ; i++ ) { + assertTrue( Hibernate.isInitialized( list.get( i ).children ) ); + assertEquals( i + 1, list.get( i ).children.size() ); + } + } + ); + } + + private Integer[] ids(int count) { + Integer[] ids = new Integer[count]; + for ( int i = 1; i <= count; i++ ) { + ids[i-1] = i; + } + return ids; + } + + @Entity( name = "Parent" ) + @Table( name = "Parent" ) + @BatchSize( size = 15 ) + public static class Parent { + Integer id; + String text; + private List children = new ArrayList<>(); + + public Parent() { + } + + public Parent(Integer id, String text) { + this.id = id; + this.text = text; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + @Fetch(FetchMode.SUBSELECT) + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + } + + @Entity( name = "Child" ) + public static class Child { + + @Id + @GeneratedValue(strategy = AUTO) + private int id; + + @ManyToOne(fetch = FetchType.LAZY, optional = true) + private Parent parent; + + public Child() { + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + public int getId() { + return id; + } + } +} From a56ff4ca9c10e3dda4314ab0e3f6ff1032632025 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 10 Jul 2018 13:16:32 +0200 Subject: [PATCH 014/772] HHH-12781 Update Javassist dependency to 3.23.1 --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 58451aca510b..b71da18125a8 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -22,7 +22,7 @@ ext { cdiVersion = '2.0' weldVersion = '3.0.0.Final' - javassistVersion = '3.22.0-GA' + javassistVersion = '3.23.1-GA' byteBuddyVersion = '1.8.12' // Now with JDK10 compatibility and preliminary support for JDK11 geolatteVersion = '1.3.0' From a0d2f539378e5f9ac6f14f804205b8b869c3e433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 09:25:20 +0200 Subject: [PATCH 015/772] HHH-12695 Explicitly mark contracts as @Incubating, since a comment in QueryParameter implies they are in fact incubating I did not mark ParameterRegistration as incubating, because it's a pre-existing interface that we can't reasonably consider as incubating. It's a bit odd to have a non-incubating contract (ParameterRegistration) extend an incubating one (ProcedureParameter), though... --- .../engine/query/spi/AbstractParameterDescriptor.java | 4 ++++ .../org/hibernate/query/procedure/ProcedureParameter.java | 4 ++++ .../query/procedure/spi/ProcedureParameterImplementor.java | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java index aab0351faae1..62e025b6df55 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java @@ -6,12 +6,16 @@ */ package org.hibernate.engine.query.spi; +import org.hibernate.Incubating; import org.hibernate.query.QueryParameter; import org.hibernate.type.Type; /** + * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM + * * @author Steve Ebersole */ +@Incubating public abstract class AbstractParameterDescriptor implements QueryParameter { private final int[] sourceLocations; diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java index 050608d0ab38..0bd9c4a2fde3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java @@ -8,12 +8,16 @@ import javax.persistence.ParameterMode; +import org.hibernate.Incubating; import org.hibernate.procedure.spi.ParameterRegistrationImplementor; import org.hibernate.query.QueryParameter; /** + * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM + * * @author Steve Ebersole */ +@Incubating public interface ProcedureParameter extends QueryParameter { /** * Retrieves the parameter "mode". Only really pertinent in regards to procedure/function calls. diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java index f671e05a2e9e..a5558058ff8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java @@ -6,11 +6,15 @@ */ package org.hibernate.query.procedure.spi; +import org.hibernate.Incubating; import org.hibernate.procedure.spi.ParameterRegistrationImplementor; import org.hibernate.query.procedure.ProcedureParameter; /** + * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM + * * @author Steve Ebersole */ +@Incubating public interface ProcedureParameterImplementor extends ProcedureParameter, ParameterRegistrationImplementor { } From c26ac23a7b9892862e582c37a25090cb7b7d39f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 09:32:18 +0200 Subject: [PATCH 016/772] HHH-12695 Add missing @Overrides in subclasses of javax.persistence.Parameter Because we are in 2018 and Java 5 is now 14 years old. --- .../hibernate/engine/query/spi/NamedParameterDescriptor.java | 1 + .../java/org/hibernate/procedure/ParameterRegistration.java | 4 ++++ .../procedure/spi/ParameterRegistrationImplementor.java | 1 + .../internal/expression/ParameterExpressionImpl.java | 5 +++++ .../hibernate/query/internal/QueryParameterNamedImpl.java | 1 + .../query/procedure/internal/ProcedureParameterImpl.java | 1 + 6 files changed, 13 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java index 38de4409d7f3..4edddeb7dbf8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java @@ -30,6 +30,7 @@ public NamedParameterDescriptor(String name, Type expectedType, int[] sourceLoca this.name = name; } + @Override public String getName() { return name; } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java index 20fb09921604..92c96fb77731 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java @@ -25,6 +25,7 @@ public interface ParameterRegistration extends ProcedureParameter { * * @return The name; */ + @Override String getName(); /** @@ -33,6 +34,7 @@ public interface ParameterRegistration extends ProcedureParameter { * * @return The name; */ + @Override Integer getPosition(); /** @@ -41,6 +43,7 @@ public interface ParameterRegistration extends ProcedureParameter { * * @return The parameter mode. */ + @Override ParameterMode getMode(); /** @@ -59,6 +62,7 @@ public interface ParameterRegistration extends ProcedureParameter { * * @param enabled {@code true} indicates that the NULL should be passed; {@code false} indicates it should not. */ + @Override void enablePassingNulls(boolean enabled); /** diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java index 805662fe1da6..6a5656660d85 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java @@ -46,6 +46,7 @@ public interface ParameterRegistrationImplementor extends ParameterRegistrati * that the parameter will simply be ignored, with the assumption that the corresponding argument * defined a default value. */ + @Override boolean isPassNullsEnabled(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java index 24f74ccacf38..3bac2a93ccf1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java @@ -52,22 +52,27 @@ public ParameterExpressionImpl( this.position = null; } + @Override public String getName() { return name; } + @Override public Integer getPosition() { return position; } + @Override public Class getParameterType() { return getJavaType(); } + @Override public void registerParameters(ParameterRegistry registry) { registry.registerParameter( this ); } + @Override public String render(RenderingContext renderingContext) { final ExplicitParameterInfo parameterInfo = renderingContext.registerExplicitParameter( this ); return parameterInfo.render(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java index 85a443fe6c6f..55731593e973 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java @@ -39,6 +39,7 @@ public Integer getPosition() { return null; } + @Override public int[] getSourceLocations() { return sourceLocations; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java index 009dd447bfa8..792ccc44662e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java @@ -308,6 +308,7 @@ private boolean canDoNameParameterBinding(Type hibernateType) { && ((ProcedureParameterNamedBinder) hibernateType).canDoSetting(); } + @Override public int[] getSqlTypes() { if ( mode == ParameterMode.REF_CURSOR ) { // we could use the Types#REF_CURSOR added in Java 8, but that would require requiring Java 8... From 68ad2130e8f70b6818d5c80727e4c352f2de06d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 09:36:18 +0200 Subject: [PATCH 017/772] HHH-12695 Rename org.hibernate.query.QueryParameter#getType to getHibernateType Because it conflicts with the Class-returning getType method we want to re-introduce in ParameterRegistration to restore backward compatibility. --- .../engine/query/spi/AbstractParameterDescriptor.java | 2 +- .../procedure/spi/ParameterRegistrationImplementor.java | 1 + .../src/main/java/org/hibernate/query/QueryParameter.java | 2 +- .../hibernate/query/internal/AbstractProducedQuery.java | 2 +- .../query/internal/QueryParameterBindingsImpl.java | 8 ++++---- .../org/hibernate/query/internal/QueryParameterImpl.java | 2 +- .../query/procedure/internal/ProcedureParameterImpl.java | 5 ----- 7 files changed, 9 insertions(+), 13 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java index 62e025b6df55..df24844621f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java @@ -42,7 +42,7 @@ public Class getParameterType() { } @Override - public Type getType() { + public Type getHibernateType() { return getExpectedType(); } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java index 6a5656660d85..130cde2d2092 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java @@ -34,6 +34,7 @@ public interface ParameterRegistrationImplementor extends ParameterRegistrati * * @return The Hibernate Type */ + @Override Type getHibernateType(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java b/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java index 25659e00a50a..a947ae7dcea6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java @@ -21,7 +21,7 @@ public interface QueryParameter extends javax.persistence.Parameter { * * @return The Hibernate Type. */ - Type getType(); + Type getHibernateType(); int[] getSourceLocations(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index a16aca136a22..847b5527deb6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -840,7 +840,7 @@ else if ( retType.isArray() ) { protected Type determineType(String namedParam, Class retType) { Type type = getQueryParameterBindings().getBinding( namedParam ).getBindType(); if ( type == null ) { - type = getParameterMetadata().getQueryParameter( namedParam ).getType(); + type = getParameterMetadata().getQueryParameter( namedParam ).getHibernateType(); } if ( type == null ) { type = getProducer().getFactory().resolveParameterBindType( retType ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java index 29bb9780eb80..8badf415be0e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java @@ -119,7 +119,7 @@ protected QueryParameterBinding makeBinding(QueryParameter queryParameter) { ); } - final QueryParameterBinding binding = makeBinding( queryParameter.getType() ); + final QueryParameterBinding binding = makeBinding( queryParameter.getHibernateType() ); parameterBindingMap.put( queryParameter, binding ); return binding; @@ -145,7 +145,7 @@ protected QueryParameterListBinding makeListBinding(QueryParameter par return parameterListBindingMap.computeIfAbsent( param, p -> new QueryParameterListBindingImpl( - param.getType(), + param.getHibernateType(), shouldValidateBindingValue() ) ); @@ -606,7 +606,7 @@ public String expandListValuedParameters(String queryString, SharedSessionContra syntheticParam = new NamedParameterDescriptor( syntheticName, - sourceParam.getType(), + sourceParam.getHibernateType(), sourceParam.getSourceLocations() ); } @@ -624,7 +624,7 @@ public String expandListValuedParameters(String queryString, SharedSessionContra syntheticParam = new OrdinalParameterDescriptor( syntheticPosition, syntheticPosition - jdbcStyleOrdinalCountBase, - sourceParam.getType(), + sourceParam.getHibernateType(), sourceParam.getSourceLocations() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java index d7f1144b58d8..8ffa92f60145 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java @@ -25,7 +25,7 @@ public QueryParameterImpl(Type expectedType) { } @Override - public Type getType() { + public Type getHibernateType() { return expectedType; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java index 792ccc44662e..63a4c0528d5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java @@ -118,11 +118,6 @@ public Integer getPosition() { return position; } - @Override - public Type getHibernateType() { - return getType(); - } - @Override public void setHibernateType(Type expectedType) { super.setHibernateType( expectedType ); From fb8e1c1d7142ff8b6478ed65e5dffbf8fdda7d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 09:40:10 +0200 Subject: [PATCH 018/772] HHH-12695 Restore the getType method in ParameterRegistration It was removed in 5.3.0.Beta1, but this breaks backward compatibility with 5.1. --- .../hibernate/procedure/ParameterRegistration.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java index 92c96fb77731..b36f656052f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java @@ -37,6 +37,17 @@ public interface ParameterRegistration extends ProcedureParameter { @Override Integer getPosition(); + /** + * Return the Java type of the parameter. + * + * @return The Java type of the parameter. + * @deprecated Call {@link #getParameterType()} instead. + */ + @Deprecated + default Class getType() { + return getParameterType(); + } + /** * Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure * definition (is it an INPUT parameter? An OUTPUT parameter? etc). From 6546858eb0c9b3632544dfdfe46ede3b10b67799 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 10 Jul 2018 15:51:55 +0100 Subject: [PATCH 019/772] HHH-12788 Enable mockito-inline for the Agroal integration module --- hibernate-agroal/hibernate-agroal.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hibernate-agroal/hibernate-agroal.gradle b/hibernate-agroal/hibernate-agroal.gradle index 29bc4b9df8cb..fb6825d4432a 100644 --- a/hibernate-agroal/hibernate-agroal.gradle +++ b/hibernate-agroal/hibernate-agroal.gradle @@ -16,5 +16,6 @@ dependencies { runtime( libraries.agroal_pool ) testCompile project( ':hibernate-testing' ) - testCompile(libraries.mockito) + testCompile( libraries.mockito ) + testCompile( libraries.mockito_inline ) } From a72102b47d381df90309d7aa2ae008696c66f8a7 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 10 Jul 2018 15:52:54 +0100 Subject: [PATCH 020/772] HHH-12789 Upgrade to Mockito 2.19.0 --- gradle/libraries.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index b71da18125a8..149b913f813f 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -120,8 +120,8 @@ ext { informix: 'com.ibm.informix:jdbc:4.10.7.20160517', jboss_jta: "org.jboss.jbossts:jbossjta:4.16.4.Final", xapool: "com.experlog:xapool:1.5.0", - mockito: 'org.mockito:mockito-core:2.18.0', - mockito_inline: 'org.mockito:mockito-inline:2.18.0', + mockito: 'org.mockito:mockito-core:2.19.0', + mockito_inline: 'org.mockito:mockito-inline:2.19.0', validator: "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}", // EL required by Hibernate Validator at test runtime From 3bba6adac9a6abe5cc023327829dea346c7f8f04 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 11 Jul 2018 18:17:15 +0300 Subject: [PATCH 021/772] HHH-12768 - TimeAndTimestampTest fails with SQL Server and MYSQL --- .../java/org/hibernate/test/type/TimeAndTimestampTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java index 29f513c0ca19..6497d3165108 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java @@ -47,14 +47,14 @@ public void test() { doInHibernate( this::sessionFactory, session -> { Event event = new Event(); event.id = 1L; - event.timeValue = new Time( 12356 ); + event.timeValue = new Time( 1000 ); event.timestampValue = new Timestamp( 45678 ); session.persist( event ); } ); doInHibernate( this::sessionFactory, session -> { Event event = session.find( Event.class, 1L ); - assertEquals(12356, event.timeValue.getTime() % TimeUnit.DAYS.toMillis( 1 )); + assertEquals(1000, event.timeValue.getTime() % TimeUnit.DAYS.toMillis( 1 )); assertEquals(45678, event.timestampValue.getTime() % TimeUnit.DAYS.toMillis( 1 )); } ); } From 65c2aca69f25368713dc4a8b872fa30f111223b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galder=20Zamarren=CC=83o?= Date: Wed, 11 Jul 2018 17:17:05 +0100 Subject: [PATCH 022/772] HHH-12177 Update relocation of Infinispan provider for 5.3 --- hibernate-infinispan/hibernate-infinispan.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hibernate-infinispan/hibernate-infinispan.gradle b/hibernate-infinispan/hibernate-infinispan.gradle index 17060d907b65..122450e2b1d1 100644 --- a/hibernate-infinispan/hibernate-infinispan.gradle +++ b/hibernate-infinispan/hibernate-infinispan.gradle @@ -11,12 +11,12 @@ apply from: rootProject.file( 'gradle/publishing-pom.gradle' ) apply plugin: 'maven-publish' apply plugin: 'maven-publish-auth' -description = '(deprecated - use org.infinispan:infinispan-hibernate-cache instead)' +description = '(deprecated - use org.infinispan:infinispan-hibernate-cache-v53 instead)' ext { relocatedGroupId = 'org.infinispan' - relocatedArtifactId = 'infinispan-hibernate-cache' - relocatedVersion = '9.1.3.Final' + relocatedArtifactId = 'infinispan-hibernate-cache-v53' + relocatedVersion = '9.3.0.Final' } publishing { From 30592b7d7493d2b58d3beaa3d2a2a0f32f5ea08d Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 11 Jul 2018 10:15:03 +0200 Subject: [PATCH 023/772] HHH-12774 Add missing jars to the distribution archives --- release/release.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/release.gradle b/release/release.gradle index 5b71c1d0deda..65de8ea7f786 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -145,7 +145,7 @@ distributions { // todo : this closure is problematic as it does not write into the hibernate-release-$project.version directory // due to http://issues.gradle.org/browse/GRADLE-1450 - [ 'hibernate-c3p0', 'hibernate-proxool' ].each { feature -> + [ 'hibernate-agroal', 'hibernate-c3p0', 'hibernate-ehcache', 'hibernate-hikaricp', 'hibernate-jcache', 'hibernate-proxool', 'hibernate-vibur' ].each { feature -> final String shortName = feature.substring( 'hibernate-'.length() ) // WORKAROUND http://issues.gradle.org/browse/GRADLE-1450 // into('lib/optional/' + shortName) { From a4df91bdb303dbef03010592c884e57e658e3059 Mon Sep 17 00:00:00 2001 From: Diego Lovison Date: Wed, 11 Jul 2018 16:51:22 -0300 Subject: [PATCH 024/772] HHH-12797 Fix cache modes relationships table layout --- .../src/main/asciidoc/userguide/chapters/caching/Caching.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc index efb5d2402137..aae506e715bc 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc @@ -399,7 +399,7 @@ and retrieval (http://docs.oracle.com/javaee/7/api/javax/persistence/CacheRetrie The relationship between Hibernate and JPA cache modes can be seen in the following table: .Cache modes relationships -[cols=",,,",options="header",] +[cols=",,",options="header",] |====================================== |Hibernate | JPA | Description |`CacheMode.NORMAL` |`CacheStoreMode.USE` and `CacheRetrieveMode.USE` | Default. Reads/writes data from/into cache From 6d0509bb34a8915b7fdb74f662a290386d995264 Mon Sep 17 00:00:00 2001 From: Fabio Massimo Ercoli Date: Wed, 11 Jul 2018 15:41:25 +0200 Subject: [PATCH 025/772] HHH-12793 Upgrade Karaf, pax-exam and reenable the OSGi tests --- hibernate-core/hibernate-core.gradle | 4 -- hibernate-osgi/hibernate-osgi.gradle | 42 +++++++------------ .../hibernate/osgi/test/testing-bundles.xml | 1 - 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/hibernate-core/hibernate-core.gradle b/hibernate-core/hibernate-core.gradle index c3dbbd47281e..3025391aee51 100644 --- a/hibernate-core/hibernate-core.gradle +++ b/hibernate-core/hibernate-core.gradle @@ -118,10 +118,6 @@ jar { // TODO: Shouldn't have to explicitly list this, but the plugin // generates it with a [1.0,2) version. "javax.persistence;version=\"${project.jpaVersion.osgiName}\"", - // Temporarily support JTA 1.1 -- Karaf and other frameworks still - // use it. Without this, the plugin generates [1.2,2). - // build.gradle adds javax.transaction for all modules - 'javax.transaction.xa;version="[1.1,2)"', // optionals 'javax.management;resolution:=optional', 'javax.naming.event;resolution:=optional', diff --git a/hibernate-osgi/hibernate-osgi.gradle b/hibernate-osgi/hibernate-osgi.gradle index 1004aa593e49..5c3f1ac0c5f3 100644 --- a/hibernate-osgi/hibernate-osgi.gradle +++ b/hibernate-osgi/hibernate-osgi.gradle @@ -10,32 +10,14 @@ apply plugin: 'com.github.lburgazzoli.karaf' description = 'Support for running Hibernate O/RM in OSGi environments' - ext { - // MUST use 4.3.1! 4.3.0 was compiled with "-target jsr14". - // http://blog.osgi.org/2012/10/43-companion-code-for-java-7.html - osgiCoreVersion = '4.3.1' - karafVersion = '3.0.3' - paxExamVersion = '4.4.0' - paxUrlVersion = '2.4.1' + osgiCoreVersion = '6.0.0' + osgiCompediumVersion = '5.0.0' + karafVersion = '4.1.0' + paxExamVersion = '4.12.0' } - -// tests were failing for me, no idea why yet: -// -// java.lang.RuntimeException: Cannot get the remote bundle context -// at org.ops4j.pax.exam.rbc.client.intern.RemoteBundleContextClientImpl.getRemoteBundleContext(RemoteBundleContextClientImpl.java:255) -// at org.ops4j.pax.exam.rbc.client.intern.RemoteBundleContextClientImpl.waitForState(RemoteBundleContextClientImpl.java:211) -// ... -// Caused by: java.rmi.ConnectException: Connection refused to host: 198.105.254.130; nested exception is: -// java.net.ConnectException: Connection timed out (Connection timed out) -// ... -// Caused by: java.net.ConnectException: Connection timed out (Connection timed out) -// ... -// -// -// todo : fix these tests and re-enable -test.enabled = false +test.enabled = true if ( JavaVersion.current().isJava9Compatible() ) { logger.warn( '[WARN] Skipping all tests for hibernate-osgi due to Karaf issues with JDK 9' ) @@ -69,13 +51,13 @@ configurations { } dependencies { - compile( project( ':hibernate-core' ) ) + compile( project( ':hibernate-core' ) ) { + exclude module: 'javax.activation-api' + } testCompile( project( ':hibernate-envers' ) ) - // MUST use 4.3.1! 4.3.0 was compiled with "-target jsr14". - // http://blog.osgi.org/2012/10/43-companion-code-for-java-7.html compile "org.osgi:org.osgi.core:${osgiCoreVersion}" - compile "org.osgi:org.osgi.compendium:${osgiCoreVersion}" + compile "org.osgi:org.osgi.compendium:${osgiCompediumVersion}" compile "net.bytebuddy:byte-buddy:${byteBuddyVersion}" // Needed by JBoss JTA @@ -134,11 +116,15 @@ task generateVersionFile { karaf { features { - xsdVersion = '1.2.0' + xsdVersion = '1.4.0' feature { name = 'hibernate-orm' description = 'Combines all Hibernate core dependencies and required modules into a single feature' includeProject = true + feature ('wrap') { + prerequisite = true + } + feature 'transaction-api' } // NOTE : would like to include spatial as well, but we need to wait for // it to not define dependency on postgresql driver diff --git a/hibernate-osgi/src/test/resources/org/hibernate/osgi/test/testing-bundles.xml b/hibernate-osgi/src/test/resources/org/hibernate/osgi/test/testing-bundles.xml index 724341033832..8b93f9997097 100644 --- a/hibernate-osgi/src/test/resources/org/hibernate/osgi/test/testing-bundles.xml +++ b/hibernate-osgi/src/test/resources/org/hibernate/osgi/test/testing-bundles.xml @@ -7,6 +7,5 @@ mvn:com.h2database/h2/1.3.170 - mvn:org.ops4j.pax.url/pax-url-aether/2.3.0 From be47ba3393bee1a7c8cd70834a15357cb020507c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Jul 2018 12:09:35 +0200 Subject: [PATCH 026/772] HHH-12793 Add a comment about why we ignore javax.activation for now --- hibernate-osgi/hibernate-osgi.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hibernate-osgi/hibernate-osgi.gradle b/hibernate-osgi/hibernate-osgi.gradle index 5c3f1ac0c5f3..ba350a983359 100644 --- a/hibernate-osgi/hibernate-osgi.gradle +++ b/hibernate-osgi/hibernate-osgi.gradle @@ -52,6 +52,9 @@ configurations { dependencies { compile( project( ':hibernate-core' ) ) { + // having javax.activation-api as a dependency requires us to have com.sun.activation:javax.activation + // this dependency wasn't there in the 5.2.x bundles so ignoring it for now + // we might reintroduce it at some point if users complain about it exclude module: 'javax.activation-api' } testCompile( project( ':hibernate-envers' ) ) From 5f6c122a3ec8c86ca0430127b7b8f889e16acd74 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Jul 2018 12:10:45 +0200 Subject: [PATCH 027/772] HHH-12793 Fix preexisting indentation issues --- hibernate-osgi/hibernate-osgi.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hibernate-osgi/hibernate-osgi.gradle b/hibernate-osgi/hibernate-osgi.gradle index ba350a983359..753db175ee6a 100644 --- a/hibernate-osgi/hibernate-osgi.gradle +++ b/hibernate-osgi/hibernate-osgi.gradle @@ -119,16 +119,16 @@ task generateVersionFile { karaf { features { - xsdVersion = '1.4.0' - feature { - name = 'hibernate-orm' - description = 'Combines all Hibernate core dependencies and required modules into a single feature' + xsdVersion = '1.4.0' + feature { + name = 'hibernate-orm' + description = 'Combines all Hibernate core dependencies and required modules into a single feature' includeProject = true feature ('wrap') { prerequisite = true } feature 'transaction-api' - } + } // NOTE : would like to include spatial as well, but we need to wait for // it to not define dependency on postgresql driver feature { From 1192eed56059ea91071c261df506e6241eb37fc5 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 11 Jul 2018 18:22:31 +0300 Subject: [PATCH 028/772] HHH-12787 - SessionJdbcBatchTest hangs with DB2 --- .../test/agroal/AgroalSkipAutoCommitTest.java | 3 + ...stractCriteriaLiteralHandlingModeTest.java | 34 ++--- .../nulliteral/CriteriaLiteralsTest.java | 48 +++---- ...eDiscardPersistenceContextOnCloseTest.java | 3 + ...eDiscardPersistenceContextOnCloseTest.java | 5 + .../EntityGraphWithFetchAnnotationTest.java | 32 ++--- ...tementIsClosedAfterALockExceptionTest.java | 3 + .../jpa/test/query/NamedQueryCommentTest.java | 118 +++++------------- .../query/InClauseParameterPaddingTest.java | 31 ++--- .../MaxInExpressionParameterPaddingTest.java | 29 ++--- ...dableWithOneToMany_HHH_11302_xml_Test.java | 11 -- ...ySQLDropConstraintThrowsExceptionTest.java | 3 + .../test/criteria/CriteriaQueryTest.java | 5 +- .../insertordering/ElementCollectionTest.java | 15 +-- ...rtOrderingWithBidirectionalManyToMany.java | 3 + ...deringWithBidirectionalMapsIdOneToOne.java | 3 + ...ertOrderingWithBidirectionalOneToMany.java | 3 + ...sertOrderingWithBidirectionalOneToOne.java | 3 + ...ertOrderingWithJoinedTableInheritance.java | 3 + ...gWithJoinedTableMultiLevelInheritance.java | 3 + .../InsertOrderingWithManyToOne.java | 3 + .../InsertOrderingWithMultipleManyToOne.java | 3 + ...ertOrderingWithSingleTableInheritance.java | 3 + ...tOrderingWithTablePerClassInheritance.java | 3 + ...ertOrderingWithUnidirectionalOneToOne.java | 3 + .../jdbc/internal/SessionJdbcBatchTest.java | 5 + .../test/queryhint/QueryHintHANATest.java | 70 ++++------- .../test/queryhint/QueryHintTest.java | 73 ++++------- .../test/querytimeout/QueryTimeOutTest.java | 3 + .../AbstractSkipAutoCommitTest.java | 3 + .../timestamp/JdbcTimeCustomTimeZoneTest.java | 3 + .../JdbcTimeDefaultTimeZoneTest.java | 4 + ...mestampCustomSessionLevelTimeZoneTest.java | 3 + .../JdbcTimestampCustomTimeZoneTest.java | 3 + .../JdbcTimestampDefaultTimeZoneTest.java | 3 + .../hikaricp/HikariCPSkipAutoCommitTest.java | 3 + .../org/hibernate/testing/DialectChecks.java | 9 ++ .../testing/jdbc/SQLStatementInterceptor.java | 26 +++- 38 files changed, 245 insertions(+), 336 deletions(-) diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java index e4025cb29855..d32cc2be75f1 100644 --- a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java @@ -9,6 +9,8 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.test.agroal.util.PreparedStatementSpyConnectionProvider; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -28,6 +30,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class AgroalSkipAutoCommitTest extends BaseCoreFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java index 2df54e6482c3..1b5dab054677 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java @@ -20,41 +20,27 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Before; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.hibernate.testing.transaction.TransactionUtil.setJdbcTimeout; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * @author Vlad Mihalcea */ public abstract class AbstractCriteriaLiteralHandlingModeTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override - protected Map getConfig() { - Map config = super.getConfig(); - config.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - return config; - } - - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildEntityManagerFactory(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); } @Override @@ -101,12 +87,12 @@ public void testLiteralHandlingMode() throws Exception { entity.get( "name" ) ); - connectionProvider.clear(); - List tuples = entityManager.createQuery( query ) - .getResultList(); + sqlStatementInterceptor.clear(); + + List tuples = entityManager.createQuery( query ).getResultList(); assertEquals( 1, tuples.size() ); - assertNotNull( connectionProvider.getPreparedStatement( expectedSQL() ) ); + sqlStatementInterceptor.assertExecuted( expectedSQL() ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java index 8d4b0a06ce6a..b38f9ab95efb 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java @@ -29,7 +29,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; -import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.junit.Before; import org.junit.Test; @@ -44,29 +44,12 @@ */ @RequiresDialect(H2Dialect.class) public class CriteriaLiteralsTest extends BaseEntityManagerFunctionalTestCase { - - private PreparedStatementSpyConnectionProvider connectionProvider; - @Override - protected Map getConfig() { - Map config = super.getConfig(); - config.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - return config; - } - - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildEntityManagerFactory(); - } + private SQLStatementInterceptor sqlStatementInterceptor; @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); } @Override @@ -128,16 +111,15 @@ public void testLiteralsInWhereClause() throws Exception { entity.get( "name" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); List tuples = entityManager.createQuery( query ) .getResultList(); assertEquals( 1, - connectionProvider.getPreparedStatements().size() + sqlStatementInterceptor.getSqlQueries().size() ); - assertNotNull( connectionProvider.getPreparedStatement( - "select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.name=?" ) ); + sqlStatementInterceptor.assertExecuted("select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.name=?"); assertTrue( tuples.isEmpty() ); } ); } @@ -178,12 +160,12 @@ private void testNumericLiterals(EntityManager entityManager, String expectedSQL entity.get( "name" ) ); - connectionProvider.clear(); - List tuples = entityManager.createQuery( query ) - .getResultList(); + sqlStatementInterceptor.clear(); + + List tuples = entityManager.createQuery( query ).getResultList(); assertEquals( 1, tuples.size() ); - assertNotNull( connectionProvider.getPreparedStatement(expectedSQL) ); + sqlStatementInterceptor.assertExecuted( expectedSQL ); } @Test @@ -200,15 +182,13 @@ public void testCriteriaParameters() throws Exception { ), cb.equal( authors.index(), 0 ) ) .select( authors.get( "name" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); entityManager.createQuery( query ).getResultList(); assertEquals( 1, - connectionProvider.getPreparedStatements().size() - ); - assertNotNull( connectionProvider.getPreparedStatement( - "select authors1_.name as col_0_0_ from Book criteriali0_ inner join Author authors1_ on criteriali0_.id=authors1_.book_id where criteriali0_.name=? and authors1_.index_id=0" ) + sqlStatementInterceptor.getSqlQueries().size() ); + sqlStatementInterceptor.assertExecuted( "select authors1_.name as col_0_0_ from Book criteriali0_ inner join Author authors1_ on criteriali0_.id=authors1_.book_id where criteriali0_.name=? and authors1_.index_id=0" ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java index 6ef95aa974e6..8ed46a1a832d 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java @@ -14,6 +14,8 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.Wallet; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -24,6 +26,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class DisableDiscardPersistenceContextOnCloseTest extends BaseEntityManagerFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java index 2fbe9cb98d9a..71fc4fda1722 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java @@ -9,10 +9,14 @@ import java.util.Map; import javax.persistence.EntityManager; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.Wallet; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -23,6 +27,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class EnableDiscardPersistenceContextOnCloseTest extends BaseEntityManagerFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java index 178fbf523426..cfcc2176b4b4 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java @@ -23,6 +23,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -36,28 +37,11 @@ public class EntityGraphWithFetchAnnotationTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override - protected Map getConfig() { - Map config = super.getConfig(); - config.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - return config; - } - - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildEntityManagerFactory(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); } @Override @@ -79,7 +63,7 @@ public void testWithoutEntityGraph() { .createQuery( Order.class ); criteriaQuery.from( Order.class ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); entityManager .createQuery( criteriaQuery ) @@ -87,7 +71,7 @@ public void testWithoutEntityGraph() { .setMaxResults( 20 ) .getResultList(); - assertFalse( connectionProvider.getPreparedSQLStatements().get( 0 ).toLowerCase().contains( "left outer join" ) ); + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().contains( "left outer join" ) ); } ); } @@ -104,7 +88,7 @@ public void testWithEntityGraph() { EntityGraph entityGraph = entityManager.createEntityGraph( Order.class ); entityGraph.addAttributeNodes( "products" ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); entityManager .createQuery( criteriaQuery ) @@ -113,7 +97,7 @@ public void testWithEntityGraph() { .setHint( QueryHints.HINT_FETCHGRAPH, entityGraph ) .getResultList(); - assertTrue( connectionProvider.getPreparedSQLStatements().get( 0 ).toLowerCase().contains( "left outer join" ) ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().contains( "left outer join" ) ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java index c571b5c09f6f..304839017aea 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java @@ -15,6 +15,8 @@ import org.hibernate.Session; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.transaction.TransactionUtil; @@ -30,6 +32,7 @@ /** * @author Andrea Boriero */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class StatementIsClosedAfterALockExceptionTest extends BaseEntityManagerFunctionalTestCase { private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider( false, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java index 73d29fa163a4..c0e07bcea0a6 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java @@ -26,6 +26,7 @@ import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.After; import org.junit.Before; @@ -41,32 +42,12 @@ @TestForIssue(jiraKey = "HHH-11640") public class NamedQueryCommentTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override - protected Map getConfig() { - Map config = super.getConfig(); - config.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - config.put( - AvailableSettings.USE_SQL_COMMENTS, - Boolean.TRUE.toString() - ); - return config; - } - - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildEntityManagerFactory(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); } private static final String[] GAME_TITLES = { "Halo", "Grand Theft Auto", "NetHack" }; @@ -76,11 +57,6 @@ public Class[] getAnnotatedClasses() { return new Class[] { Game.class }; } - @Override - protected void addConfigOptions(Map options) { - options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); - } - @Before public void setUp() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -101,22 +77,17 @@ public void tearDown() { @Test public void testSelectNamedQueryWithSqlComment() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); TypedQuery query = entityManager.createNamedQuery( "SelectNamedQuery", Game.class ); query.setParameter( "title", GAME_TITLES[0] ); List list = query.getResultList(); assertEquals( 1, list.size() ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( - "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ where namedquery0_.title=?" - ) + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ where namedquery0_.title=?" ); } ); } @@ -124,22 +95,18 @@ public void testSelectNamedQueryWithSqlComment() { @Test public void testSelectNamedNativeQueryWithSqlComment() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); TypedQuery query = entityManager.createNamedQuery( "SelectNamedNativeQuery", Game.class ); query.setParameter( "title", GAME_TITLES[0] ); List list = query.getResultList(); assertEquals( 1, list.size() ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* + INDEX (game idx_game_title) */ select * from game g where title = ?" - ) + ); } ); } @@ -147,7 +114,7 @@ public void testSelectNamedNativeQueryWithSqlComment() { @Test public void testUpdateNamedQueryWithSqlComment() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -155,15 +122,10 @@ public void testUpdateNamedQueryWithSqlComment() { int updateCount = query.executeUpdate(); assertEquals( 1, updateCount ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* COMMENT_INDEX_game_title */ update game set title = ? where id = ?" - ) ); } ); } @@ -171,7 +133,7 @@ public void testUpdateNamedQueryWithSqlComment() { @Test public void testUpdateNamedNativeQueryWithSqlComment() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -179,15 +141,10 @@ public void testUpdateNamedNativeQueryWithSqlComment() { int updateCount = query.executeUpdate(); assertEquals( 1, updateCount ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* COMMENT_INDEX_game_title */ update game set title = ? where id = ?" - ) ); } ); } @@ -196,7 +153,7 @@ public void testUpdateNamedNativeQueryWithSqlComment() { @RequiresDialect(Oracle8iDialect.class) public void testUpdateNamedNativeQueryWithQueryHintUsingOracle() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -205,15 +162,10 @@ public void testUpdateNamedNativeQueryWithQueryHintUsingOracle() { int updateCount = query.executeUpdate(); assertEquals( 1, updateCount ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* COMMENT_INDEX_game_title */ update /*+ INDEX (game idx_game_id) */ game set title = ? where id = ?" - ) ); } ); } @@ -222,7 +174,7 @@ public void testUpdateNamedNativeQueryWithQueryHintUsingOracle() { @RequiresDialect(H2Dialect.class) public void testUpdateNamedNativeQueryWithQueryHintUsingIndex() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -231,15 +183,10 @@ public void testUpdateNamedNativeQueryWithQueryHintUsingIndex() { int updateCount = query.executeUpdate(); assertEquals( 1, updateCount ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* COMMENT_INDEX_game_title */ update game set title = ? where id = ?" - ) ); } ); } @@ -249,7 +196,7 @@ public void testUpdateNamedNativeQueryWithQueryHintUsingIndex() { @RequiresDialect(H2Dialect.class) public void testSelectNamedNativeQueryWithQueryHintUsingIndex() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "SelectNamedQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -257,16 +204,11 @@ public void testSelectNamedNativeQueryWithQueryHintUsingIndex() { List list = query.getResultList(); assertEquals( 1, list.size() ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( - "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ USE INDEX (idx_game_id) where namedquery0_.title=?" - ) - ); + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ USE INDEX (idx_game_id) where namedquery0_.title=?" ) + ; } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java b/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java index f2bc0a869fb2..77860ab63fdc 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java @@ -15,12 +15,11 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** @@ -29,18 +28,13 @@ @TestForIssue( jiraKey = "HHH-12469" ) public class InClauseParameterPaddingTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider(); - super.buildEntityManagerFactory(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); + options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); } @Override @@ -50,15 +44,6 @@ public Class[] getAnnotatedClasses() { }; } - @Override - protected void addConfigOptions(Map options) { - options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); - options.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - } - @Override protected void afterEntityManagerFactoryBuilt() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -87,7 +72,7 @@ public void testInClauseParameterPadding() { } private void validateInClauseParameterPadding(String expectedInClause, Integer... ids) { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); doInJPA( this::entityManagerFactory, entityManager -> { return entityManager.createQuery( @@ -98,7 +83,7 @@ private void validateInClauseParameterPadding(String expectedInClause, Integer.. .getResultList(); } ); - assertTrue(connectionProvider.getPreparedSQLStatements().get( 0 ).endsWith( expectedInClause )); + assertTrue(sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause )); } @Entity(name = "Person") diff --git a/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java b/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java index b60e60286037..92fddb131521 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java @@ -19,6 +19,7 @@ import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -32,20 +33,15 @@ @RequiresDialect(H2Dialect.class) public class MaxInExpressionParameterPaddingTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; - public static final int MAX_COUNT = 15; - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider(); - super.buildEntityManagerFactory(); - } + private SQLStatementInterceptor sqlStatementInterceptor; @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); + options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); } @Override @@ -55,15 +51,6 @@ public Class[] getAnnotatedClasses() { }; } - @Override - protected void addConfigOptions(Map options) { - options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); - options.put( - AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - } - @Override protected Dialect getDialect() { return new MaxCountInExpressionH2Dialect(); @@ -84,7 +71,7 @@ protected void afterEntityManagerFactoryBuilt() { @Test public void testInClauseParameterPadding() { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); doInJPA( this::entityManagerFactory, entityManager -> { return entityManager.createQuery( @@ -102,7 +89,7 @@ public void testInClauseParameterPadding() { } expectedInClause.append( ")" ); - assertTrue(connectionProvider.getPreparedSQLStatements().get( 0 ).endsWith( expectedInClause.toString() )); + assertTrue(sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() )); } @Entity(name = "Person") diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java index 1e47fd62eef7..ee345f544215 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java @@ -26,8 +26,6 @@ public class EmbeddableWithOneToMany_HHH_11302_xml_Test extends BaseEntityManagerFunctionalTestCase { - PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - @Override public String[] getEjb3DD() { return new String[] { @@ -45,15 +43,6 @@ public void buildEntityManagerFactory() { "@OneToMany, @ManyToMany or @ElementCollection cannot be used inside an @Embeddable that is also contained within an @ElementCollection" ) ); } - finally { - connectionProvider.stop(); - } - } - - protected Map buildSettings() { - Map settings = super.buildSettings(); - settings.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); - return settings; } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java index 6745f5d33383..34f4911f54b4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java @@ -25,7 +25,9 @@ import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -40,6 +42,7 @@ */ @TestForIssue(jiraKey = "HHH-11236") @RequiresDialect(MySQL5Dialect.class) +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class MySQLDropConstraintThrowsExceptionTest extends BaseUnitTestCase { @Before diff --git a/hibernate-core/src/test/java/org/hibernate/test/criteria/CriteriaQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/criteria/CriteriaQueryTest.java index 2dc8188fc3de..46a980d4a8ea 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/criteria/CriteriaQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/criteria/CriteriaQueryTest.java @@ -68,7 +68,10 @@ * @author Gavin King * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ -@RequiresDialectFeature(DialectChecks.SupportsSequences.class) +@RequiresDialectFeature({ + DialectChecks.SupportsSequences.class, + DialectChecks.SupportsJdbcDriverProxying.class +}) public class CriteriaQueryTest extends BaseNonConfigCoreFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( true, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/ElementCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/ElementCollectionTest.java index 6f04248e9ba9..1f1cdd755b44 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/ElementCollectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/ElementCollectionTest.java @@ -25,7 +25,6 @@ import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @@ -41,17 +40,11 @@ protected Class[] getAnnotatedClasses() { return new Class[] {Task.class}; } - private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - @Override protected void addSettings(Map settings) { settings.put( Environment.ORDER_INSERTS, "true" ); settings.put( Environment.STATEMENT_BATCH_SIZE, "10" ); - settings.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - } + } @Test public void testBatchOrdering() { @@ -66,12 +59,6 @@ public void testBatchOrdering() { } ); } - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); - } - @Entity @Table(name = "TASK") public static class Task { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalManyToMany.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalManyToMany.java index 274fc51685e4..f46e17cb738c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalManyToMany.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalManyToMany.java @@ -22,6 +22,8 @@ import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -36,6 +38,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithBidirectionalManyToMany extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalMapsIdOneToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalMapsIdOneToOne.java index aac1176668ad..bcdc7adbad85 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalMapsIdOneToOne.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalMapsIdOneToOne.java @@ -21,6 +21,8 @@ import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -34,6 +36,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithBidirectionalMapsIdOneToOne extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToMany.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToMany.java index 40007c377c1f..d29a306b7652 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToMany.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToMany.java @@ -23,6 +23,8 @@ import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -36,6 +38,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithBidirectionalOneToMany extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToOne.java index 4d298d035280..099dcdce9b58 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToOne.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToOne.java @@ -8,6 +8,8 @@ import org.hibernate.cfg.Environment; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; @@ -25,6 +27,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithBidirectionalOneToOne extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithJoinedTableInheritance.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithJoinedTableInheritance.java index f192a47956a6..7a4092ccc340 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithJoinedTableInheritance.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithJoinedTableInheritance.java @@ -30,6 +30,8 @@ import org.hibernate.annotations.BatchSize; import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -42,6 +44,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithJoinedTableInheritance extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithJoinedTableMultiLevelInheritance.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithJoinedTableMultiLevelInheritance.java index 3d2be218de23..ab4fb1b94c60 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithJoinedTableMultiLevelInheritance.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithJoinedTableMultiLevelInheritance.java @@ -29,6 +29,8 @@ import org.hibernate.annotations.BatchSize; import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -41,6 +43,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithJoinedTableMultiLevelInheritance extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithManyToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithManyToOne.java index e4481fe79a24..8773f270f5e6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithManyToOne.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithManyToOne.java @@ -23,6 +23,8 @@ import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -36,6 +38,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithManyToOne extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java index 8ab6a8c6398b..8b7b29c575d4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java @@ -23,6 +23,8 @@ import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -37,6 +39,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-11996") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithMultipleManyToOne extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSingleTableInheritance.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSingleTableInheritance.java index d44f21e3f63e..b0b84c92ef8c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSingleTableInheritance.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSingleTableInheritance.java @@ -30,6 +30,8 @@ import org.hibernate.annotations.BatchSize; import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -42,6 +44,7 @@ * @author Steve Ebersole */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithSingleTableInheritance extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithTablePerClassInheritance.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithTablePerClassInheritance.java index d0e8bdcd8eac..a3e48b878326 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithTablePerClassInheritance.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithTablePerClassInheritance.java @@ -30,6 +30,8 @@ import org.hibernate.annotations.BatchSize; import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -42,6 +44,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithTablePerClassInheritance extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithUnidirectionalOneToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithUnidirectionalOneToOne.java index 55f0619b5fcf..95b4884d039f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithUnidirectionalOneToOne.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithUnidirectionalOneToOne.java @@ -20,6 +20,8 @@ import org.hibernate.cfg.Environment; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -33,6 +35,7 @@ * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-9864") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class InsertOrderingWithUnidirectionalOneToOne extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/SessionJdbcBatchTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/SessionJdbcBatchTest.java index 1c040b04b2c0..0e4e917d9821 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/SessionJdbcBatchTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/SessionJdbcBatchTest.java @@ -15,7 +15,11 @@ import org.hibernate.Session; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -27,6 +31,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class SessionJdbcBatchTest extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/queryhint/QueryHintHANATest.java b/hibernate-core/src/test/java/org/hibernate/test/queryhint/QueryHintHANATest.java index fb8070fef12c..ca46fd7ac221 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/queryhint/QueryHintHANATest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/queryhint/QueryHintHANATest.java @@ -26,9 +26,9 @@ import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.Query; -import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; @@ -38,26 +38,12 @@ @RequiresDialect(AbstractHANADialect.class) public class QueryHintHANATest extends BaseNonConfigCoreFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override protected void addSettings(Map settings) { settings.put( AvailableSettings.USE_SQL_COMMENTS, "true" ); - settings.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider ); - } - - @Override - protected void buildResources() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildResources(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + sqlStatementInterceptor = new SQLStatementInterceptor( settings ); } @Override @@ -84,7 +70,7 @@ protected void afterSessionFactoryBuilt(SessionFactoryImplementor sessionFactory @Test public void testQueryHint() { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); doInHibernate( this::sessionFactory, s -> { Query query = s.createQuery( "FROM QueryHintHANATest$Employee e WHERE e.department.name = :departmentName", Employee.class ) @@ -95,11 +81,9 @@ public void testQueryHint() { assertEquals( results.size(), 2 ); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() ); - assertThat( connectionProvider.getPreparedSQLStatements().get( 0 ), containsString( " with hint (NO_CS_JOIN)" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ), containsString( " with hint (NO_CS_JOIN)" ) ); + sqlStatementInterceptor.clear(); // test multiple hints doInHibernate( this::sessionFactory, s -> { @@ -112,11 +96,10 @@ public void testQueryHint() { assertEquals( results.size(), 2 ); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() ); - assertThat( connectionProvider.getPreparedSQLStatements().get( 0 ), containsString( " with hint (NO_CS_JOIN,OPTIMIZE_METAMODEL)" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + + assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ), containsString( " with hint (NO_CS_JOIN,OPTIMIZE_METAMODEL)" ) ); + sqlStatementInterceptor.clear(); // ensure the insertion logic can handle a comment appended to the front doInHibernate( this::sessionFactory, s -> { @@ -129,11 +112,10 @@ public void testQueryHint() { assertEquals( results.size(), 2 ); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() ); - assertThat( connectionProvider.getPreparedSQLStatements().get( 0 ), containsString( " with hint (NO_CS_JOIN)" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + + assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ), containsString( " with hint (NO_CS_JOIN)" ) ); + sqlStatementInterceptor.clear(); // test Criteria doInHibernate( this::sessionFactory, s -> { @@ -145,17 +127,16 @@ public void testQueryHint() { assertEquals( results.size(), 2 ); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() ); - assertThat( connectionProvider.getPreparedSQLStatements().get( 0 ), containsString( " with hint (NO_CS_JOIN)" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + + assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ), containsString( " with hint (NO_CS_JOIN)" ) ); + sqlStatementInterceptor.clear(); } @Test @TestForIssue(jiraKey = "HHH-12362") public void testQueryHintAndComment() { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); doInHibernate( this::sessionFactory, s -> { Query query = s.createQuery( "FROM QueryHintHANATest$Employee e WHERE e.department.name = :departmentName", Employee.class ) @@ -167,12 +148,11 @@ public void testQueryHintAndComment() { assertEquals( results.size(), 2 ); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() ); - assertThat( connectionProvider.getPreparedSQLStatements().get( 0 ), containsString( " with hint (NO_CS_JOIN)" ) ); - assertThat( connectionProvider.getPreparedSQLStatements().get( 0 ), containsString( "/* My_Query */ select" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + + assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ), containsString( " with hint (NO_CS_JOIN)" ) ); + assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ), containsString( "/* My_Query */ select" ) ); + sqlStatementInterceptor.clear(); } @Entity diff --git a/hibernate-core/src/test/java/org/hibernate/test/queryhint/QueryHintTest.java b/hibernate-core/src/test/java/org/hibernate/test/queryhint/QueryHintTest.java index c4ab0767b712..504d3827e143 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/queryhint/QueryHintTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/queryhint/QueryHintTest.java @@ -23,8 +23,8 @@ import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @@ -37,27 +37,12 @@ @RequiresDialect( Oracle8iDialect.class ) public class QueryHintTest extends BaseNonConfigCoreFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override protected void addSettings(Map settings) { settings.put( AvailableSettings.USE_SQL_COMMENTS, "true" ); - settings.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - } - - @Override - protected void buildResources() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildResources(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + sqlStatementInterceptor = new SQLStatementInterceptor( settings ); } @Override @@ -84,7 +69,7 @@ protected void afterSessionFactoryBuilt(SessionFactoryImplementor sessionFactory @Test public void testQueryHint() { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); // test Query w/ a simple Oracle optimizer hint doInHibernate( this::sessionFactory, s -> { @@ -96,12 +81,9 @@ public void testQueryHint() { assertEquals(results.size(), 2); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); - assertTrue( connectionProvider.getPreparedSQLStatements().get( 0 ).contains( "select /*+ ALL_ROWS */" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( "select /*+ ALL_ROWS */" ) ); + sqlStatementInterceptor.clear(); // test multiple hints doInHibernate( this::sessionFactory, s -> { @@ -114,12 +96,9 @@ public void testQueryHint() { assertEquals(results.size(), 2); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); - assertTrue( connectionProvider.getPreparedSQLStatements().get( 0 ).contains( "select /*+ ALL_ROWS, USE_CONCAT */" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( "select /*+ ALL_ROWS, USE_CONCAT */" ) ); + sqlStatementInterceptor.clear(); // ensure the insertion logic can handle a comment appended to the front doInHibernate( this::sessionFactory, s -> { @@ -132,12 +111,10 @@ public void testQueryHint() { assertEquals(results.size(), 2); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); - assertTrue( connectionProvider.getPreparedSQLStatements().get( 0 ).contains( "select /*+ ALL_ROWS */" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( "select /*+ ALL_ROWS */" ) ); + sqlStatementInterceptor.clear(); // test Criteria doInHibernate( this::sessionFactory, s -> { @@ -149,18 +126,16 @@ public void testQueryHint() { assertEquals(results.size(), 2); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); - assertTrue( connectionProvider.getPreparedSQLStatements().get( 0 ).contains( "select /*+ ALL_ROWS */" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( "select /*+ ALL_ROWS */" ) ); + sqlStatementInterceptor.clear(); } @Test @TestForIssue( jiraKey = "HHH-12362" ) public void testQueryHintAndComment() { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); doInHibernate( this::sessionFactory, s -> { Query query = s.createQuery( "FROM QueryHintTest$Employee e WHERE e.department.name = :departmentName" ) @@ -172,12 +147,10 @@ public void testQueryHintAndComment() { assertEquals(results.size(), 2); } ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); - assertTrue( connectionProvider.getPreparedSQLStatements().get( 0 ).contains( "/* My_Query */ select /*+ ALL_ROWS */" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( "/* My_Query */ select /*+ ALL_ROWS */" ) ); + sqlStatementInterceptor.clear(); } @Entity diff --git a/hibernate-core/src/test/java/org/hibernate/test/querytimeout/QueryTimeOutTest.java b/hibernate-core/src/test/java/org/hibernate/test/querytimeout/QueryTimeOutTest.java index b33af5bc3861..2ef1433cca68 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/querytimeout/QueryTimeOutTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/querytimeout/QueryTimeOutTest.java @@ -19,6 +19,8 @@ import org.hibernate.query.NativeQuery; import org.hibernate.query.Query; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -33,6 +35,7 @@ /** * @author Gail Badner */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class QueryTimeOutTest extends BaseNonConfigCoreFunctionalTestCase { private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider( true, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java index 55379eb98054..f7371903ffa9 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java @@ -17,6 +17,8 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -30,6 +32,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public abstract class AbstractSkipAutoCommitTest extends BaseEntityManagerFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = diff --git a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimeCustomTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimeCustomTimeZoneTest.java index bf18cb4db706..6baae104f61e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimeCustomTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimeCustomTimeZoneTest.java @@ -23,6 +23,8 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.MySQL5Dialect; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -42,6 +44,7 @@ * @author Vlad Mihalcea */ @SkipForDialect(MySQL5Dialect.class) +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class JdbcTimeCustomTimeZoneTest extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimeDefaultTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimeDefaultTimeZoneTest.java index 9fe9d9e26043..666e3517cab8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimeDefaultTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimeDefaultTimeZoneTest.java @@ -16,6 +16,8 @@ import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -31,6 +33,8 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) + public class JdbcTimeDefaultTimeZoneTest extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampCustomSessionLevelTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampCustomSessionLevelTimeZoneTest.java index a2b82128c38a..95a65a8f9c18 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampCustomSessionLevelTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampCustomSessionLevelTimeZoneTest.java @@ -20,6 +20,8 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.MySQL5Dialect; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -39,6 +41,7 @@ * @author Vlad Mihalcea */ @SkipForDialect(MySQL5Dialect.class) +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class JdbcTimestampCustomSessionLevelTimeZoneTest extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampCustomTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampCustomTimeZoneTest.java index d4e7355aae65..8db15237d9a7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampCustomTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampCustomTimeZoneTest.java @@ -20,6 +20,8 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.MySQL5Dialect; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -39,6 +41,7 @@ * @author Vlad Mihalcea */ @SkipForDialect(MySQL5Dialect.class) +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class JdbcTimestampCustomTimeZoneTest extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampDefaultTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampDefaultTimeZoneTest.java index 25bab7da4364..9cfb20911bb6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampDefaultTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/timestamp/JdbcTimestampDefaultTimeZoneTest.java @@ -15,6 +15,8 @@ import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -30,6 +32,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class JdbcTimestampDefaultTimeZoneTest extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java index be3318893141..491ceb6088f8 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java @@ -15,6 +15,8 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.test.util.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -29,6 +31,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class HikariCPSkipAutoCommitTest extends BaseCoreFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java index 0c7ba1fa2ecc..b5b747663004 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java @@ -6,6 +6,7 @@ */ package org.hibernate.testing; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; /** @@ -247,4 +248,12 @@ public boolean isMatch(Dialect dialect) { return dialect.forceLobAsLastValue(); } } + + public static class SupportsJdbcDriverProxying implements DialectCheck { + public boolean isMatch(Dialect dialect) { + return !( + dialect instanceof DB2Dialect + ); + } + } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInterceptor.java b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInterceptor.java index bc37c11b9739..43b114f01446 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInterceptor.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInterceptor.java @@ -7,16 +7,21 @@ package org.hibernate.testing.jdbc; import java.util.LinkedList; +import java.util.Map; import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.resource.jdbc.spi.StatementInspector; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * @author Vlad Mihalcea */ public class SQLStatementInterceptor { - private final LinkedList sqlQueries = new LinkedList<>( ); + private final LinkedList sqlQueries = new LinkedList<>(); public SQLStatementInterceptor(SessionFactoryBuilder sessionFactoryBuilder) { sessionFactoryBuilder.applyStatementInspector( (StatementInspector) sql -> { @@ -25,7 +30,26 @@ public SQLStatementInterceptor(SessionFactoryBuilder sessionFactoryBuilder) { } ); } + public SQLStatementInterceptor(Map settings) { + settings.put( AvailableSettings.STATEMENT_INSPECTOR, (StatementInspector) sql -> { + sqlQueries.add( sql ); + return sql; + } ); + } + public LinkedList getSqlQueries() { return sqlQueries; } + + public void clear() { + sqlQueries.clear(); + } + + public void assertExecuted(String expected) { + assertTrue(sqlQueries.contains( expected )); + } + + public void assertExecutedCount(int expected) { + assertEquals(expected, sqlQueries.size()); + } } From b09fbb431d1626fd4dc29a81b19d95740447d429 Mon Sep 17 00:00:00 2001 From: Jonathan Bregler Date: Thu, 12 Jul 2018 12:32:16 +0200 Subject: [PATCH 029/772] HHH-12798: Fix for nested spatial functions on SAP HANA --- .../dialect/hana/HANASpatialFunction.java | 84 ++++++++++--------- .../hana/TestHANASpatialFunctions.java | 25 ++++++ .../hana/HANAExpectationsFactory.java | 55 +++++++++--- 3 files changed, 113 insertions(+), 51 deletions(-) diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialFunction.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialFunction.java index 5fbab410fb27..5d6911deae82 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialFunction.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialFunction.java @@ -57,50 +57,44 @@ public String render(Type firstArgumentType, List arguments, SessionFactoryImple if ( arguments.size() == 0 ) { return getName() + "()"; } - else { - final StringBuilder buf = new StringBuilder(); - int firstArgumentIndex; - if ( staticFunction ) { - // Add function call - buf.append( getName() ); - firstArgumentIndex = 0; - } - else { - // If the first argument is an expression, e.g. a nested function, strip the .ST_AsEWKB() suffix - buf.append( stripEWKBSuffix( arguments.get( 0 ) ) ); - // Add function call - buf.append( "." ).append( getName() ); - - firstArgumentIndex = 1; - } + final StringBuilder buf = new StringBuilder(); + int firstArgumentIndex; + if ( this.staticFunction ) { + // Add function call + buf.append( getName() ); + firstArgumentIndex = 0; + } + else { + // If the first argument is an expression, e.g. a nested function, strip the .ST_AsEWKB() suffix + Object argument = arguments.get( 0 ); + final boolean parseFromWKB = ( "?".equals( argument ) ); + appendArgument( argument, parseFromWKB, buf ); + // Add function call + buf.append( "." ).append( getName() ); + + firstArgumentIndex = 1; + } - buf.append( '(' ); - - // Add function arguments - for ( int i = firstArgumentIndex; i < arguments.size(); i++ ) { - final Object argument = arguments.get( i ); - // Check if first argument needs to be parsed from EWKB. This is the case if the first argument is a - // parameter that is set as EWKB or if it's a nested function call. - final boolean parseFromWKB = ( isGeometryArgument( i ) && "?".equals( argument ) ); - if ( parseFromWKB ) { - buf.append( "ST_GeomFromEWKB(" ); - } - buf.append( stripEWKBSuffix( argument ) ); - if ( parseFromWKB ) { - buf.append( ")" ); - } - if ( i < arguments.size() - 1 ) { - buf.append( ", " ); - } + buf.append( '(' ); + + // Add function arguments + for ( int i = firstArgumentIndex; i < arguments.size(); i++ ) { + final Object argument = arguments.get( i ); + // Check if first argument needs to be parsed from EWKB. This is the case if the first argument is a + // parameter that is set as EWKB or if it's a nested function call. + final boolean parseFromWKB = ( isGeometryArgument( i ) && "?".equals( argument ) ); + appendArgument( argument, parseFromWKB, buf ); + if ( i < arguments.size() - 1 ) { + buf.append( ", " ); } - buf.append( ')' ); - // If it doesn't specify an explicit type, assume it's a geometry - if ( this.getType() == null ) { - buf.append( AS_EWKB_SUFFIX ); - } - return buf.toString(); } + buf.append( ')' ); + // If it doesn't specify an explicit type, assume it's a geometry + if ( this.getType() == null ) { + buf.append( AS_EWKB_SUFFIX ); + } + return buf.toString(); } private Object stripEWKBSuffix(Object argument) { @@ -115,4 +109,14 @@ private Object stripEWKBSuffix(Object argument) { private boolean isGeometryArgument(int idx) { return this.argumentIsGeometryTypeMask.size() > idx && this.argumentIsGeometryTypeMask.get( idx ); } + + private void appendArgument(Object argument, boolean parseFromWKB, StringBuilder buf) { + if ( parseFromWKB ) { + buf.append( "ST_GeomFromEWKB(" ); + } + buf.append( stripEWKBSuffix( argument ) ); + if ( parseFromWKB ) { + buf.append( ")" ); + } + } } diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/hana/TestHANASpatialFunctions.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/hana/TestHANASpatialFunctions.java index 5d1e8befde47..f1d9c9270b37 100644 --- a/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/hana/TestHANASpatialFunctions.java +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/hana/TestHANASpatialFunctions.java @@ -1018,6 +1018,31 @@ public void zmin(String pckg) throws SQLException { retrieveHQLResultsAndCompare( dbexpected, hql, pckg ); } + @Test + public void test_nestedfunction_on_jts() throws SQLException { + nestedfunction( JTS ); + } + + @Test + public void test_nestedfunction_on_geolatte() throws SQLException { + nestedfunction( GEOLATTE ); + } + + public void nestedfunction(String pckg) throws SQLException { + Map dbexpected = hanaExpectationsFactory.getNestedFunctionInner( expectationsFactory.getTestPolygon() ); + String hql = format( + "SELECT id, geom FROM org.hibernate.spatial.integration.%s.GeomEntity g where dwithin(geom, srid(:filter, 0), 1) = true", + pckg ); + Map params = createQueryParams( "filter", expectationsFactory.getTestPolygon() ); + retrieveHQLResultsAndCompare( dbexpected, hql, params, pckg ); + + dbexpected = hanaExpectationsFactory.getNestedFunctionOuter( expectationsFactory.getTestPolygon() ); + hql = format( + "SELECT id, geom FROM org.hibernate.spatial.integration.%s.GeomEntity g where dwithin(:filter, srid(geom, 0), 1) = true", + pckg ); + retrieveHQLResultsAndCompare( dbexpected, hql, params, pckg ); + } + private String getGeometryTypeFromPackage(String pckg) { switch ( pckg ) { case GEOLATTE: diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/hana/HANAExpectationsFactory.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/hana/HANAExpectationsFactory.java index 4601a5a7e311..5b7a26ab5ef0 100644 --- a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/hana/HANAExpectationsFactory.java +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/hana/HANAExpectationsFactory.java @@ -925,7 +925,7 @@ private NativeSQLStatement createNativeSnapToGridStatement() { "select t.id, t.geom.ST_SnapToGrid() from GeomTest t where t.geom.ST_SRID() = " + getTestSrid() ); } - + /** * Returns the expected startpoint of all testsuite-suite geometries. * @@ -939,7 +939,7 @@ public Map getStartPoint() throws SQLException { private NativeSQLStatement createNativeStartPointStatement() { return createNativeSQLStatement( "select id, t.geom.ST_StartPoint() from GeomTest t where t.geom.ST_GeometryType() = 'ST_LineString'" ); } - + /** * Returns the expected aggregated union of all testsuite-suite geometries. * @@ -953,7 +953,7 @@ public Map getUnionAggr() throws SQLException { private NativeSQLStatement createNativeUnionAggrStatement() { return createNativeSQLStatement( "select cast(count(*) as int), ST_UnionAggr(t.geom) from GeomTest t" ); } - + /** * Returns the x coordinate of all testsuite-suite geometries. * @@ -969,7 +969,7 @@ private NativeSQLStatement createNativeXStatement() { "select t.id, t.geom.ST_X() from GeomTest t where t.geom.ST_GeometryType() in ('ST_Point') and t.geom.ST_SRID() = " + getTestSrid() ); } - + /** * Returns the maximum x coordinate of all testsuite-suite geometries. * @@ -985,7 +985,7 @@ private NativeSQLStatement createNativeXMaxStatement() { "select t.id, t.geom.ST_XMax() from GeomTest t where t.geom.ST_SRID() = " + getTestSrid() ); } - + /** * Returns the minimum x coordinate of all testsuite-suite geometries. * @@ -1001,7 +1001,7 @@ private NativeSQLStatement createNativeXMinStatement() { "select t.id, t.geom.ST_XMin() from GeomTest t where t.geom.ST_SRID() = " + getTestSrid() ); } - + /** * Returns the y coordinate of all testsuite-suite geometries. * @@ -1017,7 +1017,7 @@ private NativeSQLStatement createNativeYStatement() { "select t.id, t.geom.ST_Y() from GeomTest t where t.geom.ST_GeometryType() in ('ST_Point') and t.geom.ST_SRID() = " + getTestSrid() ); } - + /** * Returns the maximum y coordinate of all testsuite-suite geometries. * @@ -1033,7 +1033,7 @@ private NativeSQLStatement createNativeYMaxStatement() { "select t.id, t.geom.ST_YMax() from GeomTest t where t.geom.ST_SRID() = " + getTestSrid() ); } - + /** * Returns the minimum y coordinate of all testsuite-suite geometries. * @@ -1049,7 +1049,7 @@ private NativeSQLStatement createNativeYMinStatement() { "select t.id, t.geom.ST_YMin() from GeomTest t where t.geom.ST_SRID() = " + getTestSrid() ); } - + /** * Returns the z coordinate of all testsuite-suite geometries. * @@ -1065,7 +1065,7 @@ private NativeSQLStatement createNativeZStatement() { "select t.id, t.geom.ST_Z() from GeomTest t where t.geom.ST_GeometryType() in ('ST_Point') and t.geom.ST_SRID() = " + getTestSrid() ); } - + /** * Returns the maximum z coordinate of all testsuite-suite geometries. * @@ -1081,7 +1081,7 @@ private NativeSQLStatement createNativeZMaxStatement() { "select t.id, t.geom.ST_ZMax() from GeomTest t where t.geom.ST_SRID() = " + getTestSrid() ); } - + /** * Returns the minimum z coordinate of all testsuite-suite geometries. * @@ -1097,4 +1097,37 @@ private NativeSQLStatement createNativeZMinStatement() { "select t.id, t.geom.ST_ZMin() from GeomTest t where t.geom.ST_SRID() = " + getTestSrid() ); } + + /** + * Returns the result of a nested function call with a parameter inside the inner function + * + * @return map of identifier, geometry + * @throws SQLException + */ + public Map getNestedFunctionInner(Geometry geom) throws SQLException { + return retrieveExpected( createNativeNestedFunctionInnerStatement( geom ), GEOMETRY ); + } + + private NativeSQLStatement createNativeNestedFunctionInnerStatement(Geometry geom) { + return createNativeSQLStatementAllWKTParams( + "select t.id, t.geom from GeomTest t where t.geom.ST_WithinDistance(ST_GeomFromText(?, " + getTestSrid() + + ").ST_SRID(0), 1) = 1", + geom.toText() ); + } + + /** + * Returns the result of a nested function call with a parameter inside the outer function + * + * @return map of identifier, geometry + * @throws SQLException + */ + public Map getNestedFunctionOuter(Geometry geom) throws SQLException { + return retrieveExpected( createNativeNestedFunctionOuterStatement( geom ), GEOMETRY ); + } + + private NativeSQLStatement createNativeNestedFunctionOuterStatement(Geometry geom) { + return createNativeSQLStatementAllWKTParams( + "select t.id, t.geom from GeomTest t where ST_GeomFromText(?, " + getTestSrid() + ").ST_WithinDistance(geom.ST_SRID(0), 1) = 1", + geom.toText() ); + } } From 5e6298d2f91645125534a4eeac5394f688772148 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 11 Jul 2018 17:42:05 +0300 Subject: [PATCH 030/772] HHH-12349 - User Guide documentation for @Filter is too verbose --- .../chapters/domain/basic_types.adoc | 94 ++- ...apping-filter-collection-query-example.sql | 26 +- .../basic/mapping-filter-entity-example.sql | 7 +- .../mapping-filter-entity-query-example.sql | 16 +- ...er-join-table-collection-query-example.sql | 22 - ...-filter-join-table-persistence-example.sql | 12 +- .../mapping-filter-persistence-example.sql | 6 +- ...ing-no-filter-collection-query-example.sql | 19 + ...mapping-no-filter-entity-query-example.sql | 9 + ...er-join-table-collection-query-example.sql | 17 + .../mapping/basic/FilterJoinTableTest.java | 363 +++++------ .../userguide/mapping/basic/FilterTest.java | 562 ++++++++++-------- 12 files changed, 629 insertions(+), 524 deletions(-) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql 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 972fab0c6656..64caad1e0833 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc @@ -1829,19 +1829,40 @@ include::{sourcedir}/basic/WhereJoinTableTest.java[tags=mapping-where-join-table [[mapping-column-filter]] ==== `@Filter` -The `@Filter` annotation is another way to filter out entities or collections using custom SQL criteria, for both entities and collections. +The `@Filter` annotation is another way to filter out entities or collections using custom SQL criteria. Unlike the `@Where` annotation, `@Filter` allows you to parameterize the filter clause at runtime. -[[mapping-filter-example]] -.`@Filter` mapping usage +Now, considering we have the following `Account` entity: + +[[mapping-filter-account-example]] +.`@Filter` mapping entity-level usage ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-example] +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-Account-example] ---- ==== -If the database contains the following entities: +[NOTE] +==== +Notice that the `active` property is mapped to the `active_status` column. + +This mapping was done to show you that the `@Filter` condition uses a SQL condition, and not a JPQL filtering criteria. +==== + +As already explained, we can also apply the `@Filter` annotation for collections as illustrated by the `Client` entity: + +[[mapping-filter-client-example]] +.`@Filter` mapping collection-level usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-Client-example] +---- +==== + +If we persist a `Client` with three associated `Account` entities, +Hibernate will execute the following SQL statements: [[mapping-filter-persistence-example]] .Persisting and fetching entities with a `@Filter` mapping @@ -1858,6 +1879,21 @@ include::{extrasdir}/basic/mapping-filter-persistence-example.sql[] ==== By default, without explicitly enabling the filter, Hibernate is going to fetch all `Account` entities. + +[[mapping-no-filter-entity-query-example]] +.Query entities mapped without activating the `@Filter` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-no-filter-entity-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-no-filter-entity-query-example.sql[] +---- +==== + If the filter is enabled and the filter parameter value is provided, then Hibernate is going to apply the filtering criteria to the associated `Account` entities. @@ -1878,6 +1914,7 @@ include::{extrasdir}/basic/mapping-filter-entity-query-example.sql[] [IMPORTANT] ==== Filters apply to entity queries, but not to direct fetching. + Therefore, in the following example, the filter is not taken into consideration when fetching an entity from the Persistence Context. [[mapping-filter-entity-example]] @@ -1896,7 +1933,22 @@ As you can see from the example above, contrary to an entity query, the filter d ==== Just like with entity queries, collections can be filtered as well, but only if the filter is explicitly enabled on the currently running Hibernate `Session`. -This way, when fetching the `accounts` collections, Hibernate is going to apply the `@Filter` clause filtering criteria to the associated collection entries. + +[[mapping-no-filter-collection-query-example]] +.Traversing collections without activating the `@Filter` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-no-filter-collection-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-no-filter-collection-query-example.sql[] +---- +==== + +When activating the `@Filter` and fetching the `accounts` collections, Hibernate is going to apply the filter condition to the associated collection entries. [[mapping-filter-collection-query-example]] .Traversing collections mapped with `@Filter` @@ -1922,7 +1974,7 @@ The main advantage of `@Filter` over the `@Where` clause is that the filtering c It's not possible to combine the `@Filter` and `@Cache` collection annotations. This limitation is due to ensuring consistency and because the filtering information is not stored in the second-level cache. -If caching was allowed for a currently filtered collection, then the second-level cache would store only a subset of the whole collection. +If caching were allowed for a currently filtered collection, then the second-level cache would store only a subset of the whole collection. Afterward, every other Session will get the filtered collection from the cache, even if the Session-level filters have not been explicitly activated. For this reason, the second-level collection cache is limited to storing whole collections, and not subsets. @@ -1934,7 +1986,7 @@ For this reason, the second-level collection cache is limited to storing whole c When using the `@Filter` annotation with collections, the filtering is done against the child entries (entities or embeddables). However, if you have a link table between the parent entity and the child table, then you need to use the `@FilterJoinTable` to filter child entries according to some column contained in the join table. -The `@FilterJoinTable` annotation can be, therefore, applied to a unidirectional `@OneToMany` collection as illustrate din the following mapping: +The `@FilterJoinTable` annotation can be, therefore, applied to a unidirectional `@OneToMany` collection as illustrated in the following mapping: [[mapping-filter-join-table-example]] .`@FilterJoinTable` mapping usage @@ -1945,7 +1997,11 @@ include::{sourcedir}/basic/FilterJoinTableTest.java[tags=mapping-filter-join-tab ---- ==== -If the database contains the following entities: +The `firstAccounts` filter will allow us to get only the `Account` entities that have the `order_id` +(which tells the position of every entry inside the `accounts` collection) +less than a given number (e.g. `maxOrderId`). + +Let's assume our database contains the following entities: [[mapping-filter-join-table-persistence-example]] .Persisting and fetching entities with a `@FilterJoinTable` mapping @@ -1961,8 +2017,24 @@ include::{extrasdir}/basic/mapping-filter-join-table-persistence-example.sql[] ---- ==== -The collections can be filtered if the associated filter is enabled on the currently running Hibernate `Session`. -This way, when fetching the `accounts` collections, Hibernate is going to apply the `@FilterJoinTable` clause filtering criteria to the associated collection entries. +The collections can be filtered only if the associated filter is enabled on the currently running Hibernate `Session`. + +[[mapping-no-filter-join-table-collection-query-example]] +.Traversing collections mapped with `@FilterJoinTable` without enabling the filter +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterJoinTableTest.java[tags=mapping-no-filter-join-table-collection-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-no-filter-join-table-collection-query-example.sql[] +---- +==== + +If we enable the filter and set the `maxOrderId` to `1`, when fetching the `accounts` collections, Hibernate is going to apply the `@FilterJoinTable` clause filtering criteria, and we will get just +`2` `Account` entities, with the `order_id` values of `0` and `1`. [[mapping-filter-join-table-collection-query-example]] .Traversing collections mapped with `@FilterJoinTable` diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql index d3d216ce8d2c..5282632d416f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql @@ -1,25 +1,3 @@ -SELECT - c.id as id1_1_0_, - c.name as name2_1_0_ -FROM - Client c -WHERE - c.id = 1 - -SELECT - a.id as id1_0_, - a.active as active2_0_, - a.amount as amount3_0_, - a.client_id as client_i6_0_, - a.rate as rate4_0_, - a.account_type as account_5_0_ -FROM - Account a -WHERE - a.client_id = 1 - --- Activate filter [activeAccount] - SELECT c.id as id1_1_0_, c.name as name2_1_0_ @@ -30,7 +8,7 @@ WHERE SELECT a.id as id1_0_, - a.active as active2_0_, + a.active_status as active2_0_, a.amount as amount3_0_, a.client_id as client_i6_0_, a.rate as rate4_0_, @@ -38,5 +16,5 @@ SELECT FROM Account a WHERE - accounts0_.active = true + accounts0_.active_status = true and a.client_id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql index f3d4d61627f5..aeb9bb001ee7 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql @@ -1,6 +1,6 @@ SELECT a.id as id1_0_0_, - a.active as active2_0_0_, + a.active_status as active2_0_0_, a.amount as amount3_0_0_, a.client_id as client_i6_0_0_, a.rate as rate4_0_0_, @@ -8,9 +8,6 @@ SELECT c.id as id1_1_1_, c.name as name2_1_1_ FROM - Account a -LEFT OUTER JOIN - Client c - ON a.client_id=c.id + Account a WHERE a.id = 2 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql index 88c17423ebd0..5bcf7e0c354a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql @@ -1,18 +1,6 @@ SELECT a.id as id1_0_, - a.active as active2_0_, - a.amount as amount3_0_, - a.client_id as client_i6_0_, - a.rate as rate4_0_, - a.account_type as account_5_0_ -FROM - Account a - --- Activate filter [activeAccount] - -SELECT - a.id as id1_0_, - a.active as active2_0_, + a.active_status as active2_0_, a.amount as amount3_0_, a.client_id as client_i6_0_, a.rate as rate4_0_, @@ -20,4 +8,4 @@ SELECT FROM Account a WHERE - a.active = true \ No newline at end of file + a.active_status = true \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql index f6abfd3ec97a..80226c2f508e 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql @@ -3,7 +3,6 @@ SELECT ca.accounts_id as accounts2_2_0_, ca.order_id as order_id3_0_, a.id as id1_0_1_, - a.active as active2_0_1_, a.amount as amount3_0_1_, a.rate as rate4_0_1_, a.account_type as account_5_0_1_ @@ -12,27 +11,6 @@ FROM INNER JOIN Account a ON ca.accounts_id=a.id -WHERE - ca.Client_id = ? - --- binding parameter [1] as [BIGINT] - [1] - --- Activate filter [firstAccounts] - -SELECT - ca.Client_id as Client_i1_2_0_, - ca.accounts_id as accounts2_2_0_, - ca.order_id as order_id3_0_, - a.id as id1_0_1_, - a.active as active2_0_1_, - a.amount as amount3_0_1_, - a.rate as rate4_0_1_, - a.account_type as account_5_0_1_ -FROM - Client_Account ca -INNER JOIN - Account a -ON ca.accounts_id=a.id WHERE ca.order_id <= ? AND ca.Client_id = ? diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql index e9a785d308dd..6bf698e00b3b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql @@ -1,14 +1,14 @@ INSERT INTO Client (name, id) VALUES ('John Doe', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) -VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1) +INSERT INTO Account (amount, client_id, rate, account_type, id) +VALUES (5000.0, 1, 0.0125, 'CREDIT', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) -VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2) +INSERT INTO Account (amount, client_id, rate, account_type, id) +VALUES (0.0, 1, 0.0105, 'DEBIT', 2) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) -VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3) +INSERT INTO Account (amount, client_id, rate, account_type, id) +VALUES (250.0, 1, 0.0105, 'DEBIT', 3) INSERT INTO Client_Account (Client_id, order_id, accounts_id) VALUES (1, 0, 1) diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql index 2f4abf9d6638..750c9f4f70f4 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql @@ -1,11 +1,11 @@ INSERT INTO Client (name, id) VALUES ('John Doe', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) +INSERT INTO Account (active_status, amount, client_id, rate, account_type, id) VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) +INSERT INTO Account (active_status, amount, client_id, rate, account_type, id) VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) +INSERT INTO Account (active_status, amount, client_id, rate, account_type, id) VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql new file mode 100644 index 000000000000..d80b60f607ab --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql @@ -0,0 +1,19 @@ +SELECT + c.id as id1_1_0_, + c.name as name2_1_0_ +FROM + Client c +WHERE + c.id = 1 + +SELECT + a.id as id1_0_, + a.active_status as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a +WHERE + a.client_id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql new file mode 100644 index 000000000000..4d6dbc7655a7 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql @@ -0,0 +1,9 @@ +SELECT + a.id as id1_0_, + a.active_status as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql new file mode 100644 index 000000000000..ab44fcf58102 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql @@ -0,0 +1,17 @@ +SELECT + ca.Client_id as Client_i1_2_0_, + ca.accounts_id as accounts2_2_0_, + ca.order_id as order_id3_0_, + a.id as id1_0_1_, + a.amount as amount3_0_1_, + a.rate as rate4_0_1_, + a.account_type as account_5_0_1_ +FROM + Client_Account ca +INNER JOIN + Account a +ON ca.accounts_id=a.id +WHERE + ca.Client_id = ? + +-- binding parameter [1] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java index bafed596f03c..83c3466bcfe7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -35,180 +36,190 @@ */ public class FilterJoinTableTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Client.class, - Account.class - }; - } - - @Test - public void testLifecycle() { - //tag::mapping-filter-join-table-persistence-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - - Client client = new Client(); - client.setId( 1L ); - client.setName( "John Doe" ); - entityManager.persist( client ); - - Account account1 = new Account( ); - account1.setId( 1L ); - account1.setType( AccountType.CREDIT ); - account1.setAmount( 5000d ); - account1.setRate( 1.25 / 100 ); - account1.setActive( true ); - client.getAccounts().add( account1 ); - entityManager.persist( account1 ); - - Account account2 = new Account( ); - account2.setId( 2L ); - account2.setType( AccountType.DEBIT ); - account2.setAmount( 0d ); - account2.setRate( 1.05 / 100 ); - account2.setActive( false ); - client.getAccounts().add( account2 ); - entityManager.persist( account2 ); - - Account account3 = new Account( ); - account3.setType( AccountType.DEBIT ); - account3.setId( 3L ); - account3.setAmount( 250d ); - account3.setRate( 1.05 / 100 ); - account3.setActive( true ); - client.getAccounts().add( account3 ); - entityManager.persist( account3 ); - } ); - //end::mapping-filter-join-table-persistence-example[] - - //tag::mapping-filter-join-table-collection-query-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - Client client = entityManager.find( Client.class, 1L ); - assertEquals( 3, client.getAccounts().size()); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "firstAccounts"); - - Client client = entityManager.find( Client.class, 1L ); - - entityManager - .unwrap( Session.class ) - .enableFilter( "firstAccounts" ) - .setParameter( "maxOrderId", 1); - - assertEquals( 2, client.getAccounts().size()); - } ); - //end::mapping-filter-join-table-collection-query-example[] - } - - //tag::mapping-filter-join-table-example[] - public enum AccountType { - DEBIT, - CREDIT - } - - @Entity(name = "Client") - @FilterDef(name="firstAccounts", parameters=@ParamDef( name="maxOrderId", type="int" ) ) - @Filter(name="firstAccounts", condition="order_id <= :maxOrderId") - public static class Client { - - @Id - private Long id; - - private String name; - - @OneToMany - @OrderColumn(name = "order_id") - @FilterJoinTable(name="firstAccounts", condition="order_id <= :maxOrderId") - private List accounts = new ArrayList<>( ); - - //Getters and setters omitted for brevity - - //end::mapping-filter-join-table-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getAccounts() { - return accounts; - } - //tag::mapping-filter-join-table-example[] - } - - @Entity(name = "Account") - public static class Account { - - @Id - private Long id; - - @Column(name = "account_type") - @Enumerated(EnumType.STRING) - private AccountType type; - - private Double amount; - - private Double rate; - - private boolean active; - - //Getters and setters omitted for brevity - - //end::mapping-filter-join-table-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public AccountType getType() { - return type; - } - - public void setType(AccountType type) { - this.type = type; - } - - public Double getAmount() { - return amount; - } - - public void setAmount(Double amount) { - this.amount = amount; - } - - public Double getRate() { - return rate; - } - - public void setRate(Double rate) { - this.rate = rate; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - //tag::mapping-filter-join-table-example[] - } - //end::mapping-filter-join-table-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Client.class, + Account.class + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-filter-join-table-persistence-example[] + Client client = new Client() + .setId( 1L ) + .setName( "John Doe" ); + + client.addAccount( + new Account() + .setId( 1L ) + .setType( AccountType.CREDIT ) + .setAmount( 5000d ) + .setRate( 1.25 / 100 ) + ); + + client.addAccount( + new Account() + .setId( 2L ) + .setType( AccountType.DEBIT ) + .setAmount( 0d ) + .setRate( 1.05 / 100 ) + ); + + client.addAccount( + new Account() + .setType( AccountType.DEBIT ) + .setId( 3L ) + .setAmount( 250d ) + .setRate( 1.05 / 100 ) + ); + + entityManager.persist( client ); + //end::mapping-filter-join-table-persistence-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-no-filter-join-table-collection-query-example[] + Client client = entityManager.find( Client.class, 1L ); + + assertEquals( 3, client.getAccounts().size()); + //end::mapping-no-filter-join-table-collection-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "firstAccounts"); + + //tag::mapping-filter-join-table-collection-query-example[] + Client client = entityManager.find( Client.class, 1L ); + + entityManager + .unwrap( Session.class ) + .enableFilter( "firstAccounts" ) + .setParameter( "maxOrderId", 1); + + assertEquals( 2, client.getAccounts().size()); + //end::mapping-filter-join-table-collection-query-example[] + } ); + } + + public enum AccountType { + DEBIT, + CREDIT + } + + //tag::mapping-filter-join-table-example[] + @Entity(name = "Client") + @FilterDef( + name="firstAccounts", + parameters=@ParamDef( + name="maxOrderId", + type="int" + ) + ) + @Filter( + name="firstAccounts", + condition="order_id <= :maxOrderId" + ) + public static class Client { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @OrderColumn(name = "order_id") + @FilterJoinTable( + name="firstAccounts", + condition="order_id <= :maxOrderId" + ) + private List accounts = new ArrayList<>( ); + + //Getters and setters omitted for brevity + //end::mapping-filter-join-table-example[] + public Long getId() { + return id; + } + + public Client setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Client setName(String name) { + this.name = name; + return this; + } + + public List getAccounts() { + return accounts; + } + //tag::mapping-filter-join-table-example[] + + public void addAccount(Account account) { + this.accounts.add( account ); + } + } + + @Entity(name = "Account") + public static class Account { + + @Id + private Long id; + + @Column(name = "account_type") + @Enumerated(EnumType.STRING) + private AccountType type; + + private Double amount; + + private Double rate; + + //Getters and setters omitted for brevity + //end::mapping-filter-join-table-example[] + public Long getId() { + return id; + } + + public Account setId(Long id) { + this.id = id; + return this; + } + + public AccountType getType() { + return type; + } + + public Account setType(AccountType type) { + this.type = type; + return this; + } + + public Double getAmount() { + return amount; + } + + public Account setAmount(Double amount) { + this.amount = amount; + return this; + } + + public Double getRate() { + return rate; + } + + public Account setRate(Double rate) { + this.rate = rate; + return this; + } + + //tag::mapping-filter-join-table-example[] + } + //end::mapping-filter-join-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java index a44b6a6b1fd8..f678989df3e9 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java @@ -8,10 +8,12 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.NoResultException; @@ -39,267 +41,301 @@ */ public class FilterTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Client.class, - Account.class - }; - } - - @Test - public void testLifecycle() { - //tag::mapping-filter-persistence-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - - Client client = new Client(); - client.setId( 1L ); - client.setName( "John Doe" ); - entityManager.persist( client ); - - Account account1 = new Account( ); - account1.setId( 1L ); - account1.setType( AccountType.CREDIT ); - account1.setAmount( 5000d ); - account1.setRate( 1.25 / 100 ); - account1.setActive( true ); - account1.setClient( client ); - client.getAccounts().add( account1 ); - entityManager.persist( account1 ); - - Account account2 = new Account( ); - account2.setId( 2L ); - account2.setType( AccountType.DEBIT ); - account2.setAmount( 0d ); - account2.setRate( 1.05 / 100 ); - account2.setActive( false ); - account2.setClient( client ); - client.getAccounts().add( account2 ); - entityManager.persist( account2 ); - - Account account3 = new Account( ); - account3.setType( AccountType.DEBIT ); - account3.setId( 3L ); - account3.setAmount( 250d ); - account3.setRate( 1.05 / 100 ); - account3.setActive( true ); - account3.setClient( client ); - client.getAccounts().add( account3 ); - entityManager.persist( account3 ); - } ); - //end::mapping-filter-persistence-example[] - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Account account1 = entityManager.find( Account.class, 1L ); - Account account2 = entityManager.find( Account.class, 2L ); - - assertNotNull( account1 ); - assertNotNull( account2 ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Account account1 = entityManager.createQuery( - "select a from Account a where a.id = :id", Account.class) - .setParameter( "id", 1L ) - .getSingleResult(); - assertNotNull( account1 ); - try { - Account account2 = entityManager.createQuery( - "select a from Account a where a.id = :id", Account.class) - .setParameter( "id", 2L ) - .getSingleResult(); - } - catch (NoResultException expected) { - } - } ); - - //tag::mapping-filter-entity-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Account account = entityManager.find( Account.class, 2L ); - assertFalse( account.isActive() ); - } ); - //end::mapping-filter-entity-example[] - - // tag::mapping-filter-entity-query-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - List accounts = entityManager.createQuery( - "select a from Account a", Account.class) - .getResultList(); - assertEquals( 3, accounts.size()); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - List accounts = entityManager.createQuery( - "select a from Account a", Account.class) - .getResultList(); - assertEquals( 2, accounts.size()); - } ); - //end::mapping-filter-entity-query-example[] - - //tag::mapping-filter-collection-query-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - Client client = entityManager.find( Client.class, 1L ); - assertEquals( 3, client.getAccounts().size() ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Client client = entityManager.find( Client.class, 1L ); - assertEquals( 2, client.getAccounts().size() ); - } ); - //end::mapping-filter-collection-query-example[] - } - - //tag::mapping-filter-example[] - public enum AccountType { - DEBIT, - CREDIT - } - - @Entity(name = "Client") - public static class Client { - - @Id - private Long id; - - private String name; - - @OneToMany(mappedBy = "client") - @Filter(name="activeAccount", condition="active = :active") - private List accounts = new ArrayList<>( ); - - //Getters and setters omitted for brevity - - //end::mapping-filter-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getAccounts() { - return accounts; - } - //tag::mapping-filter-example[] - } - - @Entity(name = "Account") - @FilterDef(name="activeAccount", parameters=@ParamDef( name="active", type="boolean" ) ) - @Filter(name="activeAccount", condition="active = :active") - public static class Account { - - @Id - private Long id; - - @ManyToOne - private Client client; - - @Column(name = "account_type") - @Enumerated(EnumType.STRING) - private AccountType type; - - private Double amount; - - private Double rate; - - private boolean active; - - //Getters and setters omitted for brevity - - //end::mapping-filter-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Client getClient() { - return client; - } - - public void setClient(Client client) { - this.client = client; - } - - public AccountType getType() { - return type; - } - - public void setType(AccountType type) { - this.type = type; - } - - public Double getAmount() { - return amount; - } - - public void setAmount(Double amount) { - this.amount = amount; - } - - public Double getRate() { - return rate; - } - - public void setRate(Double rate) { - this.rate = rate; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - //tag::mapping-filter-example[] - } - //end::mapping-filter-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Client.class, + Account.class + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + + //tag::mapping-filter-persistence-example[] + Client client = new Client() + .setId( 1L ) + .setName( "John Doe" ); + + client.addAccount( + new Account() + .setId( 1L ) + .setType( AccountType.CREDIT ) + .setAmount( 5000d ) + .setRate( 1.25 / 100 ) + .setActive( true ) + ); + + client.addAccount( + new Account() + .setId( 2L ) + .setType( AccountType.DEBIT ) + .setAmount( 0d ) + .setRate( 1.05 / 100 ) + .setActive( false ) + ); + + client.addAccount( + new Account() + .setType( AccountType.DEBIT ) + .setId( 3L ) + .setAmount( 250d ) + .setRate( 1.05 / 100 ) + .setActive( true ) + ); + + entityManager.persist( client ); + //end::mapping-filter-persistence-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Account account1 = entityManager.find( Account.class, 1L ); + Account account2 = entityManager.find( Account.class, 2L ); + + assertNotNull( account1 ); + assertNotNull( account2 ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Account account1 = entityManager.createQuery( + "select a from Account a where a.id = :id", Account.class) + .setParameter( "id", 1L ) + .getSingleResult(); + assertNotNull( account1 ); + try { + Account account2 = entityManager.createQuery( + "select a from Account a where a.id = :id", Account.class) + .setParameter( "id", 2L ) + .getSingleResult(); + } + catch (NoResultException expected) { + } + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + //tag::mapping-filter-entity-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Account account = entityManager.find( Account.class, 2L ); + + assertFalse( account.isActive() ); + //end::mapping-filter-entity-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-no-filter-entity-query-example[] + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + + assertEquals( 3, accounts.size()); + //end::mapping-no-filter-entity-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + //tag::mapping-filter-entity-query-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + + assertEquals( 2, accounts.size()); + //end::mapping-filter-entity-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-no-filter-collection-query-example[] + Client client = entityManager.find( Client.class, 1L ); + + assertEquals( 3, client.getAccounts().size() ); + //end::mapping-no-filter-collection-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + //tag::mapping-filter-collection-query-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Client client = entityManager.find( Client.class, 1L ); + + assertEquals( 2, client.getAccounts().size() ); + //end::mapping-filter-collection-query-example[] + } ); + } + + public enum AccountType { + DEBIT, + CREDIT + } + + //tag::mapping-filter-Client-example[] + @Entity(name = "Client") + public static class Client { + + @Id + private Long id; + + private String name; + + @OneToMany( + mappedBy = "client", + cascade = CascadeType.ALL + ) + @Filter( + name="activeAccount", + condition="active_status = :active" + ) + private List accounts = new ArrayList<>( ); + + //Getters and setters omitted for brevity + //end::mapping-filter-Client-example[] + public Long getId() { + return id; + } + + public Client setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Client setName(String name) { + this.name = name; + return this; + } + + public List getAccounts() { + return accounts; + } + //tag::mapping-filter-Client-example[] + + public void addAccount(Account account) { + account.setClient( this ); + this.accounts.add( account ); + } + } + //end::mapping-filter-Client-example[] + + //tag::mapping-filter-Account-example[] + @Entity(name = "Account") + @FilterDef( + name="activeAccount", + parameters = @ParamDef( + name="active", + type="boolean" + ) + ) + @Filter( + name="activeAccount", + condition="active_status = :active" + ) + public static class Account { + + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Client client; + + @Column(name = "account_type") + @Enumerated(EnumType.STRING) + private AccountType type; + + private Double amount; + + private Double rate; + + @Column(name = "active_status") + private boolean active; + + //Getters and setters omitted for brevity + //end::mapping-filter-Account-example[] + public Long getId() { + return id; + } + + public Account setId(Long id) { + this.id = id; + return this; + } + + public Client getClient() { + return client; + } + + public Account setClient(Client client) { + this.client = client; + return this; + } + + public AccountType getType() { + return type; + } + + public Account setType(AccountType type) { + this.type = type; + return this; + } + + public Double getAmount() { + return amount; + } + + public Account setAmount(Double amount) { + this.amount = amount; + return this; + } + + public Double getRate() { + return rate; + } + + public Account setRate(Double rate) { + this.rate = rate; + return this; + } + + public boolean isActive() { + return active; + } + + public Account setActive(boolean active) { + this.active = active; + return this; + } + + //tag::mapping-filter-Account-example[] + } + //end::mapping-filter-Account-example[] } From bb71c63853ecbcda0d893d2874d7cdc3d360db99 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 12 Jul 2018 12:32:01 +0100 Subject: [PATCH 031/772] HHH-12799 Enforce version alignment of Mockito and ByteBuddy dependencies --- gradle/libraries.gradle | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 149b913f813f..2ae19b3d825a 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -159,3 +159,12 @@ ext { jboss_annotation_spec_jar : 'org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:1.0.0.Final' ] } + +configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + //Force the "byte buddy agent" version to match the Byte Buddy version we use, as Mockito might pull in a mismatched version transitively: + if (details.requested.group + ":" + details.requested.name == 'net.bytebuddy:byte-buddy-agent') { + details.useVersion byteBuddyVersion + } + } +} From 4b51867c443891a9f6a6ff302a2516511d4f1cf8 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 11 Jul 2018 13:07:36 +0300 Subject: [PATCH 032/772] HHH-12771 - Caused by: java.lang.UnsupportedOperationException: Cache provider [org.hibernate.cache.ehcache.internal.EhcacheRegionFactory@3271ec2a] does not support `transactional` access --- .../cache/spi/SecondLevelCacheLogger.java | 9 + .../support}/CollectionTransactionAccess.java | 2 +- .../spi/support}/DomainDataRegionImpl.java | 8 +- .../support}/EntityTransactionalAccess.java | 2 +- .../NaturalIdTransactionalAccess.java | 2 +- .../internal/EhcacheRegionFactory.java | 14 ++ ...sactionalCacheConcurrencyStrategyTest.java | 162 ++++++++++++++++++ .../internal/JCacheDomainDataRegionImpl.java | 67 ++++++++ .../jcache/internal/JCacheRegionFactory.java | 14 ++ ...sactionalCacheConcurrencyStrategyTest.java | 160 +++++++++++++++++ .../testing/cache/CachingRegionFactory.java | 2 + 11 files changed, 435 insertions(+), 7 deletions(-) rename {hibernate-testing/src/main/java/org/hibernate/testing/cache => hibernate-core/src/main/java/org/hibernate/cache/spi/support}/CollectionTransactionAccess.java (96%) rename {hibernate-testing/src/main/java/org/hibernate/testing/cache => hibernate-core/src/main/java/org/hibernate/cache/spi/support}/DomainDataRegionImpl.java (91%) rename {hibernate-testing/src/main/java/org/hibernate/testing/cache => hibernate-core/src/main/java/org/hibernate/cache/spi/support}/EntityTransactionalAccess.java (97%) rename {hibernate-testing/src/main/java/org/hibernate/testing/cache => hibernate-core/src/main/java/org/hibernate/cache/spi/support}/NaturalIdTransactionalAccess.java (96%) create mode 100644 hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/EhcacheTransactionalCacheConcurrencyStrategyTest.java create mode 100644 hibernate-jcache/src/main/java/org/hibernate/cache/jcache/internal/JCacheDomainDataRegionImpl.java create mode 100644 hibernate-jcache/src/test/java/org/hibernate/test/cache/jcache/JCacheTransactionalCacheConcurrencyStrategyTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java index 1f7fce912bbd..413e741664a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java @@ -6,6 +6,7 @@ */ package org.hibernate.cache.spi; +import org.hibernate.cache.spi.access.AccessType; import org.hibernate.metamodel.model.domain.NavigableRole; import org.jboss.logging.BasicLogger; @@ -90,4 +91,12 @@ public interface SecondLevelCacheLogger extends BasicLogger { ) void usingLegacyCacheName(String currentName, String legacyName); + @LogMessage(level = WARN) + @Message( + value = "Cache [%1$s] uses the [%2$s] access type, but [%3$s] does not support it natively." + + " Make sure your cache implementation supports JTA transactions.", + id = NAMESPACE + 8 + ) + void nonStandardSupportForAccessType(String key, String accessType, String regionName); + } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cache/CollectionTransactionAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionTransactionAccess.java similarity index 96% rename from hibernate-testing/src/main/java/org/hibernate/testing/cache/CollectionTransactionAccess.java rename to hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionTransactionAccess.java index 456354e4b7e3..10fd566be7b4 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cache/CollectionTransactionAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionTransactionAccess.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.testing.cache; +package org.hibernate.cache.spi.support; import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; import org.hibernate.cache.spi.CacheKeysFactory; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cache/DomainDataRegionImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionImpl.java similarity index 91% rename from hibernate-testing/src/main/java/org/hibernate/testing/cache/DomainDataRegionImpl.java rename to hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionImpl.java index 3fc79fce61c0..a33a6e82cc16 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cache/DomainDataRegionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionImpl.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.testing.cache; +package org.hibernate.cache.spi.support; import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; @@ -15,7 +15,6 @@ import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; -import org.hibernate.cache.spi.support.DomainDataRegionTemplate; /** * @author Steve Ebersole @@ -24,13 +23,14 @@ public class DomainDataRegionImpl extends DomainDataRegionTemplate { @SuppressWarnings("WeakerAccess") public DomainDataRegionImpl( DomainDataRegionConfig regionConfig, - CachingRegionFactory regionFactory, + RegionFactoryTemplate regionFactory, + DomainDataStorageAccess domainDataStorageAccess, CacheKeysFactory defaultKeysFactory, DomainDataRegionBuildingContext buildingContext) { super( regionConfig, regionFactory, - new MapStorageAccessImpl(), + domainDataStorageAccess, defaultKeysFactory, buildingContext ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cache/EntityTransactionalAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityTransactionalAccess.java similarity index 97% rename from hibernate-testing/src/main/java/org/hibernate/testing/cache/EntityTransactionalAccess.java rename to hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityTransactionalAccess.java index 318295274440..33cd0d858360 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cache/EntityTransactionalAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityTransactionalAccess.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.testing.cache; +package org.hibernate.cache.spi.support; import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; import org.hibernate.cache.spi.CacheKeysFactory; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cache/NaturalIdTransactionalAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdTransactionalAccess.java similarity index 96% rename from hibernate-testing/src/main/java/org/hibernate/testing/cache/NaturalIdTransactionalAccess.java rename to hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdTransactionalAccess.java index ed3086256d30..66cf1a9faebb 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cache/NaturalIdTransactionalAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdTransactionalAccess.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.testing.cache; +package org.hibernate.cache.spi.support; import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; import org.hibernate.cache.spi.CacheKeysFactory; diff --git a/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/internal/EhcacheRegionFactory.java b/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/internal/EhcacheRegionFactory.java index 68bbb9d45f59..e5cea1a9f11a 100644 --- a/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/internal/EhcacheRegionFactory.java +++ b/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/internal/EhcacheRegionFactory.java @@ -25,11 +25,13 @@ import org.hibernate.cache.ehcache.MissingCacheStrategy; import org.hibernate.cache.internal.DefaultCacheKeysFactory; import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; import org.hibernate.cache.spi.SecondLevelCacheLogger; import org.hibernate.cache.spi.support.DomainDataStorageAccess; import org.hibernate.cache.spi.support.RegionFactoryTemplate; import org.hibernate.cache.spi.support.RegionNameQualifier; import org.hibernate.cache.spi.support.StorageAccess; +import org.hibernate.cache.spi.support.DomainDataRegionImpl; import org.hibernate.engine.spi.SessionFactoryImplementor; import static org.hibernate.cache.ehcache.ConfigSettings.EHCACHE_CONFIGURATION_RESOURCE_NAME; @@ -65,6 +67,18 @@ protected CacheKeysFactory getImplicitCacheKeysFactory() { return cacheKeysFactory; } + @Override + public DomainDataRegion buildDomainDataRegion( + DomainDataRegionConfig regionConfig, DomainDataRegionBuildingContext buildingContext) { + return new DomainDataRegionImpl( + regionConfig, + this, + createDomainDataStorageAccess( regionConfig, buildingContext ), + cacheKeysFactory, + buildingContext + ); + } + @Override protected DomainDataStorageAccess createDomainDataStorageAccess( DomainDataRegionConfig regionConfig, diff --git a/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/EhcacheTransactionalCacheConcurrencyStrategyTest.java b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/EhcacheTransactionalCacheConcurrencyStrategyTest.java new file mode 100644 index 000000000000..a205405dd3ca --- /dev/null +++ b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/EhcacheTransactionalCacheConcurrencyStrategyTest.java @@ -0,0 +1,162 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.ehcache.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; + +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +public class EhcacheTransactionalCacheConcurrencyStrategyTest + extends BaseNonConfigCoreFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + @Override + protected void addSettings(Map settings) { + settings.put( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + + TestingJtaBootstrap.prepare( settings ); + settings.put( Environment.TRANSACTION_COORDINATOR_STRATEGY, "jta" ); + settings.put( Environment.CACHE_REGION_FACTORY, "ehcache" ); + } + + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Entity(name = "Parent") + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + public static class Parent { + + @Id + @GeneratedValue + private Long id; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent") + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + private List children = new ArrayList(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + Child addChild() { + final Child c = new Child(); + c.setParent( this ); + this.children.add( c ); + return c; + } + + } + + @Entity(name = "Child") + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + public static class Child { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne( + fetch = FetchType.LAZY + ) + private Parent parent; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } + + + @Test + public void testTransactional() { + Parent parent = new Parent(); + + doInHibernate( this::sessionFactory, session -> { + for ( int i = 0; i < 2; i++ ) { + parent.addChild(); + + session.persist( parent ); + } + } ); + + doInHibernate( this::sessionFactory, session -> { + sqlStatementInterceptor.getSqlQueries().clear(); + + Parent _parent = session.find( Parent.class, parent.getId() ); + + assertEquals( 0, sqlStatementInterceptor.getSqlQueries().size() ); + + assertEquals( 2, _parent.getChildren().size() ); + } ); + + doInHibernate( this::sessionFactory, session -> { + sqlStatementInterceptor.getSqlQueries().clear(); + + Parent _parent = session.find( Parent.class, parent.getId() ); + + assertEquals( 2, _parent.getChildren().size() ); + + assertEquals( 0, sqlStatementInterceptor.getSqlQueries().size() ); + } ); + } + +} diff --git a/hibernate-jcache/src/main/java/org/hibernate/cache/jcache/internal/JCacheDomainDataRegionImpl.java b/hibernate-jcache/src/main/java/org/hibernate/cache/jcache/internal/JCacheDomainDataRegionImpl.java new file mode 100644 index 000000000000..3ac9552ad637 --- /dev/null +++ b/hibernate-jcache/src/main/java/org/hibernate/cache/jcache/internal/JCacheDomainDataRegionImpl.java @@ -0,0 +1,67 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.jcache.internal; + +import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; +import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.SecondLevelCacheLogger; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.access.CollectionDataAccess; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.NaturalIdDataAccess; +import org.hibernate.cache.spi.support.DomainDataRegionImpl; +import org.hibernate.cache.spi.support.DomainDataStorageAccess; +import org.hibernate.cache.spi.support.RegionFactoryTemplate; + +/** + * @author Vlad Mihalcea + */ +public class JCacheDomainDataRegionImpl extends DomainDataRegionImpl { + + public JCacheDomainDataRegionImpl( + DomainDataRegionConfig regionConfig, + RegionFactoryTemplate regionFactory, + DomainDataStorageAccess domainDataStorageAccess, + CacheKeysFactory defaultKeysFactory, + DomainDataRegionBuildingContext buildingContext) { + super( regionConfig, regionFactory, domainDataStorageAccess, defaultKeysFactory, buildingContext ); + } + + @Override + protected EntityDataAccess generateTransactionalEntityDataAccess(EntityDataCachingConfig entityAccessConfig) { + SecondLevelCacheLogger.INSTANCE.nonStandardSupportForAccessType( + getName(), + AccessType.TRANSACTIONAL.getExternalName(), + getRegionFactory().getClass().getSimpleName() + ); + return super.generateTransactionalEntityDataAccess( entityAccessConfig ); + } + + @Override + protected NaturalIdDataAccess generateTransactionalNaturalIdDataAccess(NaturalIdDataCachingConfig accessConfig) { + SecondLevelCacheLogger.INSTANCE.nonStandardSupportForAccessType( + getName(), + AccessType.TRANSACTIONAL.getExternalName(), + getRegionFactory().getClass().getSimpleName() + ); + return super.generateTransactionalNaturalIdDataAccess( accessConfig ); + } + + @Override + protected CollectionDataAccess generateTransactionalCollectionDataAccess(CollectionDataCachingConfig accessConfig) { + SecondLevelCacheLogger.INSTANCE.nonStandardSupportForAccessType( + getName(), + AccessType.TRANSACTIONAL.getExternalName(), + getRegionFactory().getClass().getSimpleName() + ); + return super.generateTransactionalCollectionDataAccess( accessConfig ); + } +} diff --git a/hibernate-jcache/src/main/java/org/hibernate/cache/jcache/internal/JCacheRegionFactory.java b/hibernate-jcache/src/main/java/org/hibernate/cache/jcache/internal/JCacheRegionFactory.java index 9467505281b3..8606a30aeecc 100644 --- a/hibernate-jcache/src/main/java/org/hibernate/cache/jcache/internal/JCacheRegionFactory.java +++ b/hibernate-jcache/src/main/java/org/hibernate/cache/jcache/internal/JCacheRegionFactory.java @@ -25,11 +25,13 @@ import org.hibernate.cache.jcache.ConfigSettings; import org.hibernate.cache.jcache.MissingCacheStrategy; import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; import org.hibernate.cache.spi.SecondLevelCacheLogger; import org.hibernate.cache.spi.support.DomainDataStorageAccess; import org.hibernate.cache.spi.support.RegionFactoryTemplate; import org.hibernate.cache.spi.support.RegionNameQualifier; import org.hibernate.cache.spi.support.StorageAccess; +import org.hibernate.cache.spi.support.DomainDataRegionImpl; import org.hibernate.engine.spi.SessionFactoryImplementor; /** @@ -60,6 +62,18 @@ protected CacheKeysFactory getImplicitCacheKeysFactory() { return cacheKeysFactory; } + @Override + public DomainDataRegion buildDomainDataRegion( + DomainDataRegionConfig regionConfig, DomainDataRegionBuildingContext buildingContext) { + return new JCacheDomainDataRegionImpl( + regionConfig, + this, + createDomainDataStorageAccess( regionConfig, buildingContext ), + cacheKeysFactory, + buildingContext + ); + } + @Override protected DomainDataStorageAccess createDomainDataStorageAccess( DomainDataRegionConfig regionConfig, diff --git a/hibernate-jcache/src/test/java/org/hibernate/test/cache/jcache/JCacheTransactionalCacheConcurrencyStrategyTest.java b/hibernate-jcache/src/test/java/org/hibernate/test/cache/jcache/JCacheTransactionalCacheConcurrencyStrategyTest.java new file mode 100644 index 000000000000..c5c5719307bc --- /dev/null +++ b/hibernate-jcache/src/test/java/org/hibernate/test/cache/jcache/JCacheTransactionalCacheConcurrencyStrategyTest.java @@ -0,0 +1,160 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.cache.jcache; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.cfg.Environment; + +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +public class JCacheTransactionalCacheConcurrencyStrategyTest + extends BaseNonConfigCoreFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + @Override + protected void addSettings(Map settings) { + settings.put( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + + TestingJtaBootstrap.prepare( settings ); + settings.put( Environment.TRANSACTION_COORDINATOR_STRATEGY, "jta" ); + settings.put( Environment.CACHE_REGION_FACTORY, "jcache" ); + } + + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Entity(name = "Parent") + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + public static class Parent { + + @Id + @GeneratedValue + private Long id; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent") + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + private List children = new ArrayList(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + Child addChild() { + final Child c = new Child(); + c.setParent( this ); + this.children.add( c ); + return c; + } + + } + + @Entity(name = "Child") + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + public static class Child { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne( + fetch = FetchType.LAZY + ) + private Parent parent; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } + + + @Test + public void testTransactional() { + Parent parent = new Parent(); + + doInHibernate( this::sessionFactory, session -> { + for ( int i = 0; i < 2; i++ ) { + parent.addChild(); + + session.persist( parent ); + } + } ); + + doInHibernate( this::sessionFactory, session -> { + sqlStatementInterceptor.getSqlQueries().clear(); + + Parent _parent = session.find( Parent.class, parent.getId() ); + + assertEquals( 0, sqlStatementInterceptor.getSqlQueries().size() ); + + assertEquals( 2, _parent.getChildren().size() ); + } ); + + doInHibernate( this::sessionFactory, session -> { + sqlStatementInterceptor.getSqlQueries().clear(); + + Parent _parent = session.find( Parent.class, parent.getId() ); + + assertEquals( 2, _parent.getChildren().size() ); + + assertEquals( 0, sqlStatementInterceptor.getSqlQueries().size() ); + } ); + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cache/CachingRegionFactory.java b/hibernate-testing/src/main/java/org/hibernate/testing/cache/CachingRegionFactory.java index 294751b14c40..cb00951cff8a 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cache/CachingRegionFactory.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/cache/CachingRegionFactory.java @@ -17,6 +17,7 @@ import org.hibernate.cache.spi.DomainDataRegion; import org.hibernate.cache.spi.support.RegionFactoryTemplate; import org.hibernate.cache.spi.support.StorageAccess; +import org.hibernate.cache.spi.support.DomainDataRegionImpl; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.jboss.logging.Logger; @@ -58,6 +59,7 @@ public DomainDataRegion buildDomainDataRegion( return new DomainDataRegionImpl( regionConfig, this, + new MapStorageAccessImpl(), cacheKeysFactory, buildingContext ); From ff18d904e8bd755c83b915110738af426c3c769a Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 13 Jul 2018 14:43:53 -0700 Subject: [PATCH 033/772] HHH-12802 : test case (cherry picked from commit 2ef777e3fe398ed64bb69eeb48dfaa2d2e121a72) --- .../hibernate/test/id/NonUniqueIdTest.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/id/NonUniqueIdTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/id/NonUniqueIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/id/NonUniqueIdTest.java new file mode 100644 index 000000000000..a9a002703eb7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/id/NonUniqueIdTest.java @@ -0,0 +1,82 @@ +/* + * 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.id; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.HibernateException; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.fail; + +/** + * @author Gail Badner + */ +public class NonUniqueIdTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Category.class }; + } + + @Before + public void setup() { + doInHibernate( + this::sessionFactory, + session -> { + // drop and recreate table so it has no primary key + + session.createNativeQuery( + "DROP TABLE CATEGORY" + ).executeUpdate(); + + session.createNativeQuery( + "create table CATEGORY( id integer not null, name varchar(255) )" + ).executeUpdate(); + + session.createNativeQuery( "insert into CATEGORY( id, name) VALUES( 1, 'clothes' )" ) + .executeUpdate(); + session.createNativeQuery( "insert into CATEGORY( id, name) VALUES( 1, 'shoes' )" ) + .executeUpdate(); + + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12802" ) + public void testLoadEntityWithNonUniqueId() { + doInHibernate( + this::sessionFactory, + session -> { + try { + session.get( Category.class, 1 ); + fail( "should have failed because there are 2 entities with id == 1" ); + } + catch ( HibernateException ex) { + // expected + } + } + ); + } + + @Entity + @Table(name = "CATEGORY") + public static class Category { + @Id + private int id; + + private String name; + } +} From 3af8b6235a706d12b8dcf5c94ba38f91389ac833 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 13 Jul 2018 14:45:11 -0700 Subject: [PATCH 034/772] HHH-12802 : Hibernate does not thrown an exception when more than entity is loaded with the same ID (cherry picked from commit 926ad5a13335ae2b6408c68203bee5198fa8c4d1) --- .../AbstractLoadPlanBasedEntityLoader.java | 29 ++++++++++++++++--- .../BatchingLoadQueryDetailsFactory.java | 4 +-- .../exec/internal/EntityLoadQueryDetails.java | 6 ++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java index 05a613203bd7..2fb8af6e0970 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java @@ -49,7 +49,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan private final Type uniqueKeyType; private final String entityName; - private final LoadQueryDetails staticLoadQuery; + private final EntityLoadQueryDetails staticLoadQuery; public AbstractLoadPlanBasedEntityLoader( OuterJoinLoadable entityPersister, @@ -189,7 +189,7 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract false, null ); - result = extractEntityResult( results ); + result = extractEntityResult( results, id ); } catch ( SQLException sqle ) { throw session.getJdbcServices().getSqlExceptionHelper().convert( @@ -208,14 +208,22 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract return result; } + /** + * @deprecated {@link #extractEntityResult(List, Serializable)} should be used instead. + */ + @Deprecated protected Object extractEntityResult(List results) { + return extractEntityResult( results, null ); + } + + protected Object extractEntityResult(List results, Serializable id) { if ( results.size() == 0 ) { return null; } else if ( results.size() == 1 ) { return results.get( 0 ); } - else { + else if ( staticLoadQuery.hasCollectionInitializers() ) { final Object row = results.get( 0 ); if ( row.getClass().isArray() ) { // the logical type of the result list is List. See if the contained @@ -230,7 +238,20 @@ else if ( results.size() == 1 ) { } } - throw new HibernateException( "Unable to interpret given query results in terms of a load-entity query" ); + if ( id == null ) { + throw new HibernateException( + "Unable to interpret given query results in terms of a load-entity query for " + + entityName + ); + } + else { + throw new HibernateException( + "More than one row with the given identifier was found: " + + id + + ", for class: " + + entityName + ); + } } protected int[] getNamedParameterLocs(String name) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java index 6803b709978b..3b03f72d9ce2 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java @@ -41,7 +41,7 @@ private BatchingLoadQueryDetailsFactory() { * * @return The EntityLoadQueryDetails */ - public LoadQueryDetails makeEntityLoadQueryDetails( + public EntityLoadQueryDetails makeEntityLoadQueryDetails( LoadPlan loadPlan, String[] keyColumnNames, QueryBuildingParameters buildingParameters, @@ -76,7 +76,7 @@ public LoadQueryDetails makeEntityLoadQueryDetails( * that add additional joins here) * @return The EntityLoadQueryDetails */ - public LoadQueryDetails makeEntityLoadQueryDetails( + public EntityLoadQueryDetails makeEntityLoadQueryDetails( EntityLoadQueryDetails entityLoadQueryDetailsTemplate, QueryBuildingParameters buildingParameters) { return new EntityLoadQueryDetails( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java index f7052d5ac8a0..2495fb7f2e72 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java @@ -14,6 +14,7 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.loader.plan.exec.process.internal.AbstractRowReader; import org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl; import org.hibernate.loader.plan.exec.process.internal.EntityReturnReader; @@ -104,6 +105,11 @@ protected EntityLoadQueryDetails( generate(); } + public boolean hasCollectionInitializers() { + return CollectionHelper.isNotEmpty( readerCollector.getArrayReferenceInitializers() ) || + CollectionHelper.isNotEmpty( readerCollector.getNonArrayCollectionReferenceInitializers() ); + } + private EntityReturn getRootEntityReturn() { return (EntityReturn) getRootReturn(); } From 7e69797a450d70b074b0e1d2f4b745d41d295fdc Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 13 Jul 2018 15:51:39 -0700 Subject: [PATCH 035/772] HHH-12802 : Fix test case to recover from exception properly (cherry picked from commit 9202a5a11d56ca6565963f3f9aa59b92421ec5fb) --- .../hibernate/test/id/NonUniqueIdTest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/id/NonUniqueIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/id/NonUniqueIdTest.java index a9a002703eb7..63d62705f50c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/id/NonUniqueIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/id/NonUniqueIdTest.java @@ -57,18 +57,18 @@ public void setup() { @Test @TestForIssue( jiraKey = "HHH-12802" ) public void testLoadEntityWithNonUniqueId() { - doInHibernate( - this::sessionFactory, - session -> { - try { - session.get( Category.class, 1 ); - fail( "should have failed because there are 2 entities with id == 1" ); + try { + doInHibernate( + this::sessionFactory, + session -> { + session.get( Category.class, 1 ); + fail( "should have failed because there are 2 entities with id == 1" ); } - catch ( HibernateException ex) { - // expected - } - } - ); + ); + } + catch ( HibernateException ex) { + // expected + } } @Entity From 9d36d1d91fcc38ed507b1bbf4720505fae766c16 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 13 Jul 2018 14:31:04 +0200 Subject: [PATCH 036/772] HHH-12800 Use a class loading strategy suitable for the JDK used The previously chosen strategy used misc.Unsafe which is not possible anymore with JDK 11. --- .../bytecode/internal/bytebuddy/ByteBuddyState.java | 2 +- .../tuplizer/bytebuddysubclass/MyEntityInstantiator.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index cc79fb90f241..151fb514c263 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -91,7 +91,7 @@ public static ByteBuddy getStaticByteBuddyInstance() { return buddy; } - public static ClassLoadingStrategy resolveClassLoadingStrategy(Class originalClass) { + public static ClassLoadingStrategy resolveClassLoadingStrategy(Class originalClass) { if ( ClassInjector.UsingLookup.isAvailable() ) { // This is only enabled for JDK 9+ diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java index 3cab644e71be..483c84e12d12 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java @@ -8,11 +8,11 @@ import java.io.Serializable; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.mapping.PersistentClass; import org.hibernate.tuple.Instantiator; import net.bytebuddy.ByteBuddy; -import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.matcher.ElementMatchers; @@ -42,7 +42,8 @@ public static E createInstance(Class entityClass) { .method( ElementMatchers.named( "toString" ) ) .intercept( FixedValue.value( "transformed" ) ) .make() - .load( entityClass.getClassLoader(), ClassLoadingStrategy.Default.INJECTION ) + // we use our internal helper to get a class loading strategy suitable for the JDK used + .load( entityClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( entityClass ) ) .getLoaded(); try { From a9e20c18c50a08eaea3bfc5ebb77d0f41e767268 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 13 Jul 2018 14:39:01 +0200 Subject: [PATCH 037/772] HHH-12801 Adjust the assertion to the new message generated by JDK 11 --- ...tionMetadataBuilderContributorIllegalClassArgumentTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java index dab29ade7b13..8c1c331119be 100644 --- a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java @@ -35,7 +35,8 @@ public void buildEntityManagerFactory() { fail("Should throw exception!"); } catch (ClassCastException e) { - assertTrue( e.getMessage().contains( "cannot be cast to org.hibernate.boot.spi.MetadataBuilderContributor" ) ); + assertTrue( e.getMessage().contains( "cannot be cast to" ) ); + assertTrue( e.getMessage().contains( "org.hibernate.boot.spi.MetadataBuilderContributor" ) ); } } From 48c44ef7d006ab44f06946f5693080518e677306 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 14 Jul 2018 11:43:28 +0200 Subject: [PATCH 038/772] HHH-12803 Upgrade ByteBuddy to 1.8.13 --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 2ae19b3d825a..a032a00b9e43 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -23,7 +23,7 @@ ext { weldVersion = '3.0.0.Final' javassistVersion = '3.23.1-GA' - byteBuddyVersion = '1.8.12' // Now with JDK10 compatibility and preliminary support for JDK11 + byteBuddyVersion = '1.8.13' // Now with JDK10 compatibility and preliminary support for JDK11 geolatteVersion = '1.3.0' From 667b56502869e0cb7eec4cd741925f71f22af5a0 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 14 Jul 2018 11:45:56 +0200 Subject: [PATCH 039/772] HHH-12804 Don't mock Map in CollectionBinderTest Apparently, Mockito + ByteBuddy are unable to mock Map on JDK 11. It might be solved in the future but there's no point in doing it so let's avoid it. --- .../cfg/annotations/CollectionBinderTest.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java b/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java index 57204005881a..edc30e455e9f 100644 --- a/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java @@ -6,8 +6,12 @@ */ package org.hibernate.cfg.annotations; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import java.sql.SQLException; -import java.util.Map; +import java.util.HashMap; import org.hibernate.MappingException; import org.hibernate.annotations.common.reflection.XClass; @@ -17,17 +21,11 @@ import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; - import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; - import org.mockito.Mockito; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - /** * Test for HHH-10106 * @@ -39,20 +37,18 @@ public class CollectionBinderTest extends BaseUnitTestCase { @TestForIssue(jiraKey = "HHH-10106") public void testAssociatedClassException() throws SQLException { final Collection collection = mock(Collection.class); - final Map persistentClasses = mock(Map.class); final XClass collectionType = mock(XClass.class); final MetadataBuildingContext buildingContext = mock(MetadataBuildingContext.class); final InFlightMetadataCollector inFly = mock(InFlightMetadataCollector.class); final PersistentClass persistentClass = mock(PersistentClass.class); final Table table = mock(Table.class); - + when(buildingContext.getMetadataCollector()).thenReturn(inFly); - when(persistentClasses.get(null)).thenReturn(null); when(collection.getOwner()).thenReturn(persistentClass); when(collectionType.getName()).thenReturn("List"); when(persistentClass.getTable()).thenReturn(table); when(table.getName()).thenReturn("Hibernate"); - + CollectionBinder collectionBinder = new CollectionBinder(false) { @Override protected Collection createCollection(PersistentClass persistentClass) { @@ -69,7 +65,7 @@ protected Collection createCollection(PersistentClass persistentClass) { String expectMessage = "Association [abc] for entity [CollectionBinderTest] references unmapped class [List]"; try { - collectionBinder.bindOneToManySecondPass(collection, persistentClasses, null, collectionType, false, false, buildingContext, null); + collectionBinder.bindOneToManySecondPass(collection, new HashMap(), null, collectionType, false, false, buildingContext, null); } catch (MappingException e) { assertEquals(expectMessage, e.getMessage()); } From 54f20d828156981ffe994330d907a8f169bbdc4e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 14 Jul 2018 11:59:53 +0200 Subject: [PATCH 040/772] HHH-12805 Upgrade Mockito to 2.19.1 --- gradle/libraries.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index a032a00b9e43..43022e139926 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -120,8 +120,8 @@ ext { informix: 'com.ibm.informix:jdbc:4.10.7.20160517', jboss_jta: "org.jboss.jbossts:jbossjta:4.16.4.Final", xapool: "com.experlog:xapool:1.5.0", - mockito: 'org.mockito:mockito-core:2.19.0', - mockito_inline: 'org.mockito:mockito-inline:2.19.0', + mockito: 'org.mockito:mockito-core:2.19.1', + mockito_inline: 'org.mockito:mockito-inline:2.19.1', validator: "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}", // EL required by Hibernate Validator at test runtime From 103d40101f6eacdbe43fb1fcd386a633e8f78a61 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 14 Jul 2018 13:34:52 +0200 Subject: [PATCH 041/772] HHH-12807 Disable the hibernate-orm-modules tests for JDK 11 --- hibernate-orm-modules/hibernate-orm-modules.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hibernate-orm-modules/hibernate-orm-modules.gradle b/hibernate-orm-modules/hibernate-orm-modules.gradle index 488e423df40b..3bb45c6b2966 100644 --- a/hibernate-orm-modules/hibernate-orm-modules.gradle +++ b/hibernate-orm-modules/hibernate-orm-modules.gradle @@ -39,6 +39,11 @@ ext { fpackStagingDir = file( "target/featurepack" ) //Target build directory for the Feature Pack } +if ( JavaVersion.current().isJava11Compatible() ) { + logger.warn( '[WARN] Skipping all tests for hibernate-orm-modules due to Gradle issues with JDK 11' ) + test.enabled = false +} + description = "Feature Pack of Hibernate ORM modules for WildFly ${project.wildFlyMajorVersion}" configurations { From 05111931a994c45a4fa4df1b3457eb0e58435b52 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 14 Jul 2018 13:39:03 +0200 Subject: [PATCH 042/772] HHH-12808 Upgrade Gradle to 4.8.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index aebe54d8e18a..52bc74a3e0af 100644 --- a/build.gradle +++ b/build.gradle @@ -79,7 +79,7 @@ task ciBuild { wrapper { - gradleVersion = '4.8' + gradleVersion = '4.8.1' distributionType = Wrapper.DistributionType.ALL } From 109481f90369bc6f9227814a3f36aaa1225c95c9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 14 Jul 2018 13:42:12 +0200 Subject: [PATCH 043/772] HHH-12809 Use an HTTP link for the Javadoc link to our Bean Validation documentation --- documentation/documentation.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/documentation.gradle b/documentation/documentation.gradle index 408b0f0f598f..858ecc8d38d8 100644 --- a/documentation/documentation.gradle +++ b/documentation/documentation.gradle @@ -124,7 +124,7 @@ task aggregateJavadocs(type: Javadoc) { links = [ 'https://docs.oracle.com/javase/8/docs/api/', - 'https://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/', + 'http://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/', 'http://docs.jboss.org/cdi/api/2.0/', 'https://javaee.github.io/javaee-spec/javadocs/' ] From e9f9b869c03b669c3a8ea7f819a39118e31ede09 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 15 Jul 2018 15:02:50 +0200 Subject: [PATCH 044/772] HHH-12813 Disable Asciidoclet in Javadoc generation We don't have the certainty that we don't use Asciidoctor Javadoc but using Asciidoclet leads to malformed Javadoc if we are using HTML Javadoc, which concerns the high majority of our files. --- gradle/published-java-module.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/gradle/published-java-module.gradle b/gradle/published-java-module.gradle index 90a3318edad3..7ec133f2d295 100644 --- a/gradle/published-java-module.gradle +++ b/gradle/published-java-module.gradle @@ -87,9 +87,6 @@ javadoc { final int currentYear = new GregorianCalendar().get( Calendar.YEAR ) configure( options ) { - docletpath = configurations.asciidoclet.files.asType(List) - doclet = 'org.asciidoctor.Asciidoclet' - windowTitle = "$project.name JavaDocs" docTitle = "$project.name JavaDocs ($project.version)" bottom = "Copyright © 2001-$currentYear Red Hat, Inc. All Rights Reserved." From 0818195cea27ea17c437937ebf4ccfcdf0185cd9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 15 Jul 2018 15:03:40 +0200 Subject: [PATCH 045/772] HHH-12807 Force to execute the release task with JDK 8 Considering hibernate-orm-modules tests are disabled with JDK 11, it's safer to prevent us from running a release with it. And all in all, we'd better release with JDK 8. --- build.gradle | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.gradle b/build.gradle index 52bc74a3e0af..beb805c4d61b 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,14 @@ task release { description = "The task performed when we are performing a release build. Relies on " + "the fact that subprojects will appropriately define a release task " + "themselves if they have any release-related activities to perform" + + // Force to release with JDK 8. Releasing with JDK 11 is not supported yet: + // - the hibernate-orm-modules tests do not run due to an issue with the ASM version currently used by Gradle + doFirst { + if ( !JavaVersion.current().isJava8() ) { + throw new IllegalStateException( "Please use JDK 8 to perform the release." ) + } + } } task publish { From 297031319d96bf7622ba4ec82df5299af921c3fa Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Jul 2018 15:46:43 +0200 Subject: [PATCH 046/772] HHH-12786 Properly indent the Bytebuddy DSL It helps to understand what exactly these calls do. --- .../internal/bytebuddy/EnhancerImpl.java | 69 ++++++++++--------- .../bytebuddy/BasicProxyFactoryImpl.java | 4 +- .../bytebuddy/BytecodeProviderImpl.java | 8 +-- .../pojo/bytebuddy/ByteBuddyProxyFactory.java | 30 ++++---- 4 files changed, 56 insertions(+), 55 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 0c4d7b641e56..75fe3079c593 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -170,42 +170,42 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes if ( collectCollectionFields( managedCtClass ).isEmpty() ) { builder = builder.implement( SelfDirtinessTracker.class ) .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) - .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( String.class ) + .intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( Advice.to( CodeTemplates.GetDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.AreFieldsDirtyWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( Advice.to( CodeTemplates.AreFieldsDirtyWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.ClearDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( Advice.to( CodeTemplates.ClearDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) - .withParameters( boolean.class ) - .intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( boolean.class ) + .intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetCollectionTrackerWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ); + .intercept( Advice.to( CodeTemplates.GetCollectionTrackerWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ); } else { builder = builder.implement( ExtendedSelfDirtinessTracker.class ) .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, CollectionTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) - .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( String.class ) + .intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( Advice.to( CodeTemplates.GetDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.AreFieldsDirty.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( Advice.to( CodeTemplates.AreFieldsDirty.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.ClearDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( Advice.to( CodeTemplates.ClearDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) - .withParameters( boolean.class ) - .intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( boolean.class ) + .intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) - .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); + .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); Implementation isDirty = StubMethod.INSTANCE, getDirtyNames = StubMethod.INSTANCE, clearDirtyNames = StubMethod.INSTANCE; for ( FieldDescription collectionField : collectCollectionFields( managedCtClass ) ) { @@ -252,13 +252,13 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) .intercept( isDirty ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, void.class, Visibility.PUBLIC ) - .withParameters( DirtyTracker.class ) - .intercept( getDirtyNames ) + .withParameters( DirtyTracker.class ) + .intercept( getDirtyNames ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.withCustomMapping().to( CodeTemplates.ClearDirtyCollectionNames.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( Advice.withCustomMapping().to( CodeTemplates.ClearDirtyCollectionNames.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, void.class, Visibility.PUBLIC ) - .withParameters( LazyAttributeLoadingInterceptor.class ) - .intercept( clearDirtyNames ); + .withParameters( LazyAttributeLoadingInterceptor.class ) + .intercept( clearDirtyNames ); } } @@ -278,21 +278,21 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, void.class, Visibility.PUBLIC ) - .withParameters( String.class, CompositeOwner.class ) - .intercept( Advice.to( CodeTemplates.SetOwner.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( String.class, CompositeOwner.class ) + .intercept( Advice.to( CodeTemplates.SetOwner.class ).wrap( StubMethod.INSTANCE ) ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER, void.class, Visibility.PUBLIC ) - .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.ClearOwner.class ).wrap( StubMethod.INSTANCE ) ); + .withParameters( String.class ) + .intercept( Advice.to( CodeTemplates.ClearOwner.class ).wrap( StubMethod.INSTANCE ) ); } return transformer.applyTo( builder, false ); @@ -348,13 +348,14 @@ private static DynamicType.Builder addFieldWithGetterAndSetter( String fieldName, String getterName, String setterName) { - return builder.defineField( fieldName, type, Visibility.PRIVATE, FieldPersistence.TRANSIENT ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + return builder + .defineField( fieldName, type, Visibility.PRIVATE, FieldPersistence.TRANSIENT ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( getterName, type, Visibility.PUBLIC ) - .intercept( FieldAccessor.ofField( fieldName ) ) + .intercept( FieldAccessor.ofField( fieldName ) ) .defineMethod( setterName, void.class, Visibility.PUBLIC ) - .withParameters( type ) - .intercept( FieldAccessor.ofField( fieldName ) ); + .withParameters( type ) + .intercept( FieldAccessor.ofField( fieldName ) ); } private List collectCollectionFields(TypeDescription managedCtClass) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index 600d375e4240..ef080f4eff94 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -39,9 +39,9 @@ public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyStat .implement( interfaces == null ? NO_INTERFACES : interfaces ) .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) .method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) ) - .intercept( MethodDelegation.toField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) ) + .intercept( MethodDelegation.toField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) ) .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) + .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) .make() .load( superClassOrMainInterface.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( superClassOrMainInterface ) ) .getLoaded(); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index d89a75d73241..68422d10afd9 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -66,7 +66,7 @@ public ReflectionOptimizer getReflectionOptimizer( new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) .subclass( ReflectionOptimizer.InstantiationOptimizer.class ) .method( newInstanceMethodName ) - .intercept( MethodCall.construct( constructor ) ) + .intercept( MethodCall.construct( constructor ) ) .make() .load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) ) .getLoaded(); @@ -84,11 +84,11 @@ public ReflectionOptimizer getReflectionOptimizer( new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) .subclass( ReflectionOptimizer.AccessOptimizer.class ) .method( getPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) ) + .intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) ) .method( setPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) ) + .intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) ) .method( getPropertyNamesMethodName ) - .intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) ) + .intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) ) .make() .load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) ) .getLoaded(); diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java index 0f8d0cd753ec..4c4173b4201f 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java @@ -98,21 +98,21 @@ public static Class buildProxy( final TypeCache cacheForProxies = ByteBuddyState.getCacheForProxies(); return cacheForProxies.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () -> - ByteBuddyState.getStaticByteBuddyInstance() - .ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) ) - .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) - .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) - .implement( (Type[]) interfaces ) - .method( isVirtual().and( not( isFinalizer() ) ) ) - .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) - .method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) ) - .intercept( SuperMethodCall.INSTANCE ) - .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) - .make() - .load( persistentClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( persistentClass ) ) - .getLoaded(), cacheForProxies ); + ByteBuddyState.getStaticByteBuddyInstance() + .ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) ) + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) + .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) + .implement( (Type[]) interfaces ) + .method( isVirtual().and( not( isFinalizer() ) ) ) + .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) + .method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) ) + .intercept( SuperMethodCall.INSTANCE ) + .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) + .implement( ProxyConfiguration.class ) + .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) + .make() + .load( persistentClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( persistentClass ) ) + .getLoaded(), cacheForProxies ); } @Override From 0fda6be86e385658ddff76245a29631840e77da4 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Jul 2018 18:43:19 +0200 Subject: [PATCH 047/772] HHH-12786 Allow to call methods when the interceptor is not set yet Typically, if the constructor calls instrumented methods, the interceptor is not defined yet and we get a NPE. --- .../bytebuddy/BasicProxyFactoryImpl.java | 3 +- .../hibernate/test/component/proxy/Adult.java | 21 ++++++ .../proxy/ComponentBasicProxyTest.java | 47 +++++++++++++ .../test/component/proxy/Person.java | 68 +++++++++++++++++++ .../test/component/proxy/PersonId.java | 42 ++++++++++++ 5 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/component/proxy/Adult.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/component/proxy/ComponentBasicProxyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/component/proxy/Person.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/component/proxy/PersonId.java diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index ef080f4eff94..581a3efd37e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -26,6 +26,7 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory { private final Class proxyClass; + @SuppressWarnings("unchecked") public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState bytebuddy) { if ( superClass == null && ( interfaces == null || interfaces.length < 1 ) ) { throw new AssertionFailure( "attempting to build proxy without any superclass or interfaces" ); @@ -39,7 +40,7 @@ public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyStat .implement( interfaces == null ? NO_INTERFACES : interfaces ) .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) .method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) ) - .intercept( MethodDelegation.toField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) ) + .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) .implement( ProxyConfiguration.class ) .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) .make() diff --git a/hibernate-core/src/test/java/org/hibernate/test/component/proxy/Adult.java b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/Adult.java new file mode 100644 index 000000000000..027f8c4fe4fd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/Adult.java @@ -0,0 +1,21 @@ +/* + * 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.component.proxy; + +import javax.persistence.Entity; + +@Entity +public class Adult extends Person { + + public Adult() { + someInitMethod(); + } + + @Override + public void someInitMethod() { + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/component/proxy/ComponentBasicProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/ComponentBasicProxyTest.java new file mode 100644 index 000000000000..59debe8ff637 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/ComponentBasicProxyTest.java @@ -0,0 +1,47 @@ +/* + * 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.component.proxy; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +import java.util.List; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +/** + * @author Guillaume Smet + * @author Oliver Libutzki + */ +public class ComponentBasicProxyTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Person.class, Adult.class + }; + } + + @Test + @TestForIssue(jiraKey = "HHH-12786") + public void testBasicProxyingWithProtectedMethodCalledInConstructor() { + doInJPA( this::entityManagerFactory, entityManager -> { + Adult adult = new Adult(); + adult.setName( "Arjun Kumar" ); + entityManager.persist( adult ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + List adultsCalledArjun = entityManager + .createQuery( "SELECT a from Adult a WHERE a.name = :name", Adult.class ) + .setParameter( "name", "Arjun Kumar" ).getResultList(); + Adult adult = adultsCalledArjun.iterator().next(); + entityManager.remove( adult ); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/component/proxy/Person.java b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/Person.java new file mode 100644 index 000000000000..7d74686429f6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/Person.java @@ -0,0 +1,68 @@ +/* + * 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.component.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Table; + +@Entity +@Table(name = "person") +@IdClass(PersonId.class) +public abstract class Person { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private int id; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "clientId") + private int clientId; + + @Column(name = "name") + private String name; + + @Column(name = "title") + private String title; + + public Person() { + someInitMethod(); + } + + public void someInitMethod() { + } + + public int getId() { + return id; + } + + public int getClientId() { + return clientId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTitle(String name) { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/component/proxy/PersonId.java b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/PersonId.java new file mode 100644 index 000000000000..a1ce7edb2a74 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/PersonId.java @@ -0,0 +1,42 @@ +/* + * 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.component.proxy; + +import java.io.Serializable; + +@SuppressWarnings("all") +public class PersonId implements Serializable { + + private int id; + + private int clientId; + + public PersonId() { + } + + public PersonId(int aId, int aClientId) { + setId( aId ); + setClientId( aClientId ); + } + + public int getId() { + return id; + } + + public void setId(int aId) { + this.id = aId; + } + + public int getClientId() { + + return clientId; + } + + public void setClientId(int aClientId) { + clientId = aClientId; + } +} From 67698b8bdbbafe8fb4b11c1372dbe3a2b17a9b80 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Jul 2018 18:44:29 +0200 Subject: [PATCH 048/772] HHH-12786 Only define the default constructor We don't need the others, better not create them in the proxy. --- .../bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index 581a3efd37e3..15f910453454 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -14,6 +14,7 @@ import net.bytebuddy.NamingStrategy; import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bytecode.assign.Assigner; @@ -36,7 +37,7 @@ public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyStat this.proxyClass = bytebuddy.getCurrentyByteBuddy() .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( superClassOrMainInterface.getName() ) ) ) - .subclass( superClass == null ? Object.class : superClass ) + .subclass( superClass == null ? Object.class : superClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR ) .implement( interfaces == null ? NO_INTERFACES : interfaces ) .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) .method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) ) From 1688c3ff8d735b4bcdfeed0951e4cd571f5f5025 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Jul 2018 18:51:06 +0200 Subject: [PATCH 049/772] HHH-12786 Improve the basic proxy interceptor Apart from cosmetic changes, we were testing in the equals() method that the instance == the proxied object which will always be true. We should use the argument of the equals() method instead to do the comparison. And we can do the comparison on the instance, instead of requiring passing the proxiedObject into the interceptor. --- .../bytebuddy/BasicProxyFactoryImpl.java | 4 +- .../bytebuddy/PassThroughInterceptor.java | 53 +++++------ .../ByteBuddyBasicProxyFactoryTest.java | 92 +++++++++++++++++++ 3 files changed, 122 insertions(+), 27 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/proxy/bytebuddy/ByteBuddyBasicProxyFactoryTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index 15f910453454..d9fe8a10ef2b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -26,6 +26,7 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory { private static final String PROXY_NAMING_SUFFIX = Environment.useLegacyProxyClassnames() ? "HibernateBasicProxy$" : "HibernateBasicProxy"; private final Class proxyClass; + private final ProxyConfiguration.Interceptor interceptor; @SuppressWarnings("unchecked") public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState bytebuddy) { @@ -47,12 +48,13 @@ public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyStat .make() .load( superClassOrMainInterface.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( superClassOrMainInterface ) ) .getLoaded(); + this.interceptor = new PassThroughInterceptor( proxyClass.getName() ); } public Object getProxy() { try { final ProxyConfiguration proxy = (ProxyConfiguration) proxyClass.newInstance(); - proxy.$$_hibernate_set_interceptor( new PassThroughInterceptor( proxy, proxyClass.getName() ) ); + proxy.$$_hibernate_set_interceptor( this.interceptor ); return proxy; } catch (Throwable t) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java index 7d45e2c6cd38..09b108a148e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java @@ -11,57 +11,58 @@ import org.hibernate.proxy.ProxyConfiguration; -import net.bytebuddy.implementation.bind.annotation.AllArguments; -import net.bytebuddy.implementation.bind.annotation.Origin; -import net.bytebuddy.implementation.bind.annotation.RuntimeType; -import net.bytebuddy.implementation.bind.annotation.This; - public class PassThroughInterceptor implements ProxyConfiguration.Interceptor { - private HashMap data = new HashMap(); - private final Object proxiedObject; + private HashMap data = new HashMap<>(); private final String proxiedClassName; - public PassThroughInterceptor(Object proxiedObject, String proxiedClassName) { - this.proxiedObject = proxiedObject; + public PassThroughInterceptor(String proxiedClassName) { this.proxiedClassName = proxiedClassName; } - @SuppressWarnings("unchecked") @Override public Object intercept(Object instance, Method method, Object[] arguments) throws Exception { final String name = method.getName(); - if ( "toString".equals( name ) ) { + + if ( "toString".equals( name ) && arguments.length == 0 ) { return proxiedClassName + "@" + System.identityHashCode( instance ); } - else if ( "equals".equals( name ) ) { - return proxiedObject == instance; + + if ( "equals".equals( name ) && arguments.length == 1 ) { + return instance == arguments[0]; } - else if ( "hashCode".equals( name ) ) { + + if ( "hashCode".equals( name ) && arguments.length == 0 ) { return System.identityHashCode( instance ); } - final boolean hasGetterSignature = method.getParameterCount() == 0 - && method.getReturnType() != null; - final boolean hasSetterSignature = method.getParameterCount() == 1 - && ( method.getReturnType() == null || method.getReturnType() == void.class ); - - if ( name.startsWith( "get" ) && hasGetterSignature ) { + if ( name.startsWith( "get" ) && hasGetterSignature( method ) ) { final String propName = name.substring( 3 ); return data.get( propName ); } - else if ( name.startsWith( "is" ) && hasGetterSignature ) { + + if ( name.startsWith( "is" ) && hasGetterSignature( method ) ) { final String propName = name.substring( 2 ); return data.get( propName ); } - else if ( name.startsWith( "set" ) && hasSetterSignature ) { + + if ( name.startsWith( "set" ) && hasSetterSignature( method ) ) { final String propName = name.substring( 3 ); data.put( propName, arguments[0] ); return null; } - else { - // todo : what else to do here? - return null; - } + + // todo : what else to do here? + return null; + } + + private boolean hasGetterSignature(Method method) { + return method.getParameterCount() == 0 + && method.getReturnType() != null; + } + + private boolean hasSetterSignature(Method method) { + return method.getParameterCount() == 1 + && ( method.getReturnType() == null || method.getReturnType() == void.class ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/proxy/bytebuddy/ByteBuddyBasicProxyFactoryTest.java b/hibernate-core/src/test/java/org/hibernate/test/proxy/bytebuddy/ByteBuddyBasicProxyFactoryTest.java new file mode 100644 index 000000000000..8caeac93d42f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/proxy/bytebuddy/ByteBuddyBasicProxyFactoryTest.java @@ -0,0 +1,92 @@ +/* + * 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.proxy.bytebuddy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.hibernate.bytecode.internal.bytebuddy.BasicProxyFactoryImpl; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-12786") +public class ByteBuddyBasicProxyFactoryTest { + + private static final BasicProxyFactoryImpl BASIC_PROXY_FACTORY = new BasicProxyFactoryImpl( Entity.class, new Class[0], new ByteBuddyState() ); + + @Test + public void testEqualsHashCode() { + Object entityProxy = BASIC_PROXY_FACTORY.getProxy(); + + assertTrue( entityProxy.equals( entityProxy ) ); + assertNotNull( entityProxy.hashCode() ); + + Object otherEntityProxy = BASIC_PROXY_FACTORY.getProxy(); + assertFalse( entityProxy.equals( otherEntityProxy ) ); + } + + @Test + public void testToString() { + Object entityProxy = BASIC_PROXY_FACTORY.getProxy(); + + assertTrue( entityProxy.toString().contains( "HibernateBasicProxy" ) ); + } + + @Test + public void testGetterSetter() { + Entity entityProxy = (Entity) BASIC_PROXY_FACTORY.getProxy(); + + entityProxy.setBool( true ); + assertTrue( entityProxy.isBool() ); + entityProxy.setBool( false ); + assertFalse( entityProxy.isBool() ); + + entityProxy.setString( "John Irving" ); + assertEquals( "John Irving", entityProxy.getString() ); + } + + @Test + public void testNonGetterSetterMethod() { + Entity entityProxy = (Entity) BASIC_PROXY_FACTORY.getProxy(); + + assertNull( entityProxy.otherMethod() ); + } + + public static class Entity { + + private String string; + + private boolean bool; + + public Entity() { + } + + public boolean isBool() { + return bool; + } + + public void setBool(boolean bool) { + this.bool = bool; + } + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + } + + public String otherMethod() { + return "a string"; + } + } +} From e4ae86cce27808a0ae5ed942d16d8adc069414a3 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 15 Jul 2018 15:06:27 +0200 Subject: [PATCH 050/772] HHH-12811 Add @Target annotations to @CreationTimestamp and @UpdateTimestamp --- .../java/org/hibernate/annotations/CreationTimestamp.java | 7 ++++++- .../java/org/hibernate/annotations/UpdateTimestamp.java | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java b/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java index 9b5405f1de92..b780103ee073 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java @@ -6,10 +6,14 @@ */ package org.hibernate.annotations; -import org.hibernate.tuple.CreationTimestampGeneration; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.tuple.CreationTimestampGeneration; /** * Marks a property as the creation timestamp of the containing entity. The property value will be set to the current @@ -38,5 +42,6 @@ */ @ValueGenerationType(generatedBy = CreationTimestampGeneration.class) @Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD }) public @interface CreationTimestamp { } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java b/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java index c1635886da95..7e442f9e1c14 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java @@ -6,10 +6,14 @@ */ package org.hibernate.annotations; -import org.hibernate.tuple.UpdateTimestampGeneration; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.tuple.UpdateTimestampGeneration; /** * Marks a property as the update timestamp of the containing entity. The property value will be set to the current VM @@ -38,5 +42,6 @@ */ @ValueGenerationType(generatedBy = UpdateTimestampGeneration.class) @Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD }) public @interface UpdateTimestamp { } From 9c0746d2cafeb2643ba961a87e8d68a0ac37fc92 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 13 Jul 2018 11:24:05 +0100 Subject: [PATCH 051/772] HHH-12742 Add the removal of JpaIntegrator to the migration guide --- migration-guide.adoc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/migration-guide.adoc b/migration-guide.adoc index 6107462220df..95fc7ac16db7 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -123,6 +123,17 @@ be lazily fetched, as expected. See details on the https://hibernate.atlassian.net/browse/HHH-12687[HHH-12687] Jira issue. +=== JpaIntegrator removed + +JPA and native implementations of Hibernate event listeners were unified (see https://hibernate.atlassian.net/browse/HHH-11264) +making the `org.hibernate.jpa.event.spi.JpaIntegrator` no longer needed. + +[NOTE] +==== +Existing applications migrating to 5.3 with classes extending `org.hibernate.jpa.event.spi.JpaIntegrator` have to change these classes to implement the `org.hibernate.integrator.spi.Integrator` interface. +==== + + === 5.3 -> 6.0 compatibility changes The original driving force behind these series of changes is an effort to be as proactive as possible From 36fcadaa6a19c49f1ba101d7d6e4f8cef31f4bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jul 2018 09:44:46 +0200 Subject: [PATCH 052/772] HHH-12795 Use the exact flush mode specified in @NamedQuery/@NamedNativeQuery when instantiating named queries ... instead of using an approximation in terms of JPA flush mode. --- .../java/org/hibernate/internal/SessionImpl.java | 7 +------ .../hibernate/jpa/test/query/AddNamedQueryTest.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index dca0c0a0ea90..de441c2b044b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -3776,12 +3776,7 @@ protected void initQueryFromNamedDefinition(Query query, NamedQueryDefinition na } if ( namedQueryDefinition.getFlushMode() != null ) { - if ( namedQueryDefinition.getFlushMode() == FlushMode.COMMIT ) { - query.setFlushMode( FlushModeType.COMMIT ); - } - else { - query.setFlushMode( FlushModeType.AUTO ); - } + query.setHibernateFlushMode( namedQueryDefinition.getFlushMode() ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java index f46e7b9eb0ab..5921fa508cd7 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java @@ -121,7 +121,8 @@ public void testConfigValueHandling() { // NOTE: here we check "query options" via the Hibernate contract (allowing nullness checking); see below for access via the JPA contract assertNull( hibernateQuery.getQueryOptions().getFirstRow() ); assertNull( hibernateQuery.getQueryOptions().getMaxRows() ); - assertEquals( FlushMode.AUTO, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); // jpa timeout is in milliseconds, whereas Hibernate's is in seconds @@ -137,7 +138,8 @@ public void testConfigValueHandling() { // NOTE: here we check "query options" via the JPA contract assertEquals( 0, hibernateQuery.getFirstResult() ); assertEquals( Integer.MAX_VALUE, hibernateQuery.getMaxResults() ); - assertEquals( FlushModeType.AUTO, hibernateQuery.getFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); assertEquals( (Integer) 10, hibernateQuery.getTimeout() ); @@ -150,7 +152,8 @@ public void testConfigValueHandling() { // assert the state of the query config settings based on the initial named query assertEquals( 0, hibernateQuery.getFirstResult() ); assertEquals( Integer.MAX_VALUE, hibernateQuery.getMaxResults() ); - assertEquals( FlushModeType.AUTO, hibernateQuery.getFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); assertEquals( (Integer) 10, hibernateQuery.getTimeout() ); @@ -163,7 +166,8 @@ public void testConfigValueHandling() { // assert the state of the query config settings based on the initial named query assertEquals( 51, hibernateQuery.getFirstResult() ); assertEquals( Integer.MAX_VALUE, hibernateQuery.getMaxResults() ); - assertEquals( FlushModeType.AUTO, hibernateQuery.getFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); assertEquals( (Integer) 10, hibernateQuery.getTimeout() ); From af427cb6c5485ff9e5ecd857ec1ae3f70044c4f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jul 2018 09:43:09 +0200 Subject: [PATCH 053/772] HHH-12795 Test the flushMode attribute of @NamedQuery and @NamedNativeQuery --- .../test/query/NamedQueryFlushModeTest.java | 257 ++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java new file mode 100644 index 000000000000..30d52f4f3811 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java @@ -0,0 +1,257 @@ +/* + * 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.query; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.FlushMode; +import org.hibernate.annotations.FlushModeType; +import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.annotations.NamedQuery; +import org.hibernate.query.NativeQuery; +import org.hibernate.query.Query; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Yoann Rodiere + */ +@TestForIssue(jiraKey = "HHH-12795") +public class NamedQueryFlushModeTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Test + public void testNamedQueryWithFlushModeManual() { + String queryName = "NamedQueryFlushModeManual"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.MANUAL, query.getHibernateFlushMode() ); + // JPA flush mode is an approximation + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModeCommit() { + String queryName = "NamedQueryFlushModeCommit"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.COMMIT, query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModeAuto() { + String queryName = "NamedQueryFlushModeAuto"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.AUTO, query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModeAlways() { + String queryName = "NamedQueryFlushModeAlways"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.ALWAYS, query.getHibernateFlushMode() ); + // JPA flush mode is an approximation + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModePersistenceContext() { + String queryName = "NamedQueryFlushModePersistenceContext"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query; + + // A null Hibernate flush mode means we will use whatever mode is set on the session + // JPA doesn't allow null flush modes, so we expect some approximation of the flush mode to be returned + + s.setHibernateFlushMode( FlushMode.MANUAL ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.COMMIT ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.AUTO ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.ALWAYS ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeManual() { + String queryName = "NamedNativeQueryFlushModeManual"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.MANUAL, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeCommit() { + String queryName = "NamedNativeQueryFlushModeCommit"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.COMMIT, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeAuto() { + String queryName = "NamedNativeQueryFlushModeAuto"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.AUTO, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeAlways() { + String queryName = "NamedNativeQueryFlushModeAlways"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.ALWAYS, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModePersistenceContext() { + String queryName = "NamedNativeQueryFlushModePersistenceContext"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query; + + // A null Hibernate flush mode means we will use whatever mode is set on the session + // JPA doesn't allow null flush modes, so we expect some approximation of the flush mode to be returned + + s.setHibernateFlushMode( FlushMode.MANUAL ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.COMMIT ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.AUTO ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.ALWAYS ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Entity(name = "TestEntity") + @NamedQuery( + name = "NamedQueryFlushModeManual", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.MANUAL + ) + @NamedQuery( + name = "NamedQueryFlushModeCommit", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.COMMIT + ) + @NamedQuery( + name = "NamedQueryFlushModeAuto", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.AUTO + ) + @NamedQuery( + name = "NamedQueryFlushModeAlways", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.ALWAYS + ) + @NamedQuery( + name = "NamedQueryFlushModePersistenceContext", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.PERSISTENCE_CONTEXT + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeManual", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.MANUAL + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeCommit", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.COMMIT + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeAuto", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.AUTO + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeAlways", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.ALWAYS + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModePersistenceContext", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.PERSISTENCE_CONTEXT + ) + public static class TestEntity { + + @Id + @GeneratedValue + private Long id; + + private String text; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } +} From 0b92eb528df18d41dcc49810339a738f460a8a8a Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 10 Jul 2018 19:25:08 -0700 Subject: [PATCH 054/772] HHH-12773 : Document org.hibernate.Query.getHibernateFirstResult(), setHibernateFirstResult(), getHibernateMaxResults(), setHibernateMaxResults() in migration guide --- migration-guide.adoc | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/migration-guide.adoc b/migration-guide.adoc index 95fc7ac16db7..4cec430b2f5b 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -75,6 +75,39 @@ did not implement "correctly" anyway The change for HHH-11356 required changes in its consumers. One such consumer is the Hibernate Statistics system.... +=== Native (non-JPA) Pagination changes + +When hibernate-entitymanager module was merged into hibernate-core in Hibernate ORM 5.2, the contracts for the +following pre-existing `org.hibernate.Query` methods were changed to be specified by JPA `javax.persistence.Query` +methods: `getFirstResult()`, `setFirstResult(int)`, `getMaxResults()`, and `setMaxResults()`. + +In 5.3.2, the following methods were temporarily added to +http://docs.jboss.org/hibernate/orm/5.3/javadocs/org/hibernate/Query.html[`org.hibernate.Query`] to make it +easier to migrate native applications from Hibernate ORM 5.1 to 5.3, and maintain 5.1 pagination behavior: + +* http://docs.jboss.org/hibernate/orm/5.3/javadocs/org/hibernate/Query.html#setHibernateFirstResult-int-[`setHibernateFirstResult(int)`] +behaves the same as the 5.1 version of `setFirstResult()`, with a small difference -- calling +`setHibernateFirstResult(int)` with a negative value will result in `getHibernateFirstResult()` returning 0, instead +of the negative value. Note that this behavior differs from JPA in that `javax.persistence.Query.setFirstResult(int)` +throws `IllegalArgumentException` when called with a negative value. +* http://docs.jboss.org/hibernate/orm/5.3/javadocs/org/hibernate/Query.html#getHibernateFirstResult--[`getHibernateFirstResult()`] +behaves the same as the 5.1 version of `getFirstResult()`, except for the difference mentioned above when `setHibernateFirstResult(int)` is called with a negative number. This method returns `null` if no value was set +via `setHibernateFirstResult(int)` (or `setFirstResult(int)`). Note that this behavior differs from JPA in that +`javax.persistence.Query.getFirstResult()` returns 0 when uninitialized. +* http://docs.jboss.org/hibernate/orm/5.3/javadocs/org/hibernate/Query.html#setHibernateMaxResults-int-[`setHibernateMaxResults`] +behaves the same as the 5.1 version of `setMaxResults()`; setting a value less than or equal to 0 is +considered uninitialized, resulting in *no limit* on the number of results. Note that this behavior differs +from JPA `javax.persistence.Query.setMaxResults(int)`, which, when called with a negative value, +throws `IllegalArgumentException`, and when called with a value of 0, indicates that the query should return +*no results*. +* http://docs.jboss.org/hibernate/orm/5.3/javadocs/org/hibernate/Query.html#getHibernateMaxResults--[`getHibernateMaxResults`] +behaves the same as the 5.1 version of `#getMaxResults`. This method returns `null` if uninitialized or +a value less than or equal to 0 was set via `setHibernateMaxResults(int)` (or `setMaxResults(int)`). +Note that this behavior differs from JPA in that `javax.persistence.Query.getMaxResults() returns +`Integer.MAX_VALUE` when uninitialized. + +These methods are deprecated, and will be removed in a future version. To be portable with future Hibernate +versions, applications should be changed to use the JPA methods. === Drop hibernate-infinispan module @@ -139,7 +172,6 @@ Existing applications migrating to 5.3 with classes extending `org.hibernate.jpa The original driving force behind these series of changes is an effort to be as proactive as possible about designing compatibility between 5.3 and 6.0. - ==== Type system changes -Use of NavigableRole, back-ported from 6.0 rather than plain String \ No newline at end of file +Use of NavigableRole, back-ported from 6.0 rather than plain String From a180caecd68dbe992af2f7c3c65916ffb6f4d23a Mon Sep 17 00:00:00 2001 From: Ulrich Bestfleisch Date: Thu, 21 Jun 2018 14:45:44 +0200 Subject: [PATCH 055/772] HHH-12718 - Entity changes in @PreUpdate callback are not persisted when lazy loading is active for more than one field --- .../DefaultFlushEntityEventListener.java | 17 +- .../PreUpdateBytecodeEnhancementTest.java | 127 ++++++++++++ ...dateCustomEntityDirtinessStrategyTest.java | 180 ++++++++++++++++++ ...PreUpdateDirtyCheckingInterceptorTest.java | 155 +++++++++++++++ 4 files changed, 473 insertions(+), 6 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java index 93694ebae0e5..8e26a83c91bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java @@ -332,15 +332,20 @@ protected boolean handleInterception(FlushEntityEvent event) { final boolean intercepted = invokeInterceptor( session, entity, entry, values, persister ); //now we might need to recalculate the dirtyProperties array - if ( intercepted && event.isDirtyCheckPossible() && !event.isDirtyCheckHandledByInterceptor() ) { - int[] dirtyProperties; - if ( event.hasDatabaseSnapshot() ) { - dirtyProperties = persister.findModified( event.getDatabaseSnapshot(), values, entity, session ); + if ( intercepted && event.isDirtyCheckPossible() ) { + if ( !event.isDirtyCheckHandledByInterceptor() ) { + int[] dirtyProperties; + if ( event.hasDatabaseSnapshot() ) { + dirtyProperties = persister.findModified( event.getDatabaseSnapshot(), values, entity, session ); + } + else { + dirtyProperties = persister.findDirty( values, entry.getLoadedState(), entity, session ); + } + event.setDirtyProperties( dirtyProperties ); } else { - dirtyProperties = persister.findDirty( values, entry.getLoadedState(), entity, session ); + dirtyCheck( event ); } - event.setDirtyProperties( dirtyProperties ); } return intercepted; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java new file mode 100644 index 000000000000..0e9ab1b32151 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java @@ -0,0 +1,127 @@ +/* + * 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.callbacks; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue(jiraKey = "HHH-12718") +@RunWith(BytecodeEnhancerRunner.class) +public class PreUpdateBytecodeEnhancementTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.CLASSLOADERS, getClass().getClassLoader() ); + options.put( AvailableSettings.ENHANCER_ENABLE_LAZY_INITIALIZATION, "true" ); + options.put( AvailableSettings.ENHANCER_ENABLE_DIRTY_TRACKING, "true" ); + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p ); + assertNotNull( p.createdAt ); + assertNull( p.lastUpdatedAt ); + + p.setName( "Changed Name" ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p.lastUpdatedAt ); + } ); + } + + @Entity(name = "Person") + private static class Person { + @Id + @GeneratedValue + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant createdAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @ElementCollection + private List tags; + + @Lob + @Basic(fetch = FetchType.LAZY) + private ByteBuffer image; + + @PrePersist + void beforeCreate() { + this.setCreatedAt( Instant.now() ); + } + + @PreUpdate + void beforeUpdate() { + this.setLastUpdatedAt( Instant.now() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java new file mode 100644 index 000000000000..bb45944c8112 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java @@ -0,0 +1,180 @@ +/* + * 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.callbacks; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import org.hibernate.CustomEntityDirtinessStrategy; +import org.hibernate.Session; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.persister.entity.EntityPersister; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-12718") +public class PreUpdateCustomEntityDirtinessStrategyTest + extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + + doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p ); + assertNotNull( p.createdAt ); + assertNull( p.lastUpdatedAt ); + + p.setName( "Changed Name" ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p.lastUpdatedAt ); + } ); + + assertTrue( DefaultCustomEntityDirtinessStrategy.INSTANCE.isPersonNameChanged() ); + assertTrue( DefaultCustomEntityDirtinessStrategy.INSTANCE.isPersonLastUpdatedAtChanged() ); + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY, DefaultCustomEntityDirtinessStrategy.INSTANCE ); + } + + public static class DefaultCustomEntityDirtinessStrategy + implements CustomEntityDirtinessStrategy { + private static final DefaultCustomEntityDirtinessStrategy INSTANCE = + new DefaultCustomEntityDirtinessStrategy(); + + private boolean personNameChanged = false; + private boolean personLastUpdatedAtChanged = false; + + @Override + public boolean canDirtyCheck(Object entity, EntityPersister persister, Session session) { + return true; + } + + @Override + public boolean isDirty(Object entity, EntityPersister persister, Session session) { + Person person = (Person) entity; + if ( !personNameChanged ) { + personNameChanged = person.getName() != null; + return personNameChanged; + } + if ( !personLastUpdatedAtChanged ) { + personLastUpdatedAtChanged = person.getLastUpdatedAt() != null; + return personLastUpdatedAtChanged; + } + return false; + } + + @Override + public void resetDirty(Object entity, EntityPersister persister, Session session) { + } + + @Override + public void findDirty( + Object entity, + EntityPersister persister, + Session session, + DirtyCheckContext dirtyCheckContext) { + } + + public boolean isPersonNameChanged() { + return personNameChanged; + } + + public boolean isPersonLastUpdatedAtChanged() { + return personLastUpdatedAtChanged; + } + } + + @Entity(name = "Person") + @DynamicUpdate + private static class Person { + @Id + @GeneratedValue + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant createdAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @ElementCollection + private List tags; + + @Lob + @Basic(fetch = FetchType.LAZY) + private ByteBuffer image; + + @PrePersist + void beforeCreate() { + this.setCreatedAt( Instant.now() ); + } + + @PreUpdate + void beforeUpdate() { + this.setLastUpdatedAt( Instant.now() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java new file mode 100644 index 000000000000..93a3f8069721 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java @@ -0,0 +1,155 @@ +/* + * 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.callbacks; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import org.hibernate.EmptyInterceptor; +import org.hibernate.SessionBuilder; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.type.Type; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernateSessionBuilder; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue(jiraKey = "HHH-12718") +public class PreUpdateDirtyCheckingInterceptorTest + extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + + doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + doInHibernateSessionBuilder( this::sessionWithInterceptor, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p ); + assertNotNull( p.createdAt ); + assertNull( p.lastUpdatedAt ); + + p.setName( "Changed Name" ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p.lastUpdatedAt ); + } ); + } + + public static class OnFlushDirtyInterceptor extends EmptyInterceptor { + + private static OnFlushDirtyInterceptor INSTANCE = new OnFlushDirtyInterceptor(); + + @Override + public int[] findDirty( + Object entity, + Serializable id, + Object[] currentState, + Object[] previousState, + String[] propertyNames, + Type[] types) { + int[] result = new int[propertyNames.length]; + int span = 0; + + for ( int i = 0; i < previousState.length; i++ ) { + if( !Objects.deepEquals(previousState[i], currentState[i])) { + result[span++] = i; + } + } + + return result; + } + } + + private SessionBuilder sessionWithInterceptor() { + return sessionFactory() + .withOptions() + .interceptor( OnFlushDirtyInterceptor.INSTANCE ); + } + + @Entity(name = "Person") + @DynamicUpdate + private static class Person { + @Id + @GeneratedValue + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant createdAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @ElementCollection + private List tags; + + @Lob + @Basic(fetch = FetchType.LAZY) + private ByteBuffer image; + + @PrePersist + void beforeCreate() { + this.setCreatedAt( Instant.now() ); + } + + @PreUpdate + void beforeUpdate() { + this.setLastUpdatedAt( Instant.now() ); + } + } +} From 47107e3e889c96467a7f0fd7fc7179f182b4a91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jul 2018 16:27:58 +0200 Subject: [PATCH 056/772] HHH-12718 Test that dirtiness strategies are invoked a second time after a flush interceptor changes the entity state Bytecode enhancement is harder to test, so I didn't add a test for that, but since bytecode enhancement dirty checking is called exactly at the same place, if one works, the other should, too. --- .../CustomDirtinessStrategyTest.java | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/dirtiness/CustomDirtinessStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/test/dirtiness/CustomDirtinessStrategyTest.java index facf3ceb7e13..7f28595a017d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dirtiness/CustomDirtinessStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/dirtiness/CustomDirtinessStrategyTest.java @@ -6,14 +6,21 @@ */ package org.hibernate.test.dirtiness; -import org.junit.Test; +import java.io.Serializable; import org.hibernate.CustomEntityDirtinessStrategy; +import org.hibernate.EmptyInterceptor; import org.hibernate.Session; +import org.hibernate.SessionBuilder; +import org.hibernate.SessionFactory; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.Type; + +import org.hibernate.testing.FailureExpected; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -68,6 +75,39 @@ public void testOnlyCustomStrategy() { session.close(); } + @Test + @FailureExpected(jiraKey = "HHH-12718") + public void testCustomStrategyWithFlushInterceptor() { + Session session = openSession(); + session.beginTransaction(); + Long id = (Long) session.save( new Thing( INITIAL_NAME ) ); + session.getTransaction().commit(); + session.close(); + + Strategy.INSTANCE.resetState(); + + session = sessionWithInterceptor().openSession(); + session.beginTransaction(); + Thing thing = (Thing) session.get( Thing.class, id ); + thing.setName( SUBSEQUENT_NAME ); + session.getTransaction().commit(); + session.close(); + + // As we used an interceptor, the custom strategy should have been called twice to find dirty properties + assertEquals( 1, Strategy.INSTANCE.canDirtyCheckCount ); + assertEquals( 1, Strategy.INSTANCE.isDirtyCount ); + assertEquals( 1, Strategy.INSTANCE.resetDirtyCount ); + assertEquals( 2, Strategy.INSTANCE.findDirtyCount ); + + session = openSession(); + session.beginTransaction(); + thing = (Thing) session.get( Thing.class, id ); + assertEquals( SUBSEQUENT_NAME, thing.getName() ); + session.delete( thing ); + session.getTransaction().commit(); + session.close(); + } + @Test public void testOnlyCustomStrategyConsultedOnNonDirty() throws Exception { Session session = openSession(); @@ -97,6 +137,12 @@ public void testOnlyCustomStrategyConsultedOnNonDirty() throws Exception { session.close(); } + private SessionBuilder sessionWithInterceptor() { + return sessionFactory().unwrap( SessionFactory.class ) + .withOptions() + .interceptor( OnFlushDirtyInterceptor.INSTANCE ); + } + public static class Strategy implements CustomEntityDirtinessStrategy { public static final Strategy INSTANCE = new Strategy(); @@ -151,4 +197,20 @@ void resetState() { } } + + public static class OnFlushDirtyInterceptor extends EmptyInterceptor { + private static OnFlushDirtyInterceptor INSTANCE = new OnFlushDirtyInterceptor(); + + @Override + public boolean onFlushDirty( + Object entity, + Serializable id, + Object[] currentState, + Object[] previousState, + String[] propertyNames, + Type[] types) { + // Tell Hibernate ORM we did change the entity state, which should trigger another dirty check + return true; + } + } } From e53e0ef790fee06347dca22fb7b03a94a335fe93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jul 2018 16:32:54 +0200 Subject: [PATCH 057/772] HHH-12718 Compute dirtiness using whatever method is appropriate after a flush event interception We used to have a simpler version of the dirtyCheck() method after an interception, but that's not enough. --- .../internal/DefaultFlushEntityEventListener.java | 14 +------------- .../dirtiness/CustomDirtinessStrategyTest.java | 2 -- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java index 8e26a83c91bd..87e0a7409fe4 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java @@ -333,19 +333,7 @@ protected boolean handleInterception(FlushEntityEvent event) { //now we might need to recalculate the dirtyProperties array if ( intercepted && event.isDirtyCheckPossible() ) { - if ( !event.isDirtyCheckHandledByInterceptor() ) { - int[] dirtyProperties; - if ( event.hasDatabaseSnapshot() ) { - dirtyProperties = persister.findModified( event.getDatabaseSnapshot(), values, entity, session ); - } - else { - dirtyProperties = persister.findDirty( values, entry.getLoadedState(), entity, session ); - } - event.setDirtyProperties( dirtyProperties ); - } - else { - dirtyCheck( event ); - } + dirtyCheck( event ); } return intercepted; diff --git a/hibernate-core/src/test/java/org/hibernate/test/dirtiness/CustomDirtinessStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/test/dirtiness/CustomDirtinessStrategyTest.java index 7f28595a017d..fdeb1a5c4adf 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dirtiness/CustomDirtinessStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/dirtiness/CustomDirtinessStrategyTest.java @@ -18,7 +18,6 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.Type; -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -76,7 +75,6 @@ public void testOnlyCustomStrategy() { } @Test - @FailureExpected(jiraKey = "HHH-12718") public void testCustomStrategyWithFlushInterceptor() { Session session = openSession(); session.beginTransaction(); From da2d986efbac0ae8b2fd483fc031061f8980677c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jul 2018 16:37:32 +0200 Subject: [PATCH 058/772] HHH-12718 Avoid double negations in DefaultFlushEntityEventListener#dirtyCheck --- .../internal/DefaultFlushEntityEventListener.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java index 87e0a7409fe4..026746d0cb84 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java @@ -553,7 +553,8 @@ public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attri event.setDatabaseSnapshot( null ); final boolean interceptorHandledDirtyCheck; - boolean cannotDirtyCheck; + //The dirty check is considered possible unless proven otherwise (see below) + boolean dirtyCheckPossible = true; if ( dirtyProperties == null ) { // Interceptor returned null, so do the dirtycheck ourself, if possible @@ -562,8 +563,8 @@ public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attri interceptorHandledDirtyCheck = false; // object loaded by update() - cannotDirtyCheck = loadedState == null; - if ( !cannotDirtyCheck ) { + dirtyCheckPossible = loadedState != null; + if ( dirtyCheckPossible ) { // dirty check against the usual snapshot of the entity dirtyProperties = persister.findDirty( values, loadedState, entity, session ); } @@ -585,14 +586,14 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif // - dirtyProperties will only contain properties that refer to transient entities final Object[] currentState = persister.getPropertyValues( event.getEntity() ); dirtyProperties = persister.findDirty( entry.getDeletedState(), currentState, entity, session ); - cannotDirtyCheck = false; + dirtyCheckPossible = true; } else { // dirty check against the database snapshot, if possible/necessary final Object[] databaseSnapshot = getDatabaseSnapshot( session, persister, id ); if ( databaseSnapshot != null ) { dirtyProperties = persister.findModified( databaseSnapshot, values, entity, session ); - cannotDirtyCheck = false; + dirtyCheckPossible = true; event.setDatabaseSnapshot( databaseSnapshot ); } } @@ -603,7 +604,6 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif } else { // the Interceptor handled the dirty checking - cannotDirtyCheck = false; interceptorHandledDirtyCheck = true; } @@ -611,7 +611,7 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif event.setDirtyProperties( dirtyProperties ); event.setDirtyCheckHandledByInterceptor( interceptorHandledDirtyCheck ); - event.setDirtyCheckPossible( !cannotDirtyCheck ); + event.setDirtyCheckPossible( dirtyCheckPossible ); } From dde8744ca3306c5b55f4970c12ef64d78fbff079 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 17 Jul 2018 11:56:50 +0200 Subject: [PATCH 059/772] HHH-12718 Add a comment about the true meaning of interceptorHandledDirtyCheck --- .../event/internal/DefaultFlushEntityEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java index 026746d0cb84..06ba4b1a3d4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java @@ -603,7 +603,7 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif } } else { - // the Interceptor handled the dirty checking + // either the Interceptor, the bytecode enhancement or a custom dirtiness strategy handled the dirty checking interceptorHandledDirtyCheck = true; } From 9ca6b1a3cfce767be5d67ecc2c286659c7e2604b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 17 Jul 2018 19:33:06 +0200 Subject: [PATCH 060/772] HHH-12816 Enable the experimental features of ByteBuddy when building with JDK 11 --- gradle/java-module.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gradle/java-module.gradle b/gradle/java-module.gradle index 4c948132867c..d7f08d966064 100644 --- a/gradle/java-module.gradle +++ b/gradle/java-module.gradle @@ -231,6 +231,12 @@ processTestResources { } } +// Enable the experimental features of ByteBuddy with JDK 11 +test { + if ( JavaVersion.current().isJava11Compatible() ) { + systemProperty 'net.bytebuddy.experimental', true + } +} // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From fd5f5cbe0af0268bf37c02d38c2b2eab030aec70 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 18 Jul 2018 12:11:15 +0200 Subject: [PATCH 061/772] HHH-12820 Add entries that have been added to the wiki --- migration-guide.adoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/migration-guide.adoc b/migration-guide.adoc index 4cec430b2f5b..26cb6fee30b2 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -42,6 +42,11 @@ For backward compatibility, a new setting called `hibernate.id.generator.stored_ Existing applications migrating to 5.3 and using the `@TableGenerator` have to set the `hibernate.id.generator.stored_last_used` configuration property to `false`. ==== +=== Query parameters + +Method `org.hibernate.type.Type getType()` in `org.hibernate.query.QueryParameter` was renamed to `getHibernateType()`. + +This was done in order to allow for the re-introduction of `Class getType()` in a sub-interface, `org.hibernate.procedure.ParameterRegistration`. That second method had been removed in 5.2, breaking compatibility with 5.1. === Second-level cache provider SPI changes From 103de8de840ba4bbe3d1ad31adcd60b12e22aa18 Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Wed, 18 Jul 2018 13:37:09 +0200 Subject: [PATCH 062/772] HHH-12823 CompositeIdTest.testDistinctCountOfEntityWithCompositeId fails on databases that don't support tuple distinct counts because it expects wrong exception The SQLGrammarException is now wrapped in a PersistenceException, we should take that into account. --- .../java/org/hibernate/test/cid/CompositeIdTest.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/cid/CompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/cid/CompositeIdTest.java index ddaa94561188..02ba8aff9cb6 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/cid/CompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cid/CompositeIdTest.java @@ -17,20 +17,18 @@ import java.util.Iterator; import java.util.List; +import javax.persistence.PersistenceException; + import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.dialect.Oracle8iDialect; -import org.hibernate.dialect.PostgreSQL9Dialect; -import org.hibernate.dialect.PostgresPlusDialect; -import org.hibernate.dialect.SQLServer2005Dialect; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.engine.query.spi.HQLQueryPlan; import org.hibernate.exception.SQLGrammarException; import org.hibernate.hql.spi.QueryTranslator; import org.hibernate.testing.SkipForDialect; -import org.hibernate.testing.SkipForDialects; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -108,12 +106,12 @@ public void testDistinctCountOfEntityWithCompositeId() { try { long count = ( Long ) s.createQuery( "select count(distinct o) FROM Order o" ).uniqueResult(); if ( ! getDialect().supportsTupleDistinctCounts() ) { - fail( "expected SQLGrammarException" ); + fail( "expected PersistenceException caused by SQLGrammarException" ); } assertEquals( 2l, count ); } - catch ( SQLGrammarException e ) { - if ( getDialect().supportsTupleDistinctCounts() ) { + catch ( PersistenceException e ) { + if ( ! (e.getCause() instanceof SQLGrammarException) || getDialect().supportsTupleDistinctCounts() ) { throw e; } } From 2f12913b9867ae646f529841eef759361ec54191 Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Wed, 18 Jul 2018 14:57:00 +0200 Subject: [PATCH 063/772] HHH-12824 ASTParserLoadingTest.testComponentNullnessChecks fails with DB2 because it uses legacy-style query parameter --- .../test/java/org/hibernate/test/hql/ASTParserLoadingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index fbda64e438d7..e13aafa0bc34 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -770,7 +770,7 @@ public void testComponentNullnessChecks() { assertEquals( 3, results.size() ); String query = ( getDialect() instanceof DB2Dialect || getDialect() instanceof HSQLDialect ) ? - "from Human where cast(? as string) is null" : + "from Human where cast(?1 as string) is null" : "from Human where ?1 is null" ; if ( getDialect() instanceof DerbyDialect ) { From d23fc129cc007ddd41a97eb6feea8f6f8a07d6f8 Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Wed, 18 Jul 2018 15:45:17 +0200 Subject: [PATCH 064/772] HHH-12825 CriteriaHQLAlignmentTest.testCountReturnValues fails on databases that don't support tuple distinct counts because it expects wrong exception --- .../org/hibernate/test/hql/CriteriaHQLAlignmentTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/CriteriaHQLAlignmentTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/CriteriaHQLAlignmentTest.java index c1f3d8d80c6b..365c044077e0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/CriteriaHQLAlignmentTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/CriteriaHQLAlignmentTest.java @@ -280,6 +280,15 @@ public void testCountReturnValues() { throw ex; } } + catch (PersistenceException e) { + SQLGrammarException cause = assertTyping( SQLGrammarException.class, e.getCause() ); + if ( ! getDialect().supportsTupleCounts() ) { + // expected + } + else { + throw e; + } + } finally { t.rollback(); s.close(); From d24685de6776d5df9eb7cdb08bbb96c1ca40c60c Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Wed, 11 Jul 2018 13:01:51 -0400 Subject: [PATCH 065/772] HHH-12542 - Add necessary privileged action blocks for SecurityManager used on WildFly. --- .../boot/cfgxml/internal/ConfigLoader.java | 47 ++++--- .../boot/jaxb/internal/AbstractBinder.java | 14 +- .../internal/ClassLoaderServiceImpl.java | 124 ++++++++++------- .../hibernate/internal/util/ConfigHelper.java | 47 ++++--- .../internal/util/ReflectHelper.java | 128 +++++++++++++----- .../internal/CallbackBuilderLegacyImpl.java | 17 ++- .../metamodel/internal/MetadataContext.java | 27 +++- .../tuple/entity/PojoEntityTuplizer.java | 45 +++--- .../reader/AuditedPropertiesReader.java | 30 +++- 9 files changed, 328 insertions(+), 151 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java b/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java index ab094f769bc8..1485347c9f82 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java @@ -12,6 +12,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Properties; import org.hibernate.boot.cfgxml.spi.LoadedConfig; @@ -48,27 +50,34 @@ public ConfigLoader(BootstrapServiceRegistry bootstrapServiceRegistry) { } public LoadedConfig loadConfigXmlResource(String cfgXmlResourceName) { - final InputStream stream = bootstrapServiceRegistry.getService( ClassLoaderService.class ).locateResourceStream( cfgXmlResourceName ); - if ( stream == null ) { - throw new ConfigurationException( "Could not locate cfg.xml resource [" + cfgXmlResourceName + "]" ); - } - - try { - final JaxbCfgHibernateConfiguration jaxbCfg = jaxbProcessorHolder.getValue().unmarshal( - stream, - new Origin( SourceType.RESOURCE, cfgXmlResourceName ) - ); + final PrivilegedAction action = new PrivilegedAction() { + @Override + public JaxbCfgHibernateConfiguration run() { + final InputStream stream = bootstrapServiceRegistry.getService( ClassLoaderService.class ).locateResourceStream( cfgXmlResourceName ); + if ( stream == null ) { + throw new ConfigurationException( "Could not locate cfg.xml resource [" + cfgXmlResourceName + "]" ); + } - return LoadedConfig.consume( jaxbCfg ); - } - finally { - try { - stream.close(); - } - catch (IOException e) { - log.debug( "Unable to close cfg.xml resource stream", e ); + try { + return jaxbProcessorHolder.getValue().unmarshal( + stream, + new Origin( SourceType.RESOURCE, cfgXmlResourceName ) + ); + } + finally { + try { + stream.close(); + } + catch ( IOException e ) { + log.debug( "Unable to close cfg.xml resource stream", e ); + } + } } - } + }; + + return LoadedConfig.consume( + System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run() + ); } public LoadedConfig loadConfigXmlFile(File cfgXmlFile) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java index 79ef80e06089..8f5ffd919d48 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java @@ -7,6 +7,9 @@ package org.hibernate.boot.jaxb.internal; import java.io.InputStream; +import java.security.AccessController; +import java.security.PrivilegedAction; + import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; @@ -98,8 +101,15 @@ protected XMLEventReader createReader(Source source, Origin origin) { private Binding doBind(XMLEventReader eventReader, Origin origin) { try { - final StartElement rootElementStartEvent = seekRootElementStartEvent( eventReader, origin ); - return doBind( eventReader, rootElementStartEvent, origin ); + final PrivilegedAction action = new PrivilegedAction() { + @Override + public Binding run() { + final StartElement rootElementStartEvent = seekRootElementStartEvent( eventReader, origin ); + return doBind( eventReader, rootElementStartEvent, origin ); + } + }; + + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } finally { try { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java index fd4deff3f3e5..c1f7fa3df1ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java @@ -83,11 +83,16 @@ public ClassLoaderServiceImpl(Collection providedClassLoaders, Tccl orderedClassLoaderSet.add( ClassLoaderServiceImpl.class.getClassLoader() ); // now build the aggregated class loader... - this.aggregatedClassLoader = AccessController.doPrivileged( new PrivilegedAction() { + final PrivilegedAction action = new PrivilegedAction() { + @Override public AggregatedClassLoader run() { return new AggregatedClassLoader( orderedClassLoaderSet, lookupPrecedence ); } - } ); + }; + + this.aggregatedClassLoader = System.getSecurityManager() != null + ? AccessController.doPrivileged( action ) + : action.run(); } /** @@ -347,49 +352,62 @@ protected Class findClass(String name) throws ClassNotFoundException { @Override @SuppressWarnings({"unchecked"}) public Class classForName(String className) { - try { - return (Class) Class.forName( className, true, getAggregatedClassLoader() ); - } - catch (Exception e) { - throw new ClassLoadingException( "Unable to load class [" + className + "]", e ); - } - catch (LinkageError e) { - throw new ClassLoadingException( "Unable to load class [" + className + "]", e ); - } + final PrivilegedAction> action = new PrivilegedAction>() { + @Override + public Class run() { + try { + return (Class) Class.forName( className, true, getAggregatedClassLoader() ); + } + catch (Exception e) { + throw new ClassLoadingException( "Unable to load class [" + className + "]", e ); + } + catch (LinkageError e) { + throw new ClassLoadingException( "Unable to load class [" + className + "]", e ); + } + } + }; + + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } @Override - public URL locateResource(String name) { - // first we try name as a URL - try { - return new URL( name ); - } - catch (Exception ignore) { - } + public URL locateResource(final String name) { + final PrivilegedAction action = new PrivilegedAction() { + @Override + public URL run() { + try { + return new URL( name ); + } + catch (Exception ignore) { + } - try { - final URL url = getAggregatedClassLoader().getResource( name ); - if ( url != null ) { - return url; - } - } - catch (Exception ignore) { - } + try { + final URL url = getAggregatedClassLoader().getResource( name ); + if ( url != null ) { + return url; + } + } + catch (Exception ignore) { + } - if ( name.startsWith( "/" ) ) { - name = name.substring( 1 ); + if ( name.startsWith( "/" ) ) { + final String resourceName = name.substring( 1 ); - try { - final URL url = getAggregatedClassLoader().getResource( name ); - if ( url != null ) { - return url; + try { + final URL url = getAggregatedClassLoader().getResource( resourceName ); + if ( url != null ) { + return url; + } + } + catch (Exception ignore) { + } } + + return null; } - catch (Exception ignore) { - } - } + }; - return null; + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } @Override @@ -456,16 +474,22 @@ public List locateResources(String name) { @Override @SuppressWarnings("unchecked") public Collection loadJavaServices(Class serviceContract) { - ServiceLoader serviceLoader = serviceLoaders.get( serviceContract ); - if ( serviceLoader == null ) { - serviceLoader = ServiceLoader.load( serviceContract, getAggregatedClassLoader() ); - serviceLoaders.put( serviceContract, serviceLoader ); - } - final LinkedHashSet services = new LinkedHashSet(); - for ( S service : serviceLoader ) { - services.add( service ); - } - return services; + final PrivilegedAction> action = new PrivilegedAction>() { + @Override + public Collection run() { + ServiceLoader serviceLoader = serviceLoaders.get( serviceContract ); + if ( serviceLoader == null ) { + serviceLoader = ServiceLoader.load( serviceContract, getAggregatedClassLoader() ); + serviceLoaders.put( serviceContract, serviceLoader ); + } + final LinkedHashSet services = new LinkedHashSet(); + for ( S service : serviceLoader ) { + services.add( service ); + } + return services; + } + }; + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } @Override @@ -480,7 +504,13 @@ public T generateProxy(InvocationHandler handler, Class... interfaces) { @Override public T workWithClassLoader(Work work) { - return work.doWork( getAggregatedClassLoader() ); + final PrivilegedAction action = new PrivilegedAction() { + @Override + public T run() { + return work.doWork( getAggregatedClassLoader() ); + } + }; + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } private ClassLoader getAggregatedClassLoader() { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java index f87581521cb8..a3383bde8696 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java @@ -10,6 +10,8 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; import org.hibernate.HibernateException; import org.hibernate.cfg.Environment; @@ -113,28 +115,33 @@ private ConfigHelper() { } public static InputStream getResourceAsStream(String resource) { - String stripped = resource.startsWith( "/" ) - ? resource.substring( 1 ) - : resource; - - InputStream stream = null; - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - if ( classLoader != null ) { - stream = classLoader.getResourceAsStream( stripped ); - } - if ( stream == null ) { - stream = Environment.class.getResourceAsStream( resource ); - } - if ( stream == null ) { - stream = Environment.class.getClassLoader().getResourceAsStream( stripped ); - } - if ( stream == null ) { - throw new HibernateException( resource + " not found" ); - } - return stream; + final PrivilegedAction action = new PrivilegedAction() { + @Override + public InputStream run() { + String stripped = resource.startsWith( "/" ) + ? resource.substring( 1 ) + : resource; + + InputStream stream = null; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if ( classLoader != null ) { + stream = classLoader.getResourceAsStream( stripped ); + } + if ( stream == null ) { + stream = Environment.class.getResourceAsStream( resource ); + } + if ( stream == null ) { + stream = Environment.class.getClassLoader().getResourceAsStream( stripped ); + } + if ( stream == null ) { + throw new HibernateException( resource + " not found" ); + } + return stream; + } + }; + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } - public static InputStream getUserResourceAsStream(String resource) { boolean hasLeadingSlash = resource.startsWith( "/" ); String stripped = hasLeadingSlash ? resource.substring( 1 ) : resource; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index 4fbff725c4bf..b192252ad542 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -13,6 +13,8 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Locale; import java.util.regex.Pattern; import javax.persistence.Transient; @@ -235,7 +237,14 @@ public static Class reflectedPropertyClass(Class clazz, String name) throws Mapp } private static Getter getter(Class clazz, String name) throws MappingException { - return PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess( clazz, name ).getGetter(); + final PrivilegedAction action = new PrivilegedAction() { + @Override + public Getter run() { + return PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess( clazz, name ).getGetter(); + } + }; + + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } public static Object getConstantValue(String name, SessionFactoryImplementor factory) { @@ -272,16 +281,23 @@ public static Constructor getDefaultConstructor(Class clazz) throws Pr return null; } - try { - Constructor constructor = clazz.getDeclaredConstructor( NO_PARAM_SIGNATURE ); - ensureAccessibility( constructor ); - return constructor; - } - catch ( NoSuchMethodException nme ) { - throw new PropertyNotFoundException( - "Object class [" + clazz.getName() + "] must declare a default (no-argument) constructor" - ); - } + final PrivilegedAction action = new PrivilegedAction() { + @Override + public Constructor run() { + try { + Constructor constructor = clazz.getDeclaredConstructor( NO_PARAM_SIGNATURE ); + ensureAccessibility( constructor ); + return constructor; + } + catch (NoSuchMethodException e) { + throw new PropertyNotFoundException( + "Object class [" + clazz.getName() + "] must declare a default (no-argument) constructor" + ); + } + } + }; + + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } /** @@ -348,12 +364,19 @@ public static Constructor getConstructor(Class clazz, Type[] types) throws Prope } public static Method getMethod(Class clazz, Method method) { - try { - return clazz.getMethod( method.getName(), method.getParameterTypes() ); - } - catch (Exception e) { - return null; - } + final PrivilegedAction action = new PrivilegedAction() { + @Override + public Method run() { + try { + return clazz.getMethod( method.getName(), method.getParameterTypes() ); + } + catch (Exception e){ + return null; + } + } + }; + + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } public static Field findField(Class containerClass, String propertyName) { @@ -364,8 +387,14 @@ else if ( containerClass == Object.class ) { throw new IllegalArgumentException( "Illegal attempt to locate field [" + propertyName + "] on Object.class" ); } - Field field = locateField( containerClass, propertyName ); + final PrivilegedAction action = new PrivilegedAction() { + @Override + public Field run() { + return locateField( containerClass, propertyName ); + } + }; + final Field field = System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); if ( field == null ) { throw new PropertyNotFoundException( String.format( @@ -383,11 +412,22 @@ else if ( containerClass == Object.class ) { } public static void ensureAccessibility(AccessibleObject accessibleObject) { - if ( accessibleObject.isAccessible() ) { - return; - } + final PrivilegedAction action = new PrivilegedAction() { + @Override + public Object run() { + if ( !accessibleObject.isAccessible() ) { + accessibleObject.setAccessible( true ); + } + return null; + } + }; - accessibleObject.setAccessible( true ); + if ( System.getSecurityManager() != null ) { + AccessController.doPrivileged( action ); + } + else { + action.run(); + } } private static Field locateField(Class clazz, String propertyName) { @@ -462,7 +502,7 @@ private static Method getGetterOrNull(Class[] interfaces, String propertyName) { } private static Method getGetterOrNull(Class containerClass, String propertyName) { - for ( Method method : containerClass.getDeclaredMethods() ) { + for ( Method method : getDeclaredMethods( containerClass ) ) { // if the method has parameters, skip it if ( method.getParameterCount() != 0 ) { continue; @@ -513,17 +553,39 @@ private static void verifyNoIsVariantExists( String propertyName, Method getMethod, String stemName) { - // verify that the Class does not also define a method with the same stem name with 'is' - try { - final Method isMethod = containerClass.getDeclaredMethod( "is" + stemName ); + final Method isMethod = getDeclaredMethod( containerClass, "is" + stemName ); + if ( isMethod != null ) { if ( !Modifier.isStatic( isMethod.getModifiers() ) && isMethod.getAnnotation( Transient.class ) == null ) { // No such method should throw the caught exception. So if we get here, there was // such a method. checkGetAndIsVariants( containerClass, propertyName, getMethod, isMethod ); } } - catch (NoSuchMethodException ignore) { - } + } + + private static Method getDeclaredMethod(Class containerClass, String methodName) { + final PrivilegedAction action = new PrivilegedAction() { + @Override + public Method run() { + try { + return containerClass.getDeclaredMethod( methodName ); + } + catch (NoSuchMethodException ignore) { + return null; + } + } + }; + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + } + + private static Method[] getDeclaredMethods(Class containerClass) { + final PrivilegedAction action = new PrivilegedAction() { + @Override + public Method[] run() { + return containerClass.getDeclaredMethods(); + } + }; + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } private static void checkGetAndIsVariants( @@ -554,16 +616,14 @@ private static void verifyNoGetVariantExists( Method isMethod, String stemName) { // verify that the Class does not also define a method with the same stem name with 'is' - try { - final Method getMethod = containerClass.getDeclaredMethod( "get" + stemName ); + final Method getMethod = getDeclaredMethod( containerClass, "get" + stemName ); + if ( getMethod != null ) { // No such method should throw the caught exception. So if we get here, there was // such a method. if ( !Modifier.isStatic( getMethod.getModifiers() ) && getMethod.getAnnotation( Transient.class ) == null ) { checkGetAndIsVariants( containerClass, propertyName, getMethod, isMethod ); } } - catch (NoSuchMethodException ignore) { - } } public static Method getterMethodOrNull(Class containerJavaType, String propertyName) { @@ -631,7 +691,7 @@ private static Method setterOrNull(Class[] interfaces, String propertyName, Clas private static Method setterOrNull(Class theClass, String propertyName, Class propertyType) { Method potentialSetter = null; - for ( Method method : theClass.getDeclaredMethods() ) { + for ( Method method : getDeclaredMethods( theClass ) ) { final String methodName = method.getName(); if ( method.getParameterCount() == 1 && methodName.startsWith( "set" ) ) { final String testOldMethod = methodName.substring( 3 ); @@ -656,7 +716,7 @@ private static Method setterOrNull(Class theClass, String propertyName, Class pr * as an abstract - but again, that is such an edge case... */ public static Method findGetterMethodForFieldAccess(Field field, String propertyName) { - for ( Method method : field.getDeclaringClass().getDeclaredMethods() ) { + for ( Method method : getDeclaredMethods( field.getDeclaringClass() ) ) { // if the method has parameters, skip it if ( method.getParameterCount() != 0 ) { continue; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java index 1a6851ba3ce9..42da63f82d0e 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java @@ -10,6 +10,8 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Target; import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.List; import javax.persistence.Entity; @@ -72,6 +74,7 @@ public void buildCallbacksForEntity(String entityClassName, CallbackRegistrar ca } continue; } + final Callback[] callbacks = resolveEntityCallbacks( entityXClass, callbackType, reflectionManager ); callbackRegistrar.registerCallbacks( entityClass, callbacks ); } @@ -119,7 +122,7 @@ public Callback[] resolveEntityCallbacks(XClass beanClass, CallbackType callback final boolean debugEnabled = log.isDebugEnabled(); do { Callback callback = null; - List methods = currentClazz.getDeclaredMethods(); + List methods = getDeclaredMethods( currentClazz ); for ( final XMethod xMethod : methods ) { if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) { Method method = reflectionManager.toMethod( xMethod ); @@ -190,7 +193,7 @@ public Callback[] resolveEntityCallbacks(XClass beanClass, CallbackType callback if ( listener != null ) { XClass xListener = reflectionManager.toXClass( listener ); callbacksMethodNames = new ArrayList<>(); - List methods = xListener.getDeclaredMethods(); + List methods = getDeclaredMethods( xListener ); for ( final XMethod xMethod : methods ) { if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) { final Method method = reflectionManager.toMethod( xMethod ); @@ -338,4 +341,14 @@ private static void getListeners(XClass currentClazz, List orderedListene } } } + + private static List getDeclaredMethods(XClass clazz) { + final PrivilegedAction> action = new PrivilegedAction>() { + @Override + public List run() { + return clazz.getDeclaredMethods(); + } + }; + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index edfc52477e5e..3dee49037312 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -7,6 +7,8 @@ package org.hibernate.metamodel.internal; import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -369,13 +371,26 @@ private void populateStaticMetamodel(AbstractManagedType managedType) { return; } final String metamodelClassName = managedTypeClass.getName() + '_'; - try { - final Class metamodelClass = Class.forName( metamodelClassName, true, managedTypeClass.getClassLoader() ); - // we found the class; so populate it... - registerAttributes( metamodelClass, managedType ); + + final PrivilegedAction action = new PrivilegedAction() { + @Override + public Object run() { + try { + final Class metamodelClass = Class.forName( metamodelClassName, true, managedTypeClass.getClassLoader() ); + // we found the class; so populate it... + registerAttributes( metamodelClass, managedType ); + } + catch (ClassNotFoundException ignore) { + // nothing to do... + } + return null; + } + }; + if ( System.getSecurityManager() != null ) { + AccessController.doPrivileged( action ); } - catch (ClassNotFoundException ignore) { - // nothing to do... + else { + action.run(); } // todo : this does not account for @MappeSuperclass, mainly because this is not being tracked in our diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java index f186e8a384c2..6efd27b543e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java @@ -8,6 +8,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -157,24 +159,31 @@ protected ProxyFactory buildProxyFactory(PersistentClass persistentClass, Getter null : ReflectHelper.getMethod( proxyInterface, idSetterMethod ); - ProxyFactory pf = buildProxyFactoryInternal( persistentClass, idGetter, idSetter ); - try { - pf.postInstantiate( - getEntityName(), - mappedClass, - proxyInterfaces, - proxyGetIdentifierMethod, - proxySetIdentifierMethod, - persistentClass.hasEmbeddedIdentifier() ? - (CompositeType) persistentClass.getIdentifier().getType() : - null - ); - } - catch (HibernateException he) { - LOG.unableToCreateProxyFactory( getEntityName(), he ); - pf = null; - } - return pf; + final PrivilegedAction action = new PrivilegedAction() { + @Override + public ProxyFactory run() { + ProxyFactory pf = buildProxyFactoryInternal( persistentClass, idGetter, idSetter ); + try { + pf.postInstantiate( + getEntityName(), + mappedClass, + proxyInterfaces, + proxyGetIdentifierMethod, + proxySetIdentifierMethod, + persistentClass.hasEmbeddedIdentifier() ? + (CompositeType) persistentClass.getIdentifier().getType() : + null + ); + } + catch (HibernateException he) { + LOG.unableToCreateProxyFactory( getEntityName(), he ); + pf = null; + } + return pf; + } + }; + + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } protected ProxyFactory buildProxyFactoryInternal( diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java index 3ddffe0a4621..a1d28592157a 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java @@ -7,6 +7,8 @@ package org.hibernate.envers.configuration.internal.metadata.reader; import java.lang.annotation.Annotation; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -47,6 +49,7 @@ import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; import org.hibernate.mapping.Value; + import org.jboss.logging.Logger; import static org.hibernate.envers.internal.tools.Tools.newHashMap; @@ -354,26 +357,47 @@ private void addPropertiesFromClass(XClass clazz) { //look in the class addFromProperties( - clazz.getDeclaredProperties( "field" ), + getPropertiesFromClassByType( clazz, AccessType.FIELD ), it -> "field", fieldAccessedPersistentProperties, allClassAudited ); + addFromProperties( - clazz.getDeclaredProperties( "property" ), + getPropertiesFromClassByType( clazz, AccessType.PROPERTY ), propertyAccessedPersistentProperties::get, propertyAccessedPersistentProperties.keySet(), allClassAudited ); if ( allClassAudited != null || !auditedPropertiesHolder.isEmpty() ) { - final XClass superclazz = clazz.getSuperclass(); + final PrivilegedAction action = new PrivilegedAction() { + @Override + public XClass run() { + return clazz.getSuperclass(); + } + }; + + final XClass superclazz = System.getSecurityManager() != null + ? AccessController.doPrivileged( action ) + : action.run(); + if ( !clazz.isInterface() && !"java.lang.Object".equals( superclazz.getName() ) ) { addPropertiesFromClass( superclazz ); } } } + private Iterable getPropertiesFromClassByType(XClass clazz, AccessType accessType) { + final PrivilegedAction> action = new PrivilegedAction>() { + @Override + public Iterable run() { + return clazz.getDeclaredProperties( accessType.getType() ); + } + }; + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + } + private void addFromProperties( Iterable properties, Function accessTypeProvider, From 310ae3ea0d964c1dbaa23a5a19dbb961149a8dd2 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Wed, 18 Jul 2018 18:06:58 -0400 Subject: [PATCH 066/772] HHH-12826 - Added test case. (cherry picked from commit 3d93073b83ce66b2cd76a3ab674eb2d26636ba2a) --- .../flush/CommitFlushCollectionTest.java | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java new file mode 100644 index 000000000000..2baf8ce656e4 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java @@ -0,0 +1,209 @@ +/* + * 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.envers.test.integration.flush; + +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.FlushModeType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Version; + +import org.hibernate.envers.AuditMappedBy; +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12826") +public class CommitFlushCollectionTest extends BaseEnversJPAFunctionalTestCase { + + @MappedSuperclass + public static abstract class AbstractEntity { + private Long id; + private Long version; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Version + @Column(nullable = false) + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + } + + @Audited + @MappedSuperclass + public static class BaseDocument extends AbstractEntity { + private String number; + private Date date; + + @Column(nullable = false) + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + @Column(nullable = false) + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + } + + @Audited + @Entity(name = "DocumentA") + public static class DocumentA extends BaseDocument { + private List lines = new LinkedList<>(); + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "document", cascade = CascadeType.ALL, orphanRemoval = true) + @AuditMappedBy(mappedBy = "document") + public List getLines() { + return lines; + } + + public void setLines(List lines) { + this.lines = lines; + } + + public DocumentA addLine(DocumentLineA line) { + if ( line != null ) { + line.setDocument( this ); + getLines().add( line ); + } + return this; + } + } + + @MappedSuperclass + public abstract static class BaseDocumentLine extends AbstractEntity { + private String text; + + @Column(nullable = false) + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + + @Audited + @Entity(name = "DocumentLineA") + public static class DocumentLineA extends BaseDocumentLine { + private DocumentA document; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(updatable = false, insertable = true, nullable = false) + public DocumentA getDocument() { + return document; + } + + public void setDocument(DocumentA document) { + this.document = document; + } + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { DocumentA.class, DocumentLineA.class }; + } + + private Long persistDocument(FlushModeType flushModeType) { + final EntityManager entityManager = getOrCreateEntityManager(); + try { + entityManager.setFlushMode( flushModeType ); + + entityManager.getTransaction().begin(); + DocumentA doc = new DocumentA(); + doc.setNumber( "1" ); + doc.setDate( new Date() ); + + DocumentLineA line = new DocumentLineA(); + line.setText( "line1" ); + doc.addLine( line ); + + entityManager.persist( doc ); + entityManager.getTransaction().commit(); + + return doc.getId(); + } + catch ( Exception e ) { + if ( entityManager != null && entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + finally { + if ( entityManager != null && entityManager.isOpen() ) { + entityManager.close(); + } + } + } + + private Long entityId1; + private Long entityId2; + + @Test + @Priority(10) + public void initData() { + // This failed when using Envers. + entityId1 = persistDocument( FlushModeType.COMMIT ); + // This worked + entityId2 = persistDocument( FlushModeType.AUTO ); + } + + @Test + public void testPersistWithFlushModeCommit() { + assertEquals( Arrays.asList( 1 ), getAuditReader().getRevisions( DocumentA.class, entityId1 ) ); + } + + @Test + @Priority(1) + public void testPersistWithFlushmodeAuto() { + assertEquals( Arrays.asList( 2 ), getAuditReader().getRevisions( DocumentA.class, entityId2 ) ); + } +} From 4fc2ce7ab7fa5c4804266fbcc36d0727527b7f4c Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Wed, 18 Jul 2018 18:08:55 -0400 Subject: [PATCH 067/772] HHH-12826 - Persist cascade of collection fails when orphan removal enabled with flush mode commit. (cherry picked from commit 3fe7d6e13ed6da80327cb9b145e0b5176fd3603a) --- .../org/hibernate/engine/internal/Collections.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java index 6fad916ebc1c..fec7aea00a8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java @@ -10,6 +10,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.action.internal.DelayedPostInsertIdentifier; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; @@ -245,9 +246,15 @@ private static void prepareCollectionForUpdate( if ( loadedPersister != null || currentPersister != null ) { // it is or was referenced _somewhere_ + // check if the key changed + // excludes marking key changed when the loaded key is a DelayedPostInsertIdentifier. + final boolean keyChanged = currentPersister != null + && entry != null + && !currentPersister.getKeyType().isEqual( entry.getLoadedKey(), entry.getCurrentKey(), factory ) + && !( entry.getLoadedKey() instanceof DelayedPostInsertIdentifier ); + // if either its role changed, or its key changed - final boolean ownerChanged = loadedPersister != currentPersister - || !currentPersister.getKeyType().isEqual( entry.getLoadedKey(), entry.getCurrentKey(), factory ); + final boolean ownerChanged = loadedPersister != currentPersister || keyChanged; if ( ownerChanged ) { // do a check From f6e8dc984503a8a1d4349766644655a33b053df8 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 19 Jul 2018 09:57:57 +0200 Subject: [PATCH 068/772] HHH-12828 Make ScannerTests more stable In a shared containerized environment like Travis, it's not obvious that the second read will be faster than the first one. And indeed, the test often fails. There's no real value in testing that, we are just testing I/O, and the OS cache. --- .../org/jboss/as/jpa/hibernate5/scan/ScannerTests.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/hibernate-jipijapa/src/test/java/org/jboss/as/jpa/hibernate5/scan/ScannerTests.java b/hibernate-jipijapa/src/test/java/org/jboss/as/jpa/hibernate5/scan/ScannerTests.java index 2bd333984e2d..5a40e079a85a 100644 --- a/hibernate-jipijapa/src/test/java/org/jboss/as/jpa/hibernate5/scan/ScannerTests.java +++ b/hibernate-jipijapa/src/test/java/org/jboss/as/jpa/hibernate5/scan/ScannerTests.java @@ -7,7 +7,6 @@ package org.jboss.as.jpa.hibernate5.scan; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.BufferedInputStream; @@ -23,14 +22,12 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import org.hibernate.boot.archive.internal.ArchiveHelper; - import org.jboss.shrinkwrap.api.ArchivePath; import org.jboss.shrinkwrap.api.ArchivePaths; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.exporter.ZipExporter; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.jboss.vfs.TempFileProvider; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -128,21 +125,16 @@ protected File buildLargeJar() throws Exception { public void testGetBytesFromInputStream() throws Exception { File file = buildLargeJar(); - long start = System.currentTimeMillis(); InputStream stream = new BufferedInputStream( new FileInputStream( file ) ); int oldLength = getBytesFromInputStream( stream ).length; stream.close(); - long oldTime = System.currentTimeMillis() - start; - start = System.currentTimeMillis(); stream = new BufferedInputStream( new FileInputStream( file ) ); int newLength = ArchiveHelper.getBytesFromInputStream( stream ).length; stream.close(); - long newTime = System.currentTimeMillis() - start; assertEquals( oldLength, newLength ); - assertTrue( oldTime > newTime ); } // This is the old getBytesFromInputStream from JarVisitorFactory before From 1945180569e7f86f8b5633418c5a1af42db3c8e2 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Jul 2018 19:03:04 +0200 Subject: [PATCH 069/772] HHH-12791 Cache the Component type to avoid generating one proxy per call While Javassist only generates one proxy as the name is stable, ByteBuddy uses random names and thus generates a new proxy for every call, leading to the generation of 18 different proxies for the other test of the test class. We can't do better than using a volatile/synchronized pattern as the Component is not fully initialized in the constructor. Maybe we could take the risk of admitting that the getType() method is called at least once before we pass the element to a multi-threaded environment but that's a bet I don't want to take alone. --- .../java/org/hibernate/mapping/Component.java | 38 ++++++++++++++----- .../proxy/ComponentBasicProxyTest.java | 33 ++++++++++++++++ 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java index 53a54777d506..f988a8ba4e7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -52,6 +52,9 @@ public class Component extends SimpleValue implements MetaAttributable { private java.util.Map tuplizerImpls; + // cache the status of the type + private volatile Type type; + /** * @deprecated User {@link Component#Component(MetadataBuildingContext, PersistentClass)} instead. */ @@ -209,13 +212,28 @@ public void setDynamic(boolean dynamic) { @Override public Type getType() throws MappingException { - // TODO : temporary initial step towards HHH-1907 - final ComponentMetamodel metamodel = new ComponentMetamodel( - this, - getMetadata().getMetadataBuildingOptions() - ); - final TypeFactory factory = getMetadata().getTypeConfiguration().getTypeResolver().getTypeFactory(); - return isEmbedded() ? factory.embeddedComponent( metamodel ) : factory.component( metamodel ); + // Resolve the type of the value once and for all as this operation generates a proxy class + // for each invocation. + // Unfortunately, there's no better way of doing that as none of the classes are immutable and + // we can't know for sure the current state of the property or the value. + Type localType = type; + + if ( localType == null ) { + synchronized ( this ) { + if ( type == null ) { + // TODO : temporary initial step towards HHH-1907 + final ComponentMetamodel metamodel = new ComponentMetamodel( + this, + getMetadata().getMetadataBuildingOptions() + ); + final TypeFactory factory = getMetadata().getTypeConfiguration().getTypeResolver().getTypeFactory(); + localType = isEmbedded() ? factory.embeddedComponent( metamodel ) : factory.component( metamodel ); + type = localType; + } + } + } + + return localType; } @Override @@ -288,15 +306,15 @@ public boolean[] getColumnUpdateability() { } return result; } - + public boolean isKey() { return isKey; } - + public void setKey(boolean isKey) { this.isKey = isKey; } - + public boolean hasPojoRepresentation() { return componentClassName!=null; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/component/proxy/ComponentBasicProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/ComponentBasicProxyTest.java index 59debe8ff637..c41a87e2544d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/component/proxy/ComponentBasicProxyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/component/proxy/ComponentBasicProxyTest.java @@ -7,11 +7,20 @@ package org.hibernate.test.component.proxy; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; import java.util.List; +import org.hibernate.EntityMode; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.test.annotations.basic.CollectionAsBasicTest.DelimitedStringsType; import org.hibernate.testing.TestForIssue; +import org.hibernate.type.ComponentType; import org.junit.Test; /** @@ -44,4 +53,28 @@ public void testBasicProxyingWithProtectedMethodCalledInConstructor() { entityManager.remove( adult ); } ); } + + @Test + @TestForIssue(jiraKey = "HHH-12791") + public void testOnlyOneProxyClassGenerated() { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); + + try { + Metadata metadata = new MetadataSources( ssr ).addAnnotatedClass( Person.class ) + .getMetadataBuilder().applyBasicType( new DelimitedStringsType() ) + .build(); + PersistentClass persistentClass = metadata.getEntityBinding( Person.class.getName() ); + + ComponentType componentType1 = (ComponentType) persistentClass.getIdentifierMapper().getType(); + Object instance1 = componentType1.instantiate( EntityMode.POJO ); + + ComponentType componentType2 = (ComponentType) persistentClass.getIdentifierMapper().getType(); + Object instance2 = componentType2.instantiate( EntityMode.POJO ); + + assertEquals( instance1.getClass(), instance2.getClass() ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } } From b84d364cd277ae7396c95a254da0525b99ac3479 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 19 Jul 2018 10:21:56 +0200 Subject: [PATCH 070/772] HHH-12829 Update the Ehcache documentation to use the new short names --- .../src/main/asciidoc/userguide/chapters/caching/Caching.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc index aae506e715bc..ecdca35c772a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc @@ -616,7 +616,7 @@ To use the `EhCacheRegionFactory`, you need to specify the following configurati ---- + value="ehcache"/> ---- ==== @@ -635,7 +635,7 @@ To use the `SingletonEhCacheRegionFactory`, you need to specify the following co ---- + value="ehcache-singleton"/> ---- ==== From 7174b03a25e02d897477ba13e3250307c1bb722f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 19 Jul 2018 13:26:52 +0200 Subject: [PATCH 071/772] HHH-12815 Remove the zeroDateTimeBehavior option It's useless for this test and it causes compatibility issues. --- .../LocalDateCustomSessionLevelTimeZoneTest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java index 53a3a1225e4d..e96b512f0f00 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java @@ -47,13 +47,7 @@ else if(!url.endsWith( "&" )) { url += "&"; } - String zeroDateTimeBehavior = "convertToNull"; - - if ( Dialect.getDialect() instanceof MySQL8Dialect ) { - zeroDateTimeBehavior = "CONVERT_TO_NULL"; - } - - url += "zeroDateTimeBehavior=" + zeroDateTimeBehavior + "&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Europe/Berlin"; + url += "useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Europe/Berlin"; configurationValues.put( AvailableSettings.URL, url); super.configure( configurationValues ); From 3747352ec52981fc0890b6b863dfb7eb389f1b45 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 19 Jul 2018 13:31:37 +0200 Subject: [PATCH 072/772] HHH-12827 Define a numeric type as decimal for DB2 DB2 converts the numeric type to decimal and returns Types.DECIMAL for a numeric. We change the type name used so that we have a match when comparing the type names as last resort. --- .../org/hibernate/dialect/DB2Dialect.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 7e3ea78b576d..13a84cee857a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -40,6 +40,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.sql.DecimalTypeDescriptor; import org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -100,7 +101,12 @@ public DB2Dialect() { registerColumnType( Types.TIME, "time" ); registerColumnType( Types.TIMESTAMP, "timestamp" ); registerColumnType( Types.VARBINARY, "varchar($l) for bit data" ); - registerColumnType( Types.NUMERIC, "numeric($p,$s)" ); + // DB2 converts numeric to decimal under the hood + // Note that the type returned by DB2 for a numeric column will be Types.DECIMAL. Thus, we have an issue when + // comparing the types during the schema validation, defining the type to decimal here as the type names will + // also be compared and there will be a match. See HHH-12827 for the details. + registerColumnType( Types.NUMERIC, "decimal($p,$s)" ); + registerColumnType( Types.DECIMAL, "decimal($p,$s)" ); registerColumnType( Types.BLOB, "blob($l)" ); registerColumnType( Types.CLOB, "clob($l)" ); registerColumnType( Types.LONGVARCHAR, "long varchar" ); @@ -210,7 +216,7 @@ public DB2Dialect() { registerKeyword( "only" ); getDefaultProperties().setProperty( Environment.STATEMENT_BATCH_SIZE, NO_BATCH ); - + uniqueDelegate = new DB2UniqueDelegate( this ); } @@ -365,7 +371,7 @@ public int registerResultSetOutParameter(CallableStatement statement, int col) t @Override public ResultSet getResultSet(CallableStatement ps) throws SQLException { boolean isResultSet = ps.execute(); - // This assumes you will want to ignore any update counts + // This assumes you will want to ignore any update counts while ( !isResultSet && ps.getUpdateCount() != -1 ) { isResultSet = ps.getMoreResults(); } @@ -478,7 +484,14 @@ public boolean supportsTupleDistinctCounts() { @Override protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { - return sqlCode == Types.BOOLEAN ? SmallIntTypeDescriptor.INSTANCE : super.getSqlTypeDescriptorOverride( sqlCode ); + if ( sqlCode == Types.BOOLEAN ) { + return SmallIntTypeDescriptor.INSTANCE; + } + else if ( sqlCode == Types.NUMERIC ) { + return DecimalTypeDescriptor.INSTANCE; + } + + return super.getSqlTypeDescriptorOverride( sqlCode ); } @Override @@ -496,12 +509,12 @@ public JDBCException convert(SQLException sqlException, String message, String s } }; } - + @Override public UniqueDelegate getUniqueDelegate() { return uniqueDelegate; } - + @Override public String getNotExpression( String expression ) { return "not (" + expression + ")"; From d6f8d2e7a06914ab32200f351bd26f8b58d7ce18 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 19 Jul 2018 13:44:07 +0200 Subject: [PATCH 073/772] HHH-12822 Skip "case when" tests requiring casts for DB2 --- .../jpa/test/criteria/selectcase/GroupBySelectCaseTest.java | 3 +++ .../hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java | 3 +++ .../internal/expression/SearchedCaseExpressionTest.java | 2 ++ 3 files changed, 8 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java index 736c85fd1b3c..f68e6b9d8676 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java @@ -16,11 +16,13 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.PostgreSQL95Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.metadata.Person_; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -29,6 +31,7 @@ import static org.junit.Assert.assertTrue; @TestForIssue(jiraKey = "HHH-12230") +@SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") public class GroupBySelectCaseTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java index f89eaa16a0d4..28486f516251 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java @@ -34,14 +34,17 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @TestForIssue( jiraKey = "HHH-9731" ) +@SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") public class SelectCaseTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java index 29a055efa439..228df1c5170a 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java @@ -18,6 +18,7 @@ import javax.persistence.criteria.Path; import javax.persistence.criteria.Root; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; @@ -61,6 +62,7 @@ public void testCaseClause() { } @Test + @SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") public void testEqualClause() { doInHibernate( this::sessionFactory, session -> { CriteriaBuilder cb = session.getCriteriaBuilder(); From 255a096ee8b0a9510fdba21d9a71f8049d451bbe Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 19 Jul 2018 13:55:58 +0200 Subject: [PATCH 074/772] HHH-12832 Skip SchemaUpdateHaltOnErrorTest and al. with DB2 DB2 has no issue with reserved keywords in the CREATE TABLE clause so let's skip these tests. There is very little value to test them for every dialect anyway. --- .../schemaupdate/SchemaMigratorHaltOnErrorTest.java | 11 +++++++---- .../schemaupdate/SchemaUpdateHaltOnErrorTest.java | 12 ++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaMigratorHaltOnErrorTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaMigratorHaltOnErrorTest.java index acd10f0b127e..9f753b41d1ba 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaMigratorHaltOnErrorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaMigratorHaltOnErrorTest.java @@ -6,22 +6,25 @@ */ package org.hibernate.test.schemaupdate; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.util.Map; + import javax.persistence.Entity; import javax.persistence.Id; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.SkipForDialect; import org.hibernate.tool.schema.spi.SchemaManagementException; - import org.junit.Test; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - /** * @author Vlad Mihalcea */ +@SkipForDialect(value = DB2Dialect.class, comment = "DB2 is far more resistant to the reserved keyword usage. See HHH-12832.") public class SchemaMigratorHaltOnErrorTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java index 24122c8d386c..3ce899354ccd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java @@ -6,6 +6,9 @@ */ package org.hibernate.test.schemaupdate; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.File; import java.io.IOException; import java.util.EnumSet; @@ -17,23 +20,20 @@ import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.schema.TargetType; import org.hibernate.tool.schema.spi.SchemaManagementException; - import org.junit.After; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - /** * @author Vlad Mihalcea * @author Gail Badner */ +@SkipForDialect(value = DB2Dialect.class, comment = "DB2 is far more resistant to the reserved keyword usage. See HHH-12832.") public class SchemaUpdateHaltOnErrorTest { private File output; From 722ba32f9f49de4c3e853f0525e263b6c1d6d123 Mon Sep 17 00:00:00 2001 From: nikowitt Date: Thu, 19 Jul 2018 12:15:31 +0200 Subject: [PATCH 075/772] HHH-12830 Improve error message when a method is called in a non active transaction --- .../hibernate/context/internal/ThreadLocalSessionContext.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java b/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java index 85cc4334dce4..b87cab0e14a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java @@ -342,7 +342,8 @@ else if ( "reconnect".equals( methodName ) || "disconnect".equals( methodName ) LOG.tracef( "Allowing invocation [%s] to proceed to real (non-transacted) session - deprecated methods", methodName ); } else { - throw new HibernateException( methodName + " is not valid without active transaction" ); + throw new HibernateException( "Calling method '" + methodName + "' is not valid without an active transaction (Current status: " + + realSession.getTransaction().getStatus() + ")" ); } } LOG.tracef( "Allowing proxy invocation [%s] to proceed to real session", methodName ); From 86abe7fd4fdbcc4d4342498ff0f1e59a35d1fb84 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Thu, 19 Jul 2018 09:54:02 -0400 Subject: [PATCH 076/772] HHH-12753 - Fix Nationalized CLOB test failures on DB2. Force column read/write to use non-nationalized driver methods since the driver does not implement the nationalized equivalents. (cherry picked from commit 005d5b7c748c4c03afddae43a6fc2299869ab0f9) --- .../org/hibernate/dialect/DB297Dialect.java | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java index 314f1eba2d54..44730c84c673 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java @@ -6,14 +6,17 @@ */ package org.hibernate.dialect; +import java.sql.Types; + import org.hibernate.dialect.function.DB2SubstringFunction; -import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.hql.spi.id.IdTableSupportStandardImpl; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; import org.hibernate.hql.spi.id.local.AfterUseAction; -import org.hibernate.hql.spi.id.local.LocalTemporaryTableBulkIdStrategy; -import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.sql.CharTypeDescriptor; +import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; /** * An SQL dialect for DB2 9.7. @@ -57,4 +60,37 @@ public String getCreateIdTableStatementOptions() { AfterUseAction.CLEAN ); } + + @Override + protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { + // See HHH-12753 + // It seems that DB2's JDBC 4.0 support as of 9.5 does not support the N-variant methods like + // NClob or NString. Therefore here we overwrite the sql type descriptors to use the non-N variants + // which are supported. + switch ( sqlCode ) { + case Types.NCHAR: + return CharTypeDescriptor.INSTANCE; + + case Types.NCLOB: + if ( useInputStreamToInsertBlob() ) { + return ClobTypeDescriptor.STREAM_BINDING; + } + else { + return ClobTypeDescriptor.CLOB_BINDING; + } + + case Types.NVARCHAR: + return VarcharTypeDescriptor.INSTANCE; + + default: + return super.getSqlTypeDescriptorOverride( sqlCode ); + } + } + + @Override + public boolean canCreateSchema() { + // this seems to only be a problem in QE where schema management is disabled. + // should we keep this? + return false; + } } From 9ca732cf29d6a71e0a7b49d52a5ea1bddf31b6ea Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Thu, 19 Jul 2018 10:01:16 -0400 Subject: [PATCH 077/772] HHH-12753 - Remove an override that was not relevant to this jira. (cherry picked from commit ad5f96c613385c6d24f844563f66c63693164a50) --- .../src/main/java/org/hibernate/dialect/DB297Dialect.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java index 44730c84c673..e7841d241776 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java @@ -86,11 +86,4 @@ protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { return super.getSqlTypeDescriptorOverride( sqlCode ); } } - - @Override - public boolean canCreateSchema() { - // this seems to only be a problem in QE where schema management is disabled. - // should we keep this? - return false; - } } From ed03dfb958f5faa694f8c5b65d667d1c3312a2eb Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Thu, 19 Jul 2018 15:39:02 +0200 Subject: [PATCH 078/772] HHH-12833 Fix UniqueConstraintDropTest with DB2 --- .../uniqueconstraint/UniqueConstraintDropTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/uniqueconstraint/UniqueConstraintDropTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/uniqueconstraint/UniqueConstraintDropTest.java index 380e4968ee35..44507f52716b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/uniqueconstraint/UniqueConstraintDropTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/uniqueconstraint/UniqueConstraintDropTest.java @@ -20,6 +20,7 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.engine.config.spi.ConfigurationService; @@ -111,6 +112,9 @@ public void testUniqueConstraintIsGenerated() throws Exception { is( true ) ); } + else if ( getDialect() instanceof DB2Dialect ) { + checkDB2DropIndex( "test_entity_item", "item" ); + } else { assertThat( "The test_entity_item table unique constraint has not been dropped", @@ -154,6 +158,12 @@ private boolean checkDropIndex(String tableName, String columnName) throws IOExc return isMatching( matches, regex ); } + private boolean checkDB2DropIndex(String tableName, String columnName) throws IOException { + boolean matches = false; + String regex = "drop index " + tableName + ".uk_(.)*"; + return isMatching( matches, regex ); + } + private boolean isMatching(boolean matches, String regex) throws IOException { final String fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase(); final String[] split = fileContent.split( System.lineSeparator() ); From e5da120f13c3479fddf75681bd3d217e342cb171 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 18 Jul 2018 21:11:25 -0700 Subject: [PATCH 079/772] HHH-12666 : Add an option for restoring 5.1 native exception handling --- .../SessionFactoryOptionsBuilder.java | 24 ++- ...stractDelegatingSessionFactoryOptions.java | 5 + .../boot/spi/SessionFactoryOptions.java | 4 + .../org/hibernate/cfg/AvailableSettings.java | 11 ++ .../hibernate/internal/CoreMessageLogger.java | 3 + .../internal/ExceptionConverterImpl.java | 150 ++++++++++-------- 6 files changed, 132 insertions(+), 65 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 2bae14fb356d..a8b393a626aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -47,6 +47,7 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.id.uuid.LocalObjectUuidHelper; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.jpa.spi.JpaCompliance; @@ -97,6 +98,7 @@ import static org.hibernate.cfg.AvailableSettings.LOG_SESSION_METRICS; import static org.hibernate.cfg.AvailableSettings.MAX_FETCH_DEPTH; import static org.hibernate.cfg.AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER; +import static org.hibernate.cfg.AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE; import static org.hibernate.cfg.AvailableSettings.ORDER_INSERTS; import static org.hibernate.cfg.AvailableSettings.ORDER_UPDATES; import static org.hibernate.cfg.AvailableSettings.PREFER_USER_TRANSACTION; @@ -123,6 +125,7 @@ import static org.hibernate.cfg.AvailableSettings.VALIDATE_QUERY_PARAMETERS; import static org.hibernate.cfg.AvailableSettings.WRAP_RESULT_SETS; import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN; +import static org.hibernate.internal.CoreLogging.messageLogger; import static org.hibernate.jpa.AvailableSettings.DISCARD_PC_ON_CLOSE; /** @@ -137,7 +140,7 @@ */ @SuppressWarnings("WeakerAccess") public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { - private static final Logger log = Logger.getLogger( SessionFactoryOptionsBuilder.class ); + private static final CoreMessageLogger log = messageLogger( SessionFactoryOptionsBuilder.class ); private final String uuid = LocalObjectUuidHelper.generateLocalObjectUuid(); private final StandardServiceRegistry serviceRegistry; @@ -239,6 +242,8 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private boolean failOnPaginationOverCollectionFetchEnabled; private boolean inClauseParameterPaddingEnabled; + private boolean nativeExceptionHandling51Compliance; + @SuppressWarnings({"WeakerAccess", "deprecation"}) public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) { this.serviceRegistry = serviceRegistry; @@ -490,7 +495,7 @@ else if ( jdbcTimeZoneValue != null ) { ); this.immutableEntityUpdateQueryHandlingMode = ImmutableEntityUpdateQueryHandlingMode.interpret( - configurationSettings.get( IMMUTABLE_ENTITY_UPDATE_QUERY_HANDLING_MODE ) + configurationSettings.get( IMMUTABLE_ENTITY_UPDATE_QUERY_HANDLING_MODE ) ); this.inClauseParameterPaddingEnabled = ConfigurationHelper.getBoolean( @@ -498,6 +503,16 @@ else if ( jdbcTimeZoneValue != null ) { configurationSettings, false ); + + this.nativeExceptionHandling51Compliance = ConfigurationHelper.getBoolean( + NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE, + configurationSettings, + false + ); + if ( context.isJpaBootstrap() && nativeExceptionHandling51Compliance ) { + log.nativeExceptionHandling51ComplianceJpaBootstrapping(); + this.nativeExceptionHandling51Compliance = false; + } } @SuppressWarnings("deprecation") @@ -1011,6 +1026,11 @@ public JpaCompliance getJpaCompliance() { return jpaCompliance; } + @Override + public boolean nativeExceptionHandling51Compliance() { + return nativeExceptionHandling51Compliance; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 793379c75a0f..44b8c5f24cd2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -427,4 +427,9 @@ public ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHandl public boolean inClauseParameterPaddingEnabled() { return delegate.inClauseParameterPaddingEnabled(); } + + @Override + public boolean nativeExceptionHandling51Compliance() { + return delegate.nativeExceptionHandling51Compliance(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 62c0a4b39f2e..08c0d66bedbb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -286,4 +286,8 @@ default ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHand default boolean inClauseParameterPaddingEnabled() { return false; } + + default boolean nativeExceptionHandling51Compliance() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index febc0c3b12fa..01b36ba06114 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -901,6 +901,17 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String WRAP_RESULT_SETS = "hibernate.jdbc.wrap_result_sets"; + /** + * Indicates if exception handling for a SessionFactory built via Hibernate's native bootstrapping + * should behave the same as in Hibernate ORM 5.1. + *

+ * This setting will be ignored if the SessionFactory was built via JPA bootstrapping. + *

+ * Values are {@code true} or {@code false}. + * Default value is {@code false} + */ + String NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE = "hibernate.native_exception_handling_51_compliance"; + /** * Enable ordering of update statements by primary key value */ diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index d3b576ab4af0..6b7d7e9e042b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1811,4 +1811,7 @@ void attemptToAssociateProxyWithTwoOpenSessions( + "in the same package as class %1$s. In this case, the class should be opened and exported to Hibernate ORM.", id = 488) String bytecodeEnhancementFailedUnableToGetPrivateLookupFor(String className); + @LogMessage(level = WARN) + @Message(value = "Setting " + AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE + "=true is not valid with JPA bootstrapping; setting will be ignored.", id = 489 ) + void nativeExceptionHandling51ComplianceJpaBootstrapping(); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java index a5b3401663fe..ae00fb791676 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java @@ -43,14 +43,18 @@ public class ExceptionConverterImpl implements ExceptionConverter { private static final EntityManagerMessageLogger log = HEMLogging.messageLogger( ExceptionConverterImpl.class ); private final SharedSessionContractImplementor sharedSessionContract; + private final boolean isJpaBootstrap; + private final boolean nativeExceptionHandling51Compliance; public ExceptionConverterImpl(SharedSessionContractImplementor sharedSessionContract) { this.sharedSessionContract = sharedSessionContract; + isJpaBootstrap = sharedSessionContract.getFactory().getSessionFactoryOptions().isJpaBootstrap(); + nativeExceptionHandling51Compliance = sharedSessionContract.getFactory().getSessionFactoryOptions().nativeExceptionHandling51Compliance(); } @Override public RuntimeException convertCommitException(RuntimeException e) { - if ( sharedSessionContract.getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { + if ( isJpaBootstrap ) { Throwable wrappedException; if ( e instanceof HibernateException ) { wrappedException = convert( (HibernateException) e ); @@ -83,72 +87,92 @@ else if ( e instanceof PersistenceException ) { @Override public RuntimeException convert(HibernateException e, LockOptions lockOptions) { - Throwable cause = e; - if ( cause instanceof StaleStateException ) { - final PersistenceException converted = wrapStaleStateException( (StaleStateException) cause ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof LockAcquisitionException ) { - final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof LockingStrategyException ) { - final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.PessimisticLockException ) { - final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.QueryTimeoutException ) { - final QueryTimeoutException converted = new QueryTimeoutException( cause.getMessage(), cause ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof ObjectNotFoundException ) { - final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.NonUniqueObjectException ) { - final EntityExistsException converted = new EntityExistsException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.NonUniqueResultException ) { - final NonUniqueResultException converted = new NonUniqueResultException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof UnresolvableObjectException ) { - final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof QueryException ) { - return new IllegalArgumentException( cause ); - } - else if ( cause instanceof MultipleBagFetchException ) { - return new IllegalArgumentException( cause ); - } - else if ( cause instanceof TransientObjectException ) { - try { - sharedSessionContract.markForRollbackOnly(); + if ( !nativeExceptionHandling51Compliance ) { + Throwable cause = e; + if ( cause instanceof StaleStateException ) { + final PersistenceException converted = wrapStaleStateException( (StaleStateException) cause ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof LockAcquisitionException ) { + final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof LockingStrategyException ) { + final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof org.hibernate.PessimisticLockException ) { + final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof org.hibernate.QueryTimeoutException ) { + final QueryTimeoutException converted = new QueryTimeoutException( cause.getMessage(), cause ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof ObjectNotFoundException ) { + final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; } - catch (Exception ne) { - //we do not want the subsequent exception to swallow the original one - log.unableToMarkForRollbackOnTransientObjectException( ne ); + else if ( cause instanceof org.hibernate.NonUniqueObjectException ) { + final EntityExistsException converted = new EntityExistsException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof org.hibernate.NonUniqueResultException ) { + final NonUniqueResultException converted = new NonUniqueResultException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof UnresolvableObjectException ) { + final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof QueryException ) { + return new IllegalArgumentException( cause ); + } + else if ( cause instanceof MultipleBagFetchException ) { + return new IllegalArgumentException( cause ); + } + else if ( cause instanceof TransientObjectException ) { + try { + sharedSessionContract.markForRollbackOnly(); + } + catch (Exception ne) { + //we do not want the subsequent exception to swallow the original one + log.unableToMarkForRollbackOnTransientObjectException( ne ); + } + return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules + } + else { + final PersistenceException converted = new PersistenceException( cause ); + handlePersistenceException( converted ); + return converted; } - return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules } else { - final PersistenceException converted = new PersistenceException( cause ); - handlePersistenceException( converted ); - return converted; + if ( e instanceof QueryException ) { + return e; + } + else if ( e instanceof MultipleBagFetchException ) { + return e; + } + else { + try { + sharedSessionContract.markForRollbackOnly(); + } + catch (Exception ne) { + //we do not want the subsequent exception to swallow the original one + log.unableToMarkForRollbackOnTransientObjectException( ne ); + } + return e; + } } } From 566a93d6c7bf5d55ec8a561a0442bc988ea94d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 18 Jul 2018 13:18:29 +0200 Subject: [PATCH 080/772] HHH-12666 Test the current behavior of exception conversion on persist/save/merge/flush/etc. Original tests by Gail Badner: https://github.com/gbadner/hibernate-core/commit/09aa6fbce2a9ba4fb82b6ce1075fd68fb09db510 --- .../BaseExceptionHandlingTest.java | 69 ++++ ...paOrNativeBootstrapFunctionalTestCase.java | 374 ++++++++++++++++++ ...straintViolationExceptionHandlingTest.java | 180 +++++++++ .../ExceptionExpectations.java | 108 +++++ .../TransientObjectExceptionHandlingTest.java | 155 ++++++++ 5 files changed, 886 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseExceptionHandlingTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseExceptionHandlingTest.java new file mode 100644 index 000000000000..2c6b4520f8e0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseExceptionHandlingTest.java @@ -0,0 +1,69 @@ +/* + * 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.exceptionhandling; + +import java.util.Arrays; +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(CustomParameterized.class) +public abstract class BaseExceptionHandlingTest extends BaseJpaOrNativeBootstrapFunctionalTestCase { + + private static final String EXCEPTION_HANDLING_PROPERTY = + AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE; + + public enum ExceptionHandlingSetting { + DEFAULT, + TRUE, + FALSE + } + + @Parameterized.Parameters(name = "Bootstrap={0}, ExceptionHandlingSetting={1}") + public static Iterable parameters() { + return Arrays.asList( new Object[][] { + { BootstrapMethod.JPA, ExceptionHandlingSetting.DEFAULT, ExceptionExpectations.jpa() }, + { BootstrapMethod.JPA, ExceptionHandlingSetting.TRUE, ExceptionExpectations.jpa() }, + { BootstrapMethod.JPA, ExceptionHandlingSetting.FALSE, ExceptionExpectations.jpa() }, + { BootstrapMethod.NATIVE, ExceptionHandlingSetting.DEFAULT, ExceptionExpectations.nativePost52() }, + { BootstrapMethod.NATIVE, ExceptionHandlingSetting.TRUE, ExceptionExpectations.nativePre52() }, + { BootstrapMethod.NATIVE, ExceptionHandlingSetting.FALSE, ExceptionExpectations.nativePost52() } + } ); + } + + private final ExceptionHandlingSetting exceptionHandlingSetting; + + protected final ExceptionExpectations exceptionExpectations; + + protected BaseExceptionHandlingTest( + BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod ); + this.exceptionHandlingSetting = exceptionHandlingSetting; + this.exceptionExpectations = exceptionExpectations; + } + + @Override + protected void configure(Map properties) { + switch ( exceptionHandlingSetting ) { + case DEFAULT: + // Keep the default + break; + case TRUE: + properties.put( EXCEPTION_HANDLING_PROPERTY, "true" ); + break; + case FALSE: + properties.put( EXCEPTION_HANDLING_PROPERTY, "false" ); + break; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java new file mode 100644 index 000000000000..732cd4c0d699 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java @@ -0,0 +1,374 @@ +/* + * 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.exceptionhandling; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.persistence.EntityManager; +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.PersistenceUnitTransactionType; + +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.boot.registry.BootstrapServiceRegistry; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.resource.transaction.spi.TransactionCoordinator; + +import org.hibernate.testing.AfterClassOnce; +import org.hibernate.testing.BeforeClassOnce; +import org.hibernate.testing.cache.CachingRegionFactory; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.After; + +import static org.junit.Assert.fail; + +/** + * A base class for all functional tests. + */ +public abstract class BaseJpaOrNativeBootstrapFunctionalTestCase extends BaseUnitTestCase { + + // IMPL NOTE : Here we use @Before and @After (instead of @BeforeClassOnce and @AfterClassOnce like we do in + // BaseCoreFunctionalTestCase) because the old HEM test methodology was to create an EMF for each test method. + + private static final Dialect dialect = Dialect.getDialect(); + + public enum BootstrapMethod { + JPA, + NATIVE + } + + private final BootstrapMethod bootstrapMethod; + + private StandardServiceRegistryImpl serviceRegistry; + private SessionFactoryImplementor sessionFactory; + + private Session session; + + protected Dialect getDialect() { + return dialect; + } + + protected SessionFactoryImplementor sessionFactory() { + return sessionFactory; + } + + protected StandardServiceRegistryImpl serviceRegistry() { + return serviceRegistry; + } + + protected Session openSession() throws HibernateException { + session = sessionFactory().openSession(); + return session; + } + + protected EntityManager openEntityManager() throws HibernateException { + return openSession().unwrap( EntityManager.class ); + } + + protected BaseJpaOrNativeBootstrapFunctionalTestCase(BootstrapMethod bootstrapMethod) { + this.bootstrapMethod = bootstrapMethod; + } + + @BeforeClassOnce + @SuppressWarnings( {"UnusedDeclaration"}) + public void buildSessionOrEntityManagerFactory() { + switch ( bootstrapMethod ) { + case JPA: + buildEntityManagerFactory(); + break; + case NATIVE: + buildSessionFactory(); + break; + } + + afterSessionOrEntityManagerFactoryBuilt(); + } + + private void buildEntityManagerFactory() { + log.trace( "Building EntityManagerFactory" ); + + Properties properties = buildProperties(); + ArrayList classes = new ArrayList(); + + classes.addAll( Arrays.asList( getAnnotatedClasses() ) ); + properties.put( AvailableSettings.LOADED_CLASSES, classes ); + + sessionFactory = Bootstrap.getEntityManagerFactoryBuilder( + buildPersistenceUnitDescriptor(), + properties + ).build().unwrap( SessionFactoryImplementor.class ); + + serviceRegistry = (StandardServiceRegistryImpl) sessionFactory.getServiceRegistry() + .getParentServiceRegistry(); + } + + private void buildSessionFactory() { + // for now, build the configuration to get all the property settings + Configuration configuration = new Configuration(); + configuration.setProperties( buildProperties() ); + + Class[] annotatedClasses = getAnnotatedClasses(); + if ( annotatedClasses != null ) { + for ( Class annotatedClass : annotatedClasses ) { + configuration.addAnnotatedClass( annotatedClass ); + } + } + + BootstrapServiceRegistry bootRegistry = buildBootstrapServiceRegistry(); + serviceRegistry = buildServiceRegistry( bootRegistry, configuration ); + sessionFactory = ( SessionFactoryImplementor ) configuration.buildSessionFactory( serviceRegistry ); + + afterSessionOrEntityManagerFactoryBuilt(); + } + + + private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { + return new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); + } + + public static class TestingPersistenceUnitDescriptorImpl implements PersistenceUnitDescriptor { + private final String name; + + public TestingPersistenceUnitDescriptorImpl(String name) { + this.name = name; + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public boolean isUseQuotedIdentifiers() { + return false; + } + + @Override + public boolean isExcludeUnlistedClasses() { + return false; + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return null; + } + + @Override + public ValidationMode getValidationMode() { + return null; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return null; + } + + @Override + public List getManagedClassNames() { + return null; + } + + @Override + public List getMappingFileNames() { + return null; + } + + @Override + public List getJarFileUrls() { + return null; + } + + @Override + public Object getNonJtaDataSource() { + return null; + } + + @Override + public Object getJtaDataSource() { + return null; + } + + @Override + public Properties getProperties() { + return null; + } + + @Override + public ClassLoader getClassLoader() { + return null; + } + + @Override + public ClassLoader getTempClassLoader() { + return null; + } + + @Override + public void pushClassTransformer(EnhancementContext enhancementContext) { + } + } + + private BootstrapServiceRegistry buildBootstrapServiceRegistry() { + final BootstrapServiceRegistryBuilder builder = new BootstrapServiceRegistryBuilder(); + builder.applyClassLoader( getClass().getClassLoader() ); + prepareBootstrapRegistryBuilder( builder ); + return builder.build(); + } + + protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { + } + + private StandardServiceRegistryImpl buildServiceRegistry(BootstrapServiceRegistry bootRegistry, Configuration configuration) { + Properties properties = new Properties(); + properties.putAll( configuration.getProperties() ); + Environment.verifyProperties( properties ); + ConfigurationHelper.resolvePlaceHolders( properties ); + + StandardServiceRegistryBuilder cfgRegistryBuilder = configuration.getStandardServiceRegistryBuilder(); + + StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder( bootRegistry, cfgRegistryBuilder.getAggregatedCfgXml() ) + .applySettings( properties ); + + return (StandardServiceRegistryImpl) registryBuilder.build(); + } + + private Properties buildProperties() { + Properties properties = Environment.getProperties(); + + properties.put( org.hibernate.cfg.AvailableSettings.CACHE_REGION_FACTORY, CachingRegionFactory.class.getName() ); + for ( Map.Entry entry : getCachedClasses().entrySet() ) { + properties.put( AvailableSettings.CLASS_CACHE_PREFIX + "." + entry.getKey().getName(), entry.getValue() ); + } + for ( Map.Entry entry : getCachedCollections().entrySet() ) { + properties.put( AvailableSettings.COLLECTION_CACHE_PREFIX + "." + entry.getKey(), entry.getValue() ); + } + + configure( properties ); + + if ( createSchema() ) { + properties.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_AUTO, "create-drop" ); + } + properties.put( org.hibernate.cfg.AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true" ); + properties.put( org.hibernate.cfg.AvailableSettings.DIALECT, getDialect().getClass().getName() ); + + return properties; + } + + protected void configure(Map properties) { + } + + protected static final Class[] NO_CLASSES = new Class[0]; + + protected Class[] getAnnotatedClasses() { + return NO_CLASSES; + } + + public Map getCachedClasses() { + return new HashMap<>(); + } + + public Map getCachedCollections() { + return new HashMap<>(); + } + + protected void afterSessionOrEntityManagerFactoryBuilt() { + } + + protected boolean createSchema() { + return true; + } + + @After + public final void afterTest() throws Exception { + completeStrayTransaction(); + + cleanupSession(); + + } + + @AfterClassOnce + @SuppressWarnings( {"UnusedDeclaration"}) + protected void releaseSessionFactory() { + if ( sessionFactory == null ) { + return; + } + sessionFactory.close(); + sessionFactory = null; + if ( serviceRegistry != null ) { + if ( serviceRegistry.isActive() ) { + try { + serviceRegistry.destroy(); + } + catch (Exception ignore) { + } + fail( "StandardServiceRegistry was not closed down as expected" ); + } + } + serviceRegistry=null; + } + + private void completeStrayTransaction() { + if ( session == null ) { + // nothing to do + return; + } + + if ( ( (SessionImplementor) session ).isClosed() ) { + // nothing to do + return; + } + + if ( !session.isConnected() ) { + // nothing to do + return; + } + + final TransactionCoordinator.TransactionDriver tdc = + ( (SessionImplementor) session ).getTransactionCoordinator().getTransactionDriverControl(); + + if ( tdc.getStatus().canRollback() ) { + session.getTransaction().rollback(); + } + session.close(); + } + + private void cleanupSession() { + if ( session != null && ! ( (SessionImplementor) session ).isClosed() ) { + session.close(); + } + session = null; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java new file mode 100644 index 000000000000..02675330e1f0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java @@ -0,0 +1,180 @@ +/* + * 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.exceptionhandling; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Session; +import org.hibernate.Transaction; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-12666") +public class ConstraintViolationExceptionHandlingTest extends BaseExceptionHandlingTest { + + public ConstraintViolationExceptionHandlingTest(BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + A.class, + AInfo.class + }; + } + + @Test + public void testConstraintViolationOnSave() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + s.flush(); + s.clear(); + try { + AInfo anotherAInfo = new AInfo(); + anotherAInfo.uniqueString = "unique"; + s.save( anotherAInfo ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnSaveAndSaveOrUpdate( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testConstraintViolationOnSaveOrUpdate() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + s.flush(); + s.clear(); + try { + AInfo anotherAInfo = new AInfo(); + anotherAInfo.uniqueString = "unique"; + s.saveOrUpdate( anotherAInfo ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnSaveAndSaveOrUpdate( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testConstraintViolationOnPersist() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + s.flush(); + s.clear(); + try { + AInfo anotherAInfo = new AInfo(); + anotherAInfo.uniqueString = "unique"; + s.persist( anotherAInfo ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testConstraintViolationOnMerge() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + s.flush(); + s.clear(); + try { + AInfo anotherAInfo = new AInfo(); + anotherAInfo.uniqueString = "unique"; + s.merge( anotherAInfo ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testConstraintViolationUpdateFlush() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + AInfo aInfo1 = new AInfo(); + s.persist( aInfo1 ); + s.flush(); + s.clear(); + try { + aInfo1 = s.get( AInfo.class, aInfo1.id ); + aInfo1.uniqueString = "unique"; + s.flush(); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Entity(name = "A") + public static class A { + @Id + private long id; + + @ManyToOne(optional = false) + private AInfo aInfo; + } + + @Entity(name = "AInfo") + public static class AInfo { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(unique = true) + private String uniqueString; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java new file mode 100644 index 000000000000..84d1dc66f34c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java @@ -0,0 +1,108 @@ +/* + * 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.exceptionhandling; + +import java.sql.SQLException; +import javax.persistence.PersistenceException; + +import org.hibernate.TransientObjectException; +import org.hibernate.exception.ConstraintViolationException; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; + +interface ExceptionExpectations { + + static ExceptionExpectations jpa() { + return new ExceptionExpectations() { + @Override + public void onConstraintViolationOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + } + + @Override + public void onConstraintViolationOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause().getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( TransientObjectException.class ) ); + } + + @Override + public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( IllegalStateException.class ) ); + assertThat( e.getCause(), instanceOf( TransientObjectException.class ) ); + } + }; + } + + static ExceptionExpectations nativePre52() { + return new ExceptionExpectations() { + @Override + public void onConstraintViolationOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onConstraintViolationOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( TransientObjectException.class ) ); + } + + @Override + public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( TransientObjectException.class ) ); + } + }; + } + + static ExceptionExpectations nativePost52() { + return new ExceptionExpectations() { + @Override + public void onConstraintViolationOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onConstraintViolationOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause().getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( TransientObjectException.class ) ); + } + + @Override + public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( IllegalStateException.class ) ); + assertThat( e.getCause(), instanceOf( TransientObjectException.class ) ); + } + }; + } + + void onConstraintViolationOnSaveAndSaveOrUpdate(RuntimeException e); + + void onConstraintViolationOnPersistAndMergeAndFlush(RuntimeException e); + + void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e); + + void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e); +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java new file mode 100644 index 000000000000..c5a5c56a9535 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java @@ -0,0 +1,155 @@ +/* + * 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.exceptionhandling; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Session; +import org.hibernate.Transaction; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-12666") +public class TransientObjectExceptionHandlingTest extends BaseExceptionHandlingTest { + + public TransientObjectExceptionHandlingTest(BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + A.class, + AInfo.class + }; + } + + @Test + public void testSave() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.save( a ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnSaveAndSaveOrUpdate( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testSaveOrUpdate() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.saveOrUpdate( a ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnSaveAndSaveOrUpdate( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testPersist() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.persist( a ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testMerge() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.merge( a ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testUpdateFlush() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.update( a ); + s.flush(); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Entity(name = "A") + public static class A { + @Id + private long id; + + @ManyToOne(optional = false) + private AInfo aInfo; + } + + @Entity(name = "AInfo") + public static class AInfo { + @Id + @GeneratedValue + private long id; + } +} From a76dde0ec6ccc3a66a95732436919a3724dd178f Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 19 Jul 2018 01:05:17 -0700 Subject: [PATCH 081/772] HHH-12666 : Added more tests --- .../ExceptionExpectations.java | 63 +++++++++- ...tifierGenerationExceptionHandlingTest.java | 86 +++++++++++++ .../QuerySyntaxExceptionHandlingTest.java | 64 ++++++++++ ...StateObjectStateExceptionHandlingTest.java | 117 ++++++++++++++++++ 4 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/IdentifierGenerationExceptionHandlingTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/StateObjectStateExceptionHandlingTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java index 84d1dc66f34c..8e5f2398264e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java @@ -7,10 +7,14 @@ package org.hibernate.test.exceptionhandling; import java.sql.SQLException; +import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; +import org.hibernate.StaleObjectStateException; import org.hibernate.TransientObjectException; import org.hibernate.exception.ConstraintViolationException; +import org.hibernate.hql.internal.ast.QuerySyntaxException; +import org.hibernate.id.IdentifierGenerationException; import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertThat; @@ -41,6 +45,24 @@ public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { assertThat( e, instanceOf( IllegalStateException.class ) ); assertThat( e.getCause(), instanceOf( TransientObjectException.class ) ); } + + @Override + public void onInvalidQueryExecuted(RuntimeException e) { + assertThat( e, instanceOf( IllegalArgumentException.class ) ); + assertThat( e.getCause(), instanceOf( QuerySyntaxException.class ) ); + } + + @Override + public void onStaleObjectMergeAndUpdateFlush(RuntimeException e) { + assertThat( e, instanceOf( OptimisticLockException.class ) ); + assertThat( e.getCause(), instanceOf( StaleObjectStateException.class ) ); + } + + @Override + public void onIdentifierGeneratorFailure(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( IdentifierGenerationException.class ) ); + } }; } @@ -67,6 +89,21 @@ public void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e) { public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { assertThat( e, instanceOf( TransientObjectException.class ) ); } + + @Override + public void onInvalidQueryExecuted(RuntimeException e) { + assertThat( e, instanceOf( QuerySyntaxException.class ) ); + } + + @Override + public void onStaleObjectMergeAndUpdateFlush(RuntimeException e) { + assertThat( e, instanceOf( StaleObjectStateException.class ) ); + } + + @Override + public void onIdentifierGeneratorFailure(RuntimeException e) { + assertThat( e, instanceOf( IdentifierGenerationException.class ) ); + } }; } @@ -95,6 +132,24 @@ public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { assertThat( e, instanceOf( IllegalStateException.class ) ); assertThat( e.getCause(), instanceOf( TransientObjectException.class ) ); } + + @Override + public void onInvalidQueryExecuted(RuntimeException e) { + assertThat( e, instanceOf( IllegalArgumentException.class ) ); + assertThat( e.getCause(), instanceOf( QuerySyntaxException.class ) ); + } + + @Override + public void onStaleObjectMergeAndUpdateFlush(RuntimeException e) { + assertThat( e, instanceOf( OptimisticLockException.class ) ); + assertThat( e.getCause(), instanceOf( StaleObjectStateException.class ) ); + } + + @Override + public void onIdentifierGeneratorFailure(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( IdentifierGenerationException.class ) ); + } }; } @@ -105,4 +160,10 @@ public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e); void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e); -} + + void onInvalidQueryExecuted(RuntimeException e); + + void onStaleObjectMergeAndUpdateFlush(RuntimeException e); + + void onIdentifierGeneratorFailure(RuntimeException e); +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/IdentifierGenerationExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/IdentifierGenerationExceptionHandlingTest.java new file mode 100644 index 000000000000..0a24498783ef --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/IdentifierGenerationExceptionHandlingTest.java @@ -0,0 +1,86 @@ +/* + * 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.exceptionhandling; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; + +import org.hibernate.Session; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-12666") +public class IdentifierGenerationExceptionHandlingTest extends BaseExceptionHandlingTest { + + public IdentifierGenerationExceptionHandlingTest( + BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Owner.class, + OwnerAddress.class + }; + } + + @Test + public void testIdentifierGeneratorException() { + OwnerAddress address = new OwnerAddress(); + address.owner = null; + + Session s = openSession(); + s.beginTransaction(); + try { + s.persist( address ); + s.flush(); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onIdentifierGeneratorFailure( expected ); + } + finally { + s.getTransaction().rollback(); + s.close(); + } + } + + @Entity(name = "OwnerAddress") + public static class OwnerAddress { + @Id + @GeneratedValue(generator = "fk_1") + @GenericGenerator(strategy = "foreign", name = "fk_1", parameters = @Parameter(name = "property", value = "owner")) + private Integer id; + + @OneToOne(mappedBy = "address") + private Owner owner; + } + + @Entity(name = "Owner") + public static class Owner { + @Id + @GeneratedValue + private Integer id; + + @OneToOne(cascade = CascadeType.ALL) + @PrimaryKeyJoinColumn + private OwnerAddress address; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java new file mode 100644 index 000000000000..a464cf3fdf8a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java @@ -0,0 +1,64 @@ +/* + * 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.exceptionhandling; + +import static org.junit.Assert.fail; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-12666") +public class QuerySyntaxExceptionHandlingTest extends BaseExceptionHandlingTest { + + public QuerySyntaxExceptionHandlingTest( + BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + A.class + }; + } + + @Test + public void testInvalidQuery() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + s.persist( a ); + s.flush(); + s.clear(); + + try { + s.createQuery( "from A where blahblahblah" ).list(); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onInvalidQueryExecuted( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Entity(name = "A") + public static class A { + @Id + private long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/StateObjectStateExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/StateObjectStateExceptionHandlingTest.java new file mode 100644 index 000000000000..7bd418d9ace8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/StateObjectStateExceptionHandlingTest.java @@ -0,0 +1,117 @@ +/* + * 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.exceptionhandling; + +import static org.junit.Assert.fail; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.Session; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-12666") +public class StateObjectStateExceptionHandlingTest extends BaseExceptionHandlingTest { + + public StateObjectStateExceptionHandlingTest( + BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + A.class + }; + } + + @Test + public void testStaleObjectMerged() { + Session s = openSession(); + s.beginTransaction(); + A a = new A(); + a.id = 1; + s.persist( a ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + A aGet = s.get( A.class, a.id ); + aGet.name = "A. Name"; + s.getTransaction().commit(); + s.close(); + + a.name = "Another Name"; + + s = openSession(); + s.beginTransaction(); + try { + s.merge( a ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onStaleObjectMergeAndUpdateFlush( expected ); + } + finally { + s.getTransaction().rollback(); + s.close(); + } + } + + @Test + public void testStaleObjectUpdatedAndFlushed() { + Session s = openSession(); + s.beginTransaction(); + A a = new A(); + a.id = 2; + s.persist( a ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + A aGet = s.get( A.class, a.id ); + aGet.name = "A. Name"; + s.getTransaction().commit(); + s.close(); + + a.name = "Another Name"; + + s = openSession(); + s.beginTransaction(); + try { + s.update( a ); + s.flush(); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onStaleObjectMergeAndUpdateFlush( expected ); + } + finally { + s.getTransaction().rollback(); + s.close(); + } + } + + @Entity(name = "A") + public static class A { + @Id + private long id; + + private String name; + + @Version + @Column(name = "ver") + private int version; + } +} From 0bbacccf8d7c2e113c5c79887712e5b5b228c310 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 19 Jul 2018 17:52:31 -0700 Subject: [PATCH 082/772] HHH-12666 : Add documentation --- .../main/asciidoc/userguide/appendices/Configurations.adoc | 6 ++++++ .../src/main/java/org/hibernate/cfg/AvailableSettings.java | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index 31f385ab7684..e81686bf34f0 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -897,6 +897,12 @@ Whether the schema migration tool should halt on error, therefore terminating th `*hibernate.jdbc.sql_exception_converter*` (e.g. Fully-qualified name of class implementing `SQLExceptionConverter`):: The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/exception/spi/SQLExceptionConverter.html[`SQLExceptionConverter`] to use for converting `SQLExceptions` to Hibernate's `JDBCException` hierarchy. The default is to use the configured https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`]'s preferred `SQLExceptionConverter`. +`*hibernate.native_exception_handling_51_compliance*` (e.g. `true` or `false` (default value)):: +Indicates if exception handling for a `SessionFactory` built via Hibernate's native bootstrapping +should behave the same as native exception handling in Hibernate ORM 5.1. When set to `true`, +`HibernateException` will be not wrapped or converted according to the JPA specification. This +setting will be ignored for a `SessionFactory` built via JPA bootstrapping. + [[configurations-session-events]] === Session events diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 01b36ba06114..4d2712bccfd5 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -903,9 +903,10 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { /** * Indicates if exception handling for a SessionFactory built via Hibernate's native bootstrapping - * should behave the same as in Hibernate ORM 5.1. + * should behave the same as native exception handling in Hibernate ORM 5.1, When set to {@code true}, + * {@link HibernateException} will not be wrapped or converted according to the JPA specification. *

- * This setting will be ignored if the SessionFactory was built via JPA bootstrapping. + * This setting will be ignored for a SessionFactory built via JPA bootstrapping. *

* Values are {@code true} or {@code false}. * Default value is {@code false} From 5fd8b36e3411f8d97a6d4de8502a2c5c818572df Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 19 Jul 2018 23:13:29 -0700 Subject: [PATCH 083/772] HHH-12666 : Add test --- .../ExceptionExpectations.java | 58 +++++ .../TransactionExceptionHandlingTest.java | 213 ++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransactionExceptionHandlingTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java index 8e5f2398264e..26daa5688e72 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java @@ -9,8 +9,10 @@ import java.sql.SQLException; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; +import javax.persistence.RollbackException; import org.hibernate.StaleObjectStateException; +import org.hibernate.TransactionException; import org.hibernate.TransientObjectException; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.hql.internal.ast.QuerySyntaxException; @@ -63,6 +65,24 @@ public void onIdentifierGeneratorFailure(RuntimeException e) { assertThat( e, instanceOf( PersistenceException.class ) ); assertThat( e.getCause(), instanceOf( IdentifierGenerationException.class ) ); } + + @Override + public void onTransactionExceptionOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( TransactionException.class ) ); + } + + @Override + public void onTransactionExceptionOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( TransactionException.class ) ); + } + + @Override + public void onTransactionExceptionOnCommit(RuntimeException e) { + assertThat( e, instanceOf( RollbackException.class ) ); + assertThat( e.getCause(), instanceOf( PersistenceException.class ) ); + assertThat( e.getCause().getCause(), instanceOf( TransactionException.class ) ); + } }; } @@ -104,6 +124,21 @@ public void onStaleObjectMergeAndUpdateFlush(RuntimeException e) { public void onIdentifierGeneratorFailure(RuntimeException e) { assertThat( e, instanceOf( IdentifierGenerationException.class ) ); } + + @Override + public void onTransactionExceptionOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( TransactionException.class ) ); + } + + @Override + public void onTransactionExceptionOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( TransactionException.class ) ); + } + + @Override + public void onTransactionExceptionOnCommit(RuntimeException e) { + assertThat( e, instanceOf( TransactionException.class ) ); + } }; } @@ -150,6 +185,23 @@ public void onIdentifierGeneratorFailure(RuntimeException e) { assertThat( e, instanceOf( PersistenceException.class ) ); assertThat( e.getCause(), instanceOf( IdentifierGenerationException.class ) ); } + + @Override + public void onTransactionExceptionOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( TransactionException.class ) ); + } + + @Override + public void onTransactionExceptionOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( TransactionException.class ) ); + } + + @Override + public void onTransactionExceptionOnCommit(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( TransactionException.class ) ); + } }; } @@ -166,4 +218,10 @@ public void onIdentifierGeneratorFailure(RuntimeException e) { void onStaleObjectMergeAndUpdateFlush(RuntimeException e); void onIdentifierGeneratorFailure(RuntimeException e); + + void onTransactionExceptionOnSaveAndSaveOrUpdate(RuntimeException e); + + void onTransactionExceptionOnPersistAndMergeAndFlush(RuntimeException e); + + void onTransactionExceptionOnCommit(RuntimeException e); } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransactionExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransactionExceptionHandlingTest.java new file mode 100644 index 000000000000..0abf30053efe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransactionExceptionHandlingTest.java @@ -0,0 +1,213 @@ +/* + * 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.exceptionhandling; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.engine.spi.SessionImplementor; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-12666") +public class TransactionExceptionHandlingTest extends BaseExceptionHandlingTest { + + public TransactionExceptionHandlingTest( + BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + A.class, + B.class + }; + } + + @Test + public void testPersistWithGeneratedValue() throws Exception { + Session s = openSession(); + // Get the transaction and set the timeout BEFORE calling begin() + Transaction t = s.getTransaction(); + t.setTimeout( 1 ); + assertEquals( + -1, + ( (SessionImplementor) s ).getJdbcCoordinator().determineRemainingTransactionTimeOutPeriod() + ); + t.begin(); + Thread.sleep( 1000 ); + try { + s.persist( new A() ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected){ + exceptionExpectations.onTransactionExceptionOnPersistAndMergeAndFlush( expected ); + } + finally { + t.rollback(); + s.close(); + } + } + + @Test + public void testMergeWithGeneratedValue() throws Exception { + Session s = openSession(); + // Get the transaction and set the timeout BEFORE calling begin() + Transaction t = s.getTransaction(); + t.setTimeout( 1 ); + assertEquals( + -1, + ( (SessionImplementor) s ).getJdbcCoordinator().determineRemainingTransactionTimeOutPeriod() + ); + t.begin(); + Thread.sleep( 1000 ); + try { + s.merge( new A() ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected){ + exceptionExpectations.onTransactionExceptionOnPersistAndMergeAndFlush( expected ); + } + finally { + t.rollback(); + s.close(); + } + } + + @Test + public void testSaveWithGeneratedValue() throws Exception { + Session s = openSession(); + // Get the transaction and set the timeout BEFORE calling begin() + Transaction t = s.getTransaction(); + t.setTimeout( 1 ); + assertEquals( + -1, + ( (SessionImplementor) s ).getJdbcCoordinator().determineRemainingTransactionTimeOutPeriod() + ); + t.begin(); + Thread.sleep( 1000 ); + try { + s.save( new A() ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected){ + exceptionExpectations.onTransactionExceptionOnSaveAndSaveOrUpdate( expected ); + } + finally { + t.rollback(); + s.close(); + } + } + + @Test + public void testSaveOrUpdateWithGeneratedValue() throws Exception { + Session s = openSession(); + // Get the transaction and set the timeout BEFORE calling begin() + Transaction t = s.getTransaction(); + t.setTimeout( 1 ); + assertEquals( + -1, + ( (SessionImplementor) s ).getJdbcCoordinator().determineRemainingTransactionTimeOutPeriod() + ); + t.begin(); + Thread.sleep( 1000 ); + try { + s.saveOrUpdate( new A() ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected){ + exceptionExpectations.onTransactionExceptionOnSaveAndSaveOrUpdate( expected ); + } + finally { + t.rollback(); + s.close(); + } + } + + @Test + public void testFlushWithAssignedValue() throws Exception { + Session s = openSession(); + // Get the transaction and set the timeout BEFORE calling begin() + Transaction t = s.getTransaction(); + t.setTimeout( 1 ); + assertEquals( + -1, + ( (SessionImplementor) s ).getJdbcCoordinator().determineRemainingTransactionTimeOutPeriod() + ); + t.begin(); + Thread.sleep( 1000 ); + try { + s.persist( new B( 1 ) ); + s.flush(); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected){ + exceptionExpectations.onTransactionExceptionOnPersistAndMergeAndFlush( expected ); + } + finally { + t.rollback(); + s.close(); + } + } + + @Test + public void testCommitWithAssignedValue() throws Exception { + Session s = openSession(); + // Get the transaction and set the timeout BEFORE calling begin() + Transaction t = s.getTransaction(); + t.setTimeout( 1 ); + assertEquals( + -1, + ( (SessionImplementor) s ).getJdbcCoordinator().determineRemainingTransactionTimeOutPeriod() + ); + t.begin(); + Thread.sleep( 1000 ); + try { + s.persist( new B( 1 ) ); + s.getTransaction().commit(); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected){ + exceptionExpectations.onTransactionExceptionOnCommit( expected ); + } + finally { + t.rollback(); + s.close(); + } + } + + @Entity(name = "A") + public static class A { + @Id + @GeneratedValue + private long id; + } + + @Entity(name = "B") + public static class B { + @Id + private long id; + + public B() { + } + + public B(long id) { + this.id = id; + } + } + +} From 1b445a7e0240de993366bc775746beeee019caf3 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 19 Jul 2018 23:56:57 -0700 Subject: [PATCH 084/772] HHH-12666 : Document hibernate.native_exception_handling_51_compliance in migration guide --- migration-guide.adoc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/migration-guide.adoc b/migration-guide.adoc index 26cb6fee30b2..f745018bd365 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -171,6 +171,17 @@ making the `org.hibernate.jpa.event.spi.JpaIntegrator` no longer needed. Existing applications migrating to 5.3 with classes extending `org.hibernate.jpa.event.spi.JpaIntegrator` have to change these classes to implement the `org.hibernate.integrator.spi.Integrator` interface. ==== +=== 5.1 -> 5.3 exception handling changes + +In 5.3 (as well as 5.2), exception handling for a `SessionFactory` built via Hibernate's native +bootstrapping wraps or converts `HibernateException` according to the JPA specification unless the +operation is Hibernate-specific (e.g., `Session#save`, `Session#saveOrUpdate`). + +In 5.3.3, a property was added, `hibernate.native_exception_handling_51_compliance`, which +indicates if exception handling for a `SessionFactory` built via Hibernate's native bootstrapping +should behave the same as native exception handling in Hibernate ORM 5.1. When set to `true`, +`HibernateException` will not be wrapped or converted according to the JPA specification. This +setting will be ignored for a `SessionFactory` built via JPA bootstrapping. === 5.3 -> 6.0 compatibility changes From ad08bdf513e354c6b349d430a4ba79b27f590d53 Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Fri, 20 Jul 2018 09:38:51 +0200 Subject: [PATCH 085/772] HHH-12838 Fix AndNationalizedTests with DB2 This is consecutive to the changes made in HHH-12753. --- .../org/hibernate/test/converter/AndNationalizedTests.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/AndNationalizedTests.java b/hibernate-core/src/test/java/org/hibernate/test/converter/AndNationalizedTests.java index 02b0a67b0e1b..483c0536a0bf 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/converter/AndNationalizedTests.java +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/AndNationalizedTests.java @@ -19,6 +19,7 @@ import org.hibernate.boot.internal.MetadataImpl; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.mapping.PersistentClass; @@ -45,8 +46,9 @@ public void basicTest() { ( (MetadataImpl) metadata ).validate(); final PersistentClass entityBinding = metadata.getEntityBinding( TestEntity.class.getName() ); - if(metadata.getDatabase().getDialect() instanceof PostgreSQL81Dialect){ - // See issue HHH-10693 + if(metadata.getDatabase().getDialect() instanceof PostgreSQL81Dialect + || metadata.getDatabase().getDialect() instanceof DB2Dialect){ + // See issue HHH-10693 for PostgreSQL, HHH-12753 for DB2 assertEquals( Types.VARCHAR, entityBinding.getProperty( "name" ).getType().sqlTypes( metadata )[0] From 0075225ab715b15dd8e5386e4dd43bd79963a13f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 20 Jul 2018 10:37:06 +0200 Subject: [PATCH 086/772] HHH-12832 Make SchemaUpdateHaltOnErrorTest use the CustomRunner Otherwise @SkipForDialect doesn't work. --- .../test/schemaupdate/SchemaUpdateHaltOnErrorTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java index 3ce899354ccd..a52c80d65e2f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java @@ -22,18 +22,21 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.dialect.DB2Dialect; import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.junit4.CustomRunner; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.schema.TargetType; import org.hibernate.tool.schema.spi.SchemaManagementException; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; /** * @author Vlad Mihalcea * @author Gail Badner */ @SkipForDialect(value = DB2Dialect.class, comment = "DB2 is far more resistant to the reserved keyword usage. See HHH-12832.") +@RunWith(CustomRunner.class) public class SchemaUpdateHaltOnErrorTest { private File output; From 0091169f9deba0fc22d51dbdf961b663c5a1a279 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 20 Jul 2018 11:26:25 +0200 Subject: [PATCH 087/772] HHH-12666 Only run the tests with H2 We don't need to run them with all our dialects: we just need to check the mechanism works correctly. At least TransactionExceptionHandlingTest fails with MariaDB so better be safe. --- .../ConstraintViolationExceptionHandlingTest.java | 8 +++++--- .../IdentifierGenerationExceptionHandlingTest.java | 8 +++++--- .../QuerySyntaxExceptionHandlingTest.java | 3 +++ .../StateObjectStateExceptionHandlingTest.java | 3 +++ .../TransactionExceptionHandlingTest.java | 4 +++- .../TransientObjectExceptionHandlingTest.java | 4 +++- 6 files changed, 22 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java index 02675330e1f0..245a5a793a15 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java @@ -6,6 +6,8 @@ */ package org.hibernate.test.exceptionhandling; +import static org.junit.Assert.fail; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -15,13 +17,13 @@ import org.hibernate.Session; import org.hibernate.Transaction; - +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; -import static org.junit.Assert.fail; - @TestForIssue(jiraKey = "HHH-12666") +@RequiresDialect(H2Dialect.class) public class ConstraintViolationExceptionHandlingTest extends BaseExceptionHandlingTest { public ConstraintViolationExceptionHandlingTest(BootstrapMethod bootstrapMethod, diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/IdentifierGenerationExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/IdentifierGenerationExceptionHandlingTest.java index 0a24498783ef..729fcfd6fd38 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/IdentifierGenerationExceptionHandlingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/IdentifierGenerationExceptionHandlingTest.java @@ -6,6 +6,8 @@ */ package org.hibernate.test.exceptionhandling; +import static org.junit.Assert.fail; + import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -16,13 +18,13 @@ import org.hibernate.Session; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; - +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; -import static org.junit.Assert.fail; - @TestForIssue(jiraKey = "HHH-12666") +@RequiresDialect(H2Dialect.class) public class IdentifierGenerationExceptionHandlingTest extends BaseExceptionHandlingTest { public IdentifierGenerationExceptionHandlingTest( diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java index a464cf3fdf8a..6f3dca8679b9 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java @@ -13,10 +13,13 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @TestForIssue(jiraKey = "HHH-12666") +@RequiresDialect(H2Dialect.class) public class QuerySyntaxExceptionHandlingTest extends BaseExceptionHandlingTest { public QuerySyntaxExceptionHandlingTest( diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/StateObjectStateExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/StateObjectStateExceptionHandlingTest.java index 7bd418d9ace8..c0c50be6ef55 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/StateObjectStateExceptionHandlingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/StateObjectStateExceptionHandlingTest.java @@ -14,10 +14,13 @@ import javax.persistence.Version; import org.hibernate.Session; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @TestForIssue(jiraKey = "HHH-12666") +@RequiresDialect(H2Dialect.class) public class StateObjectStateExceptionHandlingTest extends BaseExceptionHandlingTest { public StateObjectStateExceptionHandlingTest( diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransactionExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransactionExceptionHandlingTest.java index 0abf30053efe..4dc5800e8d56 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransactionExceptionHandlingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransactionExceptionHandlingTest.java @@ -12,8 +12,9 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.H2Dialect; import org.hibernate.engine.spi.SessionImplementor; - +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -21,6 +22,7 @@ import static org.junit.Assert.fail; @TestForIssue(jiraKey = "HHH-12666") +@RequiresDialect(H2Dialect.class) public class TransactionExceptionHandlingTest extends BaseExceptionHandlingTest { public TransactionExceptionHandlingTest( diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java index c5a5c56a9535..a59fd10df0d7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java @@ -13,13 +13,15 @@ import org.hibernate.Session; import org.hibernate.Transaction; - +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.fail; @TestForIssue(jiraKey = "HHH-12666") +@RequiresDialect(H2Dialect.class) public class TransientObjectExceptionHandlingTest extends BaseExceptionHandlingTest { public TransientObjectExceptionHandlingTest(BootstrapMethod bootstrapMethod, From d02ad78eb72f86a32931e16c1ef7d494940dfac3 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 20 Jul 2018 11:27:32 +0200 Subject: [PATCH 088/772] HHH-7404 HHH-6776 Add tests to prevent regressions The issues have apparently already been fixed but let's add the tests present in the issue to prevent regressions. --- .../OneToManyDuplicateInsertionTest.java | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/onetomany/OneToManyDuplicateInsertionTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/OneToManyDuplicateInsertionTest.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/OneToManyDuplicateInsertionTest.java new file mode 100644 index 000000000000..fad7237571b8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/OneToManyDuplicateInsertionTest.java @@ -0,0 +1,194 @@ +/* + * 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.onetomany; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.LinkedList; +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.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +public class OneToManyDuplicateInsertionTest extends BaseEntityManagerFunctionalTestCase { + + private int parentId; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Parent.class, Child.class, ParentCascade.class, ChildCascade.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-6776") + public void testDuplicateInsertion() { + // persist parent entity in a transaction + + doInJPA( this::entityManagerFactory, em -> { + Parent parent = new Parent(); + em.persist( parent ); + parentId = parent.getId(); + } ); + + // relate and persist child entity in another transaction + + doInJPA( this::entityManagerFactory, em -> { + Parent parent = em.find( Parent.class, parentId ); + Child child = new Child(); + child.setParent( parent ); + parent.getChildren().add( child ); + em.persist( child ); + + assertEquals( 1, parent.getChildren().size() ); + } ); + + // get the parent again + + doInJPA( this::entityManagerFactory, em -> { + Parent parent = em.find( Parent.class, parentId ); + + assertEquals( 1, parent.getChildren().size() ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-7404") + public void testDuplicateInsertionWithCascadeAndMerge() { + doInJPA( this::entityManagerFactory, em -> { + ParentCascade p = new ParentCascade(); + // merge with 0 children + p = em.merge( p ); + parentId = p.getId(); + } ); + + doInJPA( this::entityManagerFactory, em -> { + ParentCascade p = em.find( ParentCascade.class, parentId ); + final ChildCascade child = new ChildCascade(); + child.setParent( p ); + p.getChildren().add( child ); + em.merge( p ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + // again, load the Parent by id + ParentCascade p = em.find( ParentCascade.class, parentId ); + + // check that we have only 1 element in the list + assertEquals( 1, p.getChildren().size() ); + } ); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + @GeneratedValue + private int id; + + @OneToMany(mappedBy = "parent") + private List children = new LinkedList(); + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + } + + @Entity(name = "Child") + public static class Child { + + @Id + @GeneratedValue + private int id; + + @ManyToOne + private Parent parent; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } + + @Entity(name = "ParentCascade") + public static class ParentCascade { + + @Id + @GeneratedValue + private Integer id; + + @OneToMany(mappedBy = "parent", cascade = { CascadeType.ALL }) + private List children = new ArrayList(); + + public Integer getId() { + return id; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + } + + @Entity(name = "ChildCascade") + public static class ChildCascade { + + @Id + @GeneratedValue + private Integer id; + + @ManyToOne + private ParentCascade parent; + + public Integer getId() { + return id; + } + + public ParentCascade getParent() { + return parent; + } + + public void setParent(ParentCascade parent) { + this.parent = parent; + } + } +} From 0caee9835513c79d0cdba8ca0a7e2dc61f31a2e1 Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Fri, 20 Jul 2018 10:37:05 +0200 Subject: [PATCH 089/772] HHH-12839 Fix EntityProxySerializationTest with Oracle --- .../hibernate/serialization/EntityProxySerializationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java index 0ca5621e9de2..566e3681166a 100644 --- a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java @@ -290,7 +290,7 @@ public void setChildren(final Set children) { } - @Entity + @Entity(name = "ChildEntity") static class ChildEntity { private Long id; From 88a015368b6e94515dda51b305e83f8150faade4 Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Fri, 20 Jul 2018 13:11:27 +0200 Subject: [PATCH 090/772] HHH-12843 Fix CreateDeleteTest and FlushIdGenTest with Oracle --- .../src/test/java/org/hibernate/id/RootEntity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java b/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java index 742917dc2afd..fd9cf6ed726f 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java @@ -19,15 +19,18 @@ public class RootEntity implements Serializable { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) - @Column(name = "universalid")// "uid" is a keywork in Oracle + @Column(name = "universalid")// "uid" is a keyword in Oracle private long uid; + public String description; + @javax.persistence.OneToMany(mappedBy = "linkedRoot") private java.util.List linkedEntities = new java.util.ArrayList(); public long getUid() { return uid; } + public void setUid(long uid) { this.uid = uid; } From 95c11255f86d3f91712fa1ff9a658c501ff91b9a Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Fri, 20 Jul 2018 13:54:24 +0200 Subject: [PATCH 091/772] HHH-12844 Fix HbmWithIdentityTest with Oracle --- .../java/org/hibernate/test/annotations/xml/hbm/A.java | 4 ++++ .../org/hibernate/test/annotations/xml/hbm/AImpl.java | 10 ++++++++++ .../org/hibernate/test/annotations/xml/hbm/A.hbm.xml | 1 + 3 files changed, 15 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java index e3558de12055..fcf80d24429f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java @@ -13,4 +13,8 @@ public interface A extends java.io.Serializable { public Integer getAId(); public void setAId(Integer aId); + + String getDescription(); + + void setDescription(String description); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java index 34bab4b1dda5..3b13900d80b3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java @@ -23,6 +23,7 @@ public class AImpl implements A { private static final long serialVersionUID = 1L; private Integer aId = 0; + private String description; public AImpl() { } @@ -37,4 +38,13 @@ public Integer getAId() { public void setAId(Integer aId) { this.aId = aId; } + + @Column( name = "description" ) + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } } diff --git a/hibernate-core/src/test/resources/org/hibernate/test/annotations/xml/hbm/A.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/annotations/xml/hbm/A.hbm.xml index 070b92bd3504..ade658500fc6 100644 --- a/hibernate-core/src/test/resources/org/hibernate/test/annotations/xml/hbm/A.hbm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/test/annotations/xml/hbm/A.hbm.xml @@ -14,5 +14,6 @@ + \ No newline at end of file From 3c81ade1988d0169b3d3fb52bec2857f45b89801 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 20 Jul 2018 15:45:36 +0200 Subject: [PATCH 092/772] 5.3.3.Final --- changelog.txt | 75 ++++++++++++++++++++++++++++++++++ gradle/base-information.gradle | 2 +- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index bd4c9e7eb7ce..95956baa392c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,81 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. + +Changes in 5.3.3.final (July 23, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31687/tab/release-report-done + +** Bug + * [HHH-7686] - org.hibernate.proxy.map.MapProxy loses all important state on serialization + * [HHH-8805] - [SchemaUpdate] javax.persistence.ForeignKey doesn't respect ConstraintMode.NO_CONSTRAINT + * [HHH-12200] - Docs mention outdated APIs + * [HHH-12542] - WildFly integration test, HibernateNativeAPINaturalIdTestCase, fails when security manager is enabled + * [HHH-12666] - Add an option for restoring 5.1 native exception handling + * [HHH-12695] - Incompatibility in return value for org.hibernate.procedure.ParameterRegistration.getType() 5.1 vs 5.3 + * [HHH-12718] - Entity changes in @PreUpdate callback are not persisted when lazy loading is active for more than one field + * [HHH-12720] - LazyInitializationException with hibernate.enable_lazy_load_no_trans + * [HHH-12740] - Subselect fetching doesn't work when multiLoad was used + * [HHH-12753] - org.hibernate.envers.test.integration.collection.StringMapNationalizedLobTest fails with DB2 + * [HHH-12768] - TimeAndTimestampTest fails with SQL Server and MYSQL + * [HHH-12771] - Caused by: java.lang.UnsupportedOperationException: Cache provider [org.hibernate.cache.ehcache.internal.EhcacheRegionFactory@3271ec2a] does not support `transactional` access + * [HHH-12776] - NullPointerException when executing native query on an Audited Entity + * [HHH-12779] - Revert HHH-12670 - Allows native SQL queries that take a given resultClass to map the result set to the required type + * [HHH-12781] - Update Javassist dependency to 3.23.1 + * [HHH-12784] - Javassist support broken by HHH-12760 + * [HHH-12786] - Deleting an entity leads to NullPointerException in ByteBuddy proxy + * [HHH-12787] - SessionJdbcBatchTest hangs with DB2 + * [HHH-12791] - ComponentTuplizer generates a LOT of proxy classes when using Bytebuddy as bytecode provider + * [HHH-12795] - Setting FlushMode to manual for a @NamedQuery is ignored + * [HHH-12797] - Fix cache modes relationships table layout in the documentation + * [HHH-12798] - Nested spatial functions are not rendered correctly on SAP HANA + * [HHH-12800] - TuplizerInstantiatesByteBuddySubclassTest uses ByteBuddy operation unsafe with JDK 11 + * [HHH-12802] - Hibernate does not throw an exception when more than one entity is loaded with the same ID + * [HHH-12815] - LocalDateCustomSessionLevelTimeZoneTest fails with mysql 5.5 and 5.7 + * [HHH-12822] - Skip "case when" tests requiring casts for DB2 + * [HHH-12823] - CompositeIdTest.testDistinctCountOfEntityWithCompositeId fails on databases that don't support tuple distinct counts because it expects wrong exception + * [HHH-12824] - ASTParserLoadingTest.testComponentNullnessChecks fail with DB2 because it uses legacy-style query parameter + * [HHH-12825] - CriteriaHQLAlignmentTest.testCountReturnValues fails on databases that don't support tuple distinct counts because it expects wrong exception + * [HHH-12826] - Persist cascade of collection fails when orphan removal enabled with flush mode commit. + * [HHH-12827] - NUMERIC column type is not handled correctly on DB2 + * [HHH-12829] - Invalid references to outdated EhCache classes + * [HHH-12832] - SchemaUpdateHaltOnErrorTest and SchemaMigratorHaltOnErrorTest fail with DB2 + * [HHH-12833] - UniqueConstraintDropTest fails with DB2 + * [HHH-12838] - AndNationalizedTests fails with DB2 + * [HHH-12839] - EntityProxySerializationTest fails with oracle + * [HHH-12843] - CreateDeleteTest and FlushIdGenTest fail with ORA-00936 on oracle + * [HHH-12844] - HbmWithIdentityTest fails with ORA-00936 on oracle + +** Task + * [HHH-12742] - Document the removal of JPAIntegrator SPI + * [HHH-12773] - Document org.hibernate.Query.getHibernateFirstResult(), setHibernateFirstResult(), getHibernateMaxResults(), setHibernateMaxResults() in migration guide + * [HHH-12774] - JARs missing from the distribution ZIP + * [HHH-12785] - Test Javassist support + * [HHH-12788] - Enable mockito-inline for the Agroal integration module + * [HHH-12789] - Upgrade to Mockito 2.19.0 + * [HHH-12793] - Upgrade Karaf, pax-exam and reenable the OSGi tests + * [HHH-12799] - Enforce version alignment of Mockito and ByteBuddy dependencies + * [HHH-12801] - Error message in SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest differs with JDK 11 + * [HHH-12803] - Upgrade ByteBuddy to 1.8.13 + * [HHH-12805] - Upgrade Mockito to 2.19.1 + * [HHH-12807] - Disable the hibernate-orm-modules tests for JDK 11 + * [HHH-12808] - Upgrade Gradle to 4.8.1 + * [HHH-12809] - Use an HTTP link for the Javadoc link to our Bean Validation documentation + * [HHH-12813] - Disable Asciidoclet in Javadoc generation + * [HHH-12816] - Enable the experimental features of ByteBuddy when building with JDK 11 + * [HHH-12820] - Merge the migration guides in the code base + * [HHH-12828] - ScannerTests#testGetBytesFromInputStream() is not stable enough + +** Improvement + * [HHH-12349] - User Guide documentation for @Filter is too verbose + * [HHH-12778] - BasicProxyFactoryImpl.getProxy() swallows exception + * [HHH-12804] - No need to mock Map in CollectionBinderTest + * [HHH-12811] - @UpdateTimestamp and @CreationTimestamp missing @Target annotation and breaking in Kotlin + * [HHH-12830] - Improve error output with transaction issues + + + Changes in 5.3.2.final (July 5, 2018) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 7a71134bd99e..0746d93fbfe7 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.3-SNAPSHOT', project ) + ormVersion = new HibernateVersion( '5.3.3.Final', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From 03a209c8ece9fa8b5d12d457eabd6e44e09df40d Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 20 Jul 2018 16:18:35 +0200 Subject: [PATCH 093/772] Prepare for next development iteration --- gradle/base-information.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 0746d93fbfe7..488745e86ee9 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.3.Final', project ) + ormVersion = new HibernateVersion( '5.3.4-SNAPSHOT', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From 27449d2dc257838d62b00ec3ac77ae256c792d6d Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Mon, 23 Jul 2018 10:33:51 -0400 Subject: [PATCH 094/772] HHH-12846 - Merge cascade of collection fails when orphan removal enabled with flush mode commit. (cherry picked from commit 333c190c826e1ad4058b85b6d98b8566b6c5fc36) --- .../internal/DefaultLoadEventListener.java | 6 ++- .../flush/CommitFlushCollectionTest.java | 49 +++++++++++++++++-- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java index 855e5e8a549e..e160b24e3144 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java @@ -14,6 +14,7 @@ import org.hibernate.PersistentObjectException; import org.hibernate.TypeMismatchException; import org.hibernate.WrongClassException; +import org.hibernate.action.internal.DelayedPostInsertIdentifier; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.cache.spi.entry.CacheEntry; @@ -30,7 +31,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; -import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventType; @@ -83,7 +83,9 @@ public void onLoad( } final Class idClass = persister.getIdentifierType().getReturnedClass(); - if ( idClass != null && !idClass.isInstance( event.getEntityId() ) ) { + if ( idClass != null && + !idClass.isInstance( event.getEntityId() ) && + !DelayedPostInsertIdentifier.class.isInstance( event.getEntityId() ) ) { checkIdClass( persister, event, loadType, idClass ); } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java index 2baf8ce656e4..ccce6841d9eb 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java @@ -39,7 +39,7 @@ /** * @author Chris Cranford */ -@TestForIssue(jiraKey = "HHH-12826") +@TestForIssue(jiraKey = "HHH-12826 and HHH-12846") public class CommitFlushCollectionTest extends BaseEnversJPAFunctionalTestCase { @MappedSuperclass @@ -184,6 +184,38 @@ private Long persistDocument(FlushModeType flushModeType) { } } + private void mergeDocument(FlushModeType flushModeType, Long id) { + final EntityManager entityManager = getOrCreateEntityManager(); + try { + entityManager.setFlushMode( flushModeType ); + + entityManager.getTransaction().begin(); + DocumentA doc = entityManager.find( DocumentA.class, id ); + doc.setDate( new Date() ); + for ( DocumentLineA line : doc.getLines() ) { + line.setText( "Updated" ); + } + + DocumentLineA line = new DocumentLineA(); + line.setText( "line2" ); + doc.addLine( line ); + + entityManager.merge( doc ); + entityManager.getTransaction().commit(); + } + catch ( Exception e ) { + if ( entityManager != null && entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + finally { + if ( entityManager != null && entityManager.isOpen() ) { + entityManager.close(); + } + } + } + private Long entityId1; private Long entityId2; @@ -192,18 +224,25 @@ private Long persistDocument(FlushModeType flushModeType) { public void initData() { // This failed when using Envers. entityId1 = persistDocument( FlushModeType.COMMIT ); + // This worked entityId2 = persistDocument( FlushModeType.AUTO ); + + // This failed + mergeDocument( FlushModeType.COMMIT, entityId1 ); + + // This worked + mergeDocument( FlushModeType.AUTO, entityId2 ); } @Test - public void testPersistWithFlushModeCommit() { - assertEquals( Arrays.asList( 1 ), getAuditReader().getRevisions( DocumentA.class, entityId1 ) ); + public void testWithFlushModeCommit() { + assertEquals( Arrays.asList( 1, 3 ), getAuditReader().getRevisions( DocumentA.class, entityId1 ) ); } @Test @Priority(1) - public void testPersistWithFlushmodeAuto() { - assertEquals( Arrays.asList( 2 ), getAuditReader().getRevisions( DocumentA.class, entityId2 ) ); + public void testWithFlushModeAuto() { + assertEquals( Arrays.asList( 2, 4 ), getAuditReader().getRevisions( DocumentA.class, entityId2 ) ); } } From 3e8b35a370ad1c341ddcebe40a1cb2a7ed0bf9b9 Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Mon, 23 Jul 2018 16:16:38 +0200 Subject: [PATCH 095/772] HHH-12851 ConverterTest fails with SQL Server depending on collation --- .../java/org/hibernate/test/converter/ConverterTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/ConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/converter/ConverterTest.java index c934533796e4..6656d4e9a55a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/converter/ConverterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/ConverterTest.java @@ -40,7 +40,7 @@ protected void afterEntityManagerFactoryBuilt() { doInJPA( this::entityManagerFactory, entityManager -> { Photo photo = new Photo(); photo.setId( 1 ); - photo.setName( "Dorobanțul" ); + photo.setName( "Dorobantul" ); photo.setCaption( new Caption( "Nicolae Grigorescu" ) ); entityManager.persist( photo ); @@ -59,7 +59,7 @@ public void testJPQLUpperDbValueBindParameter() throws Exception { .getSingleResult(); //end::basic-attribute-converter-query-parameter-converter-dbdata-example[] - assertEquals( "Dorobanțul", photo.getName() ); + assertEquals( "Dorobantul", photo.getName() ); } ); } @@ -85,7 +85,7 @@ public void testJPQLUpperAttributeValueBindParameterType() throws Exception { .getSingleResult(); //end::basic-attribute-converter-query-parameter-converter-object-example[] - assertEquals( "Dorobanțul", photo.getName() ); + assertEquals( "Dorobantul", photo.getName() ); } ); } From a0ca735ae31aacfa4008824ce05a0995f565ea36 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 23 Jul 2018 21:48:12 -0700 Subject: [PATCH 096/772] HHH-12792 : Document binary incompatibility of persisters --- migration-guide.adoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/migration-guide.adoc b/migration-guide.adoc index f745018bd365..cf0aabe0ae4a 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -171,6 +171,11 @@ making the `org.hibernate.jpa.event.spi.JpaIntegrator` no longer needed. Existing applications migrating to 5.3 with classes extending `org.hibernate.jpa.event.spi.JpaIntegrator` have to change these classes to implement the `org.hibernate.integrator.spi.Integrator` interface. ==== +=== Persister changes + +Due to changes to SPIs for persisters (in `org.hibernate.persister` package), custom persisters will need +to be updated to follow the new SPIs. + === 5.1 -> 5.3 exception handling changes In 5.3 (as well as 5.2), exception handling for a `SessionFactory` built via Hibernate's native From 77661b2275813cd48fedecb4692489e0a3cd65e9 Mon Sep 17 00:00:00 2001 From: Martin Simka Date: Fri, 27 Jul 2018 10:24:45 +0200 Subject: [PATCH 097/772] HHH-12863 SchemaUpdateTest should be skipped with Sybase --- .../org/hibernate/test/schemaupdate/SchemaUpdateTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateTest.java index f2c3804e63d1..8d3c544767fc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateTest.java @@ -40,6 +40,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.tool.hbm2ddl.SchemaExport; @@ -82,8 +83,9 @@ public static Collection parameters() { @Before public void setUp() throws IOException { - if(SQLServerDialect.class.isAssignableFrom( Dialect.getDialect().getClass() )) { - // SQLServerDialect stores case-insensitive quoted identifiers in mixed case, + if(SQLServerDialect.class.isAssignableFrom( Dialect.getDialect().getClass() ) + || SybaseDialect.class.isAssignableFrom(Dialect.getDialect().getClass())) { + // SQLServerDialect and SybaseDialect stores case-insensitive quoted identifiers in mixed case, // so the checks at the end of this method won't work. skipTest = true; return; From 6cf91c832c71e23a951460b1daa23ce04e2ca9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 18 Jul 2018 16:56:08 +0200 Subject: [PATCH 098/772] HHH-12492 Test HQL Delete query with a subquery referencing the parent query's aliases --- ...SubqueryReferencingTargetPropertyTest.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/DeleteQuerySubqueryReferencingTargetPropertyTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteQuerySubqueryReferencingTargetPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteQuerySubqueryReferencingTargetPropertyTest.java new file mode 100644 index 000000000000..3d91f666200f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteQuerySubqueryReferencingTargetPropertyTest.java @@ -0,0 +1,83 @@ +package org.hibernate.test.hql; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +@TestForIssue(jiraKey = "HHH-12492") +public class DeleteQuerySubqueryReferencingTargetPropertyTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Master.class, Detail.class }; + } + + @Test + @FailureExpected(jiraKey = "HHH-12492") + public void testSubQueryReferencingTargetProperty() { + // prepare + doInJPA( this::entityManagerFactory, entityManager -> { + Master m1 = new Master(); + entityManager.persist( m1 ); + Detail d11 = new Detail( m1 ); + entityManager.persist( d11 ); + Detail d12 = new Detail( m1 ); + entityManager.persist( d12 ); + + Master m2 = new Master(); + entityManager.persist( m2 ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + // depending on the generated ids above this delete removes all masters or nothing + // removal of all masters results in foreign key constraint violation + // removal of nothing is incorrect since 2nd master does not have any details + + // DO NOT CHANGE this query: it used to trigger a very specific bug caused + // by the alias not being added to the generated query + String d = "delete from Master m where not exists (select d from Detail d where d.master=m)"; + Query del = entityManager.createQuery( d ); + del.executeUpdate(); + + // so check for exactly one master after deletion + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Master.class ); + query.select( query.from( Master.class ) ); + Assert.assertEquals( 1, entityManager.createQuery( query ).getResultList().size() ); + } ); + } + + @Entity(name = "Master") + public static class Master { + @Id + @GeneratedValue + private Integer id; + } + + @Entity(name = "Detail") + public static class Detail { + @Id + @GeneratedValue + private Integer id; + + @ManyToOne(optional = false) + private Master master; + + public Detail(Master master) { + this.master = master; + } + } +} From b8b7a0f19b2a32a436cedfd6c3632b3d9c0d7ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 18 Jul 2018 18:39:39 +0200 Subject: [PATCH 099/772] HHH-12492 Qualify references to columns from the target table in subqueries in DELETE/UPDATE queries Don't try to duplicate the logic from org.hibernate.hql.internal.ast.tree.FromElementType#toColumns(java.lang.String, java.lang.String, boolean, boolean) in other classes, it's complex enough and already seems to handle all the cases we might encounter. In this specific case, we want the table name to be used to qualify column names, because the target table doesn't have any alias (it's not supported by every version of every RDBMS), and not qualifying columns at all may lead to a confusing statement, in particular if tables referenced in the subquery contain columns with the same name. Since we use aliases for every other table in the query, referencing the table should not lead to any conflict. --- .../hibernate/hql/internal/ast/tree/FromElement.java | 10 ++++------ .../org/hibernate/hql/internal/ast/tree/IdentNode.java | 8 -------- ...leteQuerySubqueryReferencingTargetPropertyTest.java | 2 -- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java index bb47d0e090ca..1b0affe72665 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java @@ -341,12 +341,10 @@ public String[] getIdentityColumns() { final String propertyName = getIdentifierPropertyName(); - if ( getWalker().getStatementType() == HqlSqlTokenTypes.SELECT ) { - return getPropertyMapping( propertyName ).toColumns( table, propertyName ); - } - else { - return getPropertyMapping( propertyName ).toColumns( propertyName ); - } + return toColumns( + table, propertyName, + getWalker().getStatementType() == HqlSqlTokenTypes.SELECT + ); } public void setCollectionJoin(boolean collectionJoin) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java index 81a26186c3f6..1574c57feb9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java @@ -197,14 +197,6 @@ private boolean resolveAsAlias() { String[] columnExpressions = element.getIdentityColumns(); - // determine whether to apply qualification (table alias) to the column(s)... - if ( ! isFromElementUpdateOrDeleteRoot( element ) ) { - if ( StringHelper.isNotEmpty( element.getTableAlias() ) ) { - // apparently we also need to check that they are not already qualified. Ugh! - columnExpressions = StringHelper.qualifyIfNot( element.getTableAlias(), columnExpressions ); - } - } - final Dialect dialect = getWalker().getSessionFactoryHelper().getFactory().getDialect(); final boolean isInCount = getWalker().isInCount(); final boolean isInDistinctCount = isInCount && getWalker().isInCountDistinct(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteQuerySubqueryReferencingTargetPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteQuerySubqueryReferencingTargetPropertyTest.java index 3d91f666200f..9e2be86721ae 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteQuerySubqueryReferencingTargetPropertyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/DeleteQuerySubqueryReferencingTargetPropertyTest.java @@ -10,7 +10,6 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.TestForIssue; import org.junit.Assert; import org.junit.Test; @@ -26,7 +25,6 @@ protected Class[] getAnnotatedClasses() { } @Test - @FailureExpected(jiraKey = "HHH-12492") public void testSubQueryReferencingTargetProperty() { // prepare doInJPA( this::entityManagerFactory, entityManager -> { From 9ab285eb58f65c66a2a8001b3ffc203775c473ab Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 26 Jul 2018 15:20:05 +0100 Subject: [PATCH 100/772] HHH-12861 SchemaUpdate doesn't work with Sybase --- .../org/hibernate/dialect/SybaseDialect.java | 5 + ...dateWithKeywordAutoQuotingEnabledTest.java | 45 ++++---- .../NumericValidationTest.java | 100 +++++++----------- 3 files changed, 73 insertions(+), 77 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index 19020e932900..b20284657c69 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -43,4 +43,9 @@ protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { public String getNullColumnString() { return " null"; } + + @Override + public String getCurrentSchemaCommand() { + return "select db_name()"; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/foreignkeys/SchemaUpdateWithKeywordAutoQuotingEnabledTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/foreignkeys/SchemaUpdateWithKeywordAutoQuotingEnabledTest.java index 5e582570b2b5..b1e903ab953e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/foreignkeys/SchemaUpdateWithKeywordAutoQuotingEnabledTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/foreignkeys/SchemaUpdateWithKeywordAutoQuotingEnabledTest.java @@ -6,16 +6,15 @@ */ package org.hibernate.test.schemaupdate.foreignkeys; +import java.util.EnumSet; +import java.util.Map; +import java.util.TreeMap; import javax.persistence.CollectionTable; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.Table; -import java.io.IOException; -import java.util.EnumSet; -import java.util.Map; -import java.util.TreeMap; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; @@ -25,13 +24,12 @@ import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.schema.TargetType; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseUnitTestCase; - /** * @author Andrea Boriero */ @@ -41,36 +39,36 @@ public class SchemaUpdateWithKeywordAutoQuotingEnabledTest extends BaseUnitTestC private MetadataImplementor metadata; @Before - public void setUp() throws IOException { + public void setUp() { final StandardServiceRegistryBuilder standardServiceRegistryBuilder = new StandardServiceRegistryBuilder(); standardServiceRegistryBuilder.applySetting( org.hibernate.cfg.AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, "true" ); ssr = standardServiceRegistryBuilder.build(); - final MetadataSources metadataSources = new MetadataSources( ssr ); + final MetadataSources metadataSources = new MetadataSources( ssr ); metadataSources.addAnnotatedClass( Match.class ); metadata = (MetadataImplementor) metadataSources.buildMetadata(); metadata.validate(); - - new SchemaExport().setHaltOnError( true ) - .setFormat( false ) - .createOnly( EnumSet.of( TargetType.DATABASE ), metadata ); + try { + createSchema(); + } + catch (Exception e) { + tearDown(); + throw e; + } } @After - public void tearsDown() { - new SchemaExport().setHaltOnError( true ) - .setFormat( false ) - .drop( EnumSet.of( TargetType.DATABASE ), metadata ); + public void tearDown() { + dropSchema(); StandardServiceRegistryBuilder.destroy( ssr ); } @Test public void testUpdate() { new SchemaUpdate().setHaltOnError( true ) - .setFormat( false ) .execute( EnumSet.of( TargetType.DATABASE ), metadata ); } @@ -84,4 +82,15 @@ public static class Match { @CollectionTable private Map timeline = new TreeMap<>(); } + + private void createSchema() { + dropSchema(); + new SchemaExport().setHaltOnError( true ) + .createOnly( EnumSet.of( TargetType.DATABASE ), metadata ); + } + + private void dropSchema() { + new SchemaExport() + .drop( EnumSet.of( TargetType.DATABASE ), metadata ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/NumericValidationTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/NumericValidationTest.java index b39bf2fcfafe..536d6f760760 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/NumericValidationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemavalidation/NumericValidationTest.java @@ -14,7 +14,6 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; @@ -22,6 +21,7 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.schema.JdbcMetadaAccessStrategy; import org.hibernate.tool.schema.SourceType; import org.hibernate.tool.schema.TargetType; @@ -50,7 +50,10 @@ public class NumericValidationTest implements ExecutionOptions { @Parameterized.Parameters public static Collection parameters() { return Arrays.asList( - new String[] {JdbcMetadaAccessStrategy.GROUPED.toString(), JdbcMetadaAccessStrategy.INDIVIDUALLY.toString()} + new String[] { + JdbcMetadaAccessStrategy.GROUPED.toString(), + JdbcMetadaAccessStrategy.INDIVIDUALLY.toString() + } ); } @@ -58,16 +61,35 @@ public static Collection parameters() { public String jdbcMetadataExtractorStrategy; private StandardServiceRegistry ssr; + private MetadataImplementor metadata; @Before public void beforeTest() { ssr = new StandardServiceRegistryBuilder() - .applySetting( AvailableSettings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY, jdbcMetadataExtractorStrategy ) + .applySetting( + AvailableSettings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY, + jdbcMetadataExtractorStrategy + ) .build(); + metadata = (MetadataImplementor) new MetadataSources( ssr ) + .addAnnotatedClass( TestEntity.class ) + .buildMetadata(); + metadata.validate(); + + try { + dropSchema(); + // create the schema + createSchema(); + } + catch (Exception e) { + tearDown(); + throw e; + } } @After - public void afterTest() { + public void tearDown() { + dropSchema(); if ( ssr != null ) { StandardServiceRegistryBuilder.destroy( ssr ); } @@ -75,31 +97,15 @@ public void afterTest() { @Test public void testValidation() { - MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) - .addAnnotatedClass( TestEntity.class ) - .buildMetadata(); - metadata.validate(); - - - // create the schema - createSchema( metadata ); - - try { - doValidation( metadata ); - } - finally { - dropSchema( metadata ); - } + doValidation(); } - private void doValidation(MetadataImplementor metadata) { - ssr.getService( SchemaManagementTool.class ).getSchemaValidator( null ).doValidation( - metadata, - this - ); + private void doValidation() { + ssr.getService( SchemaManagementTool.class ).getSchemaValidator( null ) + .doValidation( metadata, this ); } - private void createSchema(MetadataImplementor metadata) { + private void createSchema() { ssr.getService( SchemaManagementTool.class ).getSchemaCreator( null ).doCreation( metadata, this, @@ -128,43 +134,19 @@ public ScriptTargetOutput getScriptTargetOutput() { ); } - private void dropSchema(MetadataImplementor metadata) { - ssr.getService( SchemaManagementTool.class ).getSchemaDropper( null ).doDrop( - metadata, - this, - new SourceDescriptor() { - @Override - public SourceType getSourceType() { - return SourceType.METADATA; - } - - @Override - public ScriptSourceInput getScriptSourceInput() { - return null; - } - }, - new TargetDescriptor() { - @Override - public EnumSet getTargetTypes() { - return EnumSet.of( TargetType.DATABASE ); - } - - @Override - public ScriptTargetOutput getScriptTargetOutput() { - return null; - } - } - ); + private void dropSchema() { + new SchemaExport() + .drop( EnumSet.of( TargetType.DATABASE ), metadata ); } -@Entity(name = "TestEntity") -public static class TestEntity { - @Id - public Integer id; + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + public Integer id; - @Column(name = "numberValue") - BigDecimal number; -} + @Column(name = "numberValue") + BigDecimal number; + } @Override public Map getConfigurationValues() { From e43c374f30a656a428db551746cbc23bf9c5877c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 30 Jul 2018 12:45:57 +0200 Subject: [PATCH 101/772] HHH-12868 Fix NPE when loading entity with CacheConcurrencyStrategy.NONE --- .../entity/AbstractEntityPersister.java | 8 +++- .../test/cache/CacheAnnotationTests.java | 38 ++++++++++++++++++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 1204fa1e971f..dd87b91f25c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -535,7 +535,7 @@ public AbstractEntityPersister( if ( creationContext.getSessionFactory().getSessionFactoryOptions().isSecondLevelCacheEnabled() ) { this.canWriteToCache = determineCanWriteToCache( persistentClass, cacheAccessStrategy ); - this.canReadFromCache = determineCanReadFromCache( persistentClass ); + this.canReadFromCache = determineCanReadFromCache( persistentClass, cacheAccessStrategy ); this.cacheAccessStrategy = cacheAccessStrategy; this.isLazyPropertiesCacheable = persistentClass.getRootClass().isLazyPropertiesCacheable(); this.naturalIdRegionAccessStrategy = naturalIdRegionAccessStrategy; @@ -916,7 +916,11 @@ private boolean determineCanWriteToCache(PersistentClass persistentClass, Entity } @SuppressWarnings("unchecked") - private boolean determineCanReadFromCache(PersistentClass persistentClass) { + private boolean determineCanReadFromCache(PersistentClass persistentClass, EntityDataAccess cacheAccessStrategy) { + if ( cacheAccessStrategy == null ) { + return false; + } + if ( persistentClass.isCached() ) { return true; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/cache/CacheAnnotationTests.java b/hibernate-core/src/test/java/org/hibernate/test/cache/CacheAnnotationTests.java index d4d219edc3b8..4c6340623c99 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/cache/CacheAnnotationTests.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/CacheAnnotationTests.java @@ -20,13 +20,16 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; /** * @author Chris Cranford */ -@TestForIssue(jiraKey = "HHH-12587") public class CacheAnnotationTests extends BaseCoreFunctionalTestCase { + private Integer entityId; + @Override protected void configure(Configuration configuration) { super.configure( configuration ); @@ -39,13 +42,34 @@ protected Class[] getAnnotatedClasses() { } @Test - public void testCacheConcurrencyStrategyNone() { + @TestForIssue(jiraKey = "HHH-12587") + public void testCacheWriteConcurrencyStrategyNone() { + doInHibernate( this::sessionFactory, session -> { + NoCacheConcurrencyStrategyEntity entity = new NoCacheConcurrencyStrategyEntity(); + session.save( entity ); + session.flush(); + session.clear(); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12868") + public void testCacheReadConcurrencyStrategyNone() { doInHibernate( this::sessionFactory, session -> { NoCacheConcurrencyStrategyEntity entity = new NoCacheConcurrencyStrategyEntity(); + entity.setName( "name" ); session.save( entity ); session.flush(); + + this.entityId = entity.getId(); + session.clear(); } ); + + doInHibernate( this::sessionFactory, session -> { + NoCacheConcurrencyStrategyEntity entity = session.load( NoCacheConcurrencyStrategyEntity.class, this.entityId ); + assertEquals( "name", entity.getName() ); + } ); } @Entity(name = "NoCacheConcurrencyStrategy") @@ -55,6 +79,8 @@ public static class NoCacheConcurrencyStrategyEntity { @GeneratedValue private Integer id; + private String name; + public Integer getId() { return id; } @@ -62,5 +88,13 @@ public Integer getId() { public void setId(Integer id) { this.id = id; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } } From dfa2bc0685fd14e4e2e645eceb12d9e94cc65de4 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 10 Jul 2018 23:47:29 -0700 Subject: [PATCH 102/772] HHH-12730 : User types built using 5.1 are not binary compatible with 5.3 --- .../AbstractPersistentCollection.java | 21 ++ .../internal/PersistentArrayHolder.java | 29 +- .../collection/internal/PersistentBag.java | 26 ++ .../collection/spi/PersistentCollection.java | 32 ++ .../type/AbstractStandardBasicType.java | 27 ++ .../ProcedureParameterExtractionAware.java | 39 ++ .../type/ProcedureParameterNamedBinder.java | 22 ++ .../org/hibernate/type/SingleColumnType.java | 61 +++ .../main/java/org/hibernate/type/Type.java | 356 +++++++++++++++++- .../java/org/hibernate/type/VersionType.java | 29 ++ .../hibernate/usertype/CompositeUserType.java | 98 +++++ .../usertype/UserCollectionType.java | 47 +++ .../java/org/hibernate/usertype/UserType.java | 45 ++- .../hibernate/usertype/UserVersionType.java | 30 ++ 14 files changed, 855 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index fc8389a8243d..a770e4b22222 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -26,6 +26,7 @@ import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.engine.spi.TypedValue; @@ -1281,6 +1282,26 @@ public static void identityRemove( } } + /** + * Removes entity entries that have an equal identifier with the incoming entity instance + * + * @param list The list containing the entity instances + * @param entityInstance The entity instance to match elements. + * @param entityName The entity name + * @param session The session + * + * @deprecated {@link #identityRemove(Collection, Object, String, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + public static void identityRemove( + Collection list, + Object entityInstance, + String entityName, + SessionImplementor session) { + identityRemove( list, entityInstance, entityName, (SharedSessionContractImplementor) session ); + } + @Override public Object getIdentifier(Object entry, int i) { throw new UnsupportedOperationException(); diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java index bb36565ab7db..866766afef5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java @@ -16,6 +16,7 @@ import java.util.Iterator; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.loader.CollectionAliases; @@ -55,6 +56,20 @@ public PersistentArrayHolder(SharedSessionContractImplementor session, Object ar setInitialized(); } + /** + * Constructs a PersistentCollection instance for holding an array. + * + * @param session The session + * @param array The array (the persistent "collection"). + * + * @deprecated {@link #PersistentArrayHolder(SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + public PersistentArrayHolder(SessionImplementor session, Object array) { + this( (SharedSessionContractImplementor) session, array ); + } + /** * Constructs a PersistentCollection instance for holding an array. * @@ -66,7 +81,19 @@ public PersistentArrayHolder(SharedSessionContractImplementor session, Collectio elementClass = persister.getElementClass(); } - + /** + * Constructs a PersistentCollection instance for holding an array. + * + * @param session The session + * @param persister The persister for the array + * + * @deprecated {@link #PersistentArrayHolder(SharedSessionContractImplementor, CollectionPersister)} + * should be used instead. + */ + @Deprecated + public PersistentArrayHolder(SessionImplementor session, CollectionPersister persister) { + this( (SharedSessionContractImplementor) session, persister ); + } @Override public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java index 3762d456e2b0..30ecd2b5e508 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java @@ -16,6 +16,7 @@ import java.util.ListIterator; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -49,6 +50,17 @@ public PersistentBag(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentBag + * + * @param session The session + * + * @deprecated {@link #PersistentBag(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentBag(SessionImplementor session) { + this( ( SharedSessionContractImplementor) session ); + } /** * Constructs a PersistentBag * @@ -71,6 +83,20 @@ public PersistentBag(SharedSessionContractImplementor session, Collection coll) setDirectlyAccessible( true ); } + /** + * Constructs a PersistentBag + * + * @param session The session + * @param coll The base elements. + * + * @deprecated {@link #PersistentBag(SharedSessionContractImplementor, Collection)} + * should be used instead. + */ + @Deprecated + public PersistentBag(SessionImplementor session, Collection coll) { + this( (SharedSessionContractImplementor) session, coll ); + } + @Override public boolean isWrapper(Object collection) { return bag==collection; diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java index a087beab6f85..e9020622357c 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java @@ -13,6 +13,7 @@ import java.util.Iterator; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -127,6 +128,20 @@ public interface PersistentCollection { */ boolean unsetSession(SharedSessionContractImplementor currentSession); + /** + * Disassociate this collection from the given session. + * + * @param currentSession The session we are disassociating from. Used for validations. + * + * @return true if this was currently associated with the given session + * + * @deprecated {@link #unsetSession(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default boolean unsetSession(SessionImplementor currentSession) { + return unsetSession( (SharedSessionContractImplementor) currentSession ); + } + /** * Associate the collection with the given session. * @@ -139,6 +154,23 @@ public interface PersistentCollection { */ boolean setCurrentSession(SharedSessionContractImplementor session) throws HibernateException; + /** + * Associate the collection with the given session. + * + * @param session The session to associate with + * + * @return false if the collection was already associated with the session + * + * @throws HibernateException if the collection was already associated + * with another open session + * + * @deprecated {@link #setCurrentSession(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default boolean setCurrentSession(SessionImplementor session) throws HibernateException { + return setCurrentSession( (SharedSessionContractImplementor) session ); + } + /** * Read the state of the collection from a disassembled cached value * diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java index eb91251e358f..1cc1878a0e12 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -20,6 +20,7 @@ import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.type.descriptor.WrapperOptions; @@ -257,6 +258,15 @@ public final T nullSafeGet(ResultSet rs, String name, final SharedSessionContrac return nullSafeGet( rs, name, (WrapperOptions) session ); } + /** + * @deprecated {@link #nullSafeGet(ResultSet, String, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + public final T nullSafeGet(ResultSet rs, String name, final SessionImplementor session) throws SQLException { + return nullSafeGet( rs, name, (SharedSessionContractImplementor) session ); + } + protected final T nullSafeGet(ResultSet rs, String name, WrapperOptions options) throws SQLException { return remapSqlTypeDescriptor( options ).getExtractor( javaTypeDescriptor ).extract( rs, name, options ); } @@ -265,6 +275,14 @@ public Object get(ResultSet rs, String name, SharedSessionContractImplementor se return nullSafeGet( rs, name, session ); } + /** + * @deprecated {@link #get(ResultSet, String, SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public Object get(ResultSet rs, String name, SessionImplementor session) throws HibernateException, SQLException { + return get( rs, name, (SharedSessionContractImplementor) session ); + } + @Override @SuppressWarnings({ "unchecked" }) public final void nullSafeSet( @@ -288,6 +306,15 @@ public void set(PreparedStatement st, T value, int index, SharedSessionContractI nullSafeSet( st, value, index, session ); } + /** + * @deprecated {@link #set(PreparedStatement, Object, int, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + public void set(PreparedStatement st, T value, int index, SessionImplementor session) throws HibernateException, SQLException { + set( st, value, index, (SharedSessionContractImplementor) session ); + } + @Override @SuppressWarnings({ "unchecked" }) public final String toLoggableString(Object value, SessionFactoryImplementor factory) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterExtractionAware.java b/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterExtractionAware.java index 6c3cce7b6c51..32a57ae4fcfe 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterExtractionAware.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterExtractionAware.java @@ -9,6 +9,7 @@ import java.sql.CallableStatement; import java.sql.SQLException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -39,6 +40,25 @@ public interface ProcedureParameterExtractionAware { */ T extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session) throws SQLException; + /** + * Perform the extraction + * + * @param statement The CallableStatement from which to extract the parameter value(s). + * @param startIndex The parameter index from which to start extracting; assumes the values (if multiple) are contiguous + * @param session The originating session + * + * @return The extracted value. + * + * @throws SQLException Indicates an issue calling into the CallableStatement + * @throws IllegalStateException Thrown if this method is called on instances that return {@code false} for {@link #canDoExtraction} + * + * @deprecated {@link #extract(CallableStatement, int, SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default T extract(CallableStatement statement, int startIndex, SessionImplementor session) throws SQLException { + return extract( statement, startIndex, (SharedSessionContractImplementor) session ); + } + /** * Perform the extraction * @@ -52,4 +72,23 @@ public interface ProcedureParameterExtractionAware { * @throws IllegalStateException Thrown if this method is called on instances that return {@code false} for {@link #canDoExtraction} */ T extract(CallableStatement statement, String[] paramNames, SharedSessionContractImplementor session) throws SQLException; + + /** + * Perform the extraction + * + * @param statement The CallableStatement from which to extract the parameter value(s). + * @param paramNames The parameter names. + * @param session The originating session + * + * @return The extracted value. + * + * @throws SQLException Indicates an issue calling into the CallableStatement + * @throws IllegalStateException Thrown if this method is called on instances that return {@code false} for {@link #canDoExtraction} + * + * @deprecated {@link #extract(CallableStatement, String[], SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default T extract(CallableStatement statement, String[] paramNames, SessionImplementor session) throws SQLException { + return extract( statement, paramNames, (SharedSessionContractImplementor) session ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterNamedBinder.java b/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterNamedBinder.java index c69661a9f120..7e1638026cac 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterNamedBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterNamedBinder.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -41,4 +42,25 @@ public interface ProcedureParameterNamedBinder { * @throws SQLException An error from the JDBC driver */ void nullSafeSet(CallableStatement statement, Object value, String name, SharedSessionContractImplementor session) throws SQLException; + + /** + * Bind a value to the JDBC prepared statement, ignoring some columns as dictated by the 'settable' parameter. + * Implementors should handle the possibility of null values. + * Does not support multi-column type + * + * @param statement The CallableStatement to which to bind + * @param value the object to write + * @param name parameter bind name + * @param session The originating session + * + * @throws HibernateException An error from Hibernate + * @throws SQLException An error from the JDBC driver + * + * @deprecated {@link #nullSafeSet(CallableStatement, Object, String, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default void nullSafeSet(CallableStatement statement, Object value, String name, SessionImplementor session) throws SQLException { + nullSafeSet( statement, value, name, (SharedSessionContractImplementor) session ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java b/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java index dbf8ee4ca9cf..b0185e3b0c04 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -40,6 +41,26 @@ public interface SingleColumnType extends Type { */ T nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session) throws HibernateException, SQLException; + /** + * Get a column value from a result set by name. + * + * @param rs The result set from which to extract the value. + * @param name The name of the value to extract. + * @param session The session from which the request originates + * + * @return The extracted value. + * + * @throws org.hibernate.HibernateException Generally some form of mismatch error. + * @throws java.sql.SQLException Indicates problem making the JDBC call(s). + * + * @deprecated {@link #nullSafeGet(ResultSet, String, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default T nullSafeGet(ResultSet rs, String name, final SessionImplementor session) throws SQLException { + return nullSafeGet( rs, name, (SharedSessionContractImplementor) session ); + } + /** * Get a column value from a result set, without worrying about the possibility of null values. * @@ -54,6 +75,25 @@ public interface SingleColumnType extends Type { */ Object get(ResultSet rs, String name, SharedSessionContractImplementor session) throws HibernateException, SQLException; + /** + * Get a column value from a result set, without worrying about the possibility of null values. + * + * @param rs The result set from which to extract the value. + * @param name The name of the value to extract. + * @param session The session from which the request originates + * + * @return The extracted value. + * + * @throws org.hibernate.HibernateException Generally some form of mismatch error. + * @throws java.sql.SQLException Indicates problem making the JDBC call(s). + * + * @deprecated {@link #get(ResultSet, String, SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default Object get(ResultSet rs, String name, SessionImplementor session) throws HibernateException, SQLException { + return get( rs, name, (SharedSessionContractImplementor) session ); + } + /** * Set a parameter value without worrying about the possibility of null * values. Called from {@link #nullSafeSet} after nullness checks have @@ -68,4 +108,25 @@ public interface SingleColumnType extends Type { * @throws java.sql.SQLException Indicates problem making the JDBC call(s). */ void set(PreparedStatement st, T value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException; + + /** + * Set a parameter value without worrying about the possibility of null + * values. Called from {@link #nullSafeSet} after nullness checks have + * been performed. + * + * @param st The statement into which to bind the parameter value. + * @param value The parameter value to bind. + * @param index The position or index at which to bind the param value. + * @param session The session from which the request originates + * + * @throws org.hibernate.HibernateException Generally some form of mismatch error. + * @throws java.sql.SQLException Indicates problem making the JDBC call(s). + * + * @deprecated {@link #set(PreparedStatement, Object, int, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default void set(PreparedStatement st, T value, int index, SessionImplementor session) throws HibernateException, SQLException { + set( st, value, index, (SharedSessionContractImplementor) session ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/Type.java b/hibernate-core/src/main/java/org/hibernate/type/Type.java index 07d90ccf8cc3..9895ea0e656f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/Type.java +++ b/hibernate-core/src/main/java/org/hibernate/type/Type.java @@ -17,6 +17,7 @@ import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -246,6 +247,26 @@ public interface Type extends Serializable { */ boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) throws HibernateException; + /** + * Should the parent be considered dirty, given both the old and current value? + * + * @param old the old value + * @param current the current value + * @param session The session from which the request originated. + * + * @return true if the field is dirty + * + * @throws HibernateException A problem occurred performing the checking + * + * @deprecated {@link #isDirty(Object, Object, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default boolean isDirty(Object old, Object current, SessionImplementor session) throws HibernateException { + return isDirty( old, current, (SharedSessionContractImplementor) session ); + } + + /** * Should the parent be considered dirty, given both the old and current value? * @@ -261,6 +282,27 @@ public interface Type extends Serializable { boolean isDirty(Object oldState, Object currentState, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException; + /** + * Should the parent be considered dirty, given both the old and current value? + * + * @param oldState the old value + * @param currentState the current value + * @param checkable An array of booleans indicating which columns making up the value are actually checkable + * @param session The session from which the request originated. + * + * @return true if the field is dirty + * + * @throws HibernateException A problem occurred performing the checking + * + * @deprecated {@link #isDirty(Object, Object, boolean[], SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default boolean isDirty(Object oldState, Object currentState, boolean[] checkable, SessionImplementor session) + throws HibernateException { + return isDirty( oldState, currentState, checkable, (SharedSessionContractImplementor) session ); + } + /** * Has the value been modified compared to the current database state? The difference between this * and the {@link #isDirty} methods is that here we need to account for "partially" built values. This is really @@ -283,6 +325,34 @@ boolean isModified( SharedSessionContractImplementor session) throws HibernateException; + /** + * Has the value been modified compared to the current database state? The difference between this + * and the {@link #isDirty} methods is that here we need to account for "partially" built values. This is really + * only an issue with association types. For most type implementations it is enough to simply delegate to + * {@link #isDirty} here/ + * + * @param dbState the database state, in a "hydrated" form, with identifiers unresolved + * @param currentState the current state of the object + * @param checkable which columns are actually updatable + * @param session The session from which the request originated. + * + * @return true if the field has been modified + * + * @throws HibernateException A problem occurred performing the checking + * + * @deprecated {@link #isModified(Object, Object, boolean[], SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default boolean isModified( + Object dbState, + Object currentState, + boolean[] checkable, + SessionImplementor session) + throws HibernateException { + return isModified( dbState, currentState, checkable, (SharedSessionContractImplementor) session ); + } + /** * Extract a value of the {@link #getReturnedClass() mapped class} from the JDBC result set. Implementors * should handle possibility of null values. @@ -302,6 +372,31 @@ boolean isModified( Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; + /** + * Extract a value of the {@link #getReturnedClass() mapped class} from the JDBC result set. Implementors + * should handle possibility of null values. + * + * @param rs The result set from which to extract value. + * @param names the column names making up this type value (use to read from result set) + * @param session The originating session + * @param owner the parent entity + * + * @return The extracted value + * + * @throws HibernateException An error from Hibernate + * @throws SQLException An error from the JDBC driver + * + * @see Type#hydrate(ResultSet, String[], SharedSessionContractImplementor, Object) alternative, 2-phase property initialization + * + * @deprecated {@link #nullSafeGet(ResultSet, String[], SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + return nullSafeGet( rs, names, (SharedSessionContractImplementor) session, owner ); + } + /** * Extract a value of the {@link #getReturnedClass() mapped class} from the JDBC result set. Implementors * should handle possibility of null values. This form might be called if the type is known to be a @@ -320,6 +415,30 @@ Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplemento Object nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; + /** + * Extract a value of the {@link #getReturnedClass() mapped class} from the JDBC result set. Implementors + * should handle possibility of null values. This form might be called if the type is known to be a + * single-column type. + * + * @param rs The result set from which to extract value. + * @param name the column name making up this type value (use to read from result set) + * @param session The originating session + * @param owner the parent entity + * + * @return The extracted value + * + * @throws HibernateException An error from Hibernate + * @throws SQLException An error from the JDBC driver + * + * @deprecated {@link #nullSafeGet(ResultSet, String, SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Object nullSafeGet(ResultSet rs, String name, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + return nullSafeGet( rs, name, (SharedSessionContractImplementor) session, owner ); + } + /** * Bind a value represented by an instance of the {@link #getReturnedClass() mapped class} to the JDBC prepared * statement, ignoring some columns as dictated by the 'settable' parameter. Implementors should handle the @@ -342,6 +461,34 @@ void nullSafeSet( SharedSessionContractImplementor session) throws HibernateException, SQLException; + /** + * Bind a value represented by an instance of the {@link #getReturnedClass() mapped class} to the JDBC prepared + * statement, ignoring some columns as dictated by the 'settable' parameter. Implementors should handle the + * possibility of null values. A multi-column type should bind parameters starting from index. + * + * @param st The JDBC prepared statement to which to bind + * @param value the object to write + * @param index starting parameter bind index + * @param settable an array indicating which columns to bind/ignore + * @param session The originating session + * + * @throws HibernateException An error from Hibernate + * @throws SQLException An error from the JDBC driver + * + * @deprecated {@link #nullSafeSet(PreparedStatement, Object, int, boolean[], SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default void nullSafeSet( + PreparedStatement st, + Object value, + int index, + boolean[] settable, + SessionImplementor session) + throws HibernateException, SQLException { + nullSafeSet( st, value, index, settable, (SharedSessionContractImplementor) session ); + } + /** * Bind a value represented by an instance of the {@link #getReturnedClass() mapped class} to the JDBC prepared * statement. Implementors should handle possibility of null values. A multi-column type should bind parameters @@ -358,6 +505,28 @@ void nullSafeSet( void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException; + /** + * Bind a value represented by an instance of the {@link #getReturnedClass() mapped class} to the JDBC prepared + * statement. Implementors should handle possibility of null values. A multi-column type should bind parameters + * starting from index. + * + * @param st The JDBC prepared statement to which to bind + * @param value the object to write + * @param index starting parameter bind index + * @param session The originating session + * + * @throws HibernateException An error from Hibernate + * @throws SQLException An error from the JDBC driver + * + * @deprecated {@link #nullSafeSet(PreparedStatement, Object, int, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) + throws HibernateException, SQLException { + nullSafeSet( st, value, index, (SharedSessionContractImplementor) session ); + } + /** * Generate a representation of the value for logging purposes. * @@ -415,6 +584,27 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) */ Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException; + /** + * Return a disassembled representation of the object. This is the value Hibernate will use in second level + * caching, so care should be taken to break values down to their simplest forms; for entities especially, this + * means breaking them down into their constituent parts. + * + * @param value the value to cache + * @param session the originating session + * @param owner optional parent entity object (needed for collections) + * + * @return the disassembled, deep cloned state + * + * @throws HibernateException An error from Hibernate + * + * @deprecated {@link #disassemble(Object, SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Serializable disassemble(Object value, SessionImplementor session, Object owner) throws HibernateException { + return disassemble( value, (SharedSessionContractImplementor) session, owner ); + } + /** * Reconstruct the object from its disassembled state. This method is the reciprocal of {@link #disassemble} * @@ -427,7 +617,26 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) * @throws HibernateException An error from Hibernate */ Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException; - + + /** + * Reconstruct the object from its disassembled state. This method is the reciprocal of {@link #disassemble} + * + * @param cached the disassembled state from the cache + * @param session the originating session + * @param owner the parent entity object + * + * @return the (re)assembled object + * + * @throws HibernateException An error from Hibernate + * + * @deprecated {@link #assemble(Serializable, SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException { + return assemble( cached, (SharedSessionContractImplementor) session, owner ); + } + /** * Called before assembling a query result set from the query cache, to allow batch fetching * of entities missing from the second-level cache. @@ -437,6 +646,21 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) */ void beforeAssemble(Serializable cached, SharedSessionContractImplementor session); + /** + * Called before assembling a query result set from the query cache, to allow batch fetching + * of entities missing from the second-level cache. + * + * @param cached The key + * @param session The originating session + * + * @deprecated {@link #beforeAssemble(Serializable, SharedSessionContractImplementor)} should + * be used instead + */ + @Deprecated + default void beforeAssemble(Serializable cached, SessionImplementor session) { + beforeAssemble( cached, (SharedSessionContractImplementor) session ); + } + /** * Extract a value from the JDBC result set. This is useful for 2-phase property initialization - the second * phase is a call to {@link #resolve} @@ -460,12 +684,53 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) Object hydrate(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; + /** + * Extract a value from the JDBC result set. This is useful for 2-phase property initialization - the second + * phase is a call to {@link #resolve} + * This hydrated value will be either:

    + *
  • in the case of an entity or collection type, the key
  • + *
  • otherwise, the value itself
  • + *
+ * + * @param rs The JDBC result set + * @param names the column names making up this type value (use to read from result set) + * @param session The originating session + * @param owner the parent entity + * + * @return An entity or collection key, or an actual value. + * + * @throws HibernateException An error from Hibernate + * @throws SQLException An error from the JDBC driver + * + * @see #resolve + * + * @deprecated {@link #hydrate(ResultSet, String[], SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Object hydrate(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + return hydrate( rs, names, (SharedSessionContractImplementor) session, owner ); + } + /** * @see #resolve(Object, SharedSessionContractImplementor, Object, Boolean) */ Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException; + /** + * @see #resolve(Object, SharedSessionContractImplementor, Object, Boolean) + * + * @deprecated {@link #resolve(Object, SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Object resolve(Object value, SessionImplementor session, Object owner) + throws HibernateException { + return resolve( value, (SharedSessionContractImplementor) session, owner ); + } + /** * The second phase of 2-phase loading. Only really pertinent for entities and collections. Here we resolve the * identifier to an entity or collection instance @@ -501,7 +766,28 @@ default Object resolve(Object value, SharedSessionContractImplementor session, O */ Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException; - + + /** + * Given a hydrated, but unresolved value, return a value that may be used to reconstruct property-ref + * associations. + * + * @param value The unresolved, hydrated value + * @param session THe originating session + * @param owner The value owner + * + * @return The semi-resolved value + * + * @throws HibernateException An error from Hibernate + * + * @deprecated {@link #semiResolve(Object, SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Object semiResolve(Object value, SessionImplementor session, Object owner) + throws HibernateException { + return semiResolve( value, (SharedSessionContractImplementor) session, owner ); + } + /** * As part of 2-phase loading, when we perform resolving what is the resolved type for this type? Generally * speaking the type and its semi-resolved type will be the same. The main deviation from this is in the @@ -536,7 +822,37 @@ Object replace( SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException; - + + /** + * During merge, replace the existing (target) value in the entity we are merging to + * with a new (original) value from the detached entity we are merging. For immutable + * objects, or null values, it is safe to simply return the first parameter. For + * mutable objects, it is safe to return a copy of the first parameter. For objects + * with component values, it might make sense to recursively replace component values. + * + * @param original the value from the detached entity being merged + * @param target the value in the managed entity + * @param session The originating session + * @param owner The owner of the value + * @param copyCache The cache of already copied/replaced values + * + * @return the value to be merged + * + * @throws HibernateException An error from Hibernate + * + * @deprecated {@link #replace(Object, Object, SharedSessionContractImplementor, Object, Map)} + * should be used instead. + */ + @Deprecated + default Object replace( + Object original, + Object target, + SessionImplementor session, + Object owner, + Map copyCache) throws HibernateException { + return replace( original, target, (SharedSessionContractImplementor) session, owner, copyCache ); + } + /** * During merge, replace the existing (target) value in the entity we are merging to * with a new (original) value from the detached entity we are merging. For immutable @@ -562,7 +878,39 @@ Object replace( Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException; - + + /** + * During merge, replace the existing (target) value in the entity we are merging to + * with a new (original) value from the detached entity we are merging. For immutable + * objects, or null values, it is safe to simply return the first parameter. For + * mutable objects, it is safe to return a copy of the first parameter. For objects + * with component values, it might make sense to recursively replace component values. + * + * @param original the value from the detached entity being merged + * @param target the value in the managed entity + * @param session The originating session + * @param owner The owner of the value + * @param copyCache The cache of already copied/replaced values + * @param foreignKeyDirection For associations, which direction does the foreign key point? + * + * @return the value to be merged + * + * @throws HibernateException An error from Hibernate + * + * @deprecated {@link #replace(Object, Object, SharedSessionContractImplementor, Object, Map, ForeignKeyDirection)} + * should be used instead. + */ + @Deprecated + default Object replace( + Object original, + Object target, + SessionImplementor session, + Object owner, + Map copyCache, + ForeignKeyDirection foreignKeyDirection) throws HibernateException { + return replace( original, target, (SharedSessionContractImplementor) session, owner, copyCache, foreignKeyDirection ); + } + /** * Given an instance of the type, return an array of boolean, indicating * which mapped columns would be null. diff --git a/hibernate-core/src/main/java/org/hibernate/type/VersionType.java b/hibernate-core/src/main/java/org/hibernate/type/VersionType.java index 26f741a6aeaa..556c68ee2a99 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/VersionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/VersionType.java @@ -8,7 +8,9 @@ import java.util.Comparator; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.SessionImpl; /** * Additional contract for types which may be used to version (and optimistic lock) data. @@ -25,6 +27,19 @@ public interface VersionType extends Type { */ T seed(SharedSessionContractImplementor session); + /** + * Generate an initial version. + * + * @param session The session from which this request originates. + * @return an instance of the type + * + * @deprecated {@link #seed(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default T seed(SessionImplementor session) { + return seed( (SharedSessionContractImplementor) session ); + } + /** * Increment the version. * @@ -34,6 +49,20 @@ public interface VersionType extends Type { */ T next(T current, SharedSessionContractImplementor session); + /** + * Increment the version. + * + * @param session The session from which this request originates. + * @param current the current version + * @return an instance of the type + * + * @deprecated {@link #next(Object, SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default T next(T current, SessionImplementor session) { + return next( current, (SharedSessionContractImplementor) session ); + } + /** * Get a comparator for version values. * diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java index 2b24ff4a653c..738ed6b87b19 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.Type; @@ -107,6 +108,27 @@ public interface CompositeUserType { */ Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; + /** + * Retrieve an instance of the mapped class from a JDBC resultset. Implementors + * should handle possibility of null values. + * + * @param rs a JDBC result set + * @param names the column names + * @param session + * @param owner the containing entity + * @return Object + * @throws HibernateException + * @throws SQLException + * + * @deprecated {@link #nullSafeGet(ResultSet, String[], SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + return nullSafeGet( rs, names, (SharedSessionContractImplementor) session, owner ); + } + /** * Write an instance of the mapped class to a prepared statement. Implementors * should handle possibility of null values. A multi-column type should be written @@ -121,6 +143,26 @@ public interface CompositeUserType { */ void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException; + /** + * Write an instance of the mapped class to a prepared statement. Implementors + * should handle possibility of null values. A multi-column type should be written + * to parameters starting from index. + * + * @param st a JDBC prepared statement + * @param value the object to write + * @param index statement parameter index + * @param session + * @throws HibernateException + * @throws SQLException + * + * @deprecated {@link #nullSafeSet(PreparedStatement, Object, int, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) + throws HibernateException, SQLException { + nullSafeSet( st, value, index, (SharedSessionContractImplementor) session ); + } /** * Return a deep copy of the persistent state, stopping at entities and at collections. * @@ -150,6 +192,24 @@ public interface CompositeUserType { */ Serializable disassemble(Object value, SharedSessionContractImplementor session) throws HibernateException; + /** + * Transform the object into its cacheable representation. At the very least this + * method should perform a deep copy. That may not be enough for some implementations, + * however; for example, associations must be cached as identifier values. (optional + * operation) + * + * @param value the object to be cached + * @param session + * @return a cachable representation of the object + * @throws HibernateException + * + * @deprecated {@link #disassemble(Object, SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default Serializable disassemble(Object value, SessionImplementor session) throws HibernateException { + return disassemble( value, (SharedSessionContractImplementor) session ); + } + /** * Reconstruct an object from the cacheable representation. At the very least this * method should perform a deep copy. (optional operation) @@ -162,6 +222,25 @@ public interface CompositeUserType { */ Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException; + /** + * Reconstruct an object from the cacheable representation. At the very least this + * method should perform a deep copy. (optional operation) + * + * @param cached the object to be cached + * @param session + * @param owner the owner of the cached object + * @return a reconstructed object from the cachable representation + * @throws HibernateException + * + * @deprecated {@link #assemble(Serializable, SharedSessionContractImplementor, Object)} should + * be used instead. + */ + @Deprecated + default Object assemble(Serializable cached, SessionImplementor session, Object owner) + throws HibernateException { + return assemble( cached, (SharedSessionContractImplementor) session, owner ); + } + /** * During merge, replace the existing (target) value in the entity we are merging to * with a new (original) value from the detached entity we are merging. For immutable @@ -173,4 +252,23 @@ public interface CompositeUserType { * @throws HibernateException */ Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner) throws HibernateException; + + /** + * During merge, replace the existing (target) value in the entity we are merging to + * with a new (original) value from the detached entity we are merging. For immutable + * objects, or null values, it is safe to simply return the first parameter. For + * mutable objects, it is safe to return a copy of the first parameter. However, since + * composite user types often define component values, it might make sense to recursively + * replace component values in the target object. + * + * @throws HibernateException + * + * @deprecated {@link #replace(Object, Object, SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Object replace(Object original, Object target, SessionImplementor session, Object owner) + throws HibernateException { + return replace( original, target, (SharedSessionContractImplementor) session, owner ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java index 5856c3534ddc..1a6d2c6406c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java @@ -11,6 +11,7 @@ import org.hibernate.HibernateException; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.collection.CollectionPersister; @@ -28,11 +29,33 @@ public interface UserCollectionType { PersistentCollection instantiate(SharedSessionContractImplementor session, CollectionPersister persister) throws HibernateException; + /** + * Instantiate an uninitialized instance of the collection wrapper + * + * @deprecated {@link #instantiate(SharedSessionContractImplementor, CollectionPersister)} + * should be used instead. + */ + @Deprecated + default PersistentCollection instantiate(SessionImplementor session, CollectionPersister persister) + throws HibernateException { + return instantiate( (SharedSessionContractImplementor) session, persister ); + } + /** * Wrap an instance of a collection */ PersistentCollection wrap(SharedSessionContractImplementor session, Object collection); + /** + * Wrap an instance of a collection + * + * @deprecated {@link #wrap(SharedSessionContractImplementor, Object)} should be used instead. + */ + @Deprecated + default PersistentCollection wrap(SessionImplementor session, Object collection) { + return wrap( (SharedSessionContractImplementor) session, collection ); + } + /** * Return an iterator over the elements of this collection - the passed collection * instance may or may not be a wrapper @@ -60,6 +83,30 @@ Object replaceElements( Map copyCache, SharedSessionContractImplementor session) throws HibernateException; + /** + * Replace the elements of a collection with the elements of another collection + * + * @deprecated {@link #replaceElements(Object, Object, CollectionPersister, Object, Map, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default Object replaceElements( + Object original, + Object target, + CollectionPersister persister, + Object owner, + Map copyCache, + SessionImplementor session) throws HibernateException { + return replaceElements( + original, + target, + persister, + owner, + copyCache, + (SharedSessionContractImplementor) session + ); + } + /** * Instantiate an empty instance of the "underlying" collection (not a wrapper), * but with the given anticipated size (i.e. accounting for initial size diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java index 4947f0cebf28..3cf60ff758de 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java @@ -12,9 +12,8 @@ import java.sql.SQLException; import org.hibernate.HibernateException; -import org.hibernate.boot.model.JavaTypeDescriptor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.type.spi.TypeConfiguration; /** * This interface should be implemented by user-defined "types". @@ -95,6 +94,26 @@ public interface UserType { */ Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; + /** + * Retrieve an instance of the mapped class from a JDBC resultset. Implementors + * should handle possibility of null values. + * + * @param rs a JDBC result set + * @param names the column names + * @param session + * @param owner the containing entity @return Object + * @throws HibernateException + * @throws SQLException + * + * @deprecated {@link #nullSafeGet(ResultSet, String[], SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + default Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + return nullSafeGet( rs, names, (SharedSessionContractImplementor) session, owner ); + } + /** * Write an instance of the mapped class to a prepared statement. Implementors * should handle possibility of null values. A multi-column type should be written @@ -110,6 +129,28 @@ public interface UserType { */ void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException; + /** + * Write an instance of the mapped class to a prepared statement. Implementors + * should handle possibility of null values. A multi-column type should be written + * to parameters starting from index. + * + * + * @param st a JDBC prepared statement + * @param value the object to write + * @param index statement parameter index + * @param session + * @throws HibernateException + * @throws SQLException + * + * @deprecated {@link #nullSafeSet(PreparedStatement, Object, int, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + default void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) + throws HibernateException, SQLException { + nullSafeSet( st, value, index, (SharedSessionContractImplementor) session ); + } + /** * Return a deep copy of the persistent state, stopping at entities and at * collections. It is not necessary to copy immutable objects, or null diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java index 7ae9d9c73d04..daa7425c2cf7 100755 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java @@ -8,6 +8,7 @@ import java.util.Comparator; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -26,6 +27,21 @@ public interface UserVersionType extends UserType, Comparator { */ Object seed(SharedSessionContractImplementor session); + /** + * Generate an initial version. + * + * @param session The session from which this request originates. May be + * null; currently this only happens during startup when trying to determine + * the "unsaved value" of entities. + * @return an instance of the type + * + * @deprecated {@link #seed(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default Object seed(SessionImplementor session) { + return seed( (SharedSessionContractImplementor) session ); + } + /** * Increment the version. * @@ -35,4 +51,18 @@ public interface UserVersionType extends UserType, Comparator { */ Object next(Object current, SharedSessionContractImplementor session); + /** + * Increment the version. + * + * @param session The session from which this request originates. + * @param current the current version + * @return an instance of the type + * + * @deprecated {@link #next(Object, SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + default Object next(Object current, SessionImplementor session) { + return next( current, (SharedSessionContractImplementor) session ); + } + } From a4dd9a96d95358ccdb1a5ca4e737d04a39c49ba5 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 25 Jul 2018 18:08:24 +0200 Subject: [PATCH 103/772] HHH-12730 Add deprecated constructors taking a SessionImplementor --- .../AbstractPersistentCollection.java | 8 ++++++ .../internal/PersistentIdentifierBag.java | 24 ++++++++++++++++++ .../collection/internal/PersistentList.java | 24 ++++++++++++++++++ .../collection/internal/PersistentMap.java | 25 +++++++++++++++++++ .../collection/internal/PersistentSet.java | 25 +++++++++++++++++++ .../internal/PersistentSortedMap.java | 23 +++++++++++++++++ .../internal/PersistentSortedSet.java | 23 +++++++++++++++++ 7 files changed, 152 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index a770e4b22222..44d0a506c2e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -86,6 +86,14 @@ protected AbstractPersistentCollection(SharedSessionContractImplementor session) this.session = session; } + /** + * * @deprecated {@link #AbstractPersistentCollection(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + protected AbstractPersistentCollection(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + @Override public final String getRole() { return role; diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java index f115e0b4eddf..7cacdfd4cc1e 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java @@ -18,6 +18,7 @@ import java.util.Map; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -55,6 +56,17 @@ public PersistentIdentifierBag(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentIdentifierBag. + * + * @param session The session + * @deprecated {@link #PersistentIdentifierBag(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentIdentifierBag(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentIdentifierBag. * @@ -78,6 +90,18 @@ public PersistentIdentifierBag(SharedSessionContractImplementor session, Collect identifiers = new HashMap<>(); } + /** + * Constructs a PersistentIdentifierBag. + * + * @param session The session + * @param coll The base elements + * @deprecated {@link #PersistentIdentifierBag(SharedSessionContractImplementor, Collection)} should be used instead. + */ + @Deprecated + public PersistentIdentifierBag(SessionImplementor session, Collection coll) { + this( (SharedSessionContractImplementor) session, coll ); + } + @Override public void initializeFromCache(CollectionPersister persister, Serializable disassembled, Object owner) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java index 9e74d430d915..8a0015665224 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java @@ -16,6 +16,7 @@ import java.util.ListIterator; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -46,6 +47,17 @@ public PersistentList(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentList. + * + * @param session The session + * @deprecated {@link #PersistentList(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentList(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentList. * @@ -59,6 +71,18 @@ public PersistentList(SharedSessionContractImplementor session, List list) { setDirectlyAccessible( true ); } + /** + * Constructs a PersistentList. + * + * @param session The session + * @param list The raw list + * @deprecated {@link #PersistentList(SharedSessionContractImplementor, List)} should be used instead. + */ + @Deprecated + public PersistentList(SessionImplementor session, List list) { + this( (SharedSessionContractImplementor) session, list ); + } + @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java index 0e8e3aa5593c..0976a3cf7e1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java @@ -18,6 +18,7 @@ import java.util.Set; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -54,6 +55,17 @@ public PersistentMap(SharedSessionContractImplementor session) { super( session ); } + /** + * Instantiates a lazy map (the underlying map is un-initialized). + * + * @param session The session to which this map will belong. + * @deprecated {@link #PersistentMap(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentMap(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Instantiates a non-lazy map (the underlying map is constructed * from the incoming map reference). @@ -68,6 +80,19 @@ public PersistentMap(SharedSessionContractImplementor session, Map map) { setDirectlyAccessible( true ); } + /** + * Instantiates a non-lazy map (the underlying map is constructed + * from the incoming map reference). + * + * @param session The session to which this map will belong. + * @param map The underlying map data. + * @deprecated {@link #PersistentMap(SharedSessionContractImplementor, Map)} should be used instead. + */ + @Deprecated + public PersistentMap(SessionImplementor session, Map map) { + this( (SharedSessionContractImplementor) session, map ); + } + @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java index 317c6f0dacc4..0f9a77f72f52 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java @@ -17,6 +17,7 @@ import java.util.Set; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -54,6 +55,17 @@ public PersistentSet(SharedSessionContractImplementor session) { super( session ); } + /** + * Instantiates a lazy set (the underlying set is un-initialized). + * + * @param session The session to which this set will belong. + * @deprecated {@link #PersistentSet(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentSet(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Instantiates a non-lazy set (the underlying set is constructed * from the incoming set reference). @@ -72,6 +84,19 @@ public PersistentSet(SharedSessionContractImplementor session, java.util.Set set setDirectlyAccessible( true ); } + /** + * Instantiates a non-lazy set (the underlying set is constructed + * from the incoming set reference). + * + * @param session The session to which this set will belong. + * @param set The underlying set data. + * @deprecated {@link #PersistentSet(SharedSessionContractImplementor, java.util.Set)} should be used instead. + */ + @Deprecated + public PersistentSet(SessionImplementor session, java.util.Set set) { + this( (SharedSessionContractImplementor) session, set ); + } + @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java index b3e67b28da41..f47825d0963f 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java @@ -46,6 +46,17 @@ public PersistentSortedMap(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentSortedMap. + * + * @param session The session + * @deprecated {@link #PersistentSortedMap(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentSortedMap(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentSortedMap. * @@ -57,6 +68,18 @@ public PersistentSortedMap(SharedSessionContractImplementor session, SortedMap m comparator = map.comparator(); } + /** + * Constructs a PersistentSortedMap. + * + * @param session The session + * @param map The underlying map data + * @deprecated {@link #PersistentSortedMap(SharedSessionContractImplementor, SortedMap)} should be used instead. + */ + @Deprecated + public PersistentSortedMap(SessionImplementor session, SortedMap map) { + this( (SharedSessionContractImplementor) session, map ); + } + @SuppressWarnings({"unchecked", "UnusedParameters"}) protected Serializable snapshot(BasicCollectionPersister persister, EntityMode entityMode) throws HibernateException { final TreeMap clonedMap = new TreeMap( comparator ); diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java index 75d3606bbdeb..b52e6bff31f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java @@ -43,6 +43,17 @@ public PersistentSortedSet(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentSortedSet + * + * @param session The session + * @deprecated {@link #PersistentSortedSet(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentSortedSet(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentSortedSet * @@ -54,6 +65,18 @@ public PersistentSortedSet(SharedSessionContractImplementor session, SortedSet s comparator = set.comparator(); } + /** + * Constructs a PersistentSortedSet + * + * @param session The session + * @param set The underlying set data + * @deprecated {@link #PersistentSortedSet(SharedSessionContractImplementor, SortedSet)} should be used instead. + */ + @Deprecated + public PersistentSortedSet(SessionImplementor session, SortedSet set) { + this( (SharedSessionContractImplementor) session, set ); + } + @SuppressWarnings({"unchecked", "UnusedParameters"}) protected Serializable snapshot(BasicCollectionPersister persister, EntityMode entityMode) throws HibernateException { From 338b97e7b557e2cc0f4192c318dcf286cad55cc5 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 27 Jul 2018 18:31:50 +0200 Subject: [PATCH 104/772] HHH-12730 Add missing @Overrides --- .../persister/entity/DiscriminatorType.java | 14 ++++++ .../java/org/hibernate/type/AbstractType.java | 49 +++++++++++++------ 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java index d34670893614..2bb0367175c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java @@ -37,18 +37,22 @@ public DiscriminatorType(Type underlyingType, Loadable persister) { this.persister = persister; } + @Override public Class getReturnedClass() { return Class.class; } + @Override public String getName() { return getClass().getName(); } + @Override public boolean isMutable() { return false; } + @Override public Object nullSafeGet( ResultSet rs, String[] names, @@ -57,6 +61,7 @@ public Object nullSafeGet( return nullSafeGet( rs, names[0], session, owner ); } + @Override public Object nullSafeGet( ResultSet rs, String name, @@ -71,6 +76,7 @@ public Object nullSafeGet( return ( EntityMode.POJO == entityPersister.getEntityMode() ) ? entityPersister.getMappedClass() : entityName; } + @Override public void nullSafeSet( PreparedStatement st, Object value, @@ -80,6 +86,7 @@ public void nullSafeSet( nullSafeSet( st, value, index, session ); } + @Override public void nullSafeSet( PreparedStatement st, Object value, @@ -90,26 +97,31 @@ public void nullSafeSet( underlyingType.nullSafeSet(st, entityPersister.getDiscriminatorValue(), index, session); } + @Override public String toLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException { return value == null ? "[null]" : value.toString(); } + @Override public Object deepCopy(Object value, SessionFactoryImplementor factory) throws HibernateException { return value; } + @Override public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException { return original; } + @Override public boolean[] toColumnNullness(Object value, Mapping mapping) { return value == null ? ArrayHelper.FALSE : ArrayHelper.TRUE; } + @Override public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException { return Objects.equals( old, current ); @@ -118,6 +130,7 @@ public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSe // simple delegation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + @Override public int[] sqlTypes(Mapping mapping) throws MappingException { return underlyingType.sqlTypes( mapping ); } @@ -132,6 +145,7 @@ public Size[] defaultSizes(Mapping mapping) throws MappingException { return underlyingType.defaultSizes( mapping ); } + @Override public int getColumnSpan(Mapping mapping) throws MappingException { return underlyingType.getColumnSpan( mapping ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java index 14adc63b95ba..c61dd34f418b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java @@ -19,33 +19,39 @@ /** * Abstract superclass of the built in Type hierarchy. - * + * * @author Gavin King */ public abstract class AbstractType implements Type { protected static final Size LEGACY_DICTATED_SIZE = new Size(); protected static final Size LEGACY_DEFAULT_SIZE = new Size( 19, 2, 255, Size.LobMultiplier.NONE ); // to match legacy behavior + @Override public boolean isAssociationType() { return false; } + @Override public boolean isCollectionType() { return false; } + @Override public boolean isComponentType() { return false; } + @Override public boolean isEntityType() { return false; } - + + @Override public int compare(Object x, Object y) { return ( (Comparable) x ).compareTo(y); } + @Override public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { @@ -57,6 +63,7 @@ public Serializable disassemble(Object value, SharedSessionContractImplementor s } } + @Override public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException { if ( cached==null ) { @@ -67,10 +74,12 @@ public Object assemble(Serializable cached, SharedSessionContractImplementor ses } } + @Override public boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) throws HibernateException { return !isSame( old, current ); } + @Override public Object hydrate( ResultSet rs, String[] names, @@ -82,56 +91,67 @@ public Object hydrate( return nullSafeGet(rs, names, session, owner); } + @Override public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { return value; } - public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) + @Override + public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { return value; } - + + @Override public boolean isAnyType() { return false; } + @Override public boolean isModified(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException { return isDirty(old, current, session); } - + + @Override public boolean isSame(Object x, Object y) throws HibernateException { return isEqual(x, y ); } + @Override public boolean isEqual(Object x, Object y) { return Objects.equals( x, y ); } - + + @Override public int getHashCode(Object x) { return x.hashCode(); } + @Override public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) { return isEqual(x, y ); } - + + @Override public int getHashCode(Object x, SessionFactoryImplementor factory) { return getHashCode(x ); } - + + @Override public Type getSemiResolvedType(SessionFactoryImplementor factory) { return this; } + @Override public Object replace( - Object original, - Object target, - SharedSessionContractImplementor session, - Object owner, - Map copyCache, - ForeignKeyDirection foreignKeyDirection) + Object original, + Object target, + SharedSessionContractImplementor session, + Object owner, + Map copyCache, + ForeignKeyDirection foreignKeyDirection) throws HibernateException { boolean include; if ( isAssociationType() ) { @@ -144,6 +164,7 @@ public Object replace( return include ? replace(original, target, session, owner, copyCache) : target; } + @Override public void beforeAssemble(Serializable cached, SharedSessionContractImplementor session) {} /*public Object copy(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) From 6b679c886081231d60a5630129ca777eb0eee076 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 27 Jul 2018 20:43:32 +0200 Subject: [PATCH 105/772] HHH-12730 Remove the deprecated methods previouly added We don't need them as everything is now dealt with the transformer. --- .../collection/spi/PersistentCollection.java | 62 +-- .../type/AbstractStandardBasicType.java | 31 +- .../ProcedureParameterExtractionAware.java | 39 -- .../type/ProcedureParameterNamedBinder.java | 22 -- .../org/hibernate/type/SingleColumnType.java | 61 --- .../main/java/org/hibernate/type/Type.java | 358 +----------------- .../java/org/hibernate/type/VersionType.java | 29 -- .../hibernate/usertype/CompositeUserType.java | 100 +---- .../usertype/UserCollectionType.java | 47 --- .../java/org/hibernate/usertype/UserType.java | 43 --- .../hibernate/usertype/UserVersionType.java | 33 +- 11 files changed, 24 insertions(+), 801 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java index e9020622357c..185ed8752fa6 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java @@ -13,7 +13,6 @@ import java.util.Iterator; import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -84,7 +83,7 @@ public interface PersistentCollection { * database state is now synchronized with the memory state. */ void postAction(); - + /** * Return the user-visible collection (or array) instance * @@ -103,7 +102,7 @@ public interface PersistentCollection { * @return Whether to end the read. */ boolean endRead(); - + /** * Called after initializing from cache * @@ -128,20 +127,6 @@ public interface PersistentCollection { */ boolean unsetSession(SharedSessionContractImplementor currentSession); - /** - * Disassociate this collection from the given session. - * - * @param currentSession The session we are disassociating from. Used for validations. - * - * @return true if this was currently associated with the given session - * - * @deprecated {@link #unsetSession(SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default boolean unsetSession(SessionImplementor currentSession) { - return unsetSession( (SharedSessionContractImplementor) currentSession ); - } - /** * Associate the collection with the given session. * @@ -154,23 +139,6 @@ default boolean unsetSession(SessionImplementor currentSession) { */ boolean setCurrentSession(SharedSessionContractImplementor session) throws HibernateException; - /** - * Associate the collection with the given session. - * - * @param session The session to associate with - * - * @return false if the collection was already associated with the session - * - * @throws HibernateException if the collection was already associated - * with another open session - * - * @deprecated {@link #setCurrentSession(SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default boolean setCurrentSession(SessionImplementor session) throws HibernateException { - return setCurrentSession( (SharedSessionContractImplementor) session ); - } - /** * Read the state of the collection from a disassembled cached value * @@ -216,7 +184,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The identifier value */ Object getIdentifier(Object entry, int i); - + /** * Get the index of the given collection entry * @@ -227,7 +195,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The index value */ Object getIndex(Object entry, int i, CollectionPersister persister); - + /** * Get the value of the given collection entry. Generally the given entry parameter value will just be returned. * Might get a different value for a duplicate entries in a Set. @@ -237,7 +205,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The corresponding object that is part of the collection elements. */ Object getElement(Object entry); - + /** * Get the snapshot value of the given collection entry * @@ -275,7 +243,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return {@code true} if the given snapshot is empty */ boolean isSnapshotEmpty(Serializable snapshot); - + /** * Disassemble the collection to get it ready for the cache * @@ -387,7 +355,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The iterator */ Iterator queuedAdditionIterator(); - + /** * Get the "queued" orphans * @@ -396,28 +364,28 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The orphaned elements */ Collection getQueuedOrphans(String entityName); - + /** * Get the current collection key value * * @return the current collection key value */ Serializable getKey(); - + /** * Get the current role name * * @return the collection role name */ String getRole(); - + /** * Is the collection unreferenced? * * @return {@code true} if the collection is no longer referenced by an owner */ boolean isUnreferenced(); - + /** * Is the collection dirty? Note that this is only * reliable during the flush cycle, after the @@ -437,19 +405,19 @@ default boolean isElementRemoved(){ * to the database. */ void clearDirty(); - + /** * Get the snapshot cached by the collection instance * * @return The internally stored snapshot state */ Serializable getStoredSnapshot(); - + /** * Mark the collection as dirty */ void dirty(); - + /** * Called before inserting rows, to ensure that any surrogate keys * are fully generated @@ -476,5 +444,5 @@ default boolean isElementRemoved(){ * @return The orphans */ Collection getOrphans(Serializable snapshot, String entityName); - + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java index 1cc1878a0e12..d45e85d65384 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -20,7 +20,6 @@ import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.type.descriptor.WrapperOptions; @@ -107,13 +106,13 @@ protected static Size getDefaultSize() { protected Size getDictatedSize() { return dictatedSize; } - + // final implementations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public final JavaTypeDescriptor getJavaTypeDescriptor() { return javaTypeDescriptor; } - + public final void setJavaTypeDescriptor( JavaTypeDescriptor javaTypeDescriptor ) { this.javaTypeDescriptor = javaTypeDescriptor; } @@ -258,15 +257,6 @@ public final T nullSafeGet(ResultSet rs, String name, final SharedSessionContrac return nullSafeGet( rs, name, (WrapperOptions) session ); } - /** - * @deprecated {@link #nullSafeGet(ResultSet, String, SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - public final T nullSafeGet(ResultSet rs, String name, final SessionImplementor session) throws SQLException { - return nullSafeGet( rs, name, (SharedSessionContractImplementor) session ); - } - protected final T nullSafeGet(ResultSet rs, String name, WrapperOptions options) throws SQLException { return remapSqlTypeDescriptor( options ).getExtractor( javaTypeDescriptor ).extract( rs, name, options ); } @@ -275,14 +265,6 @@ public Object get(ResultSet rs, String name, SharedSessionContractImplementor se return nullSafeGet( rs, name, session ); } - /** - * @deprecated {@link #get(ResultSet, String, SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - public Object get(ResultSet rs, String name, SessionImplementor session) throws HibernateException, SQLException { - return get( rs, name, (SharedSessionContractImplementor) session ); - } - @Override @SuppressWarnings({ "unchecked" }) public final void nullSafeSet( @@ -306,15 +288,6 @@ public void set(PreparedStatement st, T value, int index, SharedSessionContractI nullSafeSet( st, value, index, session ); } - /** - * @deprecated {@link #set(PreparedStatement, Object, int, SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - public void set(PreparedStatement st, T value, int index, SessionImplementor session) throws HibernateException, SQLException { - set( st, value, index, (SharedSessionContractImplementor) session ); - } - @Override @SuppressWarnings({ "unchecked" }) public final String toLoggableString(Object value, SessionFactoryImplementor factory) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterExtractionAware.java b/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterExtractionAware.java index 32a57ae4fcfe..6c3cce7b6c51 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterExtractionAware.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterExtractionAware.java @@ -9,7 +9,6 @@ import java.sql.CallableStatement; import java.sql.SQLException; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -40,25 +39,6 @@ public interface ProcedureParameterExtractionAware { */ T extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session) throws SQLException; - /** - * Perform the extraction - * - * @param statement The CallableStatement from which to extract the parameter value(s). - * @param startIndex The parameter index from which to start extracting; assumes the values (if multiple) are contiguous - * @param session The originating session - * - * @return The extracted value. - * - * @throws SQLException Indicates an issue calling into the CallableStatement - * @throws IllegalStateException Thrown if this method is called on instances that return {@code false} for {@link #canDoExtraction} - * - * @deprecated {@link #extract(CallableStatement, int, SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default T extract(CallableStatement statement, int startIndex, SessionImplementor session) throws SQLException { - return extract( statement, startIndex, (SharedSessionContractImplementor) session ); - } - /** * Perform the extraction * @@ -72,23 +52,4 @@ default T extract(CallableStatement statement, int startIndex, SessionImplemento * @throws IllegalStateException Thrown if this method is called on instances that return {@code false} for {@link #canDoExtraction} */ T extract(CallableStatement statement, String[] paramNames, SharedSessionContractImplementor session) throws SQLException; - - /** - * Perform the extraction - * - * @param statement The CallableStatement from which to extract the parameter value(s). - * @param paramNames The parameter names. - * @param session The originating session - * - * @return The extracted value. - * - * @throws SQLException Indicates an issue calling into the CallableStatement - * @throws IllegalStateException Thrown if this method is called on instances that return {@code false} for {@link #canDoExtraction} - * - * @deprecated {@link #extract(CallableStatement, String[], SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default T extract(CallableStatement statement, String[] paramNames, SessionImplementor session) throws SQLException { - return extract( statement, paramNames, (SharedSessionContractImplementor) session ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterNamedBinder.java b/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterNamedBinder.java index 7e1638026cac..c69661a9f120 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterNamedBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ProcedureParameterNamedBinder.java @@ -10,7 +10,6 @@ import java.sql.SQLException; import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -42,25 +41,4 @@ public interface ProcedureParameterNamedBinder { * @throws SQLException An error from the JDBC driver */ void nullSafeSet(CallableStatement statement, Object value, String name, SharedSessionContractImplementor session) throws SQLException; - - /** - * Bind a value to the JDBC prepared statement, ignoring some columns as dictated by the 'settable' parameter. - * Implementors should handle the possibility of null values. - * Does not support multi-column type - * - * @param statement The CallableStatement to which to bind - * @param value the object to write - * @param name parameter bind name - * @param session The originating session - * - * @throws HibernateException An error from Hibernate - * @throws SQLException An error from the JDBC driver - * - * @deprecated {@link #nullSafeSet(CallableStatement, Object, String, SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default void nullSafeSet(CallableStatement statement, Object value, String name, SessionImplementor session) throws SQLException { - nullSafeSet( statement, value, name, (SharedSessionContractImplementor) session ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java b/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java index b0185e3b0c04..dbf8ee4ca9cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/SingleColumnType.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -41,26 +40,6 @@ public interface SingleColumnType extends Type { */ T nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session) throws HibernateException, SQLException; - /** - * Get a column value from a result set by name. - * - * @param rs The result set from which to extract the value. - * @param name The name of the value to extract. - * @param session The session from which the request originates - * - * @return The extracted value. - * - * @throws org.hibernate.HibernateException Generally some form of mismatch error. - * @throws java.sql.SQLException Indicates problem making the JDBC call(s). - * - * @deprecated {@link #nullSafeGet(ResultSet, String, SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default T nullSafeGet(ResultSet rs, String name, final SessionImplementor session) throws SQLException { - return nullSafeGet( rs, name, (SharedSessionContractImplementor) session ); - } - /** * Get a column value from a result set, without worrying about the possibility of null values. * @@ -75,25 +54,6 @@ default T nullSafeGet(ResultSet rs, String name, final SessionImplementor sessio */ Object get(ResultSet rs, String name, SharedSessionContractImplementor session) throws HibernateException, SQLException; - /** - * Get a column value from a result set, without worrying about the possibility of null values. - * - * @param rs The result set from which to extract the value. - * @param name The name of the value to extract. - * @param session The session from which the request originates - * - * @return The extracted value. - * - * @throws org.hibernate.HibernateException Generally some form of mismatch error. - * @throws java.sql.SQLException Indicates problem making the JDBC call(s). - * - * @deprecated {@link #get(ResultSet, String, SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default Object get(ResultSet rs, String name, SessionImplementor session) throws HibernateException, SQLException { - return get( rs, name, (SharedSessionContractImplementor) session ); - } - /** * Set a parameter value without worrying about the possibility of null * values. Called from {@link #nullSafeSet} after nullness checks have @@ -108,25 +68,4 @@ default Object get(ResultSet rs, String name, SessionImplementor session) throws * @throws java.sql.SQLException Indicates problem making the JDBC call(s). */ void set(PreparedStatement st, T value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException; - - /** - * Set a parameter value without worrying about the possibility of null - * values. Called from {@link #nullSafeSet} after nullness checks have - * been performed. - * - * @param st The statement into which to bind the parameter value. - * @param value The parameter value to bind. - * @param index The position or index at which to bind the param value. - * @param session The session from which the request originates - * - * @throws org.hibernate.HibernateException Generally some form of mismatch error. - * @throws java.sql.SQLException Indicates problem making the JDBC call(s). - * - * @deprecated {@link #set(PreparedStatement, Object, int, SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default void set(PreparedStatement st, T value, int index, SessionImplementor session) throws HibernateException, SQLException { - set( st, value, index, (SharedSessionContractImplementor) session ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/Type.java b/hibernate-core/src/main/java/org/hibernate/type/Type.java index 9895ea0e656f..0008c0cc7e6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/Type.java +++ b/hibernate-core/src/main/java/org/hibernate/type/Type.java @@ -17,7 +17,6 @@ import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -223,7 +222,7 @@ public interface Type extends Serializable { * @throws HibernateException A problem occurred calculating the hash code */ int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException; - + /** * Perform a {@link java.util.Comparator} style comparison between values * @@ -234,19 +233,6 @@ public interface Type extends Serializable { */ int compare(Object x, Object y); - /** - * Should the parent be considered dirty, given both the old and current value? - * - * @param old the old value - * @param current the current value - * @param session The session from which the request originated. - * - * @return true if the field is dirty - * - * @throws HibernateException A problem occurred performing the checking - */ - boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) throws HibernateException; - /** * Should the parent be considered dirty, given both the old and current value? * @@ -257,15 +243,8 @@ public interface Type extends Serializable { * @return true if the field is dirty * * @throws HibernateException A problem occurred performing the checking - * - * @deprecated {@link #isDirty(Object, Object, SharedSessionContractImplementor)} - * should be used instead. */ - @Deprecated - default boolean isDirty(Object old, Object current, SessionImplementor session) throws HibernateException { - return isDirty( old, current, (SharedSessionContractImplementor) session ); - } - + boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) throws HibernateException; /** * Should the parent be considered dirty, given both the old and current value? @@ -282,27 +261,6 @@ default boolean isDirty(Object old, Object current, SessionImplementor session) boolean isDirty(Object oldState, Object currentState, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException; - /** - * Should the parent be considered dirty, given both the old and current value? - * - * @param oldState the old value - * @param currentState the current value - * @param checkable An array of booleans indicating which columns making up the value are actually checkable - * @param session The session from which the request originated. - * - * @return true if the field is dirty - * - * @throws HibernateException A problem occurred performing the checking - * - * @deprecated {@link #isDirty(Object, Object, boolean[], SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default boolean isDirty(Object oldState, Object currentState, boolean[] checkable, SessionImplementor session) - throws HibernateException { - return isDirty( oldState, currentState, checkable, (SharedSessionContractImplementor) session ); - } - /** * Has the value been modified compared to the current database state? The difference between this * and the {@link #isDirty} methods is that here we need to account for "partially" built values. This is really @@ -325,34 +283,6 @@ boolean isModified( SharedSessionContractImplementor session) throws HibernateException; - /** - * Has the value been modified compared to the current database state? The difference between this - * and the {@link #isDirty} methods is that here we need to account for "partially" built values. This is really - * only an issue with association types. For most type implementations it is enough to simply delegate to - * {@link #isDirty} here/ - * - * @param dbState the database state, in a "hydrated" form, with identifiers unresolved - * @param currentState the current state of the object - * @param checkable which columns are actually updatable - * @param session The session from which the request originated. - * - * @return true if the field has been modified - * - * @throws HibernateException A problem occurred performing the checking - * - * @deprecated {@link #isModified(Object, Object, boolean[], SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default boolean isModified( - Object dbState, - Object currentState, - boolean[] checkable, - SessionImplementor session) - throws HibernateException { - return isModified( dbState, currentState, checkable, (SharedSessionContractImplementor) session ); - } - /** * Extract a value of the {@link #getReturnedClass() mapped class} from the JDBC result set. Implementors * should handle possibility of null values. @@ -372,31 +302,6 @@ default boolean isModified( Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; - /** - * Extract a value of the {@link #getReturnedClass() mapped class} from the JDBC result set. Implementors - * should handle possibility of null values. - * - * @param rs The result set from which to extract value. - * @param names the column names making up this type value (use to read from result set) - * @param session The originating session - * @param owner the parent entity - * - * @return The extracted value - * - * @throws HibernateException An error from Hibernate - * @throws SQLException An error from the JDBC driver - * - * @see Type#hydrate(ResultSet, String[], SharedSessionContractImplementor, Object) alternative, 2-phase property initialization - * - * @deprecated {@link #nullSafeGet(ResultSet, String[], SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) - throws HibernateException, SQLException { - return nullSafeGet( rs, names, (SharedSessionContractImplementor) session, owner ); - } - /** * Extract a value of the {@link #getReturnedClass() mapped class} from the JDBC result set. Implementors * should handle possibility of null values. This form might be called if the type is known to be a @@ -415,30 +320,6 @@ default Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor sess Object nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; - /** - * Extract a value of the {@link #getReturnedClass() mapped class} from the JDBC result set. Implementors - * should handle possibility of null values. This form might be called if the type is known to be a - * single-column type. - * - * @param rs The result set from which to extract value. - * @param name the column name making up this type value (use to read from result set) - * @param session The originating session - * @param owner the parent entity - * - * @return The extracted value - * - * @throws HibernateException An error from Hibernate - * @throws SQLException An error from the JDBC driver - * - * @deprecated {@link #nullSafeGet(ResultSet, String, SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Object nullSafeGet(ResultSet rs, String name, SessionImplementor session, Object owner) - throws HibernateException, SQLException { - return nullSafeGet( rs, name, (SharedSessionContractImplementor) session, owner ); - } - /** * Bind a value represented by an instance of the {@link #getReturnedClass() mapped class} to the JDBC prepared * statement, ignoring some columns as dictated by the 'settable' parameter. Implementors should handle the @@ -461,34 +342,6 @@ void nullSafeSet( SharedSessionContractImplementor session) throws HibernateException, SQLException; - /** - * Bind a value represented by an instance of the {@link #getReturnedClass() mapped class} to the JDBC prepared - * statement, ignoring some columns as dictated by the 'settable' parameter. Implementors should handle the - * possibility of null values. A multi-column type should bind parameters starting from index. - * - * @param st The JDBC prepared statement to which to bind - * @param value the object to write - * @param index starting parameter bind index - * @param settable an array indicating which columns to bind/ignore - * @param session The originating session - * - * @throws HibernateException An error from Hibernate - * @throws SQLException An error from the JDBC driver - * - * @deprecated {@link #nullSafeSet(PreparedStatement, Object, int, boolean[], SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default void nullSafeSet( - PreparedStatement st, - Object value, - int index, - boolean[] settable, - SessionImplementor session) - throws HibernateException, SQLException { - nullSafeSet( st, value, index, settable, (SharedSessionContractImplementor) session ); - } - /** * Bind a value represented by an instance of the {@link #getReturnedClass() mapped class} to the JDBC prepared * statement. Implementors should handle possibility of null values. A multi-column type should bind parameters @@ -505,28 +358,6 @@ default void nullSafeSet( void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException; - /** - * Bind a value represented by an instance of the {@link #getReturnedClass() mapped class} to the JDBC prepared - * statement. Implementors should handle possibility of null values. A multi-column type should bind parameters - * starting from index. - * - * @param st The JDBC prepared statement to which to bind - * @param value the object to write - * @param index starting parameter bind index - * @param session The originating session - * - * @throws HibernateException An error from Hibernate - * @throws SQLException An error from the JDBC driver - * - * @deprecated {@link #nullSafeSet(PreparedStatement, Object, int, SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) - throws HibernateException, SQLException { - nullSafeSet( st, value, index, (SharedSessionContractImplementor) session ); - } - /** * Generate a representation of the value for logging purposes. * @@ -584,27 +415,6 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) */ Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException; - /** - * Return a disassembled representation of the object. This is the value Hibernate will use in second level - * caching, so care should be taken to break values down to their simplest forms; for entities especially, this - * means breaking them down into their constituent parts. - * - * @param value the value to cache - * @param session the originating session - * @param owner optional parent entity object (needed for collections) - * - * @return the disassembled, deep cloned state - * - * @throws HibernateException An error from Hibernate - * - * @deprecated {@link #disassemble(Object, SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Serializable disassemble(Object value, SessionImplementor session, Object owner) throws HibernateException { - return disassemble( value, (SharedSessionContractImplementor) session, owner ); - } - /** * Reconstruct the object from its disassembled state. This method is the reciprocal of {@link #disassemble} * @@ -618,25 +428,6 @@ default Serializable disassemble(Object value, SessionImplementor session, Objec */ Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException; - /** - * Reconstruct the object from its disassembled state. This method is the reciprocal of {@link #disassemble} - * - * @param cached the disassembled state from the cache - * @param session the originating session - * @param owner the parent entity object - * - * @return the (re)assembled object - * - * @throws HibernateException An error from Hibernate - * - * @deprecated {@link #assemble(Serializable, SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException { - return assemble( cached, (SharedSessionContractImplementor) session, owner ); - } - /** * Called before assembling a query result set from the query cache, to allow batch fetching * of entities missing from the second-level cache. @@ -646,21 +437,6 @@ default Object assemble(Serializable cached, SessionImplementor session, Object */ void beforeAssemble(Serializable cached, SharedSessionContractImplementor session); - /** - * Called before assembling a query result set from the query cache, to allow batch fetching - * of entities missing from the second-level cache. - * - * @param cached The key - * @param session The originating session - * - * @deprecated {@link #beforeAssemble(Serializable, SharedSessionContractImplementor)} should - * be used instead - */ - @Deprecated - default void beforeAssemble(Serializable cached, SessionImplementor session) { - beforeAssemble( cached, (SharedSessionContractImplementor) session ); - } - /** * Extract a value from the JDBC result set. This is useful for 2-phase property initialization - the second * phase is a call to {@link #resolve} @@ -668,7 +444,7 @@ default void beforeAssemble(Serializable cached, SessionImplementor session) { *
  • in the case of an entity or collection type, the key
  • *
  • otherwise, the value itself
  • * - * + * * @param rs The JDBC result set * @param names the column names making up this type value (use to read from result set) * @param session The originating session @@ -684,53 +460,12 @@ default void beforeAssemble(Serializable cached, SessionImplementor session) { Object hydrate(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; - /** - * Extract a value from the JDBC result set. This is useful for 2-phase property initialization - the second - * phase is a call to {@link #resolve} - * This hydrated value will be either:
      - *
    • in the case of an entity or collection type, the key
    • - *
    • otherwise, the value itself
    • - *
    - * - * @param rs The JDBC result set - * @param names the column names making up this type value (use to read from result set) - * @param session The originating session - * @param owner the parent entity - * - * @return An entity or collection key, or an actual value. - * - * @throws HibernateException An error from Hibernate - * @throws SQLException An error from the JDBC driver - * - * @see #resolve - * - * @deprecated {@link #hydrate(ResultSet, String[], SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Object hydrate(ResultSet rs, String[] names, SessionImplementor session, Object owner) - throws HibernateException, SQLException { - return hydrate( rs, names, (SharedSessionContractImplementor) session, owner ); - } - /** * @see #resolve(Object, SharedSessionContractImplementor, Object, Boolean) */ Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException; - /** - * @see #resolve(Object, SharedSessionContractImplementor, Object, Boolean) - * - * @deprecated {@link #resolve(Object, SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Object resolve(Object value, SessionImplementor session, Object owner) - throws HibernateException { - return resolve( value, (SharedSessionContractImplementor) session, owner ); - } - /** * The second phase of 2-phase loading. Only really pertinent for entities and collections. Here we resolve the * identifier to an entity or collection instance @@ -767,27 +502,6 @@ default Object resolve(Object value, SharedSessionContractImplementor session, O Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException; - /** - * Given a hydrated, but unresolved value, return a value that may be used to reconstruct property-ref - * associations. - * - * @param value The unresolved, hydrated value - * @param session THe originating session - * @param owner The value owner - * - * @return The semi-resolved value - * - * @throws HibernateException An error from Hibernate - * - * @deprecated {@link #semiResolve(Object, SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Object semiResolve(Object value, SessionImplementor session, Object owner) - throws HibernateException { - return semiResolve( value, (SharedSessionContractImplementor) session, owner ); - } - /** * As part of 2-phase loading, when we perform resolving what is the resolved type for this type? Generally * speaking the type and its semi-resolved type will be the same. The main deviation from this is in the @@ -823,36 +537,6 @@ Object replace( Object owner, Map copyCache) throws HibernateException; - /** - * During merge, replace the existing (target) value in the entity we are merging to - * with a new (original) value from the detached entity we are merging. For immutable - * objects, or null values, it is safe to simply return the first parameter. For - * mutable objects, it is safe to return a copy of the first parameter. For objects - * with component values, it might make sense to recursively replace component values. - * - * @param original the value from the detached entity being merged - * @param target the value in the managed entity - * @param session The originating session - * @param owner The owner of the value - * @param copyCache The cache of already copied/replaced values - * - * @return the value to be merged - * - * @throws HibernateException An error from Hibernate - * - * @deprecated {@link #replace(Object, Object, SharedSessionContractImplementor, Object, Map)} - * should be used instead. - */ - @Deprecated - default Object replace( - Object original, - Object target, - SessionImplementor session, - Object owner, - Map copyCache) throws HibernateException { - return replace( original, target, (SharedSessionContractImplementor) session, owner, copyCache ); - } - /** * During merge, replace the existing (target) value in the entity we are merging to * with a new (original) value from the detached entity we are merging. For immutable @@ -879,47 +563,15 @@ Object replace( Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException; - /** - * During merge, replace the existing (target) value in the entity we are merging to - * with a new (original) value from the detached entity we are merging. For immutable - * objects, or null values, it is safe to simply return the first parameter. For - * mutable objects, it is safe to return a copy of the first parameter. For objects - * with component values, it might make sense to recursively replace component values. - * - * @param original the value from the detached entity being merged - * @param target the value in the managed entity - * @param session The originating session - * @param owner The owner of the value - * @param copyCache The cache of already copied/replaced values - * @param foreignKeyDirection For associations, which direction does the foreign key point? - * - * @return the value to be merged - * - * @throws HibernateException An error from Hibernate - * - * @deprecated {@link #replace(Object, Object, SharedSessionContractImplementor, Object, Map, ForeignKeyDirection)} - * should be used instead. - */ - @Deprecated - default Object replace( - Object original, - Object target, - SessionImplementor session, - Object owner, - Map copyCache, - ForeignKeyDirection foreignKeyDirection) throws HibernateException { - return replace( original, target, (SharedSessionContractImplementor) session, owner, copyCache, foreignKeyDirection ); - } - /** * Given an instance of the type, return an array of boolean, indicating * which mapped columns would be null. - * + * * @param value an instance of the type * @param mapping The mapping abstraction * * @return array indicating column nullness for a value instance */ boolean[] toColumnNullness(Object value, Mapping mapping); - + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/VersionType.java b/hibernate-core/src/main/java/org/hibernate/type/VersionType.java index 556c68ee2a99..26f741a6aeaa 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/VersionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/VersionType.java @@ -8,9 +8,7 @@ import java.util.Comparator; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.SessionImpl; /** * Additional contract for types which may be used to version (and optimistic lock) data. @@ -27,19 +25,6 @@ public interface VersionType extends Type { */ T seed(SharedSessionContractImplementor session); - /** - * Generate an initial version. - * - * @param session The session from which this request originates. - * @return an instance of the type - * - * @deprecated {@link #seed(SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default T seed(SessionImplementor session) { - return seed( (SharedSessionContractImplementor) session ); - } - /** * Increment the version. * @@ -49,20 +34,6 @@ default T seed(SessionImplementor session) { */ T next(T current, SharedSessionContractImplementor session); - /** - * Increment the version. - * - * @param session The session from which this request originates. - * @param current the current version - * @return an instance of the type - * - * @deprecated {@link #next(Object, SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default T next(T current, SessionImplementor session) { - return next( current, (SharedSessionContractImplementor) session ); - } - /** * Get a comparator for version values. * diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java index 738ed6b87b19..85130a0bc025 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.Type; @@ -88,7 +87,7 @@ public interface CompositeUserType { * @throws HibernateException */ boolean equals(Object x, Object y) throws HibernateException; - + /** * Get a hashcode for the instance, consistent with persistence "equality" */ @@ -108,27 +107,6 @@ public interface CompositeUserType { */ Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; - /** - * Retrieve an instance of the mapped class from a JDBC resultset. Implementors - * should handle possibility of null values. - * - * @param rs a JDBC result set - * @param names the column names - * @param session - * @param owner the containing entity - * @return Object - * @throws HibernateException - * @throws SQLException - * - * @deprecated {@link #nullSafeGet(ResultSet, String[], SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) - throws HibernateException, SQLException { - return nullSafeGet( rs, names, (SharedSessionContractImplementor) session, owner ); - } - /** * Write an instance of the mapped class to a prepared statement. Implementors * should handle possibility of null values. A multi-column type should be written @@ -143,26 +121,6 @@ default Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor sess */ void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException; - /** - * Write an instance of the mapped class to a prepared statement. Implementors - * should handle possibility of null values. A multi-column type should be written - * to parameters starting from index. - * - * @param st a JDBC prepared statement - * @param value the object to write - * @param index statement parameter index - * @param session - * @throws HibernateException - * @throws SQLException - * - * @deprecated {@link #nullSafeSet(PreparedStatement, Object, int, SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) - throws HibernateException, SQLException { - nullSafeSet( st, value, index, (SharedSessionContractImplementor) session ); - } /** * Return a deep copy of the persistent state, stopping at entities and at collections. * @@ -192,24 +150,6 @@ default void nullSafeSet(PreparedStatement st, Object value, int index, SessionI */ Serializable disassemble(Object value, SharedSessionContractImplementor session) throws HibernateException; - /** - * Transform the object into its cacheable representation. At the very least this - * method should perform a deep copy. That may not be enough for some implementations, - * however; for example, associations must be cached as identifier values. (optional - * operation) - * - * @param value the object to be cached - * @param session - * @return a cachable representation of the object - * @throws HibernateException - * - * @deprecated {@link #disassemble(Object, SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default Serializable disassemble(Object value, SessionImplementor session) throws HibernateException { - return disassemble( value, (SharedSessionContractImplementor) session ); - } - /** * Reconstruct an object from the cacheable representation. At the very least this * method should perform a deep copy. (optional operation) @@ -222,25 +162,6 @@ default Serializable disassemble(Object value, SessionImplementor session) throw */ Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException; - /** - * Reconstruct an object from the cacheable representation. At the very least this - * method should perform a deep copy. (optional operation) - * - * @param cached the object to be cached - * @param session - * @param owner the owner of the cached object - * @return a reconstructed object from the cachable representation - * @throws HibernateException - * - * @deprecated {@link #assemble(Serializable, SharedSessionContractImplementor, Object)} should - * be used instead. - */ - @Deprecated - default Object assemble(Serializable cached, SessionImplementor session, Object owner) - throws HibernateException { - return assemble( cached, (SharedSessionContractImplementor) session, owner ); - } - /** * During merge, replace the existing (target) value in the entity we are merging to * with a new (original) value from the detached entity we are merging. For immutable @@ -252,23 +173,4 @@ default Object assemble(Serializable cached, SessionImplementor session, Object * @throws HibernateException */ Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner) throws HibernateException; - - /** - * During merge, replace the existing (target) value in the entity we are merging to - * with a new (original) value from the detached entity we are merging. For immutable - * objects, or null values, it is safe to simply return the first parameter. For - * mutable objects, it is safe to return a copy of the first parameter. However, since - * composite user types often define component values, it might make sense to recursively - * replace component values in the target object. - * - * @throws HibernateException - * - * @deprecated {@link #replace(Object, Object, SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Object replace(Object original, Object target, SessionImplementor session, Object owner) - throws HibernateException { - return replace( original, target, (SharedSessionContractImplementor) session, owner ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java index 1a6d2c6406c3..5856c3534ddc 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserCollectionType.java @@ -11,7 +11,6 @@ import org.hibernate.HibernateException; import org.hibernate.collection.spi.PersistentCollection; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.collection.CollectionPersister; @@ -29,33 +28,11 @@ public interface UserCollectionType { PersistentCollection instantiate(SharedSessionContractImplementor session, CollectionPersister persister) throws HibernateException; - /** - * Instantiate an uninitialized instance of the collection wrapper - * - * @deprecated {@link #instantiate(SharedSessionContractImplementor, CollectionPersister)} - * should be used instead. - */ - @Deprecated - default PersistentCollection instantiate(SessionImplementor session, CollectionPersister persister) - throws HibernateException { - return instantiate( (SharedSessionContractImplementor) session, persister ); - } - /** * Wrap an instance of a collection */ PersistentCollection wrap(SharedSessionContractImplementor session, Object collection); - /** - * Wrap an instance of a collection - * - * @deprecated {@link #wrap(SharedSessionContractImplementor, Object)} should be used instead. - */ - @Deprecated - default PersistentCollection wrap(SessionImplementor session, Object collection) { - return wrap( (SharedSessionContractImplementor) session, collection ); - } - /** * Return an iterator over the elements of this collection - the passed collection * instance may or may not be a wrapper @@ -83,30 +60,6 @@ Object replaceElements( Map copyCache, SharedSessionContractImplementor session) throws HibernateException; - /** - * Replace the elements of a collection with the elements of another collection - * - * @deprecated {@link #replaceElements(Object, Object, CollectionPersister, Object, Map, SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default Object replaceElements( - Object original, - Object target, - CollectionPersister persister, - Object owner, - Map copyCache, - SessionImplementor session) throws HibernateException { - return replaceElements( - original, - target, - persister, - owner, - copyCache, - (SharedSessionContractImplementor) session - ); - } - /** * Instantiate an empty instance of the "underlying" collection (not a wrapper), * but with the given anticipated size (i.e. accounting for initial size diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java index 3cf60ff758de..21cac8b61ed8 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java @@ -12,7 +12,6 @@ import java.sql.SQLException; import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -94,26 +93,6 @@ public interface UserType { */ Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException; - /** - * Retrieve an instance of the mapped class from a JDBC resultset. Implementors - * should handle possibility of null values. - * - * @param rs a JDBC result set - * @param names the column names - * @param session - * @param owner the containing entity @return Object - * @throws HibernateException - * @throws SQLException - * - * @deprecated {@link #nullSafeGet(ResultSet, String[], SharedSessionContractImplementor, Object)} - * should be used instead. - */ - @Deprecated - default Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) - throws HibernateException, SQLException { - return nullSafeGet( rs, names, (SharedSessionContractImplementor) session, owner ); - } - /** * Write an instance of the mapped class to a prepared statement. Implementors * should handle possibility of null values. A multi-column type should be written @@ -129,28 +108,6 @@ default Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor sess */ void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException; - /** - * Write an instance of the mapped class to a prepared statement. Implementors - * should handle possibility of null values. A multi-column type should be written - * to parameters starting from index. - * - * - * @param st a JDBC prepared statement - * @param value the object to write - * @param index statement parameter index - * @param session - * @throws HibernateException - * @throws SQLException - * - * @deprecated {@link #nullSafeSet(PreparedStatement, Object, int, SharedSessionContractImplementor)} - * should be used instead. - */ - @Deprecated - default void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) - throws HibernateException, SQLException { - nullSafeSet( st, value, index, (SharedSessionContractImplementor) session ); - } - /** * Return a deep copy of the persistent state, stopping at entities and at * collections. It is not necessary to copy immutable objects, or null diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java index daa7425c2cf7..abd55a59efd8 100755 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java @@ -8,12 +8,11 @@ import java.util.Comparator; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** * A user type that may be used for a version property - * + * * @author Gavin King */ public interface UserVersionType extends UserType, Comparator { @@ -27,21 +26,6 @@ public interface UserVersionType extends UserType, Comparator { */ Object seed(SharedSessionContractImplementor session); - /** - * Generate an initial version. - * - * @param session The session from which this request originates. May be - * null; currently this only happens during startup when trying to determine - * the "unsaved value" of entities. - * @return an instance of the type - * - * @deprecated {@link #seed(SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default Object seed(SessionImplementor session) { - return seed( (SharedSessionContractImplementor) session ); - } - /** * Increment the version. * @@ -50,19 +34,4 @@ default Object seed(SessionImplementor session) { * @return an instance of the type */ Object next(Object current, SharedSessionContractImplementor session); - - /** - * Increment the version. - * - * @param session The session from which this request originates. - * @param current the current version - * @return an instance of the type - * - * @deprecated {@link #next(Object, SharedSessionContractImplementor)} should be used instead. - */ - @Deprecated - default Object next(Object current, SessionImplementor session) { - return next( current, (SharedSessionContractImplementor) session ); - } - } From 92f194f2912f13c6a0c4ab1e32253162b654c174 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 31 Jul 2018 18:34:27 +0200 Subject: [PATCH 106/772] HHH-10603 Avoid doing distinct and comparisons on byte arrays They are stored as blobs starting with Oracle12cDialect and distinct and comparisons on blobs are not supported. Some tests were adapted, some are now skipped with Oracle12cDialect. --- .../MultiTypedBasicAttributesEntity.java | 20 ++--- .../jpa/test/criteria/ParameterTest.java | 18 ++-- .../test/criteria/basic/PredicateTest.java | 26 +++--- .../test/criteria/paths/ImplicitJoinTest.java | 15 ++-- .../jpa/test/criteria/paths/LineItem.java | 66 ++++++++++++++ .../jpa/test/criteria/paths/Order.java | 88 +++++++++++++++++++ .../org/hibernate/test/legacy/FooBarTest.java | 9 +- 7 files changed, 203 insertions(+), 39 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java index 3267b8b0e229..a8ece15d23f3 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java @@ -26,8 +26,8 @@ public class MultiTypedBasicAttributesEntity { @GeneratedValue( generator = "increment" ) @GenericGenerator( name = "increment", strategy = "increment" ) private Long id; - private byte[] someBytes; - private Byte[] someWrappedBytes; + private int[] someInts; + private Integer[] someWrappedIntegers; public Long getId() { return id; @@ -37,19 +37,19 @@ public void setId(Long id) { this.id = id; } - public byte[] getSomeBytes() { - return someBytes; + public int[] getSomeInts() { + return someInts; } - public void setSomeBytes(byte[] someBytes) { - this.someBytes = someBytes; + public void setSomeInts(int[] someInts) { + this.someInts = someInts; } - public Byte[] getSomeWrappedBytes() { - return someWrappedBytes; + public Integer[] getSomeWrappedIntegers() { + return someWrappedIntegers; } - public void setSomeWrappedBytes(Byte[] someWrappedBytes) { - this.someWrappedBytes = someWrappedBytes; + public void setSomeWrappedIntegers(Integer[] someWrappedIntegers) { + this.someWrappedIntegers = someWrappedIntegers; } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java index 231466791896..7a201372b94f 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java @@ -40,12 +40,12 @@ public void testPrimitiveArrayParameterBinding() { CriteriaQuery criteria = em.getCriteriaBuilder() .createQuery( MultiTypedBasicAttributesEntity.class ); Root rootEntity = criteria.from( MultiTypedBasicAttributesEntity.class ); - Path someBytesPath = rootEntity.get( MultiTypedBasicAttributesEntity_.someBytes ); - ParameterExpression param = em.getCriteriaBuilder().parameter( byte[].class, "theBytes" ); - criteria.where( em.getCriteriaBuilder().equal( someBytesPath, param ) ); + Path someIntsPath = rootEntity.get( MultiTypedBasicAttributesEntity_.someInts ); + ParameterExpression param = em.getCriteriaBuilder().parameter( int[].class, "theInts" ); + criteria.where( em.getCriteriaBuilder().equal( someIntsPath, param ) ); TypedQuery query = em.createQuery( criteria ); - query.setParameter( param, new byte[] { 1,1,1 } ); - assertThat( query.getParameterValue( param.getName() ), instanceOf( byte[].class) ); + query.setParameter( param, new int[] { 1,1,1 } ); + assertThat( query.getParameterValue( param.getName() ), instanceOf( int[].class) ); query.getResultList(); em.getTransaction().commit(); em.close(); @@ -58,12 +58,12 @@ public void testNonPrimitiveArrayParameterBinding() { CriteriaQuery criteria = em.getCriteriaBuilder() .createQuery( MultiTypedBasicAttributesEntity.class ); Root rootEntity = criteria.from( MultiTypedBasicAttributesEntity.class ); - Path thePath = rootEntity.get( MultiTypedBasicAttributesEntity_.someWrappedBytes ); - ParameterExpression param = em.getCriteriaBuilder().parameter( Byte[].class, "theBytes" ); + Path thePath = rootEntity.get( MultiTypedBasicAttributesEntity_.someWrappedIntegers ); + ParameterExpression param = em.getCriteriaBuilder().parameter( Integer[].class, "theIntegers" ); criteria.where( em.getCriteriaBuilder().equal( thePath, param ) ); TypedQuery query = em.createQuery( criteria ); - query.setParameter( param, new Byte[] { Byte.valueOf((byte)1), Byte.valueOf((byte)1), Byte.valueOf((byte)1) } ); - assertThat( query.getParameterValue( param.getName() ), instanceOf( Byte[].class ) ); + query.setParameter( param, new Integer[] { Integer.valueOf(1), Integer.valueOf(1), Integer.valueOf(1) } ); + assertThat( query.getParameterValue( param.getName() ), instanceOf( Integer[].class ) ); query.getResultList(); em.getTransaction().commit(); em.close(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java index bd21d68e8bdc..c88117627659 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java @@ -6,7 +6,11 @@ */ package org.hibernate.jpa.test.criteria.basic; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.util.List; + import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -14,20 +18,20 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import org.junit.Before; -import org.junit.Test; - +import org.hibernate.dialect.Oracle12cDialect; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.dialect.Oracle9Dialect; +import org.hibernate.dialect.OracleDialect; import org.hibernate.jpa.test.metamodel.AbstractMetamodelSpecificTest; import org.hibernate.jpa.test.metamodel.CreditCard; import org.hibernate.jpa.test.metamodel.CreditCard_; import org.hibernate.jpa.test.metamodel.Customer_; import org.hibernate.jpa.test.metamodel.Order; import org.hibernate.jpa.test.metamodel.Order_; - +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; /** * Test the various predicates. @@ -211,7 +215,7 @@ public void testCharArray() { em.getTransaction().begin(); CriteriaQuery orderCriteria = builder.createQuery( Order.class ); Root orderRoot = orderCriteria.from( Order.class ); - + orderCriteria.select( orderRoot ); Predicate p = builder.equal( orderRoot.get( "domen" ), new char[]{'r','u'} ); orderCriteria.where( p ); @@ -223,15 +227,17 @@ public void testCharArray() { } /** - * Check predicate for field which has simple char array type (byte[]). + * Check predicate for field which has simple byte array type (byte[]). */ @Test + @SkipForDialect(value = Oracle12cDialect.class, jiraKey = "HHH-10603", + comment = "Oracle12cDialect uses blob to store byte arrays and it's not possible to compare blobs with simple equality operators.") public void testByteArray() { EntityManager em = getOrCreateEntityManager(); em.getTransaction().begin(); CriteriaQuery orderCriteria = builder.createQuery( Order.class ); Root orderRoot = orderCriteria.from( Order.class ); - + orderCriteria.select( orderRoot ); Predicate p = builder.equal( orderRoot.get( "number" ), new byte[]{'1','2'} ); orderCriteria.where( p ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java index 069a1ca3b84c..4c9cae742c84 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java @@ -13,18 +13,19 @@ import javax.persistence.criteria.Join; import javax.persistence.criteria.Root; -import org.hibernate.jpa.test.metamodel.AbstractMetamodelSpecificTest; -import org.hibernate.jpa.test.metamodel.LineItem; -import org.hibernate.jpa.test.metamodel.LineItem_; -import org.hibernate.jpa.test.metamodel.Order; -import org.hibernate.jpa.test.metamodel.Order_; - +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.junit.Test; /** * @author Steve Ebersole */ -public class ImplicitJoinTest extends AbstractMetamodelSpecificTest { +public class ImplicitJoinTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Order.class, LineItem.class }; + } + @Test public void testImplicitJoinFromExplicitCollectionJoin() { EntityManager em = getOrCreateEntityManager(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java new file mode 100644 index 000000000000..03583422f1eb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java @@ -0,0 +1,66 @@ +/* + * 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.criteria.paths; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "LINEITEM_TABLE") +public class LineItem { + + private String id; + private int quantity; + private Order order; + + public LineItem() { + } + + public LineItem(String v1, int v2, Order v3) { + id = v1; + quantity = v2; + order = v3; + } + + public LineItem(String v1, int v2) { + id = v1; + quantity = v2; + } + + @Id + @Column(name = "ID") + public String getId() { + return id; + } + + public void setId(String v) { + id = v; + } + + @Column(name = "QUANTITY") + public int getQuantity() { + return quantity; + } + + public void setQuantity(int v) { + quantity = v; + } + + @ManyToOne + @JoinColumn(name = "FK1_FOR_ORDER_TABLE") + public Order getOrder() { + return order; + } + + public void setOrder(Order v) { + order = v; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java new file mode 100644 index 000000000000..ff82ed9b80a4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java @@ -0,0 +1,88 @@ +/* + * 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.criteria.paths; + +import java.util.Collection; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "ORDER_TABLE") +public class Order { + + private String id; + private double totalPrice; + private LineItem sampleLineItem; + private Collection lineItems = new java.util.ArrayList(); + + public Order() { + } + + public Order(String id, double totalPrice) { + this.id = id; + this.totalPrice = totalPrice; + } + + public Order(String id) { + this.id = id; + } + + // ==================================================================== + // getters and setters for State fields + + @Id + @Column(name = "ID") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Column(name = "TOTALPRICE") + public double getTotalPrice() { + return totalPrice; + } + + public void setTotalPrice(double price) { + this.totalPrice = price; + } + + // ==================================================================== + // getters and setters for Association fields + + // 1x1 + + @OneToOne(cascade = CascadeType.REMOVE) + @JoinColumn(name = "FK0_FOR_LINEITEM_TABLE") + public LineItem getSampleLineItem() { + return sampleLineItem; + } + + public void setSampleLineItem(LineItem l) { + this.sampleLineItem = l; + } + + // 1xMANY + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "order") + public Collection getLineItems() { + return lineItems; + } + + public void setLineItems(Collection c) { + this.lineItems = c; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java index 3c9ba5db95e5..a4e410751164 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java @@ -53,6 +53,7 @@ import org.hibernate.dialect.InterbaseDialect; import org.hibernate.dialect.MckoiDialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.Oracle12cDialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PointbaseDialect; import org.hibernate.dialect.PostgreSQL81Dialect; @@ -1645,7 +1646,7 @@ public void testLimit() throws Exception { count++; } assertEquals(4, count); - iter = s.createQuery("select distinct foo from Foo foo") + iter = s.createQuery("select foo from Foo foo") .setMaxResults(2) .setFirstResult(2) .list() @@ -1656,7 +1657,7 @@ public void testLimit() throws Exception { count++; } assertTrue(count==2); - iter = s.createQuery("select distinct foo from Foo foo") + iter = s.createQuery("select foo from Foo foo") .setMaxResults(3) .list() .iterator(); @@ -2519,7 +2520,9 @@ public void testPersistCollections() throws Exception { ).list(); assertTrue( "collection.elements find", list.size()==2 ); } - if (!(getDialect() instanceof SAPDBDialect) ) { // SAPDB doesn't like distinct with binary type + // SAPDB doesn't like distinct with binary type + // Oracle12cDialect stores binary types as blobs and do no support distinct on blobs + if ( !(getDialect() instanceof SAPDBDialect) && !(getDialect() instanceof Oracle12cDialect) ) { List list = s.createQuery( "select distinct foo from Baz baz join baz.fooArray foo" ).list(); assertTrue( "collection.elements find", list.size()==2 ); } From 98f46d64e7cca185af10d9630a39a629d44814ab Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 31 Jul 2018 17:55:32 +0200 Subject: [PATCH 107/772] HHH-12848 Restore the original Oracle LimitHandler Oracle does not support using the SQL 2008 standard limit handler with FOR UPDATE clauses. Thus we need to get back to the old limit handler. --- .../main/java/org/hibernate/dialect/Oracle12cDialect.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java index c031ae765e31..941159b4b458 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java @@ -10,8 +10,6 @@ import org.hibernate.cfg.Environment; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.Oracle12cIdentityColumnSupport; -import org.hibernate.dialect.pagination.LimitHandler; -import org.hibernate.dialect.pagination.SQL2008StandardLimitHandler; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.service.ServiceRegistry; @@ -55,11 +53,6 @@ protected void registerDefaultProperties() { getDefaultProperties().setProperty( Environment.USE_GET_GENERATED_KEYS, "true" ); } - @Override - public LimitHandler getLimitHandler() { - return SQL2008StandardLimitHandler.INSTANCE; - } - @Override public String getNativeIdentifierGeneratorStrategy() { return "sequence"; From 0d224e45d62543677e3c31202d207954172699a8 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 31 Jul 2018 17:11:24 +0200 Subject: [PATCH 108/772] HHH-12835 Fix an incorrect assertion in BatchFetchQueueHelper --- .../internal/BatchFetchQueueHelper.java | 3 +- .../org/hibernate/test/batchfetch/City.java | 72 +++++++++++++++++++ .../hibernate/test/batchfetch/Country.java | 66 +++++++++++++++++ .../batchfetch/PaddedBatchFetchTestCase.java | 64 +++++++++++++++++ 4 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java index 5d04bbaaa909..8e8e06f4e0ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java @@ -54,11 +54,12 @@ public static void removeNotFoundBatchLoadableEntityKeys( } LOG.debug( "Not all entities were loaded." ); Set idSet = new HashSet<>( Arrays.asList( ids ) ); + int originalIdSetSize = idSet.size(); for ( Object result : results ) { // All results should be in the PersistenceContext idSet.remove( session.getContextEntityIdentifier( result ) ); } - assert idSet.size() == ids.length - results.size(); + assert idSet.size() == originalIdSetSize - results.size(); if ( LOG.isDebugEnabled() ) { LOG.debug( "Entities of type [" + persister.getEntityName() + "] not found; IDs: " + idSet ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java new file mode 100644 index 000000000000..83a800a26706 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java @@ -0,0 +1,72 @@ +/* + * 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.batchfetch; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "city") +public class City { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Integer id; + + @Column(name = "name") + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "country_id") + private Country country; + + public City() { + } + + public City(String name, Country country) { + super(); + this.name = name; + this.country = country; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } + + @Override + public String toString() { + return name + " (" + ( country == null ? "?" : country.getName() ) + ")"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java new file mode 100644 index 000000000000..76e40d62ca6b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java @@ -0,0 +1,66 @@ +/* + * 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.batchfetch; + +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name = "country") +public class Country { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Integer id; + + @Column(name = "name") + private String name; + + @OneToMany(mappedBy = "country") + private List cities; + + public Country() { + } + + public Country(String name) { + super(); + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getCities() { + return cities; + } + + @Override + public String toString() { + return name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java new file mode 100644 index 000000000000..08c5f471b0cf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java @@ -0,0 +1,64 @@ +/* + * 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.batchfetch; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import java.util.stream.IntStream; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.loader.BatchFetchStyle; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +public class PaddedBatchFetchTestCase extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Country.class, City.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + + configuration.setProperty( AvailableSettings.SHOW_SQL, Boolean.TRUE.toString() ); + configuration.setProperty( AvailableSettings.FORMAT_SQL, Boolean.TRUE.toString() ); + + configuration.setProperty( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.name() ); + configuration.setProperty( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "15" ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12835") + public void paddedBatchFetchTest() throws Exception { + doInHibernate( this::sessionFactory, session -> { + // Having DEFAULT_BATCH_FETCH_SIZE=15 + // results in batchSizes = [15, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + // Let's create 11 countries so batch size 15 will be used with padded values, + // this causes to have to remove 4 elements from list + int numberOfCountries = 11; + + IntStream.range( 0, numberOfCountries ).forEach( i -> { + Country c = new Country( "Country " + i ); + session.save( c ); + session.save( new City( "City " + i, c ) ); + } ); + } ); + + doInHibernate( this::sessionFactory, session -> { + List allCities = session.createQuery( "from City", City.class ).list(); + + // this triggers countries to be fetched in batch + assertNotNull( allCities.get( 0 ).getCountry().getName() ); + } ); + } +} From 5d5cfe82ef259686239c47f57f082d9bbba4d339 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 31 Jul 2018 16:37:35 +0200 Subject: [PATCH 109/772] HHH-12869 Do not check the cacheManager is not null when creating it --- .../internal/EhcacheRegionFactory.java | 4 ++- ...FactoryClasspathConfigurationFileTest.java | 35 +++++++++++++++++++ .../ehcache-configuration.xml | 19 ++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/SingletonEhCacheRegionFactoryClasspathConfigurationFileTest.java create mode 100644 hibernate-ehcache/src/test/resources/hibernate-config/ehcache-configuration.xml diff --git a/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/internal/EhcacheRegionFactory.java b/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/internal/EhcacheRegionFactory.java index e5cea1a9f11a..650ee2725830 100644 --- a/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/internal/EhcacheRegionFactory.java +++ b/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/internal/EhcacheRegionFactory.java @@ -283,7 +283,9 @@ private static URL loadResource(String configurationResourceName, SessionFactory * Load a resource from the classpath. */ protected URL loadResource(String configurationResourceName) { - if ( ! isStarted() ) { + // we use this method to create the cache manager so we can't check it is non null + // calling the super method then + if ( ! super.isStarted() ) { throw new IllegalStateException( "Cannot load resource through a non-started EhcacheRegionFactory" ); } diff --git a/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/SingletonEhCacheRegionFactoryClasspathConfigurationFileTest.java b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/SingletonEhCacheRegionFactoryClasspathConfigurationFileTest.java new file mode 100644 index 000000000000..1b353ec496b0 --- /dev/null +++ b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/SingletonEhCacheRegionFactoryClasspathConfigurationFileTest.java @@ -0,0 +1,35 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.cache.ehcache.test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNotNull; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cache.ehcache.ConfigSettings; +import org.hibernate.cache.ehcache.internal.SingletonEhcacheRegionFactory; +import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-12869") +public class SingletonEhCacheRegionFactoryClasspathConfigurationFileTest { + + @Test + public void testCacheInitialization() { + try ( SessionFactoryImplementor sessionFactory = TestHelper.buildStandardSessionFactory( + builder -> builder.applySetting( AvailableSettings.CACHE_REGION_FACTORY, "ehcache-singleton" ) + .applySetting( ConfigSettings.EHCACHE_CONFIGURATION_RESOURCE_NAME, + "/hibernate-config/ehcache-configuration.xml" ) ) ) { + assertNotNull( sessionFactory ); + } + } +} diff --git a/hibernate-ehcache/src/test/resources/hibernate-config/ehcache-configuration.xml b/hibernate-ehcache/src/test/resources/hibernate-config/ehcache-configuration.xml new file mode 100644 index 000000000000..f67d705302a0 --- /dev/null +++ b/hibernate-ehcache/src/test/resources/hibernate-config/ehcache-configuration.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + From 7d9ebd262d9d6060873c2ca249c7f5d5d54026df Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 1 Aug 2018 11:37:00 +0100 Subject: [PATCH 110/772] HHH-12849 - QuotedIdentifierTest fails with ORA-04043 on Oracle12c --- .../hibernate/id/QuotedIdentifierTest.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java index 78cb64dbb0f6..ff75830628bb 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java @@ -13,39 +13,37 @@ import javax.persistence.Id; import javax.persistence.Table; -import org.hibernate.Session; -import org.hibernate.Transaction; +import org.hibernate.dialect.Oracle12cDialect; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertNotNull; /** * @author Vlad Mihalcea */ @RequiresDialectFeature( value = DialectChecks.SupportsIdentityColumns.class, jiraKey = "HHH-9271") +@SkipForDialect(value = Oracle12cDialect.class, comment = "Oracle and identity column: java.sql.Connection#prepareStatement(String sql, int columnIndexes[]) does not work with quoted table names and/or quoted columnIndexes") public class QuotedIdentifierTest extends BaseCoreFunctionalTestCase { @Test - public void testDirectIdPropertyAccess() throws Exception { - Session s = openSession(); - Transaction transaction = s.beginTransaction(); - QuotedIdentifier o = new QuotedIdentifier(); - o.timestamp = System.currentTimeMillis(); - o.from = "HHH-9271"; - s.persist( o ); - transaction.commit(); - s.close(); + public void testDirectIdPropertyAccess() { + QuotedIdentifier quotedIdentifier = new QuotedIdentifier(); + doInHibernate( this::sessionFactory, session -> { + quotedIdentifier.timestamp = System.currentTimeMillis(); + quotedIdentifier.from = "HHH-9271"; + session.persist( quotedIdentifier ); + } ); - s = openSession(); - transaction = s.beginTransaction(); - o = session.get( QuotedIdentifier.class, o.index ); - assertNotNull(o); - transaction.commit(); - s.close(); + doInHibernate( this::sessionFactory, session -> { + QuotedIdentifier result = session.get( QuotedIdentifier.class, quotedIdentifier.index ); + assertNotNull( result ); + } ); } @Override From c9de4b8ce7b00f8fd7aeeef051959c803a82bd0f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 1 Aug 2018 16:07:49 +0200 Subject: [PATCH 111/772] HHH-12835 In the end, remove the assertion It's not really compatible with batch loading, padding and a couple of other corner cases. And I don't think it has a lot of value by itself. --- .../org/hibernate/engine/internal/BatchFetchQueueHelper.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java index 8e8e06f4e0ba..225bef5735f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java @@ -54,12 +54,10 @@ public static void removeNotFoundBatchLoadableEntityKeys( } LOG.debug( "Not all entities were loaded." ); Set idSet = new HashSet<>( Arrays.asList( ids ) ); - int originalIdSetSize = idSet.size(); for ( Object result : results ) { // All results should be in the PersistenceContext idSet.remove( session.getContextEntityIdentifier( result ) ); } - assert idSet.size() == originalIdSetSize - results.size(); if ( LOG.isDebugEnabled() ) { LOG.debug( "Entities of type [" + persister.getEntityName() + "] not found; IDs: " + idSet ); } From 129530e464a406c97dcf3e0df31214f937449f53 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 25 Jul 2018 11:53:32 +0200 Subject: [PATCH 112/772] HHH-12857 Avoid using a static ByteBuddy object In the end, it does not change anything as the BytecodeProvider is still static but it helps as a general cleanup for the following commits. --- .../internal/bytebuddy/EnhancerImpl.java | 10 +- .../bytebuddy/BasicProxyFactoryImpl.java | 2 +- .../internal/bytebuddy/ByteBuddyState.java | 42 ++--- .../bytebuddy/BytecodeProviderImpl.java | 26 ++- .../bytebuddy/ProxyFactoryFactoryImpl.java | 14 +- .../pojo/bytebuddy/ByteBuddyProxyFactory.java | 142 +-------------- .../pojo/bytebuddy/ByteBuddyProxyHelper.java | 168 ++++++++++++++++++ .../pojo/bytebuddy/SerializableProxy.java | 12 +- .../ByteBuddyBasicProxyFactoryTest.java | 2 +- .../bytebuddy/EnhancerWildFlyNamesTest.java | 2 +- 10 files changed, 237 insertions(+), 183 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java rename hibernate-core/src/test/java/org/hibernate/{test/proxy => bytecode/internal}/bytebuddy/ByteBuddyBasicProxyFactoryTest.java (97%) rename hibernate-core/src/test/java/org/hibernate/{test/bytecode/enhancement => bytecode/internal}/bytebuddy/EnhancerWildFlyNamesTest.java (97%) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 75fe3079c593..28fe6091cc9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -68,7 +68,7 @@ public class EnhancerImpl implements Enhancer { private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class ); protected final ByteBuddyEnhancementContext enhancementContext; - private final ByteBuddyState bytebuddy; + private final ByteBuddyState byteBuddyState; private final TypePool classPool; @@ -77,11 +77,11 @@ public class EnhancerImpl implements Enhancer { * * @param enhancementContext Describes the context in which enhancement will occur so as to give access * to contextual/environmental information. - * @param bytebuddy refers to the ByteBuddy instance to use + * @param byteBuddyState refers to the ByteBuddy instance to use */ - public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState bytebuddy) { + public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState) { this.enhancementContext = new ByteBuddyEnhancementContext( enhancementContext ); - this.bytebuddy = bytebuddy; + this.byteBuddyState = byteBuddyState; this.classPool = buildClassPool( this.enhancementContext ); } @@ -103,7 +103,7 @@ public synchronized byte[] enhance(String className, byte[] originalBytes) throw try { final TypeDescription managedCtClass = classPool.describe( safeClassName ).resolve(); DynamicType.Builder builder = doEnhance( - bytebuddy.getCurrentyByteBuddy().ignore( isDefaultFinalizer() ).redefine( managedCtClass, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ), + byteBuddyState.getCurrentByteBuddy().ignore( isDefaultFinalizer() ).redefine( managedCtClass, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ), managedCtClass ); if ( builder == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index d9fe8a10ef2b..493398796955 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -36,7 +36,7 @@ public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyStat final Class superClassOrMainInterface = superClass != null ? superClass : interfaces[0]; - this.proxyClass = bytebuddy.getCurrentyByteBuddy() + this.proxyClass = bytebuddy.getCurrentByteBuddy() .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( superClassOrMainInterface.getName() ) ) ) .subclass( superClass == null ? Object.class : superClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR ) .implement( interfaces == null ? NO_INTERFACES : interfaces ) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index 151fb514c263..56d6eaca1b68 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -31,42 +31,34 @@ public final class ByteBuddyState { private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class ); - /** - * Ideally shouldn't be static but it has to until we can remove the - * deprecated static methods. - */ - private static final ByteBuddy buddy = new ByteBuddy().with( TypeValidation.DISABLED ); + private final ByteBuddy byteBuddy; /** - * This currently needs to be static: the BytecodeProvider is a static field of Environment and - * is being accessed from static methods. * It will be easier to maintain the cache and its state when it will no longer be static * in Hibernate ORM 6+. * Opted for WEAK keys to avoid leaking the classloader in case the SessionFactory isn't closed. * Avoiding Soft keys as they are prone to cause issues with unstable performance. */ - private static final TypeCache CACHE = new TypeCache.WithInlineExpunction( - TypeCache.Sort.WEAK ); + private final TypeCache typeCache; private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + ByteBuddyState() { + this.byteBuddy = new ByteBuddy().with( TypeValidation.DISABLED ); + this.typeCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK ); + } + /** * Access to ByteBuddy. It's almost equivalent to creating a new ByteBuddy instance, * yet slightly preferrable so to be able to reuse the same instance. * @return */ - public ByteBuddy getCurrentyByteBuddy() { - return buddy; + public ByteBuddy getCurrentByteBuddy() { + return byteBuddy; } - /** - * @deprecated as we should not need static access to this state. - * This will be removed with no replacement. - * It's actually likely that this whole class becomes unnecessary in the near future. - */ - @Deprecated - public static TypeCache getCacheForProxies() { - return CACHE; + public TypeCache getCacheForProxies() { + return typeCache; } /** @@ -78,17 +70,7 @@ public static TypeCache getCacheForProxies() { * of re-creating the small helpers should be negligible. */ void clearState() { - CACHE.clear(); - } - - /** - * @deprecated as we should not need static access to this state. - * This will be removed with no replacement. - * It's actually likely that this whole class becomes unnecessary in the near future. - */ - @Deprecated - public static ByteBuddy getStaticByteBuddyInstance() { - return buddy; + typeCache.clear(); } public static ClassLoadingStrategy resolveClassLoadingStrategy(Class originalClass) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index 68422d10afd9..d1c589e51b91 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -18,6 +18,7 @@ import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.bytecode.spi.ProxyFactoryFactory; import org.hibernate.bytecode.spi.ReflectionOptimizer; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper; import net.bytebuddy.NamingStrategy; import net.bytebuddy.description.method.MethodDescription; @@ -37,7 +38,6 @@ public class BytecodeProviderImpl implements BytecodeProvider { - private static final ByteBuddyState bytebuddy = new ByteBuddyState(); private static final String INSTANTIATOR_PROXY_NAMING_SUFFIX = "HibernateInstantiator"; private static final String OPTIMIZER_PROXY_NAMING_SUFFIX = "HibernateAccessOptimizer"; private static final ElementMatcher.Junction newInstanceMethodName = ElementMatchers.named( "newInstance" ); @@ -45,9 +45,18 @@ public class BytecodeProviderImpl implements BytecodeProvider { private static final ElementMatcher.Junction setPropertyValuesMethodName = ElementMatchers.named( "setPropertyValues" ); private static final ElementMatcher.Junction getPropertyNamesMethodName = ElementMatchers.named( "getPropertyNames" ); + private final ByteBuddyState byteBuddyState; + + private final ByteBuddyProxyHelper byteBuddyProxyHelper; + + public BytecodeProviderImpl() { + this.byteBuddyState = new ByteBuddyState(); + this.byteBuddyProxyHelper = new ByteBuddyProxyHelper( byteBuddyState ); + } + @Override public ProxyFactoryFactory getProxyFactoryFactory() { - return new ProxyFactoryFactoryImpl( bytebuddy ); + return new ProxyFactoryFactoryImpl( byteBuddyState, byteBuddyProxyHelper ); } @Override @@ -61,7 +70,7 @@ public ReflectionOptimizer getReflectionOptimizer( // we only provide a fast class instantiator if the class can be instantiated final Constructor constructor = findConstructor( clazz ); - fastClass = bytebuddy.getCurrentyByteBuddy() + fastClass = byteBuddyState.getCurrentByteBuddy() .with( new NamingStrategy.SuffixingRandom( INSTANTIATOR_PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) .subclass( ReflectionOptimizer.InstantiationOptimizer.class ) @@ -79,7 +88,7 @@ public ReflectionOptimizer getReflectionOptimizer( final Method[] setters = new Method[setterNames.length]; findAccessors( clazz, getterNames, setterNames, types, getters, setters ); - final Class bulkAccessor = bytebuddy.getCurrentyByteBuddy() + final Class bulkAccessor = byteBuddyState.getCurrentByteBuddy() .with( new NamingStrategy.SuffixingRandom( OPTIMIZER_PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) .subclass( ReflectionOptimizer.AccessOptimizer.class ) @@ -104,6 +113,10 @@ public ReflectionOptimizer getReflectionOptimizer( } } + public ByteBuddyProxyHelper getByteBuddyProxyHelper() { + return byteBuddyProxyHelper; + } + private static class GetPropertyValues implements ByteCodeAppender { private final Class clazz; @@ -271,11 +284,12 @@ public String[] call() { @Override public Enhancer getEnhancer(EnhancementContext enhancementContext) { - return new EnhancerImpl( enhancementContext, bytebuddy ); + return new EnhancerImpl( enhancementContext, byteBuddyState ); } + @Override public void resetCaches() { - bytebuddy.clearState(); + byteBuddyState.clearState(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java index 9df70b85b1e2..0f68aead89c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java @@ -11,22 +11,26 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.proxy.ProxyFactory; import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper; public class ProxyFactoryFactoryImpl implements ProxyFactoryFactory { - private final ByteBuddyState bytebuddy; + private final ByteBuddyState byteBuddyState; - public ProxyFactoryFactoryImpl(ByteBuddyState bytebuddy) { - this.bytebuddy = bytebuddy; + private final ByteBuddyProxyHelper byteBuddyProxyHelper; + + public ProxyFactoryFactoryImpl(ByteBuddyState byteBuddyState, ByteBuddyProxyHelper byteBuddyProxyHelper) { + this.byteBuddyState = byteBuddyState; + this.byteBuddyProxyHelper = byteBuddyProxyHelper; } @Override public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) { - return new ByteBuddyProxyFactory(); + return new ByteBuddyProxyFactory( byteBuddyProxyHelper ); } @Override public BasicProxyFactory buildBasicProxyFactory(Class superClass, Class[] interfaces) { - return new BasicProxyFactoryImpl( superClass, interfaces, bytebuddy ); + return new BasicProxyFactoryImpl( superClass, interfaces, byteBuddyState ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java index 4c4173b4201f..d67b9720c671 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java @@ -6,16 +6,13 @@ */ package org.hibernate.proxy.pojo.bytebuddy; +import static org.hibernate.internal.CoreLogging.messageLogger; + import java.io.Serializable; import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Locale; import java.util.Set; import org.hibernate.HibernateException; -import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.cfg.Environment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; @@ -26,28 +23,11 @@ import org.hibernate.proxy.ProxyFactory; import org.hibernate.type.CompositeType; -import net.bytebuddy.NamingStrategy; -import net.bytebuddy.TypeCache; -import net.bytebuddy.description.modifier.Visibility; -import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; -import net.bytebuddy.implementation.FieldAccessor; -import net.bytebuddy.implementation.MethodDelegation; -import net.bytebuddy.implementation.SuperMethodCall; -import net.bytebuddy.implementation.bytecode.assign.Assigner; - -import static net.bytebuddy.matcher.ElementMatchers.isFinalizer; -import static net.bytebuddy.matcher.ElementMatchers.isSynthetic; -import static net.bytebuddy.matcher.ElementMatchers.isVirtual; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.not; -import static net.bytebuddy.matcher.ElementMatchers.returns; -import static org.hibernate.internal.CoreLogging.messageLogger; - public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class ); - private static final String PROXY_NAMING_SUFFIX = Environment.useLegacyProxyClassnames() ? "HibernateProxy$" : "HibernateProxy"; + + private final ByteBuddyProxyHelper byteBuddyProxyHelper; private Class persistentClass; private String entityName; @@ -59,6 +39,10 @@ public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { private Class proxyClass; + public ByteBuddyProxyFactory(ByteBuddyProxyHelper byteBuddyProxyHelper) { + this.byteBuddyProxyHelper = byteBuddyProxyHelper; + } + @Override public void postInstantiate( String entityName, @@ -75,7 +59,7 @@ public void postInstantiate( this.componentIdType = componentIdType; this.overridesEquals = ReflectHelper.overridesEquals( persistentClass ); - this.proxyClass = buildProxy( persistentClass, this.interfaces ); + this.proxyClass = byteBuddyProxyHelper.buildProxy( persistentClass, this.interfaces ); } private Class[] toArray(Set interfaces) { @@ -86,35 +70,6 @@ private Class[] toArray(Set interfaces) { return interfaces.toArray( new Class[interfaces.size()] ); } - public static Class buildProxy( - final Class persistentClass, - final Class[] interfaces) { - Set> key = new HashSet>(); - if ( interfaces.length == 1 ) { - key.add( persistentClass ); - } - key.addAll( Arrays.>asList( interfaces ) ); - - final TypeCache cacheForProxies = ByteBuddyState.getCacheForProxies(); - - return cacheForProxies.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () -> - ByteBuddyState.getStaticByteBuddyInstance() - .ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) ) - .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) - .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) - .implement( (Type[]) interfaces ) - .method( isVirtual().and( not( isFinalizer() ) ) ) - .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) - .method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) ) - .intercept( SuperMethodCall.INSTANCE ) - .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) - .make() - .load( persistentClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( persistentClass ) ) - .getLoaded(), cacheForProxies ); - } - @Override public HibernateProxy getProxy( Serializable id, @@ -142,83 +97,4 @@ public HibernateProxy getProxy( throw new HibernateException( LOG.bytecodeEnhancementFailed( entityName ), t ); } } - - public static HibernateProxy deserializeProxy(SerializableProxy serializableProxy) { - final ByteBuddyInterceptor interceptor = new ByteBuddyInterceptor( - serializableProxy.getEntityName(), - serializableProxy.getPersistentClass(), - serializableProxy.getInterfaces(), - serializableProxy.getId(), - resolveIdGetterMethod( serializableProxy ), - resolveIdSetterMethod( serializableProxy ), - serializableProxy.getComponentIdType(), - null, - ReflectHelper.overridesEquals( serializableProxy.getPersistentClass() ) - ); - - // note: interface is assumed to already contain HibernateProxy.class - try { - final Class proxyClass = buildProxy( - serializableProxy.getPersistentClass(), - serializableProxy.getInterfaces() - ); - final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); - ( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor ); - return proxy; - } - catch (Throwable t) { - final String message = LOG.bytecodeEnhancementFailed( serializableProxy.getEntityName() ); - LOG.error( message, t ); - throw new HibernateException( message, t ); - } - } - - @SuppressWarnings("unchecked") - private static Method resolveIdGetterMethod(SerializableProxy serializableProxy) { - if ( serializableProxy.getIdentifierGetterMethodName() == null ) { - return null; - } - - try { - return serializableProxy.getIdentifierGetterMethodClass().getDeclaredMethod( serializableProxy.getIdentifierGetterMethodName() ); - } - catch (NoSuchMethodException e) { - throw new HibernateException( - String.format( - Locale.ENGLISH, - "Unable to deserialize proxy [%s, %s]; could not locate id getter method [%s] on entity class [%s]", - serializableProxy.getEntityName(), - serializableProxy.getId(), - serializableProxy.getIdentifierGetterMethodName(), - serializableProxy.getIdentifierGetterMethodClass() - ) - ); - } - } - - @SuppressWarnings("unchecked") - private static Method resolveIdSetterMethod(SerializableProxy serializableProxy) { - if ( serializableProxy.getIdentifierSetterMethodName() == null ) { - return null; - } - - try { - return serializableProxy.getIdentifierSetterMethodClass().getDeclaredMethod( - serializableProxy.getIdentifierSetterMethodName(), - serializableProxy.getIdentifierSetterMethodParams() - ); - } - catch (NoSuchMethodException e) { - throw new HibernateException( - String.format( - Locale.ENGLISH, - "Unable to deserialize proxy [%s, %s]; could not locate id setter method [%s] on entity class [%s]", - serializableProxy.getEntityName(), - serializableProxy.getId(), - serializableProxy.getIdentifierSetterMethodName(), - serializableProxy.getIdentifierSetterMethodClass() - ) - ); - } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java new file mode 100644 index 000000000000..b453d76d4d42 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java @@ -0,0 +1,168 @@ +/* + * 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.proxy.pojo.bytebuddy; + +import static net.bytebuddy.matcher.ElementMatchers.isFinalizer; +import static net.bytebuddy.matcher.ElementMatchers.isSynthetic; +import static net.bytebuddy.matcher.ElementMatchers.isVirtual; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static org.hibernate.internal.CoreLogging.messageLogger; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.hibernate.HibernateException; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.ProxyConfiguration; +import org.hibernate.proxy.ProxyFactory; +import org.hibernate.type.CompositeType; + +import net.bytebuddy.NamingStrategy; +import net.bytebuddy.TypeCache; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.DynamicType.Unloaded; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.SuperMethodCall; +import net.bytebuddy.implementation.bytecode.assign.Assigner; + +public class ByteBuddyProxyHelper implements Serializable { + + private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyHelper.class ); + private static final String PROXY_NAMING_SUFFIX = Environment.useLegacyProxyClassnames() ? "HibernateProxy$" : "HibernateProxy"; + + private final ByteBuddyState byteBuddyState; + + public ByteBuddyProxyHelper(ByteBuddyState byteBuddyState) { + this.byteBuddyState = byteBuddyState; + } + + public Class buildProxy( + final Class persistentClass, + final Class[] interfaces) { + Set> key = new HashSet>(); + if ( interfaces.length == 1 ) { + key.add( persistentClass ); + } + key.addAll( Arrays.>asList( interfaces ) ); + + final TypeCache cacheForProxies = byteBuddyState.getCacheForProxies(); + + return cacheForProxies.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () -> + byteBuddyState.getCurrentByteBuddy() + .ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) ) + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) + .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) + .implement( (Type[]) interfaces ) + .method( isVirtual().and( not( isFinalizer() ) ) ) + .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) + .method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) ) + .intercept( SuperMethodCall.INSTANCE ) + .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) + .implement( ProxyConfiguration.class ) + .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) + .make() + .load( persistentClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( persistentClass ) ) + .getLoaded(), cacheForProxies ); + } + + public HibernateProxy deserializeProxy(SerializableProxy serializableProxy) { + final ByteBuddyInterceptor interceptor = new ByteBuddyInterceptor( + serializableProxy.getEntityName(), + serializableProxy.getPersistentClass(), + serializableProxy.getInterfaces(), + serializableProxy.getId(), + resolveIdGetterMethod( serializableProxy ), + resolveIdSetterMethod( serializableProxy ), + serializableProxy.getComponentIdType(), + null, + ReflectHelper.overridesEquals( serializableProxy.getPersistentClass() ) + ); + + // note: interface is assumed to already contain HibernateProxy.class + try { + final Class proxyClass = buildProxy( + serializableProxy.getPersistentClass(), + serializableProxy.getInterfaces() + ); + final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); + ( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor ); + return proxy; + } + catch (Throwable t) { + final String message = LOG.bytecodeEnhancementFailed( serializableProxy.getEntityName() ); + LOG.error( message, t ); + throw new HibernateException( message, t ); + } + } + + @SuppressWarnings("unchecked") + private static Method resolveIdGetterMethod(SerializableProxy serializableProxy) { + if ( serializableProxy.getIdentifierGetterMethodName() == null ) { + return null; + } + + try { + return serializableProxy.getIdentifierGetterMethodClass().getDeclaredMethod( serializableProxy.getIdentifierGetterMethodName() ); + } + catch (NoSuchMethodException e) { + throw new HibernateException( + String.format( + Locale.ENGLISH, + "Unable to deserialize proxy [%s, %s]; could not locate id getter method [%s] on entity class [%s]", + serializableProxy.getEntityName(), + serializableProxy.getId(), + serializableProxy.getIdentifierGetterMethodName(), + serializableProxy.getIdentifierGetterMethodClass() + ) + ); + } + } + + @SuppressWarnings("unchecked") + private static Method resolveIdSetterMethod(SerializableProxy serializableProxy) { + if ( serializableProxy.getIdentifierSetterMethodName() == null ) { + return null; + } + + try { + return serializableProxy.getIdentifierSetterMethodClass().getDeclaredMethod( + serializableProxy.getIdentifierSetterMethodName(), + serializableProxy.getIdentifierSetterMethodParams() + ); + } + catch (NoSuchMethodException e) { + throw new HibernateException( + String.format( + Locale.ENGLISH, + "Unable to deserialize proxy [%s, %s]; could not locate id setter method [%s] on entity class [%s]", + serializableProxy.getEntityName(), + serializableProxy.getId(), + serializableProxy.getIdentifierSetterMethodName(), + serializableProxy.getIdentifierSetterMethodClass() + ) + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java index 0d9839af4d49..546247f60649 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java @@ -9,6 +9,11 @@ import java.io.Serializable; import java.lang.reflect.Method; +import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl; +import org.hibernate.bytecode.internal.bytebuddy.ProxyFactoryFactoryImpl; +import org.hibernate.bytecode.spi.BytecodeProvider; +import org.hibernate.bytecode.spi.ProxyFactoryFactory; +import org.hibernate.cfg.Environment; import org.hibernate.proxy.AbstractSerializableProxy; import org.hibernate.proxy.HibernateProxy; import org.hibernate.type.CompositeType; @@ -125,7 +130,12 @@ protected CompositeType getComponentIdType() { } private Object readResolve() { - HibernateProxy proxy = ByteBuddyProxyFactory.deserializeProxy( this ); + BytecodeProvider bytecodeProvider = Environment.getBytecodeProvider(); + if ( !( bytecodeProvider instanceof BytecodeProviderImpl ) ) { + throw new IllegalStateException( "The bytecode provider is not ByteBuddy, unable to deserialize a ByteBuddy proxy." ); + } + + HibernateProxy proxy = ( (BytecodeProviderImpl) bytecodeProvider ).getByteBuddyProxyHelper().deserializeProxy( this ); afterDeserialization( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); return proxy; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/proxy/bytebuddy/ByteBuddyBasicProxyFactoryTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyBasicProxyFactoryTest.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/proxy/bytebuddy/ByteBuddyBasicProxyFactoryTest.java rename to hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyBasicProxyFactoryTest.java index 8caeac93d42f..afc655116b2c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/proxy/bytebuddy/ByteBuddyBasicProxyFactoryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyBasicProxyFactoryTest.java @@ -4,7 +4,7 @@ * 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.proxy.bytebuddy; +package org.hibernate.bytecode.internal.bytebuddy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/bytebuddy/EnhancerWildFlyNamesTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/bytebuddy/EnhancerWildFlyNamesTest.java rename to hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java index a04909395657..cb8ddaf6d6ff 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/bytebuddy/EnhancerWildFlyNamesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java @@ -4,7 +4,7 @@ * 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.bytecode.enhancement.bytebuddy; +package org.hibernate.bytecode.internal.bytebuddy; import java.io.ByteArrayOutputStream; import java.io.IOException; From fdf19f17b40f10db085605f91baf7ddf75096285 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 25 Jul 2018 15:44:13 +0200 Subject: [PATCH 113/772] HHH-12857 Rewrite getDeclaredMethod() calls in static initializers We rewrite them to run them as privileged blocks. --- .../internal/bytebuddy/EnhancerImpl.java | 28 ++- .../bytebuddy/BasicProxyFactoryImpl.java | 45 +++-- .../internal/bytebuddy/ByteBuddyState.java | 182 ++++++++++++++++-- .../bytebuddy/BytecodeProviderImpl.java | 12 +- .../HibernateMethodLookupDispatcher.java | 181 +++++++++++++++++ .../pojo/bytebuddy/ByteBuddyProxyHelper.java | 32 ++- .../bytebuddy/GenerateProxiesTest.java | 53 +++++ .../HibernateMethodLookupDispatcherTest.java | 58 ++++++ .../internal/bytebuddy/SimpleEntity.java | 40 ++++ 9 files changed, 559 insertions(+), 72 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java create mode 100644 hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 28fe6091cc9b..65b898c8c50f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -6,6 +6,9 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; +import static net.bytebuddy.matcher.ElementMatchers.isGetter; + import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -13,6 +16,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; + import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Transient; @@ -26,10 +30,10 @@ import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; -import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.engine.spi.CompositeTracker; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; import org.hibernate.engine.spi.Managed; import org.hibernate.engine.spi.ManagedComposite; import org.hibernate.engine.spi.ManagedEntity; @@ -40,7 +44,6 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import net.bytebuddy.ByteBuddy; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.annotation.AnnotationDescription; import net.bytebuddy.description.field.FieldDescription; @@ -53,16 +56,12 @@ import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.scaffold.MethodGraph; -import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; import net.bytebuddy.pool.TypePool; -import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; -import static net.bytebuddy.matcher.ElementMatchers.isGetter; - public class EnhancerImpl implements Enhancer { private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class ); @@ -101,17 +100,12 @@ public synchronized byte[] enhance(String className, byte[] originalBytes) throw //Classpool#describe does not accept '/' in the description name as it expects a class name. See HHH-12545 final String safeClassName = className.replace( '/', '.' ); try { - final TypeDescription managedCtClass = classPool.describe( safeClassName ).resolve(); - DynamicType.Builder builder = doEnhance( - byteBuddyState.getCurrentByteBuddy().ignore( isDefaultFinalizer() ).redefine( managedCtClass, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ), - managedCtClass - ); - if ( builder == null ) { - return originalBytes; - } - else { - return builder.make().getBytes(); - } + final TypeDescription typeDescription = classPool.describe( safeClassName ).resolve(); + + return byteBuddyState.rewrite( safeClassName, originalBytes, byteBuddy -> doEnhance( + byteBuddy.ignore( isDefaultFinalizer() ).redefine( typeDescription, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ), + typeDescription + ) ); } catch (RuntimeException e) { throw new EnhancementException( "Failed to enhance class " + className, e ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index 493398796955..46ba636b4397 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -6,6 +6,10 @@ */ package org.hibernate.bytecode.internal.bytebuddy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.bytecode.spi.BasicProxyFactory; @@ -13,6 +17,7 @@ import org.hibernate.proxy.ProxyConfiguration; import net.bytebuddy.NamingStrategy; +import net.bytebuddy.TypeCache; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; import net.bytebuddy.implementation.FieldAccessor; @@ -28,29 +33,29 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory { private final Class proxyClass; private final ProxyConfiguration.Interceptor interceptor; - @SuppressWarnings("unchecked") - public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState bytebuddy) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState byteBuddyState) { if ( superClass == null && ( interfaces == null || interfaces.length < 1 ) ) { throw new AssertionFailure( "attempting to build proxy without any superclass or interfaces" ); } final Class superClassOrMainInterface = superClass != null ? superClass : interfaces[0]; + final TypeCache.SimpleKey cacheKey = getCacheKey( superClass, interfaces ); - this.proxyClass = bytebuddy.getCurrentByteBuddy() - .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( superClassOrMainInterface.getName() ) ) ) - .subclass( superClass == null ? Object.class : superClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR ) - .implement( interfaces == null ? NO_INTERFACES : interfaces ) - .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) ) - .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) - .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) - .make() - .load( superClassOrMainInterface.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( superClassOrMainInterface ) ) - .getLoaded(); + this.proxyClass = byteBuddyState.loadBasicProxy( superClassOrMainInterface, cacheKey, byteBuddy -> byteBuddy + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( superClassOrMainInterface.getName() ) ) ) + .subclass( superClass == null ? Object.class : superClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR ) + .implement( interfaces == null ? NO_INTERFACES : interfaces ) + .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) + .method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) ) + .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) + .implement( ProxyConfiguration.class ) + .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) + ); this.interceptor = new PassThroughInterceptor( proxyClass.getName() ); } + @Override public Object getProxy() { try { final ProxyConfiguration proxy = (ProxyConfiguration) proxyClass.newInstance(); @@ -65,4 +70,16 @@ public Object getProxy() { public boolean isInstance(Object object) { return proxyClass.isInstance( object ); } + + private TypeCache.SimpleKey getCacheKey(Class superClass, Class[] interfaces) { + Set> key = new HashSet>(); + if ( superClass != null ) { + key.add( superClass ); + } + if ( interfaces != null ) { + key.addAll( Arrays.>asList( interfaces ) ); + } + + return new TypeCache.SimpleKey( key ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index 56d6eaca1b68..9b14b8fe0116 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -8,57 +8,129 @@ import static org.hibernate.internal.CoreLogging.messageLogger; +import java.io.File; +import java.io.IOException; import java.lang.invoke.MethodHandles; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.Function; import org.hibernate.HibernateException; +import org.hibernate.bytecode.spi.BasicProxyFactory; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory; +import org.hibernate.proxy.ProxyFactory; import net.bytebuddy.ByteBuddy; import net.bytebuddy.TypeCache; +import net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods; +import net.bytebuddy.asm.MemberSubstitution; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.DynamicType.Unloaded; import net.bytebuddy.dynamic.loading.ClassInjector; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.dynamic.scaffold.TypeValidation; +import net.bytebuddy.matcher.ElementMatchers; /** - * An utility to hold all ByteBuddy related state, as in the current version of + * A utility to hold all ByteBuddy related state, as in the current version of * Hibernate the Bytecode Provider state is held in a static field, yet ByteBuddy * is able to benefit from some caching and general state reuse. */ public final class ByteBuddyState { - private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class ); + private static final CoreMessageLogger LOG = messageLogger( ByteBuddyState.class ); + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private static final boolean DEBUG = false; private final ByteBuddy byteBuddy; + private final ForDeclaredMethods getDeclaredMethodMemberSubstitution; + private final ForDeclaredMethods getMethodMemberSubstitution; + /** * It will be easier to maintain the cache and its state when it will no longer be static * in Hibernate ORM 6+. * Opted for WEAK keys to avoid leaking the classloader in case the SessionFactory isn't closed. * Avoiding Soft keys as they are prone to cause issues with unstable performance. */ - private final TypeCache typeCache; - - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private final TypeCache proxyCache; + private final TypeCache basicProxyCache; ByteBuddyState() { this.byteBuddy = new ByteBuddy().with( TypeValidation.DISABLED ); - this.typeCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK ); + + this.proxyCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK ); + this.basicProxyCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK ); + + if ( System.getSecurityManager() != null ) { + this.getDeclaredMethodMemberSubstitution = getDeclaredMethodMemberSubstitution(); + this.getMethodMemberSubstitution = getMethodMemberSubstitution(); + } + else { + this.getDeclaredMethodMemberSubstitution = null; + this.getMethodMemberSubstitution = null; + } } /** - * Access to ByteBuddy. It's almost equivalent to creating a new ByteBuddy instance, - * yet slightly preferrable so to be able to reuse the same instance. - * @return + * Load a proxy as generated by the {@link ProxyFactory}. + * + * @param referenceClass The main class to proxy - might be an interface. + * @param cacheKey The cache key. + * @param makeProxyFunction A function building the proxy. + * @return The loaded proxy class. */ - public ByteBuddy getCurrentByteBuddy() { - return byteBuddy; + public Class loadProxy(Class referenceClass, TypeCache.SimpleKey cacheKey, + Function> makeProxyFunction) { + return load( referenceClass, proxyCache, cacheKey, makeProxyFunction ); } - public TypeCache getCacheForProxies() { - return typeCache; + /** + * Load a proxy as generated by the {@link BasicProxyFactory}. + * + * @param referenceClass The main class to proxy - might be an interface. + * @param cacheKey The cache key. + * @param makeProxyFunction A function building the proxy. + * @return The loaded proxy class. + */ + Class loadBasicProxy(Class referenceClass, TypeCache.SimpleKey cacheKey, + Function> makeProxyFunction) { + return load( referenceClass, basicProxyCache, cacheKey, makeProxyFunction ); + } + + /** + * Load a class generated by ByteBuddy. + * + * @param referenceClass The main class to proxy - might be an interface. + * @param makeClassFunction A function building the class. + * @return The loaded generated class. + */ + public Class load(Class referenceClass, Function> makeClassFunction) { + return make( makeClassFunction.apply( byteBuddy ) ) + .load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) ) + .getLoaded(); + } + + /** + * Rewrite a class, used by the enhancer. + * + * @param className The original class name. + * @param originalBytes The original content of the class. + * @param rewriteClassFunction The function used to rewrite the class. + * @return The rewritten content of the class. + */ + public byte[] rewrite(String className, byte[] originalBytes, + Function> rewriteClassFunction) { + DynamicType.Builder builder = rewriteClassFunction.apply( byteBuddy ); + if ( builder == null ) { + return originalBytes; + } + + return make( builder ).getBytes(); } /** @@ -70,9 +142,46 @@ public TypeCache getCacheForProxies() { * of re-creating the small helpers should be negligible. */ void clearState() { - typeCache.clear(); + proxyCache.clear(); + basicProxyCache.clear(); + } + + private Class load(Class referenceClass, TypeCache cache, + TypeCache.SimpleKey cacheKey, Function> makeProxyFunction) { + return cache.findOrInsert( + referenceClass.getClassLoader(), + cacheKey, + () -> make( makeProxyFunction.apply( byteBuddy ) ) + .load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) ) + .getLoaded(), + cache ); } + private Unloaded make(DynamicType.Builder builder) { + if ( System.getSecurityManager() != null ) { + builder = builder.visit( getDeclaredMethodMemberSubstitution ); + builder = builder.visit( getMethodMemberSubstitution ); + } + + Unloaded unloadedClass = builder.make(); + if ( DEBUG ) { + try { + unloadedClass.saveIn( new File( System.getProperty( "java.io.tmpdir" ) + "/bytebuddy/" ) ); + } + catch (IOException e) { + LOG.warn( "Unable to save generated class %1$s", unloadedClass.getTypeDescription().getName(), e ); + } + } + + if ( System.getSecurityManager() != null ) { + // we authorize the proxy class to access the method lookup dispatcher + HibernateMethodLookupDispatcher.registerAuthorizedClass( unloadedClass.getTypeDescription().getName() ); + } + + return unloadedClass; + } + + // This method is kept public static as it is also required by a test. public static ClassLoadingStrategy resolveClassLoadingStrategy(Class originalClass) { if ( ClassInjector.UsingLookup.isAvailable() ) { // This is only enabled for JDK 9+ @@ -111,4 +220,47 @@ public static ClassLoadingStrategy resolveClassLoadingStrategy(Clas } } + private static ForDeclaredMethods getDeclaredMethodMemberSubstitution() { + return MemberSubstitution.relaxed() + .method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class, + "getDeclaredMethod", String.class, Class[].class ) ) ) ) + .replaceWith( + AccessController.doPrivileged( new GetDeclaredMethodAction( HibernateMethodLookupDispatcher.class, + "getDeclaredMethod", Class.class, String.class, Class[].class ) ) ) + .on( ElementMatchers.isTypeInitializer() ); + } + + private static ForDeclaredMethods getMethodMemberSubstitution() { + return MemberSubstitution.relaxed() + .method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class, + "getMethod", String.class, Class[].class ) ) ) ) + .replaceWith( + AccessController.doPrivileged( new GetDeclaredMethodAction( HibernateMethodLookupDispatcher.class, + "getMethod", Class.class, String.class, Class[].class ) ) ) + .on( ElementMatchers.isTypeInitializer() ); + } + + private static class GetDeclaredMethodAction implements PrivilegedAction { + private final Class clazz; + private final String methodName; + private final Class[] parameterTypes; + + private GetDeclaredMethodAction(Class clazz, String methodName, Class... parameterTypes) { + this.clazz = clazz; + this.methodName = methodName; + this.parameterTypes = parameterTypes; + } + + @Override + public Method run() { + try { + Method method = clazz.getDeclaredMethod( methodName, parameterTypes ); + + return method; + } + catch (NoSuchMethodException e) { + throw new HibernateException( "Unable to prepare getDeclaredMethod()/getMethod() substitution", e ); + } + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index d1c589e51b91..15f4c1ecc88e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -70,15 +70,13 @@ public ReflectionOptimizer getReflectionOptimizer( // we only provide a fast class instantiator if the class can be instantiated final Constructor constructor = findConstructor( clazz ); - fastClass = byteBuddyState.getCurrentByteBuddy() + fastClass = byteBuddyState.load( clazz, byteBuddy -> byteBuddy .with( new NamingStrategy.SuffixingRandom( INSTANTIATOR_PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) .subclass( ReflectionOptimizer.InstantiationOptimizer.class ) .method( newInstanceMethodName ) .intercept( MethodCall.construct( constructor ) ) - .make() - .load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) ) - .getLoaded(); + ); } else { fastClass = null; @@ -88,7 +86,7 @@ public ReflectionOptimizer getReflectionOptimizer( final Method[] setters = new Method[setterNames.length]; findAccessors( clazz, getterNames, setterNames, types, getters, setters ); - final Class bulkAccessor = byteBuddyState.getCurrentByteBuddy() + final Class bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy .with( new NamingStrategy.SuffixingRandom( OPTIMIZER_PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) .subclass( ReflectionOptimizer.AccessOptimizer.class ) @@ -98,9 +96,7 @@ public ReflectionOptimizer getReflectionOptimizer( .intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) ) .method( getPropertyNamesMethodName ) .intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) ) - .make() - .load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) ) - .getLoaded(); + ); try { return new ReflectionOptimizerImpl( diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java new file mode 100644 index 000000000000..bb3b287f6a16 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java @@ -0,0 +1,181 @@ +/* + * 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.bytecode.internal.bytebuddy; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.hibernate.HibernateException; + +public class HibernateMethodLookupDispatcher { + + private static final SecurityActions SECURITY_ACTIONS = new SecurityActions(); + + private static final Function> STACK_FRAME_GET_DECLARING_CLASS_FUNCTION; + private static Object stackWalker; + private static Method stackWalkerWalkMethod; + private static Method stackFrameGetDeclaringClass; + + // Currently, the bytecode provider is created statically and shared between all the session factories. Thus we + // can't clear this set when we close a session factory as we might remove elements coming from another one. + // Considering we can't clear these elements, we use the class names instead of the classes themselves to avoid + // issues. + private static Set authorizedClasses = ConcurrentHashMap.newKeySet(); + + public static Method getDeclaredMethod(Class type, String name, Class[] parameters) { + PrivilegedAction getDeclaredMethodAction = new PrivilegedAction() { + + @Override + public Method run() { + try { + return type.getDeclaredMethod( name, parameters ); + } + catch (NoSuchMethodException | SecurityException e) { + return null; + } + } + }; + + return doPrivilegedAction( getDeclaredMethodAction ); + } + + public static Method getMethod(Class type, String name, Class[] parameters) { + PrivilegedAction getMethodAction = new PrivilegedAction() { + + @Override + public Method run() { + try { + return type.getMethod( name, parameters ); + } + catch (NoSuchMethodException | SecurityException e) { + return null; + } + } + }; + + return doPrivilegedAction( getMethodAction ); + } + + private static Method doPrivilegedAction(PrivilegedAction privilegedAction) { + Class callerClass = getCallerClass(); + + if ( !authorizedClasses.contains( callerClass.getName() ) ) { + throw new SecurityException( "Unauthorized call by class " + callerClass ); + } + + return System.getSecurityManager() != null ? AccessController.doPrivileged( privilegedAction ) : + privilegedAction.run(); + } + + static void registerAuthorizedClass(String className) { + authorizedClasses.add( className ); + } + + static { + PrivilegedAction initializeGetCallerClassRequirementsAction = new PrivilegedAction() { + + @Override + public Void run() { + Class stackWalkerClass = null; + try { + stackWalkerClass = Class.forName( "java.lang.StackWalker" ); + } + catch (ClassNotFoundException e) { + // ignore, we will deal with that later. + } + + if ( stackWalkerClass != null ) { + try { + Class optionClass = Class.forName( "java.lang.StackWalker$Option" ); + stackWalker = stackWalkerClass.getMethod( "getInstance", optionClass ) + // The first one is RETAIN_CLASS_REFERENCE + .invoke( null, optionClass.getEnumConstants()[0] ); + + stackWalkerWalkMethod = stackWalkerClass.getMethod( "walk", Function.class ); + stackFrameGetDeclaringClass = Class.forName( "java.lang.StackWalker$StackFrame" ) + .getMethod( "getDeclaringClass" ); + } + catch (Throwable e) { + throw new HibernateException( "Unable to initialize the stack walker", e ); + } + } + + return null; + } + }; + + if ( System.getSecurityManager() != null ) { + AccessController.doPrivileged( initializeGetCallerClassRequirementsAction ); + } + else { + initializeGetCallerClassRequirementsAction.run(); + } + + STACK_FRAME_GET_DECLARING_CLASS_FUNCTION = new Function>() { + @Override + public Class apply(Object t) { + try { + return (Class) stackFrameGetDeclaringClass.invoke( t ); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new HibernateException( "Unable to get stack frame declaring class", e ); + } + } + }; + } + + private static Class getCallerClass() { + PrivilegedAction> getCallerClassAction = new PrivilegedAction>() { + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Class run() { + try { + if ( stackWalker != null ) { + Optional> clazzOptional = (Optional>) stackWalkerWalkMethod.invoke( stackWalker, new Function() { + @Override + public Object apply(Stream stream) { + return stream.map( STACK_FRAME_GET_DECLARING_CLASS_FUNCTION ) + .skip( System.getSecurityManager() != null ? 6 : 5 ) + .findFirst(); + } + }); + + if ( !clazzOptional.isPresent() ) { + throw new HibernateException( "Unable to determine the caller class" ); + } + + return clazzOptional.get(); + } + else { + return SECURITY_ACTIONS.getCallerClass(); + } + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new SecurityException( "Unable to determine the caller class", e ); + } + } + }; + + return System.getSecurityManager() != null ? AccessController.doPrivileged( getCallerClassAction ) : + getCallerClassAction.run(); + } + + private static class SecurityActions extends SecurityManager { + + private Class getCallerClass() { + return getClassContext()[7]; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java index b453d76d4d42..f94cf4b94357 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java @@ -58,6 +58,7 @@ public ByteBuddyProxyHelper(ByteBuddyState byteBuddyState) { this.byteBuddyState = byteBuddyState; } + @SuppressWarnings({ "unchecked", "rawtypes" }) public Class buildProxy( final Class persistentClass, final Class[] interfaces) { @@ -67,24 +68,19 @@ public Class buildProxy( } key.addAll( Arrays.>asList( interfaces ) ); - final TypeCache cacheForProxies = byteBuddyState.getCacheForProxies(); - - return cacheForProxies.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () -> - byteBuddyState.getCurrentByteBuddy() - .ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) ) - .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) - .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) - .implement( (Type[]) interfaces ) - .method( isVirtual().and( not( isFinalizer() ) ) ) - .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) - .method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) ) - .intercept( SuperMethodCall.INSTANCE ) - .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) - .make() - .load( persistentClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( persistentClass ) ) - .getLoaded(), cacheForProxies ); + return byteBuddyState.loadProxy( persistentClass, new TypeCache.SimpleKey(key), byteBuddy -> byteBuddy + .ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) ) + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) + .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) + .implement( (Type[]) interfaces ) + .method( isVirtual().and( not( isFinalizer() ) ) ) + .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) + .method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) ) + .intercept( SuperMethodCall.INSTANCE ) + .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) + .implement( ProxyConfiguration.class ) + .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) + ); } public HibernateProxy deserializeProxy(SerializableProxy serializableProxy) { diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java new file mode 100644 index 000000000000..3c016a2e949e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java @@ -0,0 +1,53 @@ +package org.hibernate.bytecode.internal.bytebuddy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.EnhancementException; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.spi.ByteCodeHelper; +import org.hibernate.bytecode.spi.ReflectionOptimizer; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper; +import org.junit.Test; + +public class GenerateProxiesTest { + + @Test + public void generateBasicProxy() { + BasicProxyFactoryImpl basicProxyFactory = new BasicProxyFactoryImpl( SimpleEntity.class, new Class[0], + new ByteBuddyState() ); + assertNotNull( basicProxyFactory.getProxy() ); + } + + @Test + public void generateProxy() throws InstantiationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException, SecurityException { + ByteBuddyProxyHelper byteBuddyProxyHelper = new ByteBuddyProxyHelper( new ByteBuddyState() ); + Class proxyClass = byteBuddyProxyHelper.buildProxy( SimpleEntity.class, new Class[0] ); + assertNotNull( proxyClass ); + assertNotNull( proxyClass.getConstructor().newInstance() ); + } + + @Test + public void generateFastClassAndReflectionOptimizer() { + BytecodeProviderImpl bytecodeProvider = new BytecodeProviderImpl(); + ReflectionOptimizer reflectionOptimizer = bytecodeProvider.getReflectionOptimizer( SimpleEntity.class, + new String[]{ "getId", "getName" }, new String[]{ "setId", "setName" }, + new Class[]{ Long.class, String.class } ); + assertEquals( 2, reflectionOptimizer.getAccessOptimizer().getPropertyNames().length ); + assertNotNull( reflectionOptimizer.getInstantiationOptimizer().newInstance() ); + } + + @Test + public void generateEnhancedClass() throws EnhancementException, IOException { + Enhancer enhancer = new EnhancerImpl( new DefaultEnhancementContext(), new ByteBuddyState() ); + enhancer.enhance( SimpleEntity.class.getName(), + ByteCodeHelper.readByteCode( SimpleEntity.class.getClassLoader() + .getResourceAsStream( SimpleEntity.class.getName().replace( '.', '/' ) + ".class" ) ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java new file mode 100644 index 000000000000..184eca1a6bd6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java @@ -0,0 +1,58 @@ +/* + * 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.bytecode.internal.bytebuddy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.reflect.Method; + +import org.junit.Test; + +public class HibernateMethodLookupDispatcherTest { + + @Test + public void testAuthorizedClass() { + HibernateMethodLookupDispatcher.registerAuthorizedClass( AuthorizedClass.class.getName() ); + + AuthorizedClass authorizedClass = new AuthorizedClass(); + assertNotNull( authorizedClass.declaredMethod ); + assertEquals( "myMethod", authorizedClass.declaredMethod.getName() ); + } + + @Test( expected = SecurityException.class ) + public void testUnauthorizedClass() { + new UnauthorizedClass(); + } + + public static class AuthorizedClass { + + private Method declaredMethod; + + public AuthorizedClass() { + declaredMethod = HibernateMethodLookupDispatcher.getDeclaredMethod( AuthorizedClass.class, "myMethod", + new Class[]{ String.class } ); + } + + public void myMethod(String myParameter) { + } + } + + public static class UnauthorizedClass { + + @SuppressWarnings("unused") + private Method declaredMethod; + + public UnauthorizedClass() { + declaredMethod = HibernateMethodLookupDispatcher.getDeclaredMethod( AuthorizedClass.class, "myMethod", + new Class[]{ String.class } ); + } + + public void myMethod(String myParameter) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java new file mode 100644 index 000000000000..4b886db308be --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java @@ -0,0 +1,40 @@ +/* + * 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.bytecode.internal.bytebuddy; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity(name = "SimpleEntity") +public class SimpleEntity { + + @Id + @GeneratedValue + private Long id; + + @Basic(fetch = FetchType.LAZY) + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} From 5150fd5d606468b66e1e3832c8684eae620164d1 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 26 Jul 2018 13:14:09 +0200 Subject: [PATCH 114/772] HHH-12857 Reuse the TypePool created in EnhancerImpl --- .../internal/bytebuddy/EnhancerImpl.java | 12 ++++++------ .../internal/bytebuddy/ByteBuddyState.java | 19 ++++++++++++++++--- .../internal/bytebuddy/SimpleEntity.java | 4 ++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 65b898c8c50f..8e06c2e8494d 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -69,7 +69,7 @@ public class EnhancerImpl implements Enhancer { protected final ByteBuddyEnhancementContext enhancementContext; private final ByteBuddyState byteBuddyState; - private final TypePool classPool; + private final TypePool typePool; /** * Constructs the Enhancer, using the given context. @@ -81,7 +81,7 @@ public class EnhancerImpl implements Enhancer { public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState) { this.enhancementContext = new ByteBuddyEnhancementContext( enhancementContext ); this.byteBuddyState = byteBuddyState; - this.classPool = buildClassPool( this.enhancementContext ); + this.typePool = buildTypePool( this.enhancementContext ); } /** @@ -100,9 +100,9 @@ public synchronized byte[] enhance(String className, byte[] originalBytes) throw //Classpool#describe does not accept '/' in the description name as it expects a class name. See HHH-12545 final String safeClassName = className.replace( '/', '.' ); try { - final TypeDescription typeDescription = classPool.describe( safeClassName ).resolve(); + final TypeDescription typeDescription = typePool.describe( safeClassName ).resolve(); - return byteBuddyState.rewrite( safeClassName, originalBytes, byteBuddy -> doEnhance( + return byteBuddyState.rewrite( typePool, safeClassName, originalBytes, byteBuddy -> doEnhance( byteBuddy.ignore( isDefaultFinalizer() ).redefine( typeDescription, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ), typeDescription ) ); @@ -112,7 +112,7 @@ public synchronized byte[] enhance(String className, byte[] originalBytes) throw } } - private TypePool buildClassPool(final ByteBuddyEnhancementContext enhancementContext) { + private TypePool buildTypePool(final ByteBuddyEnhancementContext enhancementContext) { return TypePool.Default.WithLazyResolution.of( enhancementContext.getLoadingClassLoader() ); } @@ -128,7 +128,7 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes return null; } - PersistentAttributeTransformer transformer = PersistentAttributeTransformer.collectPersistentFields( managedCtClass, enhancementContext, classPool ); + PersistentAttributeTransformer transformer = PersistentAttributeTransformer.collectPersistentFields( managedCtClass, enhancementContext, typePool ); if ( enhancementContext.isEntityClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as Entity", managedCtClass.getName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index 9b14b8fe0116..3acdf37439fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -32,6 +32,7 @@ import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.pool.TypePool; /** * A utility to hold all ByteBuddy related state, as in the current version of @@ -118,19 +119,20 @@ public Class load(Class referenceClass, Function> rewriteClassFunction) { DynamicType.Builder builder = rewriteClassFunction.apply( byteBuddy ); if ( builder == null ) { return originalBytes; } - return make( builder ).getBytes(); + return make( typePool, builder ).getBytes(); } /** @@ -158,12 +160,23 @@ private Class load(Class referenceClass, TypeCache ca } private Unloaded make(DynamicType.Builder builder) { + return make( null, builder ); + } + + private Unloaded make(TypePool typePool, DynamicType.Builder builder) { if ( System.getSecurityManager() != null ) { builder = builder.visit( getDeclaredMethodMemberSubstitution ); builder = builder.visit( getMethodMemberSubstitution ); } - Unloaded unloadedClass = builder.make(); + Unloaded unloadedClass; + if ( typePool != null ) { + unloadedClass = builder.make( typePool ); + } + else { + unloadedClass = builder.make(); + } + if ( DEBUG ) { try { unloadedClass.saveIn( new File( System.getProperty( "java.io.tmpdir" ) + "/bytebuddy/" ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java index 4b886db308be..e402cf1e000c 100644 --- a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java @@ -6,6 +6,8 @@ */ package org.hibernate.bytecode.internal.bytebuddy; +import java.util.regex.Pattern; + import javax.persistence.Basic; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -15,6 +17,8 @@ @Entity(name = "SimpleEntity") public class SimpleEntity { + private static final Pattern PATTERN = Pattern.compile( "whatever" ); + @Id @GeneratedValue private Long id; From d8a1c9911750898269bf3f51dd69d32d5955270f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 2 Aug 2018 00:00:29 +0200 Subject: [PATCH 115/772] HHH-12877 Upgrade ByteBuddy to 1.8.15 --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 43022e139926..1684b9087656 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -23,7 +23,7 @@ ext { weldVersion = '3.0.0.Final' javassistVersion = '3.23.1-GA' - byteBuddyVersion = '1.8.13' // Now with JDK10 compatibility and preliminary support for JDK11 + byteBuddyVersion = '1.8.15' // Now with JDK10 compatibility and preliminary support for JDK11 geolatteVersion = '1.3.0' From 236033bdafe55decd803910add731f1253766ab7 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 23 Jul 2018 17:10:22 +0200 Subject: [PATCH 116/772] HHH-12847 Consider LockOptions for getting the LockMode Otherwise, it leads to null pointer exceptions. --- .../AbstractLoadPlanBasedEntityLoader.java | 9 +- ...oaderInitializationWithNoLockModeTest.java | 114 ++++++++++++++++++ 2 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java index 2fb8af6e0970..5beadbe81227 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java @@ -65,17 +65,20 @@ public AbstractLoadPlanBasedEntityLoader( final LoadPlanBuildingAssociationVisitationStrategy strategy; if ( buildingParameters.getQueryInfluencers().getFetchGraph() != null ) { strategy = new FetchGraphLoadPlanBuildingStrategy( - factory, buildingParameters.getQueryInfluencers(),buildingParameters.getLockMode() + factory, buildingParameters.getQueryInfluencers(), + buildingParameters.getLockOptions() != null ? buildingParameters.getLockOptions().getLockMode() : buildingParameters.getLockMode() ); } else if ( buildingParameters.getQueryInfluencers().getLoadGraph() != null ) { strategy = new LoadGraphLoadPlanBuildingStrategy( - factory, buildingParameters.getQueryInfluencers(),buildingParameters.getLockMode() + factory, buildingParameters.getQueryInfluencers(), + buildingParameters.getLockOptions() != null ? buildingParameters.getLockOptions().getLockMode() : buildingParameters.getLockMode() ); } else { strategy = new FetchStyleLoadPlanBuildingAssociationVisitationStrategy( - factory, buildingParameters.getQueryInfluencers(),buildingParameters.getLockMode() + factory, buildingParameters.getQueryInfluencers(), + buildingParameters.getLockOptions() != null ? buildingParameters.getLockOptions().getLockMode() : buildingParameters.getLockMode() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java new file mode 100644 index 000000000000..461e57c7a831 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java @@ -0,0 +1,114 @@ +package org.hibernate.test.batchfetch; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.annotations.Fetch; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.loader.BatchFetchStyle; +import org.hibernate.metamodel.spi.MetamodelImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.junit.Test; + +public class BatchingEntityLoaderInitializationWithNoLockModeTest extends BaseEntityManagerFunctionalTestCase { + + private Long mainId; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { MainEntity.class, SubEntity.class }; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + protected Map buildSettings() { + Map settings = super.buildSettings(); + settings.put( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.LEGACY ); + settings.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, 5 ); + return settings; + } + + @Test + public void testJoin() { + doInJPA( this::entityManagerFactory, em -> { + SubEntity sub = new SubEntity(); + em.persist( sub ); + + MainEntity main = new MainEntity(); + main.setSub( sub ); + em.persist( main ); + + this.mainId = main.getId(); + }); + + doInJPA( this::entityManagerFactory, em -> { + EntityPersister entityPersister = ( (MetamodelImplementor) em.getMetamodel() ) + .entityPersister( MainEntity.class ); + + // use some specific lock options to trigger the creation of a loader with lock options + LockOptions lockOptions = new LockOptions( LockMode.NONE ); + lockOptions.setTimeOut( 10 ); + + MainEntity main = (MainEntity) entityPersister.load( this.mainId, null, lockOptions, + (SharedSessionContractImplementor) em ); + assertNotNull( main.getSub() ); + } ); + } + + @Entity(name = "MainEntity") + public static class MainEntity { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @Fetch(org.hibernate.annotations.FetchMode.JOIN) + private SubEntity sub; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public SubEntity getSub() { + return sub; + } + + public void setSub(SubEntity sub) { + this.sub = sub; + } + } + + @Entity(name = "SubEntity") + public static class SubEntity { + + @Id + @GeneratedValue + private Long id; + + public Long getId() { + return id; + } + + + public void setId(Long id) { + this.id = id; + } + } +} From beb623093a0bd4f8413f8c12e8be52332595d078 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 2 Aug 2018 14:31:53 +0200 Subject: [PATCH 117/772] HHH-12880 LockModeTest hangs indefinitely with Sybase due to HHH-12847 --- .../hibernate/test/locking/LockModeTest.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/locking/LockModeTest.java b/hibernate-core/src/test/java/org/hibernate/test/locking/LockModeTest.java index 4ff62308f25d..6dd9305a2d46 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/locking/LockModeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/locking/LockModeTest.java @@ -16,6 +16,7 @@ import org.hibernate.Session; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.testing.SkipForDialect; @@ -242,12 +243,19 @@ private void nowAttemptToUpdateRow() { doInHibernate( this::sessionFactory, _session -> { TransactionUtil.setJdbcTimeout( _session ); try { - // load with write lock to deal with databases that block (wait indefinitely) direct attempts - // to write a locked row + // We used to load with write lock here to deal with databases that block (wait indefinitely) + // direct attempts to write a locked row. + // At some point, due to a bug, the lock mode was lost when applied via lock options, leading + // this code to not apply the pessimistic write lock. + // See HHH-12847 + https://github.com/hibernate/hibernate-orm/commit/719e5d0c12a6ef709bee907b8b651d27b8b08a6a. + // At least Sybase waits indefinitely when really applying a PESSIMISTIC_WRITE lock here (and + // the NO_WAIT part is not applied by the Sybase dialect so it doesn't help). + // For now going back to LockMode.NONE as it's the lock mode that has been applied for quite + // some time and it seems our supported databases don't have a problem with it. A it = _session.get( A.class, id, - new LockOptions( LockMode.PESSIMISTIC_WRITE ).setTimeOut( LockOptions.NO_WAIT ) + new LockOptions( LockMode.NONE ).setTimeOut( LockOptions.NO_WAIT ) ); _session.createNativeQuery( updateStatement() ) .setParameter( "value", "changed" ) @@ -272,7 +280,8 @@ private void nowAttemptToUpdateRow() { } protected String updateStatement() { - if( SQLServerDialect.class.isAssignableFrom( DIALECT.getClass() ) ) { + if ( SQLServerDialect.class.isAssignableFrom( DIALECT.getClass() ) + || SybaseDialect.class.isAssignableFrom( DIALECT.getClass() ) ) { return "UPDATE T_LOCK_A WITH(NOWAIT) SET a_value = :value where id = :id"; } return "UPDATE T_LOCK_A SET a_value = :value where id = :id"; From b4897fbab224d44e252229f5d00adb773091cbe7 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Thu, 2 Aug 2018 11:05:12 -0400 Subject: [PATCH 118/772] HHH-12834 - Disable test which fails on Sybase. (cherry picked from commit 8178d76ca50a1b87de451ba8cb57a3ad059fb4a7) --- .../integration/collection/StringMapNationalizedLobTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapNationalizedLobTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapNationalizedLobTest.java index f4ffdf692337..4c94ab6ac356 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapNationalizedLobTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapNationalizedLobTest.java @@ -19,6 +19,7 @@ import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.envers.Audited; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.Priority; @@ -38,6 +39,7 @@ @SkipForDialect(Oracle8iDialect.class) @SkipForDialect(value = PostgreSQL81Dialect.class, jiraKey = "HHH-11477", comment = "@Lob field in HQL predicate fails with error about text = bigint") @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA doesn't support comparing LOBs with the = operator") +@SkipForDialect(value = SybaseDialect.class, comment = "Sybase doesn't support comparing LOBs with the = operator") public class StringMapNationalizedLobTest extends BaseEnversJPAFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { From 922c4694c76eedeefe29d39bb86129585391be80 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 2 Aug 2018 19:43:13 +0200 Subject: [PATCH 119/772] 5.3.4.Final --- changelog.txt | 32 ++++++++++++++++++++++++++++++++ gradle/base-information.gradle | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 95956baa392c..bcd9aa0cbef8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,38 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.3.4.final (August 2nd, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31688/tab/release-report-done + +** Bug + * [HHH-10603] - ORA-00932: inconsistent datatypes: expected - got BLOB after HHH-10345 with Oracle12cDialect + * [HHH-12492] - JPA delete query generated has missing table alias and thus incorrect semantics + * [HHH-12834] - org.hibernate.envers.test.integration.collection.StringMapNationalizedLobTest fails with Sybase + * [HHH-12835] - Wrong assertion in BatchFetchQueueHelper + * [HHH-12846] - Merge cascade of collection fails when orphan removal enabled with flush mode commit + * [HHH-12847] - NullPointerException in FetchStyleLoadPlanBuildingAssociationVisitationStrategy::adjustJoinFetchIfNeeded + * [HHH-12848] - UpgradeSkipLockedTest, PessimisticReadSkipLockedTest and OracleFollowOnLockingTest fail with Oracle12c + * [HHH-12849] - QuotedIdentifierTest fails with ORA-04043 on Oracle12c + * [HHH-12851] - ConverterTest fails with SQL Server depending on collation + * [HHH-12861] - SchemaUpdate doesn't work with Sybase + * [HHH-12863] - SchemaUpdateTest should be skipped with Sybase + * [HHH-12868] - Using CacheConcurrencyStrategy.NONE leads to a NPE when trying to load an entity + * [HHH-12869] - SingletonEhcacheRegionFactory initialization fails + * [HHH-12880] - LockModeTest hangs indefinitely with Sybase due to HHH-12847 + +** New Feature + * [HHH-12608] - Add the ST_DWithin() function in DB2 Spatial Dialect + * [HHH-12857] - Support the security manager with ByteBuddy as bytecode provider + +** Task + * [HHH-12730] - User types built using 5.1 are not binary compatible with 5.3 + * [HHH-12792] - Document binary incompatibility of persisters and tuplizers + * [HHH-12877] - Upgrade ByteBuddy to 1.8.15 + + + Changes in 5.3.3.final (July 23, 2018) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 488745e86ee9..5040c3628d17 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.4-SNAPSHOT', project ) + ormVersion = new HibernateVersion( '5.3.4.Final', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From 0694793966a25cb9c2966fde23cfa671ceb8782a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 2 Aug 2018 20:20:14 +0200 Subject: [PATCH 120/772] Prepare next development iteration --- gradle/base-information.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 5040c3628d17..5b2765087294 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.4.Final', project ) + ormVersion = new HibernateVersion( '5.3.5-SNAPSHOT', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From a6c773317913f41c2634b1c420c32d0ddafc1ce3 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 1 Aug 2018 17:37:29 +0200 Subject: [PATCH 121/772] HHH-12196 Implement a naive limit handler for Sybase It doesn't manage all the corner cases but it should be safe enough as only triggered in the simple cases. --- .../dialect/SybaseASE157Dialect.java | 19 +++++ .../pagination/SybaseASE157LimitHandler.java | 83 +++++++++++++++++++ .../dialect/SybaseASE157LimitHandlerTest.java | 54 ++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java create mode 100644 hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java index 5b8d17073ec9..01155cd99404 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java @@ -12,6 +12,8 @@ import org.hibernate.JDBCException; import org.hibernate.LockOptions; import org.hibernate.dialect.function.SQLFunctionTemplate; +import org.hibernate.dialect.pagination.LimitHandler; +import org.hibernate.dialect.pagination.SybaseASE157LimitHandler; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.LockTimeoutException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; @@ -27,6 +29,8 @@ */ public class SybaseASE157Dialect extends SybaseASE15Dialect { + private static final SybaseASE157LimitHandler LIMIT_HANDLER = new SybaseASE157LimitHandler(); + /** * Constructs a SybaseASE157Dialect */ @@ -102,4 +106,19 @@ public JDBCException convert(SQLException sqlException, String message, String s } }; } + + @Override + public boolean supportsLimit() { + return true; + } + + @Override + public boolean supportsLimitOffset() { + return false; + } + + @Override + public LimitHandler getLimitHandler() { + return LIMIT_HANDLER; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java new file mode 100644 index 000000000000..5137385a0505 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java @@ -0,0 +1,83 @@ +/* + * 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.dialect.pagination; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.hibernate.engine.spi.RowSelection; + +/** + * This limit handler is very conservative and is only triggered in simple cases involving a select or select distinct. + *

    + * Note that if the query already contains "top" just after the select or select distinct, we don't add anything to the + * query. It might just be a column name but, in any case, we just don't add the top clause and default to the previous + * behavior so it's not an issue. + */ +public class SybaseASE157LimitHandler extends AbstractLimitHandler { + + private static final Pattern SELECT_DISTINCT_PATTERN = Pattern.compile( "^(\\s*select\\s+distinct\\s+).*", + Pattern.CASE_INSENSITIVE ); + private static final Pattern SELECT_PATTERN = Pattern.compile( "^(\\s*select\\s+).*", Pattern.CASE_INSENSITIVE ); + private static final Pattern TOP_PATTERN = Pattern.compile( "^\\s*top\\s+.*", Pattern.CASE_INSENSITIVE ); + + @Override + public String processSql(String sql, RowSelection selection) { + if ( selection.getMaxRows() == null ) { + return sql; + } + + int top = getMaxOrLimit( selection ); + if ( top == Integer.MAX_VALUE ) { + return sql; + } + + Matcher selectDistinctMatcher = SELECT_DISTINCT_PATTERN.matcher( sql ); + if ( selectDistinctMatcher.matches() ) { + return insertTop( selectDistinctMatcher, sql, top ); + } + + Matcher selectMatcher = SELECT_PATTERN.matcher( sql ); + if ( selectMatcher.matches() ) { + return insertTop( selectMatcher, sql, top ); + } + + return sql; + } + + @Override + public boolean supportsLimit() { + return true; + } + + @Override + public boolean supportsLimitOffset() { + return false; + } + + @Override + public boolean useMaxForLimit() { + return true; + } + + @Override + public boolean supportsVariableLimit() { + return false; + } + + private static String insertTop(Matcher matcher, String sql, int top) { + int end = matcher.end( 1 ); + + if ( TOP_PATTERN.matcher( sql.substring( end ) ).matches() ) { + return sql; + } + + StringBuilder sb = new StringBuilder( sql ); + sb.insert( end, "top " + top + " " ); + return sb.toString(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java new file mode 100644 index 000000000000..ceb764d59bf2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java @@ -0,0 +1,54 @@ +/* + * 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.dialect; + +import static org.junit.Assert.assertEquals; + +import org.hibernate.dialect.pagination.SybaseASE157LimitHandler; +import org.hibernate.engine.spi.RowSelection; +import org.junit.Test; + +public class SybaseASE157LimitHandlerTest { + + @Test + public void testLimitHandler() { + assertEquals( "select * from entity", processSql( "select * from entity", null, null ) ); + assertEquals( "select * from entity", processSql( "select * from entity", 15, null ) ); + assertEquals( "select top 15 * from entity", processSql( "select * from entity", null, 15 ) ); + assertEquals( "select top 18 * from entity", processSql( "select * from entity", 3, 15 ) ); + assertEquals( "SELECT top 18 * FROM entity", processSql( "SELECT * FROM entity", 3, 15 ) ); + assertEquals( " select top 18 * from entity", processSql( " select * from entity", 3, 15 ) ); + assertEquals( "selectand", processSql( "selectand", 3, 15 ) ); + assertEquals( "select distinct top 15 id from entity", + processSql( "select distinct id from entity", null, 15 ) ); + assertEquals( "select distinct top 18 id from entity", processSql( "select distinct id from entity", 3, 15 ) ); + assertEquals( " select distinct top 18 id from entity", + processSql( " select distinct id from entity", 3, 15 ) ); + assertEquals( + "WITH employee AS (SELECT * FROM Employees) SELECT * FROM employee WHERE ID < 20 UNION ALL SELECT * FROM employee WHERE Sex = 'M'", + processSql( + "WITH employee AS (SELECT * FROM Employees) SELECT * FROM employee WHERE ID < 20 UNION ALL SELECT * FROM employee WHERE Sex = 'M'", + 3, 15 ) ); + + assertEquals( "select top 5 * from entity", processSql( "select top 5 * from entity", 3, 15 ) ); + assertEquals( "select distinct top 7 * from entity", processSql( "select distinct top 7 * from entity", 3, 15 ) ); + assertEquals( "select distinct top 18 top_column from entity", processSql( "select distinct top_column from entity", 3, 15 ) ); + } + + private String processSql(String sql, Integer offset, Integer limit) { + RowSelection rowSelection = new RowSelection(); + if ( offset != null ) { + rowSelection.setFirstRow( offset ); + } + if (limit != null) { + rowSelection.setMaxRows( limit ); + } + + SybaseASE157LimitHandler limitHandler = new SybaseASE157LimitHandler(); + return limitHandler.processSql( sql, rowSelection ); + } +} From e12ede311c47ea022dce700d968b1198d02d2f1c Mon Sep 17 00:00:00 2001 From: Emmanuel Bernard Date: Tue, 7 Aug 2018 16:22:22 +0200 Subject: [PATCH 122/772] HHH-12890 Fix link to JPA metamodel generator documentation --- .../asciidoc/userguide/chapters/query/criteria/Criteria.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc index a6e3e4d4e1d4..bfbfdbad3efa 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc @@ -69,7 +69,7 @@ It was done here only for completeness of an example. The `Person_.name` reference is an example of the static form of JPA Metamodel reference. We will use that form exclusively in this chapter. -See the documentation for the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/topical/html/metamodelgen/MetamodelGenerator.html[Hibernate JPA Metamodel Generator] for additional details on the JPA static Metamodel. +See the documentation for the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/topical/html_single/metamodelgen/MetamodelGenerator.html[Hibernate JPA Metamodel Generator] for additional details on the JPA static Metamodel. ==== [[criteria-typedquery-expression]] From 9f3b1cfb5bd8f817d0d15e2095aa992c84231a2c Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Tue, 7 Aug 2018 09:27:00 -0400 Subject: [PATCH 123/772] HHH-12871 - Added test cases. (cherry picked from commit 6a594712dbbc7cac8394c95ccb8a19a5ec760189) --- .../AbstractJpaMetamodelPopulationTest.java | 183 ++++++++++++++++++ .../JpaMetamodelDisabledPopulationTest.java | 20 ++ .../JpaMetamodelEnabledPopulationTest.java | 20 ++ ...amodelIgnoreUnsupportedPopulationTest.java | 20 ++ .../test/metamodel/CompositeId2Entity.hbm.xml | 14 ++ .../test/metamodel/CompositeIdEntity.hbm.xml | 14 ++ .../jpa/test/metamodel/SimpleEntity.hbm.xml | 13 ++ 7 files changed, 284 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java create mode 100644 hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/CompositeId2Entity.hbm.xml create mode 100644 hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/CompositeIdEntity.hbm.xml create mode 100644 hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/SimpleEntity.hbm.xml diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.java new file mode 100644 index 000000000000..6f487f2b8219 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.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.metamodel; + +import java.io.Serializable; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.metamodel.EmbeddableType; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.Metamodel; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public abstract class AbstractJpaMetamodelPopulationTest extends BaseEntityManagerFunctionalTestCase { + @Entity(name = "SimpleAnnotatedEntity") + public static class SimpleAnnotatedEntity { + @Id + @GeneratedValue + private Integer id; + private String data; + } + + @Entity(name = "CompositeIdAnnotatedEntity") + public static class CompositeIdAnnotatedEntity { + @EmbeddedId + private CompositeIdId id; + private String data; + } + + @Embeddable + public static class CompositeIdId implements Serializable { + private Integer id1; + private Integer id2; + } + + @Override + protected String[] getMappings() { + return new String[] { + "org/hibernate/jpa/test/metamodel/SimpleEntity.hbm.xml", + "org/hibernate/jpa/test/metamodel/CompositeIdEntity.hbm.xml", + "org/hibernate/jpa/test/metamodel/CompositeId2Entity.hbm.xml" + }; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleAnnotatedEntity.class, CompositeIdAnnotatedEntity.class }; + } + + protected abstract String getJpaMetamodelPopulationValue(); + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + options.put( AvailableSettings.JPA_METAMODEL_POPULATION, getJpaMetamodelPopulationValue() ); + } + + @Test + public void testMetamodel() { + EntityManager entityManager = getOrCreateEntityManager(); + try { + final Metamodel metamodel = entityManager.getMetamodel(); + + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "disabled" ) ) { + // In 5.1, metamodel returned null. + // In 5.2+, metamodel erturned as a non-null instance. + assertNotNull( metamodel ); + assertEquals( 0, metamodel.getManagedTypes().size() ); + assertEquals( 0, metamodel.getEntities().size() ); + assertEquals( 0, metamodel.getEmbeddables().size() ); + return; + } + + assertNotNull( metamodel ); + + assertManagedTypes( metamodel ); + assertEntityTypes( metamodel ); + assertEmbeddableTypes( metamodel ); + } + finally { + entityManager.close(); + } + } + + private void assertManagedTypes(Metamodel metamodel) { + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "enabled" ) ) { + // All managed types should be included, dynamic-map and annotation based. + // EntityType(SimpleAnnotatedEntity) + // EntityType(CompositeIdAnnotatedEntity) + // EntityType(null) - SimpleEntity (dynamic-map entity) + // EntityType(null) - CompositeIdEntity (dynamic-map entity) + // EntityType(null) - CompositeId2Entity (dynamic-map entity) + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + // EmbeddableType(Map) - CompositeIdEntity's (dynamic-map entity) identifier + // EmbeddableType(Map) - CompositeId2Entity's (dynamic-map entity) identifier + assertEquals( 8, metamodel.getManagedTypes().size() ); + } + else { + // When ignoreUnsupported is used, any managed-type that refers to a dynamic-map entity type + // or a managed type that is owned by said dynamic-map entity type should be excluded. + // Therefore this collection should only include 3 elements + // EntityType(SimpleAnnotated) + // EntityType(CompositeIdAnnotatedEntity) + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + assertEquals( 3, metamodel.getManagedTypes().size() ); + } + } + + private void assertEntityTypes(Metamodel metamodel) { + final Set entityNames = metamodel.getEntities().stream() + .map( EntityType::getName ) + .collect( Collectors.toSet() ); + + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "enabled" ) ) { + // Should include all entity types + // EntityType(SimpleAnnotatedEntity) + // EntityType(CompositeIdAnnotatedEntity) + // EntityType(null) - SimpleEntity (dynamic-map entity) + // EntityType(null) - CompositeIdEntity (dynamic-map entity) + // EntityType(null) - CompositeId2Entity (dynamic-map entity) + assertEquals( 5, entityNames.size() ); + assertTrue( entityNames.contains( "SimpleAnnotatedEntity" ) ); + assertTrue( entityNames.contains( "CompositeIdAnnotatedEntity" ) ); + assertTrue( entityNames.contains( "SimpleEntity" ) ); + assertTrue( entityNames.contains( "CompositeIdEntity" ) ); + assertTrue( entityNames.contains( "CompositeId2Entity" ) ); + } + else { + // In 5.1, this returns 5 elements + // CompositeIdAnnotatedEntity + // SimpleAnnotatedEntity + // SimpleEntity <-- this should not exist since its entity-type is filtered + // CompositeIdEntity <-- this should not exist since its entity-type is filtered + // CompsoiteId2Entity <-- this should not exist since its entity-type is filtered + // + // In 5.2, this returns 5 elements too. + // In 5.3, this returns 5 elements too. + assertEquals( 2, entityNames.size() ); + assertTrue( entityNames.contains( "SimpleAnnotatedEntity" ) ); + assertTrue( entityNames.contains( "CompositeIdAnnotatedEntity" ) ); + } + } + + private void assertEmbeddableTypes(Metamodel metamodel) { + final Set> embeddableTypes = metamodel.getEmbeddables(); + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "enabled" ) ) { + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + // EmbeddableType(Map) - CompositeIdEntity (dynamic-map entity) identifier + // EmbeddableType(Map) - CompositeId2Entity (dynamic-map entity) identifier + assertEquals( 3, embeddableTypes.size() ); + } + else { + // This should return only 1 element + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + // The dynamic-map entity type's composite-id embeddable types should be excluded. + assertEquals( 1, embeddableTypes.size() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java new file mode 100644 index 000000000000..f8e9ee2c378e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java @@ -0,0 +1,20 @@ +/* + * 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.metamodel; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public class JpaMetamodelDisabledPopulationTest extends AbstractJpaMetamodelPopulationTest { + @Override + protected String getJpaMetamodelPopulationValue() { + return "disabled"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java new file mode 100644 index 000000000000..fa1eed102447 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java @@ -0,0 +1,20 @@ +/* + * 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.metamodel; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public class JpaMetamodelEnabledPopulationTest extends AbstractJpaMetamodelPopulationTest { + @Override + protected String getJpaMetamodelPopulationValue() { + return "enabled"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java new file mode 100644 index 000000000000..b54d40d7bb72 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java @@ -0,0 +1,20 @@ +/* + * 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.metamodel; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public class JpaMetamodelIgnoreUnsupportedPopulationTest extends AbstractJpaMetamodelPopulationTest { + @Override + protected String getJpaMetamodelPopulationValue() { + return "ignoreUnsupported"; + } +} diff --git a/hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/CompositeId2Entity.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/CompositeId2Entity.hbm.xml new file mode 100644 index 000000000000..9cf56c19ee99 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/CompositeId2Entity.hbm.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/CompositeIdEntity.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/CompositeIdEntity.hbm.xml new file mode 100644 index 000000000000..54b542be4fd4 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/CompositeIdEntity.hbm.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/SimpleEntity.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/SimpleEntity.hbm.xml new file mode 100644 index 000000000000..bb15207ae831 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/jpa/test/metamodel/SimpleEntity.hbm.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file From dc49ef6ef4964f2ef840e4b09eda3c821add16cd Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Tue, 7 Aug 2018 09:29:00 -0400 Subject: [PATCH 124/772] HHH-12871 - Fix metamodel to properly exclude dynamic-map based types when using ignoreUnsupported. (cherry picked from commit b9e0449602fdfa490fdcf666b6f5c4ccf2832c34) --- .../hibernate/metamodel/internal/MetadataContext.java | 9 ++++++++- .../org/hibernate/metamodel/internal/MetamodelImpl.java | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index 3dee49037312..1012c391c13d 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -116,16 +116,23 @@ public Map, MappedSuperclassType> getMappedSuperclassTypeMap() { } /*package*/ void registerEntityType(PersistentClass persistentClass, EntityTypeImpl entityType) { + if ( ignoreUnsupported && entityType.getBindableJavaType() == null ) { + return; + } + if ( entityType.getBindableJavaType() != null ) { entityTypes.put( entityType.getBindableJavaType(), entityType ); } + entityTypesByEntityName.put( persistentClass.getEntityName(), entityType ); entityTypesByPersistentClass.put( persistentClass, entityType ); orderedMappings.add( persistentClass ); } /*package*/ void registerEmbeddedableType(EmbeddableTypeImpl embeddableType) { - embeddables.add( embeddableType ); + if ( !( ignoreUnsupported && embeddableType.getParent().getJavaType() == null ) ) { + embeddables.add( embeddableType ); + } } /*package*/ void registerMappedSuperclassType( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java index b3a3dc4b17de..87ce3408342c 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java @@ -570,7 +570,7 @@ public Set> getManagedTypes() { jpaEntityTypeMap.size() + jpaMappedSuperclassTypeMap.size() + jpaEmbeddableTypes.size() ); final Set> managedTypes = new HashSet>( setSize ); - managedTypes.addAll( jpaEntityTypeMap.values() ); + managedTypes.addAll( jpaEntityTypesByEntityName.values() ); managedTypes.addAll( jpaMappedSuperclassTypeMap.values() ); managedTypes.addAll( jpaEmbeddableTypes ); return managedTypes; From 0b17fc32254d3dce9910e7777d37bcc60db2a84a Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Tue, 7 Aug 2018 09:30:38 -0400 Subject: [PATCH 125/772] HHH-12871 - Fix test that uses dynamic-map entities which failed. (cherry picked from commit 8bd79b29cfa7b2d539a746dc356d60b66e1e596b) --- .../paths/SingularAttributeJoinTest.java | 181 ++++++++++-------- 1 file changed, 96 insertions(+), 85 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java index ba2a4efda79f..800f7dd4123b 100755 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java @@ -1,85 +1,96 @@ -/* - * 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.criteria.paths; - -import javax.persistence.EntityManager; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.From; -import javax.persistence.criteria.JoinType; -import javax.persistence.criteria.Path; -import javax.persistence.metamodel.Attribute; -import javax.persistence.metamodel.Bindable; -import javax.persistence.metamodel.SingularAttribute; -import javax.persistence.metamodel.Type; - -import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; -import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; -import org.hibernate.query.criteria.internal.PathSource; -import org.hibernate.query.criteria.internal.path.SingularAttributeJoin; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.withSettings; - -/** - * @author Brad Koehn - */ -public class SingularAttributeJoinTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected String[] getMappings() { - return new String[] { - getClass().getPackage().getName().replace( '.', '/' ) + "/PolicyAndDistribution.hbm.xml" - }; - } - - /** - * When building a join from a non-class based entity (EntityMode.MAP), make sure you get the Bindable from - * the SingularAttribute as the join model. If you don't, you'll get the first non-classed based entity - * you added to your configuration. Regression for HHH-9142. - */ - @Test - public void testEntityModeMapJoins() throws Exception { - CriteriaBuilderImpl criteriaBuilder = mock( CriteriaBuilderImpl.class); - PathSource pathSource = mock( PathSource.class); - SingularAttribute joinAttribute = mock( SingularAttribute.class); - when(joinAttribute.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.MANY_TO_ONE); - Type joinType = mock( Type.class, withSettings().extraInterfaces( Bindable.class)); - when(joinAttribute.getType()).thenReturn(joinType); - SingularAttributeJoin join = new SingularAttributeJoin(criteriaBuilder, null, pathSource, joinAttribute, JoinType.LEFT); - - assertEquals( joinType, join.getModel()); - } - - @Test - public void testEntityModeMapJoinCriteriaQuery() throws Exception { - final EntityManager entityManager = entityManagerFactory().createEntityManager(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(); - javax.persistence.metamodel.EntityType distributionEntity = getEntityType("Distribution"); - From distributionFrom = criteriaQuery.from(distributionEntity); - From policyJoin = distributionFrom.join("policy"); - Path policyId = policyJoin.get("policyId"); - criteriaQuery.select(policyId); - TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); -// typedQuery.getResultList(); - } - - private javax.persistence.metamodel.EntityType getEntityType(String entityName) { - for(javax.persistence.metamodel.EntityType entityType : entityManagerFactory().getMetamodel().getEntities()) { - if (entityType.getName().equals("Distribution")) { - return entityType; - } - } - - throw new IllegalStateException("Unable to find entity " + entityName); - } -} +/* + * 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.criteria.paths; + +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.From; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Path; +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.Bindable; +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.Type; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; +import org.hibernate.query.criteria.internal.PathSource; +import org.hibernate.query.criteria.internal.path.SingularAttributeJoin; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +/** + * @author Brad Koehn + */ +public class SingularAttributeJoinTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected String[] getMappings() { + return new String[] { + getClass().getPackage().getName().replace( '.', '/' ) + "/PolicyAndDistribution.hbm.xml" + }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + + // make sure that dynamic-map mode entity types are returned in the metamodel. + options.put( AvailableSettings.JPA_METAMODEL_POPULATION, "enabled" ); + } + + /** + * When building a join from a non-class based entity (EntityMode.MAP), make sure you get the Bindable from + * the SingularAttribute as the join model. If you don't, you'll get the first non-classed based entity + * you added to your configuration. Regression for HHH-9142. + */ + @Test + public void testEntityModeMapJoins() throws Exception { + CriteriaBuilderImpl criteriaBuilder = mock( CriteriaBuilderImpl.class); + PathSource pathSource = mock( PathSource.class); + SingularAttribute joinAttribute = mock( SingularAttribute.class); + when(joinAttribute.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.MANY_TO_ONE); + Type joinType = mock( Type.class, withSettings().extraInterfaces( Bindable.class)); + when(joinAttribute.getType()).thenReturn(joinType); + SingularAttributeJoin join = new SingularAttributeJoin(criteriaBuilder, null, pathSource, joinAttribute, JoinType.LEFT); + + assertEquals( joinType, join.getModel()); + } + + @Test + public void testEntityModeMapJoinCriteriaQuery() throws Exception { + final EntityManager entityManager = entityManagerFactory().createEntityManager(); + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(); + javax.persistence.metamodel.EntityType distributionEntity = getEntityType("Distribution"); + From distributionFrom = criteriaQuery.from(distributionEntity); + From policyJoin = distributionFrom.join("policy"); + Path policyId = policyJoin.get("policyId"); + criteriaQuery.select(policyId); + TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); +// typedQuery.getResultList(); + } + + private javax.persistence.metamodel.EntityType getEntityType(String entityName) { + for(javax.persistence.metamodel.EntityType entityType : entityManagerFactory().getMetamodel().getEntities()) { + if (entityType.getName().equals("Distribution")) { + return entityType; + } + } + + throw new IllegalStateException("Unable to find entity " + entityName); + } +} From ad4978c36a4241244088f7ebc96e68399cd1e617 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 8 Aug 2018 16:21:25 +0200 Subject: [PATCH 126/772] HHH-10782 Add a comment about what clearing the query plan cache means --- .../org/hibernate/engine/query/spi/QueryPlanCache.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java index f3356a3bf6de..3abec770b27a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java @@ -220,7 +220,13 @@ public NativeSQLQueryPlan getNativeSQLQueryPlan(final NativeSQLQuerySpecificatio } /** - * clean up QueryPlanCache when SessionFactory is closed + * Clean up the caches when the SessionFactory is closed. + *

    + * Note that depending on the cache strategy implementation chosen, clearing the cache might not reclaim all the + * memory. + *

    + * Typically, when using LIRS, clearing the cache only invalidates the entries but the outdated entries are kept in + * memory until they are replaced by others. It is not considered a memory leak as the cache is bounded. */ public void cleanup() { LOG.trace( "Cleaning QueryPlan Cache" ); From 703f53668df3ad0a9b571fa3a912039499edbe69 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 9 Aug 2018 11:34:31 +0100 Subject: [PATCH 127/772] HHH-12898 Enable integration tests for Oracle Standard Edition Two 12.1.0.2.v12 on the AWS build slaves --- databases/oracle/resources/hibernate.properties | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/databases/oracle/resources/hibernate.properties b/databases/oracle/resources/hibernate.properties index eeb6db8d9ddd..05f554eec394 100644 --- a/databases/oracle/resources/hibernate.properties +++ b/databases/oracle/resources/hibernate.properties @@ -7,8 +7,9 @@ hibernate.dialect org.hibernate.dialect.Oracle12cDialect hibernate.connection.driver_class oracle.jdbc.driver.OracleDriver -hibernate.connection.url jdbc:oracle:thin:@orm-testing.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL -hibernate.connection.username ormmasteruser +hibernate.connection.url jdbc:oracle:thin:@hibernate-testing-oracle-se.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL +hibernate.connection.username hibernate_orm_test +hibernate.connection.password hibernate_orm_test hibernate.connection.pool_size 5 From a47554039e512fcb59d3207c9c70525d299825d1 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 9 Aug 2018 12:33:41 +0100 Subject: [PATCH 128/772] HHH-12901 Enable loading of additional JDBC drivers from a local path, rename the Oracle dependency --- build.gradle | 7 +++++++ databases/oracle/matrix.gradle | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index beb805c4d61b..0094881aad0d 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,13 @@ allprojects { name "jboss-snapshots" url "http://snapshots.jboss.org/maven2/" } + //Allow loading additional dependencies from a local path; + //useful to load JDBC drivers which can not be distributed in public. + if (System.env['ADDITIONAL_REPO'] != null) { + flatDir { + dirs "${System.env.ADDITIONAL_REPO}" + } + } } apply plugin: 'idea' diff --git a/databases/oracle/matrix.gradle b/databases/oracle/matrix.gradle index b4a64b9e80a1..2355701ef41c 100644 --- a/databases/oracle/matrix.gradle +++ b/databases/oracle/matrix.gradle @@ -4,4 +4,6 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -jdbcDependency 'com.oracle.ojdbc:ojdbc7:12.1.0.2.0' +// Expected to match the jar name: drop a copy of ojdbc8.jar in a local directory, +// then point the environment variable ADDITIONAL_REPO to that directory. +jdbcDependency name : 'ojdbc8' From b211e423cb2621eb00f195407d0493f2ff9bf755 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 9 Aug 2018 11:44:16 +0100 Subject: [PATCH 129/772] HHH-12899 Enable integration tests for MS SQL Server on the AWS build slaves --- databases/mssqlserver/matrix.gradle | 14 ++++++++ .../resources/hibernate.properties | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 databases/mssqlserver/matrix.gradle create mode 100644 databases/mssqlserver/resources/hibernate.properties diff --git a/databases/mssqlserver/matrix.gradle b/databases/mssqlserver/matrix.gradle new file mode 100644 index 000000000000..9e763e239a6b --- /dev/null +++ b/databases/mssqlserver/matrix.gradle @@ -0,0 +1,14 @@ +/* + * 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 . + */ + +/* + * 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 . + */ +jdbcDependency 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8' \ No newline at end of file diff --git a/databases/mssqlserver/resources/hibernate.properties b/databases/mssqlserver/resources/hibernate.properties new file mode 100644 index 000000000000..7f582bf0a02b --- /dev/null +++ b/databases/mssqlserver/resources/hibernate.properties @@ -0,0 +1,33 @@ +# +# 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 . +# + +# +# 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 . +# + +hibernate.dialect org.hibernate.dialect.SQLServer2012Dialect +hibernate.connection.driver_class com.microsoft.sqlserver.jdbc.SQLServerDriver +hibernate.connection.url jdbc:sqlserver://hibernate-testing-mssql-express.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com +hibernate.connection.username hibernate_orm_test +hibernate.connection.password hibernate_orm_test + +hibernate.connection.pool_size 5 + +hibernate.show_sql false +hibernate.format_sql true + +hibernate.max_fetch_depth 5 + +hibernate.cache.region_prefix hibernate.test +hibernate.cache.region.factory_class org.hibernate.testing.cache.CachingRegionFactory + +javax.persistence.validation.mode=NONE +hibernate.service.allow_crawling=false +hibernate.session.events.log=true \ No newline at end of file From c59932786d4b4c622a9028b73251e7723eab98dd Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 10 Aug 2018 10:46:53 -0400 Subject: [PATCH 130/772] HHH-12903 - Fix CommitFlushCollectionTest failing on Oracle. (cherry picked from commit 8dab6974ef266434d3cc0a8aff780f521097427f) --- .../test/integration/flush/CommitFlushCollectionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java index ccce6841d9eb..1b46c5de2f2d 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java @@ -48,7 +48,7 @@ public static abstract class AbstractEntity { private Long version; @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue public Long getId() { return id; } From 1dd5eeb79d9c48eaa8599f162244e564345d85e1 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 10 Aug 2018 17:00:55 +0200 Subject: [PATCH 131/772] Remove HHH-12608 from the 5.3.4 changelog as it has not been included --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index bcd9aa0cbef8..93f65b1e5682 100644 --- a/changelog.txt +++ b/changelog.txt @@ -26,7 +26,6 @@ https://hibernate.atlassian.net/projects/HHH/versions/31688/tab/release-report-d * [HHH-12880] - LockModeTest hangs indefinitely with Sybase due to HHH-12847 ** New Feature - * [HHH-12608] - Add the ST_DWithin() function in DB2 Spatial Dialect * [HHH-12857] - Support the security manager with ByteBuddy as bytecode provider ** Task From 366698e2298a4a189b44acac30a22e58c9262ff3 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 10 Aug 2018 13:33:08 -0400 Subject: [PATCH 132/772] HHH-12903 - Fix CommitFlushCollectionTest failing on Oracle. --- .../test/integration/flush/CommitFlushCollectionTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java index 1b46c5de2f2d..5c60e2173faf 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java @@ -71,6 +71,7 @@ public void setVersion(Long version) { @Audited @MappedSuperclass public static class BaseDocument extends AbstractEntity { + @Column(name = "numberValue") private String number; private Date date; @@ -119,6 +120,7 @@ public DocumentA addLine(DocumentLineA line) { @MappedSuperclass public abstract static class BaseDocumentLine extends AbstractEntity { + @Column(name = "textValue") private String text; @Column(nullable = false) From 46ed100baf5cd88d7b73fff715864130629fef22 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 12 Aug 2018 17:17:16 +0200 Subject: [PATCH 133/772] HHH-12903 Add the column names to the proper @Column annotations --- .../flush/CommitFlushCollectionTest.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java index 5c60e2173faf..ff310a7f6efd 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/flush/CommitFlushCollectionTest.java @@ -6,6 +6,8 @@ */ package org.hibernate.envers.test.integration.flush; +import static org.junit.Assert.assertEquals; + import java.util.Arrays; import java.util.Date; import java.util.LinkedList; @@ -18,7 +20,6 @@ import javax.persistence.FetchType; import javax.persistence.FlushModeType; import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @@ -30,11 +31,8 @@ import org.hibernate.envers.Audited; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.Priority; -import org.junit.Test; - import org.hibernate.testing.TestForIssue; - -import static org.junit.Assert.assertEquals; +import org.junit.Test; /** * @author Chris Cranford @@ -71,11 +69,10 @@ public void setVersion(Long version) { @Audited @MappedSuperclass public static class BaseDocument extends AbstractEntity { - @Column(name = "numberValue") private String number; private Date date; - @Column(nullable = false) + @Column(name = "numberValue", nullable = false) public String getNumber() { return number; } @@ -84,7 +81,7 @@ public void setNumber(String number) { this.number = number; } - @Column(nullable = false) + @Column(name = "dateValue", nullable = false) public Date getDate() { return date; } @@ -120,10 +117,9 @@ public DocumentA addLine(DocumentLineA line) { @MappedSuperclass public abstract static class BaseDocumentLine extends AbstractEntity { - @Column(name = "textValue") private String text; - @Column(nullable = false) + @Column(name = "textValue", nullable = false) public String getText() { return text; } From cbd141996ad2b2fa60aa1e85c2d8f1afa08ae7ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Beaufum=C3=A9?= <> Date: Mon, 13 Aug 2018 11:07:10 +0200 Subject: [PATCH 134/772] HHH-12906 Used the correct attribute in getAllCollectionRoles() --- .../java/org/hibernate/metamodel/internal/MetamodelImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java index 87ce3408342c..fb499da06d6c 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java @@ -711,7 +711,7 @@ public String[] getAllEntityNames() { @Override public String[] getAllCollectionRoles() { - return ArrayHelper.toStringArray( entityPersisterMap.keySet() ); + return ArrayHelper.toStringArray( collectionPersisterMap.keySet() ); } @Override From 61bdbabf180201ddf02d43df957edf7d279fecec Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 13 Aug 2018 11:48:39 +0200 Subject: [PATCH 135/772] HHH-12906 Add tests --- .../test/metamodel/MetamodelTest.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/metamodel/MetamodelTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/metamodel/MetamodelTest.java b/hibernate-core/src/test/java/org/hibernate/test/metamodel/MetamodelTest.java new file mode 100644 index 000000000000..2a8e3f62446b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/metamodel/MetamodelTest.java @@ -0,0 +1,96 @@ +/* + * 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.metamodel; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Set; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +public class MetamodelTest extends BaseEntityManagerFunctionalTestCase { + + @Test + @TestForIssue(jiraKey = "HHH-12906") + public void testGetAllCollectionRoles() { + String[] collectionRoles = entityManagerFactory().getMetamodel().getAllCollectionRoles(); + Arrays.sort( collectionRoles ); + assertArrayEquals( collectionRoles, new String[]{ EntityWithCollection.class.getName() + ".collection", + EntityWithCollection2.class.getName() + ".collection2" } ); + } + + @Test + public void testGetCollectionRolesByEntityParticipant() { + Set collectionRolesByEntityParticipant = entityManagerFactory().getMetamodel() + .getCollectionRolesByEntityParticipant( ElementOfCollection.class.getName() ); + assertEquals( 1, collectionRolesByEntityParticipant.size() ); + assertEquals( EntityWithCollection.class.getName() + ".collection", + collectionRolesByEntityParticipant.iterator().next() ); + } + + @Test + public void testEntityNames() { + String[] entityNames = entityManagerFactory().getMetamodel().getAllEntityNames(); + Arrays.sort( entityNames ); + assertArrayEquals( entityNames, + new String[]{ ElementOfCollection.class.getName(), ElementOfCollection2.class.getName(), + EntityWithCollection.class.getName(), EntityWithCollection2.class.getName() } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ EntityWithCollection.class, ElementOfCollection.class, EntityWithCollection2.class, + ElementOfCollection2.class }; + } + + @Entity(name = "EntityWithCollection") + public static class EntityWithCollection { + + @Id + @GeneratedValue + private Long id; + + @ManyToMany + private Set collection; + } + + @Entity(name = "ElementOfCollection") + public static class ElementOfCollection { + + @Id + @GeneratedValue + private Long id; + } + + @Entity(name = "EntityWithCollection2") + public static class EntityWithCollection2 { + + @Id + @GeneratedValue + private Long id; + + @ManyToMany + private Set collection2; + } + + @Entity(name = "ElementOfCollection2") + public static class ElementOfCollection2 { + + @Id + @GeneratedValue + private Long id; + } +} From b65121c55721074cbe03c6f461d3890a22845aed Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Mon, 30 Jul 2018 15:11:46 +0300 Subject: [PATCH 136/772] HHH-12361 - In the User Guide, omit constructors and equals/hashCode for brevity --- .../chapters/pc/PersistenceContext.adoc | 7 +- .../ManyToManyBidirectionalTest.java | 11 + ...ToManyBidirectionalWithLinkEntityTest.java | 27 +- .../ManyToManyUnidirectionalTest.java | 10 + .../userguide/associations/ManyToOneTest.java | 9 +- .../OneToManyBidirectionalTest.java | 11 + .../OneToManyUnidirectionalTest.java | 12 + .../OneToOneBidirectionalLazyTest.java | 15 +- .../OneToOneBidirectionalTest.java | 17 +- .../OneToOneUnidirectionalTest.java | 10 + .../caching/NonStrictReadWriteCacheTest.java | 5 + .../caching/SecondLevelCacheTest.java | 7 +- .../userguide/collections/ArrayTest.java | 6 + .../BasicTypeElementCollectionTest.java | 5 + .../collections/BidirectionalBagTest.java | 11 + .../collections/BidirectionalMapTest.java | 10 + .../collections/BidirectionalSetTest.java | 10 + .../collections/ElementCollectionMapTest.java | 10 + .../EmbeddableTypeElementCollectionTest.java | 10 + .../userguide/collections/MapKeyTypeTest.java | 6 +- .../collections/OrderedBySQLTest.java | 15 +- .../collections/UnidirectionalBagTest.java | 11 + ...UnidirectionalComparatorSortedSetTest.java | 11 + .../collections/UnidirectionalMapTest.java | 16 +- .../UnidirectionalOrderedByListTest.java | 11 + .../collections/UnidirectionalSetTest.java | 10 + .../UnidirectionalSortedSetTest.java | 11 + .../userguide/events/BaseEntity.java | 5 + .../fetching/DirectVsQueryFetchingTest.java | 1 - .../userguide/flush/AutoFlushTest.java | 11 +- .../DiscriminatorNotNullSingleTableTest.java | 20 ++ .../JoinTablePrimaryKeyJoinColumnTest.java | 15 ++ .../userguide/inheritance/JoinTableTest.java | 15 ++ .../inheritance/MappedSuperclassTest.java | 15 ++ .../SingleTableDiscriminatorFormulaTest.java | 15 ++ .../inheritance/SingleTableTest.java | 15 ++ .../inheritance/TablePerClassTest.java | 15 ++ .../mapping/basic/BitSetTypeDefTest.java | 4 + .../mapping/basic/BitSetTypeTest.java | 4 + .../mapping/basic/BitSetUserTypeTest.java | 4 +- .../basic/JoinColumnOrFormulaTest.java | 10 +- .../mapping/basic/JoinFormulaTest.java | 8 + .../userguide/mapping/basic/ParentTest.java | 8 + .../mapping/converter/MoneyConverterTest.java | 37 +-- .../EmbeddableImplicitOverrideTest.java | 6 +- .../embeddable/EmbeddableOverrideTest.java | 7 +- .../embeddable/SimpleEmbeddableTest.java | 8 +- .../generated/CreationTimestampTest.java | 4 + .../DatabaseValueGenerationTest.java | 7 + .../InMemoryValueGenerationTest.java | 6 + .../userguide/pc/BytecodeEnhancementTest.java | 17 +- .../userguide/pc/CascadeOnDeleteTest.java | 18 +- .../userguide/pc/PersistenceContextTest.java | 5 + .../org/hibernate/userguide/pc/Person.java | 5 + .../org/hibernate/userguide/pc/Phone.java | 4 + .../hibernate/userguide/persister/Author.java | 1 + .../schema/SchemaGenerationTest.java | 17 +- .../org/hibernate/userguide/sql/Captain.java | 5 + .../sql/CustomSQLSecondaryTableTest.java | 239 +++++++++--------- .../userguide/sql/CustomSQLTest.java | 13 +- .../hibernate/userguide/sql/Dimensions.java | 5 + .../org/hibernate/userguide/sql/Identity.java | 5 + .../userguide/sql/PersonSummaryDTO.java | 2 + .../org/hibernate/userguide/sql/SQLTest.java | 8 +- .../hibernate/userguide/sql/SpaceShip.java | 5 + .../bulkid/AbstractBulkCompositeIdTest.java | 10 + 66 files changed, 708 insertions(+), 185 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc index 76cfe479c9cb..00b79e0bd934 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc @@ -829,7 +829,12 @@ as illustrated by the following example. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-mapping-example] +include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-mapping-Person-example] +---- + +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-mapping-Phone-example] ---- [source, SQL, indent=0] diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java index e239f3b0b21a..81fb51ead265 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java @@ -71,9 +71,14 @@ public static class Person { @NaturalId private String registrationNumber; + @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private List

    addresses = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-example[] + public Person() { } @@ -85,6 +90,7 @@ public List
    getAddresses() { return addresses; } + //tag::associations-many-to-many-bidirectional-example[] public void addAddress(Address address) { addresses.add( address ); address.getOwners().add( this ); @@ -130,6 +136,10 @@ public static class Address { @ManyToMany(mappedBy = "addresses") private List owners = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-example[] + public Address() { } @@ -159,6 +169,7 @@ public List getOwners() { return owners; } + //tag::associations-many-to-many-bidirectional-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java index be795f603073..4f4d64594597 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java @@ -81,9 +81,17 @@ public static class Person implements Serializable { @NaturalId private String registrationNumber; - @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany( + mappedBy = "person", + cascade = CascadeType.ALL, + orphanRemoval = true + ) private List addresses = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-with-link-entity-example[] + public Person() { } @@ -99,6 +107,7 @@ public List getAddresses() { return addresses; } + //tag::associations-many-to-many-bidirectional-with-link-entity-example[] public void addAddress(Address address) { PersonAddress personAddress = new PersonAddress( this, address ); addresses.add( personAddress ); @@ -142,6 +151,10 @@ public static class PersonAddress implements Serializable { @ManyToOne private Address address; + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-with-link-entity-example[] + public PersonAddress() { } @@ -166,6 +179,7 @@ public void setAddress(Address address) { this.address = address; } + //tag::associations-many-to-many-bidirectional-with-link-entity-example[] @Override public boolean equals(Object o) { if ( this == o ) { @@ -199,9 +213,17 @@ public static class Address implements Serializable { private String postalCode; - @OneToMany(mappedBy = "address", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany( + mappedBy = "address", + cascade = CascadeType.ALL, + orphanRemoval = true + ) private List owners = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-with-link-entity-example[] + public Address() { } @@ -231,6 +253,7 @@ public List getOwners() { return owners; } + //tag::associations-many-to-many-bidirectional-with-link-entity-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java index a8e8174f06c7..99c5ba5c75f3 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java @@ -100,12 +100,17 @@ public static class Person { @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private List
    addresses = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-unidirectional-example[] + public Person() { } public List
    getAddresses() { return addresses; } + //tag::associations-many-to-many-unidirectional-example[] } @Entity(name = "Address") @@ -120,6 +125,10 @@ public static class Address { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-unidirectional-example[] + public Address() { } @@ -139,6 +148,7 @@ public String getStreet() { public String getNumber() { return number; } + //tag::associations-many-to-many-unidirectional-example[] } //end::associations-many-to-many-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java index eb99f055582a..93c98facf337 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java @@ -58,8 +58,8 @@ public static class Person { @GeneratedValue private Long id; - public Person() { - } + //Getters and setters are omitted for brevity + } @Entity(name = "Phone") @@ -78,6 +78,10 @@ public static class Phone { ) private Person person; + //Getters and setters are omitted for brevity + + //end::associations-many-to-one-example[] + public Phone() { } @@ -100,6 +104,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; } + //tag::associations-many-to-one-example[] } //end::associations-many-to-one-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java index 2a21d8191824..2bd1ad812350 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java @@ -62,9 +62,14 @@ public static class Person { @Id @GeneratedValue private Long id; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-bidirectional-example[] + public Person() { } @@ -76,6 +81,7 @@ public List getPhones() { return phones; } + //tag::associations-one-to-many-bidirectional-example[] public void addPhone(Phone phone) { phones.add( phone ); phone.setPerson( this ); @@ -101,6 +107,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-bidirectional-example[] + public Phone() { } @@ -124,6 +134,7 @@ public void setPerson(Person person) { this.person = person; } + //tag::associations-one-to-many-bidirectional-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java index 132f1dda9495..7e9406421008 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java @@ -59,15 +59,22 @@ public static class Person { @Id @GeneratedValue private Long id; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-unidirectional-example[] + public Person() { } public List getPhones() { return phones; } + + //tag::associations-one-to-many-unidirectional-example[] } @Entity(name = "Phone") @@ -80,6 +87,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-unidirectional-example[] + public Phone() { } @@ -94,6 +105,7 @@ public Long getId() { public String getNumber() { return number; } + //tag::associations-one-to-many-unidirectional-example[] } //end::associations-one-to-many-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java index b6764982b871..fcdd7722533e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java @@ -64,15 +64,16 @@ public static class Phone { @LazyToOne( LazyToOneOption.NO_PROXY ) private PhoneDetails details; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-lazy-example[] + public Phone() { } public Phone(String number) { this.number = number; } - //Getters and setters are omitted for brevity - - //end::associations-one-to-one-bidirectional-lazy-example[] public Long getId() { return id; @@ -86,6 +87,7 @@ public PhoneDetails getDetails() { return details; } + //tag::associations-one-to-one-bidirectional-lazy-example[] public void addDetails(PhoneDetails details) { details.setPhone( this ); this.details = details; @@ -97,7 +99,6 @@ public void removeDetails() { this.details = null; } } - //tag::associations-one-to-one-bidirectional-lazy-example[] } @Entity(name = "PhoneDetails") @@ -115,6 +116,10 @@ public static class PhoneDetails { @JoinColumn(name = "phone_id") private Phone phone; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-lazy-example[] + public PhoneDetails() { } @@ -124,8 +129,6 @@ public PhoneDetails(String provider, String technology) { } //Getters and setters are omitted for brevity - //end::associations-one-to-one-bidirectional-lazy-example[] - public String getProvider() { return provider; } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java index 542dcd0563e8..bb6a4ba800be 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java @@ -94,9 +94,18 @@ public static class Phone { @Column(name = "`number`") private String number; - @OneToOne(mappedBy = "phone", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @OneToOne( + mappedBy = "phone", + cascade = CascadeType.ALL, + orphanRemoval = true, + fetch = FetchType.LAZY + ) private PhoneDetails details; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-example[] + public Phone() { } @@ -116,6 +125,7 @@ public PhoneDetails getDetails() { return details; } + //tag::associations-one-to-one-bidirectional-example[] public void addDetails(PhoneDetails details) { details.setPhone( this ); this.details = details; @@ -144,6 +154,10 @@ public static class PhoneDetails { @JoinColumn(name = "phone_id") private Phone phone; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-example[] + public PhoneDetails() { } @@ -171,6 +185,7 @@ public Phone getPhone() { public void setPhone(Phone phone) { this.phone = phone; } + //tag::associations-one-to-one-bidirectional-example[] } //end::associations-one-to-one-bidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java index 11136ed7415c..b596d540dac7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java @@ -59,6 +59,10 @@ public static class Phone { @JoinColumn(name = "details_id") private PhoneDetails details; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-unidirectional-example[] + public Phone() { } @@ -81,6 +85,7 @@ public PhoneDetails getDetails() { public void setDetails(PhoneDetails details) { this.details = details; } + //tag::associations-one-to-one-unidirectional-example[] } @Entity(name = "PhoneDetails") @@ -94,6 +99,10 @@ public static class PhoneDetails { private String technology; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-unidirectional-example[] + public PhoneDetails() { } @@ -113,6 +122,7 @@ public String getTechnology() { public void setTechnology(String technology) { this.technology = technology; } + //tag::associations-one-to-one-unidirectional-example[] } //end::associations-one-to-one-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java b/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java index fb7e80c24d53..91f339832676 100644 --- a/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java @@ -164,6 +164,10 @@ public static class Phone { @Version private int version; + //Getters and setters are omitted for brevity + + //end::caching-entity-mapping-example[] + public Phone() {} public Phone(String mobile) { @@ -185,6 +189,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; } + //tag::caching-entity-mapping-example[] } //end::caching-entity-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java index 19d450b86b03..4f12e691c2a6 100644 --- a/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java @@ -267,7 +267,11 @@ public static class Person { @Column(name = "code", unique = true) private String code; - public Person() {} + //Getters and setters are omitted for brevity + + //end::caching-entity-natural-id-mapping-example[] + + public Person() {} public Person(String name) { this.name = name; @@ -292,6 +296,7 @@ public String getCode() { public void setCode(String code) { this.code = code; } + //tag::caching-entity-natural-id-mapping-example[] } //end::caching-entity-natural-id-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java index 3ef6e3601b86..fad6eb82ab06 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java @@ -51,8 +51,13 @@ public static class Person { @Id private Long id; + private String[] phones; + //Getters and setters are omitted for brevity + + //end::collections-array-binary-example[] + public Person() { } @@ -67,6 +72,7 @@ public String[] getPhones() { public void setPhones(String[] phones) { this.phones = phones; } + //tag::collections-array-binary-example[] } //end::collections-array-binary-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java index 777979396689..f432646cac10 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java @@ -90,9 +90,14 @@ public static class Person { @ElementCollection private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-collection-proxy-entity-example[] + public List getPhones() { return phones; } + //tag::collections-collection-proxy-entity-example[] } //end::collections-collection-proxy-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java index fb537a348345..6b407f7c4a73 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java @@ -56,9 +56,14 @@ public static class Person { @Id private Long id; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-bag-example[] + public Person() { } @@ -70,6 +75,7 @@ public List getPhones() { return phones; } + //tag::collections-bidirectional-bag-example[] public void addPhone(Phone phone) { phones.add( phone ); phone.setPerson( this ); @@ -96,6 +102,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-bag-example[] + public Phone() { } @@ -125,6 +135,7 @@ public void setPerson(Person person) { this.person = person; } + //tag::collections-bidirectional-bag-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java index a75e26e1e087..a9739f5007cf 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java @@ -74,6 +74,10 @@ public static class Person { @MapKeyEnumerated private Map phoneRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-bidirectional-example[] + public Person() { } @@ -85,6 +89,7 @@ public Map getPhoneRegister() { return phoneRegister; } + //tag::collections-map-bidirectional-example[] public void addPhone(Phone phone) { phone.setPerson( this ); phoneRegister.put( phone.getType(), phone ); @@ -108,6 +113,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::collections-map-bidirectional-example[] + public Phone() { } @@ -136,6 +145,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; } + //tag::collections-map-bidirectional-example[] } //end::collections-map-bidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java index 16e3df90a7bf..171a3bd5f485 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java @@ -69,6 +69,10 @@ public static class Person { @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private Set phones = new HashSet<>(); + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-set-example[] + public Person() { } @@ -80,6 +84,7 @@ public Set getPhones() { return phones; } + //tag::collections-bidirectional-set-example[] public void addPhone(Phone phone) { phones.add( phone ); phone.setPerson( this ); @@ -106,6 +111,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-set-example[] + public Phone() { } @@ -135,6 +144,7 @@ public void setPerson(Person person) { this.person = person; } + //tag::collections-bidirectional-set-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java index 52391b68ff1e..a1a4d272ca79 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java @@ -78,6 +78,10 @@ public static class Person { @Column(name = "since") private Map phoneRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-value-type-entity-key-example[] + public Person() {} public Person(Long id) { @@ -87,6 +91,7 @@ public Person(Long id) { public Map getPhoneRegister() { return phoneRegister; } + //tag::collections-map-value-type-entity-key-example[] } @Embeddable @@ -97,6 +102,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-map-value-type-entity-key-example[] + public Phone() { } @@ -112,6 +121,7 @@ public PhoneType getType() { public String getNumber() { return number; } + //tag::collections-map-value-type-entity-key-example[] } //end::collections-map-value-type-entity-key-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java index 8d38a3ccf6b7..128fd2f53735 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java @@ -55,9 +55,14 @@ public static class Person { @ElementCollection private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-embeddable-type-collection-lifecycle-entity-example[] + public List getPhones() { return phones; } + //tag::collections-embeddable-type-collection-lifecycle-entity-example[] } @Embeddable @@ -68,6 +73,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-embeddable-type-collection-lifecycle-entity-example[] + public Phone() { } @@ -83,6 +92,7 @@ public String getType() { public String getNumber() { return number; } + //tag::collections-embeddable-type-collection-lifecycle-entity-example[] } //end::collections-embeddable-type-collection-lifecycle-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java index deec47df3b99..67c80a2e4b39 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java @@ -147,6 +147,10 @@ public static class Person { @Column(name = "phone_number") private Map callRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-custom-key-type-mapping-example[] + public void setId(Long id) { this.id = id; } @@ -154,7 +158,7 @@ public void setId(Long id) { public Map getCallRegister() { return callRegister; } + //tag::collections-map-custom-key-type-mapping-example[] } - //end::collections-map-custom-key-type-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java index 59d05cf6e788..cd641a82492a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java @@ -80,8 +80,13 @@ public static class Person { private String name; - @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) - @org.hibernate.annotations.OrderBy(clause = "CHAR_LENGTH(name) DESC") + @OneToMany( + mappedBy = "person", + cascade = CascadeType.ALL + ) + @org.hibernate.annotations.OrderBy( + clause = "CHAR_LENGTH(name) DESC" + ) private List
    articles = new ArrayList<>(); //Getters and setters are omitted for brevity @@ -128,6 +133,9 @@ public static class Article { @ManyToOne(fetch = FetchType.LAZY) private Person person; + //Getters and setters are omitted for brevity + //end::collections-customizing-ordered-by-sql-clause-mapping-example[] + private Article() { } @@ -136,9 +144,6 @@ public Article(String name, String content) { this.content = content; } - //Getters and setters are omitted for brevity - //end::collections-customizing-ordered-by-sql-clause-mapping-example[] - public Long getId() { return id; } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java index 35cb180a3f6e..4cea55699793 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java @@ -55,9 +55,14 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-bag-example[] + public Person() { } @@ -68,6 +73,7 @@ public Person(Long id) { public List getPhones() { return phones; } + //tag::collections-unidirectional-bag-example[] } @Entity(name = "Phone") @@ -81,6 +87,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-bag-example[] + public Phone() { } @@ -101,6 +111,7 @@ public String getType() { public String getNumber() { return number; } + //tag::collections-unidirectional-bag-example[] } //end::collections-unidirectional-bag-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java index 104bcb0d9bc5..c91d76b7b432 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java @@ -75,6 +75,10 @@ public static class Person { @SortComparator(ReverseComparator.class) private SortedSet phones = new TreeSet<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-custom-comparator-example[] + public Person() { } @@ -85,9 +89,11 @@ public Person(Long id) { public Set getPhones() { return phones; } + //tag::collections-unidirectional-sorted-set-custom-comparator-example[] } public static class ReverseComparator implements Comparator { + @Override public int compare(Phone o1, Phone o2) { return o2.compareTo( o1 ); @@ -106,6 +112,10 @@ public static class Phone implements Comparable { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-custom-comparator-example[] + public Phone() { } @@ -127,6 +137,7 @@ public String getNumber() { return number; } + //tag::collections-unidirectional-sorted-set-custom-comparator-example[] @Override public int compareTo(Phone o) { return number.compareTo( o.getNumber() ); diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java index 549097850625..26bd94c90a7e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java @@ -77,13 +77,17 @@ public static class Person { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinTable( - name = "phone_register", - joinColumns = @JoinColumn(name = "phone_id"), - inverseJoinColumns = @JoinColumn(name = "person_id")) + name = "phone_register", + joinColumns = @JoinColumn(name = "phone_id"), + inverseJoinColumns = @JoinColumn(name = "person_id")) @MapKey(name = "since") @MapKeyTemporal(TemporalType.TIMESTAMP) private Map phoneRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-unidirectional-example[] + public Person() { } @@ -95,6 +99,7 @@ public Map getPhoneRegister() { return phoneRegister; } + //tag::collections-map-unidirectional-example[] public void addPhone(Phone phone) { phoneRegister.put( phone.getSince(), phone ); } @@ -114,6 +119,10 @@ public static class Phone { private Date since; + //Getters and setters are omitted for brevity + + //end::collections-map-unidirectional-example[] + public Phone() { } @@ -134,6 +143,7 @@ public String getNumber() { public Date getSince() { return since; } + //tag::collections-map-unidirectional-example[] } //end::collections-map-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java index f986287e190f..0aaf90a899f0 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java @@ -54,10 +54,15 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) @OrderBy("number") private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-ordered-list-order-by-example[] + public Person() { } @@ -68,6 +73,7 @@ public Person(Long id) { public List getPhones() { return phones; } + //tag::collections-unidirectional-ordered-list-order-by-example[] } @Entity(name = "Phone") @@ -81,6 +87,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-ordered-list-order-by-example[] + public Phone() { } @@ -101,6 +111,7 @@ public String getType() { public String getNumber() { return number; } + //tag::collections-unidirectional-ordered-list-order-by-example[] } //end::collections-unidirectional-ordered-list-order-by-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java index e531e713feaa..2f16438f95d7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java @@ -64,9 +64,13 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) private Set phones = new HashSet<>(); + //Getters and setters are omitted for brevity + //end::collections-unidirectional-set-example[] + public Person() { } @@ -77,6 +81,7 @@ public Person(Long id) { public Set getPhones() { return phones; } + //tag::collections-unidirectional-set-example[] } @Entity(name = "Phone") @@ -91,6 +96,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-set-example[] + public Phone() { } @@ -112,6 +121,7 @@ public String getNumber() { return number; } + //tag::collections-unidirectional-set-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java index e9a19768b1dd..f330b31d0d2b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java @@ -69,10 +69,15 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) @SortNatural private SortedSet phones = new TreeSet<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-natural-comparator-example[] + public Person() { } @@ -83,6 +88,7 @@ public Person(Long id) { public Set getPhones() { return phones; } + //tag::collections-unidirectional-sorted-set-natural-comparator-example[] } @Entity(name = "Phone") @@ -97,6 +103,10 @@ public static class Phone implements Comparable { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-natural-comparator-example[] + public Phone() { } @@ -118,6 +128,7 @@ public String getNumber() { return number; } + //tag::collections-unidirectional-sorted-set-natural-comparator-example[] @Override public int compareTo(Phone o) { return number.compareTo( o.getNumber() ); diff --git a/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java b/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java index f8e5f4d6da73..213fb0268562 100644 --- a/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java +++ b/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java @@ -14,6 +14,10 @@ public abstract class BaseEntity { private Timestamp updatedOn; + //Getters and setters are omitted for brevity + +//end::events-default-listener-mapping-example[] + public Timestamp getCreatedOn() { return createdOn; } @@ -29,6 +33,7 @@ public Timestamp getUpdatedOn() { void setUpdatedOn(Timestamp updatedOn) { this.updatedOn = updatedOn; } +//tag::events-default-listener-mapping-example[] } //end::events-default-listener-mapping-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java index 14a823708dd8..e464f8150ee3 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java @@ -79,7 +79,6 @@ public static class Department { //Getters and setters omitted for brevity } - //tag::fetching-direct-vs-query-domain-model-example[] @Entity(name = "Employee") public static class Employee { diff --git a/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java b/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java index 047976dad0a2..1e7dd08a1970 100644 --- a/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java @@ -174,6 +174,10 @@ public static class Person { private String name; + //Getters and setters are omitted for brevity + + //end::flushing-auto-flush-jpql-entity-example[] + public Person() {} public Person(String name) { @@ -187,7 +191,7 @@ public Long getId() { public String getName() { return name; } - + //tag::flushing-auto-flush-jpql-entity-example[] } @Entity(name = "Advertisement") @@ -199,6 +203,10 @@ public static class Advertisement { private String title; + //Getters and setters are omitted for brevity + + //end::flushing-auto-flush-jpql-entity-example[] + public Long getId() { return id; } @@ -214,6 +222,7 @@ public String getTitle() { public void setTitle(String title) { this.title = title; } + //tag::flushing-auto-flush-jpql-entity-example[] } //end::flushing-auto-flush-jpql-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java index e3dea013e31e..2c3bbf9ef3c5 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java @@ -120,6 +120,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public Long getId() { return id; } @@ -151,6 +155,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } @Entity(name = "DebitAccount") @@ -159,6 +164,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -166,6 +175,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } @Entity(name = "CreditAccount") @@ -174,6 +184,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -181,6 +195,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } @Entity(name = "OtherAccount") @@ -189,6 +204,10 @@ public static class OtherAccount extends Account { private boolean active; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public boolean isActive() { return active; } @@ -196,6 +215,7 @@ public boolean isActive() { public void setActive(boolean active) { this.active = active; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } //end::entity-inheritance-single-table-discriminator-value-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java index d4fc162da793..bfe845bd7593 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java @@ -74,6 +74,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-primary-key-join-column-example[] + public Long getId() { return id; } @@ -105,6 +109,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-joined-table-primary-key-join-column-example[] } @Entity(name = "DebitAccount") @@ -113,6 +118,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-primary-key-join-column-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -120,6 +129,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-joined-table-primary-key-join-column-example[] } @Entity(name = "CreditAccount") @@ -128,6 +138,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-primary-key-join-column-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -135,6 +149,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-joined-table-primary-key-join-column-example[] } //end::entity-inheritance-joined-table-primary-key-join-column-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java index 889ec4df5ba4..9442c377f72d 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java @@ -76,6 +76,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-example[] + public Long getId() { return id; } @@ -107,6 +111,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-joined-table-example[] } @Entity(name = "DebitAccount") @@ -114,6 +119,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -121,6 +130,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-joined-table-example[] } @Entity(name = "CreditAccount") @@ -128,6 +138,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -135,6 +149,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-joined-table-example[] } //end::entity-inheritance-joined-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java index 4323f77913d6..1ddbf7f551c8 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java @@ -65,6 +65,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-mapped-superclass-example[] + public Long getId() { return id; } @@ -96,6 +100,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-mapped-superclass-example[] } @Entity(name = "DebitAccount") @@ -103,6 +108,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-mapped-superclass-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -110,6 +119,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-mapped-superclass-example[] } @Entity(name = "CreditAccount") @@ -117,6 +127,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-mapped-superclass-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -124,6 +138,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-mapped-superclass-example[] } //end::entity-inheritance-mapped-superclass-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java index 1cd3d890c869..ec59c3bc2da4 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java @@ -90,6 +90,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-formula-example[] + public Long getId() { return id; } @@ -121,6 +125,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-single-table-discriminator-formula-example[] } @Entity(name = "DebitAccount") @@ -131,6 +136,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-formula-example[] + private DebitAccount() { } @@ -149,6 +158,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-single-table-discriminator-formula-example[] } @Entity(name = "CreditAccount") @@ -159,6 +169,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-formula-example[] + private CreditAccount() { } @@ -177,6 +191,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-single-table-discriminator-formula-example[] } //end::entity-inheritance-single-table-discriminator-formula-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java index 1a225b6791eb..fc0316edfc36 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java @@ -78,6 +78,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-example[] + public Long getId() { return id; } @@ -109,6 +113,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-single-table-example[] } @Entity(name = "DebitAccount") @@ -116,6 +121,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -123,6 +132,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-single-table-example[] } @Entity(name = "CreditAccount") @@ -130,6 +140,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -137,6 +151,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-single-table-example[] } //end::entity-inheritance-single-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java index d4994028d78d..2db426456cc8 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java @@ -76,6 +76,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-table-per-class-example[] + public Long getId() { return id; } @@ -107,6 +111,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-table-per-class-example[] } @Entity(name = "DebitAccount") @@ -114,6 +119,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-table-per-class-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -121,6 +130,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-table-per-class-example[] } @Entity(name = "CreditAccount") @@ -128,6 +138,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-table-per-class-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -135,6 +149,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-table-per-class-example[] } //end::entity-inheritance-table-per-class-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java index b0492aafa44c..43eeb43f084c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java @@ -66,6 +66,9 @@ public static class Product { private BitSet bitSet; + //Getters and setters are omitted for brevity + //end::basic-custom-type-BitSetTypeDef-mapping-example[] + public Integer getId() { return id; } @@ -81,6 +84,7 @@ public BitSet getBitSet() { public void setBitSet(BitSet bitSet) { this.bitSet = bitSet; } + //tag::basic-custom-type-BitSetTypeDef-mapping-example[] } //end::basic-custom-type-BitSetTypeDef-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java index 9d76689f7c7a..d7e426463351 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java @@ -76,6 +76,9 @@ public Integer getId() { return id; } + //Getters and setters are omitted for brevity + //end::basic-custom-type-BitSetType-mapping-example[] + public void setId(Integer id) { this.id = id; } @@ -87,6 +90,7 @@ public BitSet getBitSet() { public void setBitSet(BitSet bitSet) { this.bitSet = bitSet; } + //tag::basic-custom-type-BitSetType-mapping-example[] } //end::basic-custom-type-BitSetType-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java index 2452c6bb92e7..e642addd209f 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java @@ -119,6 +119,8 @@ public static class Product { @Type( type = "bitset" ) private BitSet bitSet; + + //Constructors, getters and setters are omitted for brevity //end::basic-custom-type-BitSetUserType-mapping-example[] public Product() { } @@ -127,7 +129,6 @@ public Product(Number id, BitSet bitSet) { this.id = id.intValue(); this.bitSet = bitSet; } - //tag::basic-custom-type-BitSetUserType-mapping-example[] public Integer getId() { return id; @@ -144,6 +145,7 @@ public BitSet getBitSet() { public void setBitSet(BitSet bitSet) { this.bitSet = bitSet; } + //tag::basic-custom-type-BitSetUserType-mapping-example[] } //end::basic-custom-type-BitSetUserType-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java index d904fe94c7c0..0ed53059ab95 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java @@ -164,8 +164,11 @@ public void setCountry(Country country) { this.country = country; } - //tag::mapping-JoinColumnOrFormula-example[] + //tag::mapping-JoinColumnOrFormula-example[] } + //end::mapping-JoinColumnOrFormula-example[] + + //tag::mapping-JoinColumnOrFormula-example[] @Entity(name = "Country") @Table(name = "countries") @@ -181,6 +184,10 @@ public static class Country implements Serializable { @Column(name = "is_default") private boolean _default; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::mapping-JoinColumnOrFormula-example[] + public int getId() { return id; } @@ -229,6 +236,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getId() ); } + //tag::mapping-JoinColumnOrFormula-example[] } //end::mapping-JoinColumnOrFormula-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java index bb6411061d59..53c129cf9eab 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java @@ -143,6 +143,9 @@ public Country getCountry() { //tag::mapping-JoinFormula-example[] } + //end::mapping-JoinFormula-example[] + + //tag::mapping-JoinFormula-example[] @Entity(name = "Country") @Table(name = "countries") @@ -153,6 +156,10 @@ public static class Country { private String name; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::mapping-JoinFormula-example[] + public int getId() { return id; } @@ -185,6 +192,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getId() ); } + //tag::mapping-JoinFormula-example[] } //end::mapping-JoinFormula-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java index 178b8523a575..6a9b25b800bc 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java @@ -70,6 +70,10 @@ public static class GPS { @Parent private City city; + //Getters and setters omitted for brevity + + //end::mapping-Parent-example[] + private GPS() { } @@ -93,7 +97,11 @@ public City getCity() { public void setCity(City city) { this.city = city; } + //tag::mapping-Parent-example[] } + //end::mapping-Parent-example[] + + //tag::mapping-Parent-example[] @Entity(name = "City") public static class City { diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java index 9a658a46899f..6fc5389b293f 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java @@ -54,6 +54,9 @@ public static class Money { private long cents; + //Getters and setters are omitted for brevity + //end::basic-jpa-convert-money-converter-mapping-example[] + public Money(long cents) { this.cents = cents; } @@ -65,23 +68,12 @@ public long getCents() { public void setCents(long cents) { this.cents = cents; } + //tag::basic-jpa-convert-money-converter-mapping-example[] } - - public static class MoneyConverter - implements AttributeConverter { - - @Override - public Long convertToDatabaseColumn(Money attribute) { - return attribute == null ? null : attribute.getCents(); - } - - @Override - public Money convertToEntityAttribute(Long dbData) { - return dbData == null ? null : new Money( dbData ); - } - } + //end::basic-jpa-convert-money-converter-mapping-example[] //tag::basic-jpa-convert-money-converter-mapping-example[] + @Entity(name = "Account") public static class Account { @@ -94,8 +86,7 @@ public static class Account { private Money balance; //Getters and setters are omitted for brevity - - //end::basic-jpa-convert-money-converter-mapping-example[] + //end::basic-jpa-convert-money-converter-mapping-example[] public Long getId() { return id; } @@ -121,5 +112,19 @@ public void setBalance(Money balance) { } //tag::basic-jpa-convert-money-converter-mapping-example[] } + + public static class MoneyConverter + implements AttributeConverter { + + @Override + public Long convertToDatabaseColumn(Money attribute) { + return attribute == null ? null : attribute.getCents(); + } + + @Override + public Money convertToEntityAttribute(Long dbData) { + return dbData == null ? null : new Money( dbData ); + } + } //end::basic-jpa-convert-money-converter-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java index 42c6ee12a785..c6dc6f87f0a1 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java @@ -141,6 +141,9 @@ public static class Publisher { @ManyToOne(fetch = FetchType.LAZY) private Country country; + //Getters and setters, equals and hashCode methods omitted for brevity + //end::embeddable-multiple-namingstrategy-entity-mapping[] + public Publisher(String name, Country country) { this.name = name; this.country = country; @@ -148,9 +151,6 @@ public Publisher(String name, Country country) { private Publisher() {} - //Getters and setters are omitted for brevity - //end::embeddable-multiple-namingstrategy-entity-mapping[] - public String getName() { return name; } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java index c6c0c2969fb2..31d070b62c5a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java @@ -161,6 +161,10 @@ public static class Publisher { @ManyToOne(fetch = FetchType.LAZY) private Country country; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::embeddable-type-association-mapping-example[] + public Publisher(String name, Country country) { this.name = name; this.country = country; @@ -168,9 +172,6 @@ public Publisher(String name, Country country) { private Publisher() {} - //Getters and setters are omitted for brevity - //end::embeddable-type-association-mapping-example[] - public String getName() { return name; } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java index d1a741d3f3e8..6ae683fde56d 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java @@ -110,6 +110,11 @@ public static class Publisher { @Column(name = "publisher_country") private String country; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::embeddable-type-mapping-example[] + + public Publisher(String name, String country) { this.name = name; this.country = country; @@ -117,9 +122,6 @@ public Publisher(String name, String country) { private Publisher() {} - //Getters and setters are omitted for brevity - //end::embeddable-type-mapping-example[] - public String getName() { return name; } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java index 0d89292922c3..0d8291ebb492 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java @@ -53,6 +53,9 @@ public static class Event { @CreationTimestamp private Date timestamp; + //Constructors, getters and setters are omitted for brevity + //end::mapping-generated-CreationTimestamp-example[] + public Event() {} public Long getId() { @@ -62,6 +65,7 @@ public Long getId() { public Date getTimestamp() { return timestamp; } + //tag::mapping-generated-CreationTimestamp-example[] } //end::mapping-generated-CreationTimestamp-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java index f758bf49f217..c88983b86ed9 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java @@ -56,6 +56,9 @@ public static class Event { @FunctionCreationTimestamp private Date timestamp; + //Constructors, getters and setters are omitted for brevity + //end::mapping-database-generated-value-example[] + public Event() {} public Long getId() { @@ -65,7 +68,11 @@ public Long getId() { public Date getTimestamp() { return timestamp; } + //tag::mapping-database-generated-value-example[] } + //end::mapping-database-generated-value-example[] + + //tag::mapping-database-generated-value-example[] @ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class) @Retention(RetentionPolicy.RUNTIME) diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java index d3bd2e47ed95..705dd6fd0050 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java @@ -56,6 +56,8 @@ public static class Event { @FunctionCreationTimestamp private Date timestamp; + //Constructors, getters and setters are omitted for brevity + //end::mapping-in-memory-generated-value-example[] public Event() {} public Long getId() { @@ -65,7 +67,11 @@ public Long getId() { public Date getTimestamp() { return timestamp; } + //tag::mapping-in-memory-generated-value-example[] } + //end::mapping-in-memory-generated-value-example[] + + //tag::mapping-in-memory-generated-value-example[] @ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class) @Retention(RetentionPolicy.RUNTIME) diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java index e082d7bcd668..e8f447acb745 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java @@ -88,6 +88,10 @@ public class Customer { @LazyGroup( "lobs" ) private Blob image; + //Getters and setters are omitted for brevity + + //end::BytecodeEnhancement-lazy-loading-example[] + public Integer getId() { return id; } @@ -119,6 +123,7 @@ public Blob getImage() { public void setImage(Blob image) { this.image = image; } + //tag::BytecodeEnhancement-lazy-loading-example[] } //end::BytecodeEnhancement-lazy-loading-example[] @@ -132,7 +137,11 @@ public static class Person { private String name; @OneToMany(mappedBy = "author") - private List books = new ArrayList<>( ); + private List books = new ArrayList<>(); + + //Getters and setters are omitted for brevity + + //end::BytecodeEnhancement-dirty-tracking-bidirectional-example[] public Long getId() { return id; @@ -153,6 +162,7 @@ public void setName(String name) { public List getBooks() { return books; } + //tag::BytecodeEnhancement-dirty-tracking-bidirectional-example[] } @Entity(name = "Book") @@ -169,6 +179,10 @@ public static class Book { @ManyToOne private Person author; + //Getters and setters are omitted for brevity + + //end::BytecodeEnhancement-dirty-tracking-bidirectional-example[] + public Long getId() { return id; } @@ -200,6 +214,7 @@ public String getIsbn() { public void setIsbn(String isbn) { this.isbn = isbn; } + //tag::BytecodeEnhancement-dirty-tracking-bidirectional-example[] } //end::BytecodeEnhancement-dirty-tracking-bidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java index a8ebb432baaf..be78241ad8ed 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java @@ -50,7 +50,7 @@ public void test() { } ); } - //tag::pc-cascade-on-delete-mapping-example[] + //tag::pc-cascade-on-delete-mapping-Person-example[] @Entity(name = "Person") public static class Person { @@ -60,7 +60,8 @@ public static class Person { private String name; //Getters and setters are omitted for brevity - //end::pc-cascade-on-delete-mapping-example[] + + //end::pc-cascade-on-delete-mapping-Person-example[] public Long getId() { return id; @@ -77,9 +78,11 @@ public String getName() { public void setName(String name) { this.name = name; } - //tag::pc-cascade-on-delete-mapping-example[] + //tag::pc-cascade-on-delete-mapping-Person-example[] } - + //end::pc-cascade-on-delete-mapping-Person-example[] + + //tag::pc-cascade-on-delete-mapping-Phone-example[] @Entity(name = "Phone") public static class Phone { @@ -94,7 +97,8 @@ public static class Phone { private Person owner; //Getters and setters are omitted for brevity - //end::pc-cascade-on-delete-mapping-example[] + + //end::pc-cascade-on-delete-mapping-Phone-example[] public Long getId() { return id; @@ -119,7 +123,7 @@ public Person getOwner() { public void setOwner(Person owner) { this.owner = owner; } - //tag::pc-cascade-on-delete-mapping-example[] + //tag::pc-cascade-on-delete-mapping-Phone-example[] } - //end::pc-cascade-on-delete-mapping-example[] + //end::pc-cascade-on-delete-mapping-Phone-example[] } \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java index 87d7c75254be..48908b4f63ca 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java @@ -418,6 +418,10 @@ public static class Book { @ManyToOne private Person author; + //Getters and setters are omitted for brevity + + //end::pc-find-by-natural-id-entity-example[] + public Long getId() { return id; } @@ -449,6 +453,7 @@ public String getIsbn() { public void setIsbn(String isbn) { this.isbn = isbn; } + //tag::pc-find-by-natural-id-entity-example[] } //end::pc-find-by-natural-id-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/Person.java b/documentation/src/test/java/org/hibernate/userguide/pc/Person.java index 452e8a918602..620af209a6c8 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/Person.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/Person.java @@ -22,6 +22,9 @@ public class Person { @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity +//end::pc-cascade-domain-model-example[] + public Long getId() { return id; } @@ -42,6 +45,8 @@ public List getPhones() { return phones; } +//tag::pc-cascade-domain-model-example[] + public void addPhone(Phone phone) { this.phones.add( phone ); phone.setOwner( this ); diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java b/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java index d9e58df21254..9208313a0cbe 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java @@ -22,6 +22,9 @@ public class Phone { @ManyToOne(fetch = FetchType.LAZY) private Person owner; + //Getters and setters are omitted for brevity +//end::pc-cascade-domain-model-example[] + public Long getId() { return id; } @@ -45,5 +48,6 @@ public Person getOwner() { public void setOwner(Person owner) { this.owner = owner; } +//tag::pc-cascade-domain-model-example[] } //end::pc-cascade-domain-model-example[] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/persister/Author.java b/documentation/src/test/java/org/hibernate/userguide/persister/Author.java index f1ca79138da5..4dd34a788188 100644 --- a/documentation/src/test/java/org/hibernate/userguide/persister/Author.java +++ b/documentation/src/test/java/org/hibernate/userguide/persister/Author.java @@ -31,6 +31,7 @@ public class Author { public Set books = new HashSet<>(); //Getters and setters omitted for brevity + //end::entity-persister-mapping[] public Integer getId() { diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java index 0f75862e0292..dc0ecf863a5e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java @@ -91,6 +91,10 @@ public class Customer { @LazyGroup( "lobs" ) private Blob image; + //Getters and setters are omitted for brevity + + //end::schema-generation-domain-model-example[] + public Integer getId() { return id; } @@ -122,6 +126,7 @@ public Blob getImage() { public void setImage(Blob image) { this.image = image; } + //tag::schema-generation-domain-model-example[] } @Entity(name = "Person") @@ -133,7 +138,11 @@ public static class Person { private String name; @OneToMany(mappedBy = "author") - private List books = new ArrayList<>( ); + private List books = new ArrayList<>(); + + //Getters and setters are omitted for brevity + + //end::schema-generation-domain-model-example[] public Long getId() { return id; @@ -154,6 +163,7 @@ public void setName(String name) { public List getBooks() { return books; } + //tag::schema-generation-domain-model-example[] } @Entity(name = "Book") @@ -170,6 +180,10 @@ public static class Book { @ManyToOne private Person author; + //Getters and setters are omitted for brevity + + //end::schema-generation-domain-model-example[] + public Long getId() { return id; } @@ -201,6 +215,7 @@ public String getIsbn() { public void setIsbn(String isbn) { this.isbn = isbn; } + //tag::schema-generation-domain-model-example[] } //end::schema-generation-domain-model-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java b/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java index aaa2e2a76c57..a48168b5a56a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java @@ -21,6 +21,10 @@ public class Captain { @EmbeddedId private Identity id; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public Identity getId() { return id; } @@ -28,5 +32,6 @@ public Identity getId() { public void setId(Identity id) { this.id = id; } +//tag::sql-composite-key-entity-associations_named-query-example[] } //end::sql-composite-key-entity-associations_named-query-example[] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java index 9635a8b5348d..6f7067f747f7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java @@ -43,122 +43,127 @@ @RequiresDialect(PostgreSQL82Dialect.class) public class CustomSQLSecondaryTableTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Person.class - }; - } - - @Before - public void init() { - doInJPA( this::entityManagerFactory, entityManager -> { - Session session = entityManager.unwrap( Session.class ); - session.doWork( connection -> { - try(Statement statement = connection.createStatement(); ) { - statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" ); - statement.executeUpdate( "ALTER TABLE person_details ADD COLUMN valid boolean" ); - } - } ); - }); - } - - @Test - public void test_sql_custom_crud() { - - Person _person = doInJPA( this::entityManagerFactory, entityManager -> { - Person person = new Person(); - person.setName( "John Doe" ); - entityManager.persist( person ); - person.setImage( new byte[] {1, 2, 3} ); - return person; - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - Long postId = _person.getId(); - Person person = entityManager.find( Person.class, postId ); - assertArrayEquals(new byte[] {1, 2, 3}, person.getImage()); - entityManager.remove( person ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - Long postId = _person.getId(); - Person person = entityManager.find( Person.class, postId ); - assertNull(person); - } ); - } - - - //tag::sql-custom-crud-secondary-table-example[] - @Entity(name = "Person") - @Table(name = "person") - @SQLInsert( - sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) " - ) - @SQLDelete( - sql = "UPDATE person SET valid = false WHERE id = ? " - ) - @SecondaryTable(name = "person_details", - pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id")) - @org.hibernate.annotations.Table( - appliesTo = "person_details", - sqlInsert = @SQLInsert( - sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ", - check = ResultCheckStyle.COUNT - ), - sqlDelete = @SQLDelete( - sql = "UPDATE person_details SET valid = false WHERE person_id = ? " - ) - ) - @Loader(namedQuery = "find_valid_person") - @NamedNativeQueries({ - @NamedNativeQuery( - name = "find_valid_person", - query = "select " + - " p.id, " + - " p.name, " + - " pd.image " + - "from person p " + - "left outer join person_details pd on p.id = pd.person_id " + - "where p.id = ? and p.valid = true and pd.valid = true", - resultClass = Person.class - ) - }) - public static class Person { - - @Id - @GeneratedValue - private Long id; - - private String name; - - @Column(name = "image", table = "person_details") - private byte[] image; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public byte[] getImage() { - return image; - } - - public void setImage(byte[] image) { - this.image = image; - } - } - //end::sql-custom-crud-secondary-table-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + session.doWork( connection -> { + try(Statement statement = connection.createStatement(); ) { + statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" ); + statement.executeUpdate( "ALTER TABLE person_details ADD COLUMN valid boolean" ); + } + } ); + }); + } + + @Test + public void test_sql_custom_crud() { + + Person _person = doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setName( "John Doe" ); + entityManager.persist( person ); + person.setImage( new byte[] {1, 2, 3} ); + return person; + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Long postId = _person.getId(); + Person person = entityManager.find( Person.class, postId ); + assertArrayEquals(new byte[] {1, 2, 3}, person.getImage()); + entityManager.remove( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Long postId = _person.getId(); + Person person = entityManager.find( Person.class, postId ); + assertNull(person); + } ); + } + + + //tag::sql-custom-crud-secondary-table-example[] + @Entity(name = "Person") + @Table(name = "person") + @SQLInsert( + sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) " + ) + @SQLDelete( + sql = "UPDATE person SET valid = false WHERE id = ? " + ) + @SecondaryTable(name = "person_details", + pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id")) + @org.hibernate.annotations.Table( + appliesTo = "person_details", + sqlInsert = @SQLInsert( + sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ", + check = ResultCheckStyle.COUNT + ), + sqlDelete = @SQLDelete( + sql = "UPDATE person_details SET valid = false WHERE person_id = ? " + ) + ) + @Loader(namedQuery = "find_valid_person") + @NamedNativeQueries({ + @NamedNativeQuery( + name = "find_valid_person", + query = "SELECT " + + " p.id, " + + " p.name, " + + " pd.image " + + "FROM person p " + + "LEFT OUTER JOIN person_details pd ON p.id = pd.person_id " + + "WHERE p.id = ? AND p.valid = true AND pd.valid = true", + resultClass = Person.class + ) + }) + public static class Person { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Column(name = "image", table = "person_details") + private byte[] image; + + //Getters and setters are omitted for brevity + + //end::sql-custom-crud-secondary-table-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public byte[] getImage() { + return image; + } + + public void setImage(byte[] image) { + this.image = image; + } + //tag::sql-custom-crud-secondary-table-example[] + } + //end::sql-custom-crud-secondary-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java index 427ff6ac4993..0489bcb251c6 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java @@ -99,7 +99,6 @@ public void test_sql_custom_crud() { } ); } - //tag::sql-custom-crud-example[] @Entity(name = "Person") @SQLInsert( @@ -107,9 +106,11 @@ public void test_sql_custom_crud() { check = ResultCheckStyle.COUNT ) @SQLUpdate( - sql = "UPDATE person SET name = ? where id = ? ") + sql = "UPDATE person SET name = ? where id = ? " + ) @SQLDelete( - sql = "UPDATE person SET valid = false WHERE id = ? ") + sql = "UPDATE person SET valid = false WHERE id = ? " + ) @Loader(namedQuery = "find_valid_person") @NamedNativeQueries({ @NamedNativeQuery( @@ -136,6 +137,10 @@ public static class Person { @Where( clause = "valid = true" ) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::sql-custom-crud-example[] + public Long getId() { return id; } @@ -155,7 +160,7 @@ public void setName(String name) { public List getPhones() { return phones; } + //tag::sql-custom-crud-example[] } //end::sql-custom-crud-example[] - } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java b/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java index 30c8f9ff6ca9..e34918adf321 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java @@ -21,6 +21,10 @@ public class Dimensions { private int width; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public int getLength() { return length; } @@ -36,5 +40,6 @@ public int getWidth() { public void setWidth(int width) { this.width = width; } +//tag::sql-composite-key-entity-associations_named-query-example[] } //end::sql-composite-key-entity-associations_named-query-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java b/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java index af6162aba20c..ea195067bc99 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java @@ -22,6 +22,10 @@ public class Identity implements Serializable { private String lastname; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public String getFirstname() { return firstname; } @@ -38,6 +42,7 @@ public void setLastname(String lastname) { this.lastname = lastname; } +//tag::sql-composite-key-entity-associations_named-query-example[] public boolean equals(Object o) { if ( this == o ) return true; if ( o == null || getClass() != o.getClass() ) return false; diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java b/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java index 0a1d2f223573..92d6d1e459ce 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java @@ -16,6 +16,8 @@ public class PersonSummaryDTO { private String name; + //Getters and setters are omitted for brevity + public Number getId() { return id; } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java index a25e23a9279e..dec186e2a262 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java @@ -495,10 +495,10 @@ public void test_sql_hibernate_multi_entity_query_example() { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-multi-entity-query-example[] List entities = session.createNativeQuery( - "SELECT * " + - "FROM Person pr, Partner pt " + - "WHERE pr.name = pt.name" ) - .list(); + "SELECT * " + + "FROM Person pr, Partner pt " + + "WHERE pr.name = pt.name" ) + .list(); //end::sql-hibernate-multi-entity-query-example[] assertEquals( 2, entities.size() ); } ); diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java b/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java index 9fca478e1cbb..c2b56e7f6509 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java @@ -81,6 +81,10 @@ public class SpaceShip { private Dimensions dimensions; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public String getName() { return name; } @@ -120,5 +124,6 @@ public Dimensions getDimensions() { public void setDimensions(Dimensions dimensions) { this.dimensions = dimensions; } +//tag::sql-composite-key-entity-associations_named-query-example[] } //end::sql-composite-key-entity-associations_named-query-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java index 55fd7babe647..a21b27896239 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java @@ -127,6 +127,10 @@ public static class Person implements Serializable { private boolean employed; + //Getters and setters are omitted for brevity + + //end::batch-bulk-hql-temp-table-base-class-example[] + public Integer getId() { return id; } @@ -176,6 +180,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getId(), getCompanyName() ); } + //tag::batch-bulk-hql-temp-table-base-class-example[] } //end::batch-bulk-hql-temp-table-base-class-example[] @@ -189,6 +194,10 @@ public static class Engineer extends Person { private boolean fellow; + //Getters and setters are omitted for brevity + + //end::batch-bulk-hql-temp-table-sub-classes-example[] + public boolean isFellow() { return fellow; } @@ -196,6 +205,7 @@ public boolean isFellow() { public void setFellow(boolean fellow) { this.fellow = fellow; } + //tag::batch-bulk-hql-temp-table-sub-classes-example[] } //end::batch-bulk-hql-temp-table-sub-classes-example[] } \ No newline at end of file From 7eb0cee1787ce5c4a0c91da56f2218f42db5662c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 13 Aug 2018 13:17:25 +0200 Subject: [PATCH 137/772] HHH-12907 Avoid creating too many ByteBuddy objects This commit should reduce the garbage collection pressure as reported at https://github.com/raphw/byte-buddy/issues/515. --- .../bytebuddy/BasicProxyFactoryImpl.java | 10 +-- .../internal/bytebuddy/ByteBuddyState.java | 71 +++++++++++++++++++ .../pojo/bytebuddy/ByteBuddyProxyHelper.java | 27 ++----- 3 files changed, 79 insertions(+), 29 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index 46ba636b4397..eae143eb673a 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -20,10 +20,6 @@ import net.bytebuddy.TypeCache; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; -import net.bytebuddy.implementation.FieldAccessor; -import net.bytebuddy.implementation.MethodDelegation; -import net.bytebuddy.implementation.bytecode.assign.Assigner; -import net.bytebuddy.matcher.ElementMatchers; public class BasicProxyFactoryImpl implements BasicProxyFactory { @@ -47,10 +43,10 @@ public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyStat .subclass( superClass == null ? Object.class : superClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR ) .implement( interfaces == null ? NO_INTERFACES : interfaces ) .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) ) - .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) + .method( byteBuddyState.getProxyDefinitionHelpers().getVirtualNotFinalizerFilter() ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getDelegateToInterceptorDispatcherMethodDelegation() ) .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ) ); this.interceptor = new PassThroughInterceptor( proxyClass.getName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index 3acdf37439fb..0c464204728b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -6,6 +6,13 @@ */ package org.hibernate.bytecode.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.isFinalizer; +import static net.bytebuddy.matcher.ElementMatchers.isSynthetic; +import static net.bytebuddy.matcher.ElementMatchers.isVirtual; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.returns; import static org.hibernate.internal.CoreLogging.messageLogger; import java.io.File; @@ -20,17 +27,23 @@ import org.hibernate.HibernateException; import org.hibernate.bytecode.spi.BasicProxyFactory; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.proxy.ProxyConfiguration; import org.hibernate.proxy.ProxyFactory; import net.bytebuddy.ByteBuddy; import net.bytebuddy.TypeCache; import net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods; import net.bytebuddy.asm.MemberSubstitution; +import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.DynamicType.Unloaded; import net.bytebuddy.dynamic.loading.ClassInjector; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.dynamic.scaffold.TypeValidation; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.pool.TypePool; @@ -52,6 +65,8 @@ public final class ByteBuddyState { private final ForDeclaredMethods getDeclaredMethodMemberSubstitution; private final ForDeclaredMethods getMethodMemberSubstitution; + private final ProxyDefinitionHelpers proxyDefinitionHelpers; + /** * It will be easier to maintain the cache and its state when it will no longer be static * in Hibernate ORM 6+. @@ -75,6 +90,8 @@ public final class ByteBuddyState { this.getDeclaredMethodMemberSubstitution = null; this.getMethodMemberSubstitution = null; } + + this.proxyDefinitionHelpers = new ProxyDefinitionHelpers(); } /** @@ -135,6 +152,17 @@ public byte[] rewrite(TypePool typePool, String className, byte[] originalBytes, return make( typePool, builder ).getBytes(); } + /** + * Returns the proxy definition helpers to reuse when defining proxies. + *

    + * These elements are shared as they are immutable. + * + * @return The proxy definition helpers. + */ + public ProxyDefinitionHelpers getProxyDefinitionHelpers() { + return proxyDefinitionHelpers; + } + /** * Wipes out all known caches used by ByteBuddy. This implies it might trigger the need * to re-create some helpers if used at runtime, especially as this state is shared by @@ -276,4 +304,47 @@ public Method run() { } } } + + /** + * Shared proxy definition helpers. They are immutable so we can safely share them. + */ + public static class ProxyDefinitionHelpers { + + private final ElementMatcher groovyGetMetaClassFilter; + private final ElementMatcher virtualNotFinalizerFilter; + private final ElementMatcher hibernateGeneratedMethodFilter; + private final MethodDelegation delegateToInterceptorDispatcherMethodDelegation; + private final FieldAccessor.PropertyConfigurable interceptorFieldAccessor; + + private ProxyDefinitionHelpers() { + this.groovyGetMetaClassFilter = isSynthetic().and( named( "getMetaClass" ) + .and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ); + this.virtualNotFinalizerFilter = isVirtual().and( not( isFinalizer() ) ); + this.hibernateGeneratedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() ); + this.delegateToInterceptorDispatcherMethodDelegation = MethodDelegation + .to( ProxyConfiguration.InterceptorDispatcher.class ); + this.interceptorFieldAccessor = FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) + .withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ); + } + + public ElementMatcher getGroovyGetMetaClassFilter() { + return groovyGetMetaClassFilter; + } + + public ElementMatcher getVirtualNotFinalizerFilter() { + return virtualNotFinalizerFilter; + } + + public ElementMatcher getHibernateGeneratedMethodFilter() { + return hibernateGeneratedMethodFilter; + } + + public MethodDelegation getDelegateToInterceptorDispatcherMethodDelegation() { + return delegateToInterceptorDispatcherMethodDelegation; + } + + public FieldAccessor.PropertyConfigurable getInterceptorFieldAccessor() { + return interceptorFieldAccessor; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java index f94cf4b94357..2e3d5fdef541 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java @@ -6,17 +6,8 @@ */ package org.hibernate.proxy.pojo.bytebuddy; -import static net.bytebuddy.matcher.ElementMatchers.isFinalizer; -import static net.bytebuddy.matcher.ElementMatchers.isSynthetic; -import static net.bytebuddy.matcher.ElementMatchers.isVirtual; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.not; -import static net.bytebuddy.matcher.ElementMatchers.returns; import static org.hibernate.internal.CoreLogging.messageLogger; -import java.io.File; -import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -28,24 +19,16 @@ import org.hibernate.HibernateException; import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.cfg.Environment; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; -import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.ProxyConfiguration; -import org.hibernate.proxy.ProxyFactory; -import org.hibernate.type.CompositeType; import net.bytebuddy.NamingStrategy; import net.bytebuddy.TypeCache; import net.bytebuddy.description.modifier.Visibility; -import net.bytebuddy.dynamic.DynamicType.Unloaded; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; -import net.bytebuddy.implementation.FieldAccessor; -import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.SuperMethodCall; -import net.bytebuddy.implementation.bytecode.assign.Assigner; public class ByteBuddyProxyHelper implements Serializable { @@ -69,17 +52,17 @@ public Class buildProxy( key.addAll( Arrays.>asList( interfaces ) ); return byteBuddyState.loadProxy( persistentClass, new TypeCache.SimpleKey(key), byteBuddy -> byteBuddy - .ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) ) + .ignore( byteBuddyState.getProxyDefinitionHelpers().getGroovyGetMetaClassFilter() ) .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) .implement( (Type[]) interfaces ) - .method( isVirtual().and( not( isFinalizer() ) ) ) - .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) - .method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) ) + .method( byteBuddyState.getProxyDefinitionHelpers().getVirtualNotFinalizerFilter() ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getDelegateToInterceptorDispatcherMethodDelegation() ) + .method( byteBuddyState.getProxyDefinitionHelpers().getHibernateGeneratedMethodFilter() ) .intercept( SuperMethodCall.INSTANCE ) .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ) ); } From 6eaaa49faa14e49bc81b7b4a9520f26238185c9b Mon Sep 17 00:00:00 2001 From: Karel Maesen Date: Sun, 12 Aug 2018 17:08:06 +0200 Subject: [PATCH 138/772] HHH-12608 Support for ST_Dwithin() in DB2 --- .../dialect/db2/DB2SpatialDialect.java | 28 ++++++++++++++++++- .../dialects/db2/DB2ExpectationsFactory.java | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/db2/DB2SpatialDialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/db2/DB2SpatialDialect.java index 1e1afc6d5061..263b946efddb 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/db2/DB2SpatialDialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/db2/DB2SpatialDialect.java @@ -7,6 +7,7 @@ package org.hibernate.spatial.dialect.db2; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.hibernate.HibernateException; @@ -14,6 +15,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.GeolatteGeometryJavaTypeDescriptor; import org.hibernate.spatial.GeolatteGeometryType; @@ -25,6 +27,9 @@ import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.Type; + +import static java.lang.String.format; /** * @author David Adler, Adtech Geospatial @@ -223,6 +228,7 @@ private void registerSpatialFunctions() { ) ); // Register non-SFS functions listed in Hibernate Spatial + registerFunction( "dwithin", new DWithinFunction()); // // The srid parameter needs to be explicitly cast to INTEGER to avoid a -245 SQLCODE, // // ambiguous parameter. @@ -244,7 +250,7 @@ private void registerSpatialFunctions() { @Override public String getDWithinSQL(String columnName) { - return "db2gse.ST_Distance(" + columnName + ",?) < ?"; + return "db2gse.ST_Intersects(" + columnName + ", db2gse.ST_Buffer(?, ?, 'METER')) = 1"; } @Override @@ -317,4 +323,24 @@ public boolean supports(SpatialFunction function) { public boolean supportsFiltering() { return false; } + + + private static class DWithinFunction extends StandardSQLFunction { + + public DWithinFunction() { + super( "db2gse.ST_Dwithin" , StandardBasicTypes.NUMERIC_BOOLEAN); + } + + public String render(Type firstArgumentType, final List args, final SessionFactoryImplementor factory) { + StringBuilder sb = new StringBuilder( "db2gse.ST_Intersects( " ); + sb.append( (String)args.get(0) ) // + .append(", db2gse.ST_Buffer(") + .append((String)args.get(1) ) + .append(", ") + .append((String)args.get(2) ) + .append(", 'METER'))"); + return sb.toString(); + } + + } } diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/db2/DB2ExpectationsFactory.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/db2/DB2ExpectationsFactory.java index e90d3b03c9d6..c23873824e30 100644 --- a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/db2/DB2ExpectationsFactory.java +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/db2/DB2ExpectationsFactory.java @@ -88,7 +88,7 @@ protected NativeSQLStatement createNativeRelateStatement(Geometry geom, String m @Override protected NativeSQLStatement createNativeDwithinStatement(Point geom, double distance) { - String sql = "select t.id, DB2GSE.ST_dwithin(t.geom, DB2GSE.ST_GeomFromText(?, 4326), " + distance + " ) from GeomTest t where DB2GSE.ST_dwithin(t.geom, DB2GSE.ST_GeomFromText(?, 4326), " + distance + ") = 1 and db2gse.st_srid(t.geom) = 4326"; + String sql = "select t.id, DB2GSE.ST_dwithin(DB2GSE.ST_GeomFromText(?, 4326), t.geom, " + distance + " , 'METER') from GeomTest t where DB2GSE.ST_dwithin(DB2GSE.ST_GeomFromText(?, 4326), t.geom, " + distance + ", 'METER') = 1 and db2gse.st_srid(t.geom) = 4326"; return createNativeSQLStatementAllWKTParams( sql, geom.toText() ); } From 98bd7e38981dbfdb7d7f2b9dbcb718045b2a7a6c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 13 Aug 2018 17:31:47 +0200 Subject: [PATCH 139/772] HHH-12909 Upgrade ByteBuddy to 1.8.17 --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 1684b9087656..3f0ed28a6a3d 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -23,7 +23,7 @@ ext { weldVersion = '3.0.0.Final' javassistVersion = '3.23.1-GA' - byteBuddyVersion = '1.8.15' // Now with JDK10 compatibility and preliminary support for JDK11 + byteBuddyVersion = '1.8.17' // Now with JDK10 compatibility and preliminary support for JDK11 geolatteVersion = '1.3.0' From d0d95ab086e52771bf0a05d844facfd2668b083a Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 8 Aug 2018 13:29:16 +0300 Subject: [PATCH 140/772] HHH-12892 - Fix spelling issues in the User Guide --- .../chapters/services/Services.adoc | 2 +- .../asciidoc/quickstart/guides/preface.adoc | 2 +- .../bootstrap/LegacyBootstrapping.adoc | 2 +- .../bootstrap/NativeBootstrapping.adoc | 2 +- .../topical/bytecode/BytecodeEnhancement.adoc | 6 +- .../metamodelgen/MetamodelGenerator.adoc | 2 +- .../topical/registries/ServiceRegistries.adoc | 2 +- .../asciidoc/topical/wildfly/Wildfly.adoc | 6 +- .../src/main/asciidoc/userguide/Preface.adoc | 4 +- .../userguide/appendices/Annotations.adoc | 86 +++++++++---------- .../userguide/appendices/BestPractices.adoc | 8 +- .../userguide/appendices/Configurations.adoc | 76 ++++++++-------- .../appendices/Legacy_Bootstrap.adoc | 2 +- .../userguide/appendices/Legacy_Criteria.adoc | 6 +- .../appendices/Legacy_DomainModel.adoc | 2 +- .../appendices/Legacy_Native_Queries.adoc | 2 +- .../chapters/architecture/Architecture.adoc | 2 +- .../userguide/chapters/batch/Batching.adoc | 10 +-- .../chapters/bootstrap/Bootstrap.adoc | 20 ++--- .../userguide/chapters/caching/Caching.adoc | 22 ++--- .../chapters/domain/associations.adoc | 6 +- .../chapters/domain/basic_types.adoc | 50 +++++------ .../chapters/domain/collections.adoc | 24 +++--- .../chapters/domain/dynamic_model.adoc | 6 +- .../chapters/domain/embeddables.adoc | 28 +++--- .../userguide/chapters/domain/entity.adoc | 20 ++--- .../chapters/domain/identifiers.adoc | 24 +++--- .../chapters/domain/inheritance.adoc | 10 +-- .../userguide/chapters/domain/naming.adoc | 6 +- .../userguide/chapters/domain/natural_id.adoc | 3 +- .../userguide/chapters/domain/types.adoc | 4 +- .../userguide/chapters/envers/Envers.adoc | 82 +++++++++--------- .../userguide/chapters/events/Events.adoc | 10 +-- .../userguide/chapters/fetching/Fetching.adoc | 12 +-- .../userguide/chapters/flushing/Flushing.adoc | 16 ++-- .../chapters/jdbc/Database_Access.adoc | 8 +- .../userguide/chapters/locking/Locking.adoc | 18 ++-- .../chapters/multitenancy/MultiTenancy.adoc | 4 +- .../userguide/chapters/osgi/OSGi.adoc | 12 +-- .../chapters/pc/BytecodeEnhancement.adoc | 26 +++--- .../chapters/pc/PersistenceContext.adoc | 26 +++--- .../chapters/portability/Portability.adoc | 8 +- .../chapters/query/criteria/Criteria.adoc | 8 +- .../userguide/chapters/query/hql/HQL.adoc | 68 +++++++-------- .../chapters/query/native/Native.adoc | 31 +++---- .../chapters/query/spatial/Spatial.adoc | 21 ++--- .../userguide/chapters/schema/Schema.adoc | 2 +- .../chapters/transactions/Transactions.adoc | 26 +++--- .../userguide/hql/CallStatistics.java | 6 +- .../mapping/basic/BitSetUserTypeTest.java | 2 +- .../generated/CreationTimestampTest.java | 2 +- .../DatabaseValueGenerationTest.java | 2 +- .../InMemoryValueGenerationTest.java | 2 +- 53 files changed, 419 insertions(+), 418 deletions(-) diff --git a/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc b/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc index 9ba671f81ea5..a23200c17388 100644 --- a/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc +++ b/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc @@ -11,7 +11,7 @@ It will also delve into the ways third-party integrators and applications can le === What is a Service? A services provides a certain types of functionality, in a pluggable manner. -Specifically they are interfaces defining certain functionality and then implementations of those `Service` contract interfaces. +Specifically, they are interfaces defining certain functionality and then implementations of those `Service` contract interfaces. The interface is known as the `Service` role; the implementation class is known as the `Service` implementation. The pluggability comes from the fact that the `Service` implementation adheres to contract defined by the interface of the `Service` role and that consumers of the `Service` program to the `Service` role, not the implementation. diff --git a/documentation/src/main/asciidoc/quickstart/guides/preface.adoc b/documentation/src/main/asciidoc/quickstart/guides/preface.adoc index 77c2417a8a77..ba4111af91ba 100644 --- a/documentation/src/main/asciidoc/quickstart/guides/preface.adoc +++ b/documentation/src/main/asciidoc/quickstart/guides/preface.adoc @@ -3,7 +3,7 @@ [preface] == Preface -Working with both Object-Oriented software and Relational Databases can be cumbersome and time consuming. +Working with both Object-Oriented software and Relational Databases can be cumbersome and time-consuming. Development costs are significantly higher due to a paradigm mismatch between how data is represented in objects versus relational databases. Hibernate is an Object/Relational Mapping (ORM) solution for Java environments. The term Object/Relational Mapping refers to the technique of mapping data between an object model representation to diff --git a/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc b/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc index 2f9641b4efea..6f82eda7c655 100644 --- a/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc +++ b/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc @@ -58,7 +58,7 @@ There are other ways to specify configuration properties, including: * Place a file named hibernate.properties in a root directory of the classpath. * Place a file named hibernate.properties in a root directory of the classpath. * Pass an instance of java.util.Properties to `Configuration#setProperties`. -* Set System properties using java `-Dproperty=value`. +* Set System properties using Java `-Dproperty=value`. * Include `` elements in `hibernate.cfg.xml` diff --git a/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc b/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc index c6312902b77f..e596c9ef3dd0 100644 --- a/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc +++ b/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc @@ -4,7 +4,7 @@ This guide discusses the process of bootstrapping a Hibernate `org.hibernate.SessionFactory`. It also discusses the ways in which applications and integrators can hook-in to and affect that process. This bootstrapping process is defined in 2 distinct steps. The first step is the building of a ServiceRegistry -holding the services Hibernate will need at bootstrap- and run-time. The second step is the building of +holding the services Hibernate will need at bootstrap- and runtime. The second step is the building of a Metadata object representing the mapping information for the application's model and its mapping to the database. diff --git a/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc b/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc index 70e602683806..3e5922680f19 100644 --- a/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc +++ b/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc @@ -23,11 +23,11 @@ Ultimately all enhancement is handled by the `org.hibernate.bytecode.enhance.spi enhancement can certainly be crafted on top of Enhancer, but that is beyond the scope of this guide. Here we will focus on the means Hibernate already exposes for performing these enhancements. -=== Run-time enhancement +=== Runtime enhancement -Currently run-time enhancement of the domain model is only supported in managed JPA environments following the JPA defined SPI for performing class transformations. +Currently runtime enhancement of the domain model is only supported in managed JPA environments following the JPA defined SPI for performing class transformations. -Even then, this support is disabled by default. To enable run-time enhancement, specify one of the following configuration properties: +Even then, this support is disabled by default. To enable runtime enhancement, specify one of the following configuration properties: `*hibernate.enhancer.enableDirtyTracking*` (e.g. `true` or `false` (default value)):: Enable dirty tracking feature in runtime bytecode enhancement. diff --git a/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc b/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc index 8b203354ee70..ed04869b9790 100644 --- a/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc +++ b/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc @@ -187,7 +187,7 @@ http://sourceforge.net/projects/hibernate/files/hibernate4[SourceForge]. In most cases the annotation processor will automatically run provided the processor jar is added to the build classpath and a JDK >6 is used. This happens due to Java's Service Provider contract and the fact -the the Hibernate Static Metamodel Generator jar files contains the +the Hibernate Static Metamodel Generator jar files contains the file _javax.annotation.processing.Processor_ in the _META-INF/services_ directory. The fully qualified name of the processor itself is: diff --git a/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc b/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc index 462d2d4ec498..856dba0fc0d9 100644 --- a/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc +++ b/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc @@ -12,7 +12,7 @@ applications can leverage and customize Services and Registries. == What is a Service? -Services provide various types of functionality, in a pluggable manner. Specifically they are interfaces defining +Services provide various types of functionality, in a pluggable manner. Specifically, they are interfaces defining certain functionality and then implementations of those service contract interfaces. The interface is known as the service role; the implementation class is known as the service implementation. The pluggability comes from the fact that the service implementation adheres to contract defined by the interface of the service role and that consumers diff --git a/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc b/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc index 128393652bf2..33abdd0d2f8d 100644 --- a/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc +++ b/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc @@ -266,9 +266,9 @@ By convention all modules included with WildFly use the "main" slot, while the m will use a slot name which matches the version, and also provide an alias to match its "major.minor" version. Our suggestion is to depend on the module using the "major.minor" representation, as this simplifies rolling out bugfix -releases (micro version updates) of Hibernate ORM without changing application configuration (micro versions are always expected to be backwards compatible and released as bugfix only). +releases (micro version updates) of Hibernate ORM without changing application configuration (micro versions are always expected to be backward compatible and released as bugfix only). -For example if your application wants to use the latest version of Hibernate ORM version {majorMinorVersion}.x it should declare to use the module _org.hibernate:{majorMinorVersion}_. You can of course decide to use the full version instead for more precise control, in case an application requires a very specific version. +For example, if your application wants to use the latest version of Hibernate ORM version {majorMinorVersion}.x it should declare to use the module _org.hibernate:{majorMinorVersion}_. You can of course decide to use the full version instead for more precise control, in case an application requires a very specific version. == Switch to a different Hibernate ORM slot @@ -311,7 +311,7 @@ you might want to check it out as it lists several other useful properties. When using the custom modules provided by the feature packs you're going to give up on some of the integration which the application server normally automates. -For example enabling an Infinispan 2nd level cache is straight forward when using the default Hibernate ORM +For example, enabling an Infinispan 2nd level cache is straight forward when using the default Hibernate ORM module, as WildFly will automatically setup the dependency to the Infinispan and clustering components. When using these custom modules such integration will no longer work automatically: you can still enable all normally available features but these will require explicit configuration, as if you were diff --git a/documentation/src/main/asciidoc/userguide/Preface.adoc b/documentation/src/main/asciidoc/userguide/Preface.adoc index ec196cd5a514..d21ebf8f564f 100644 --- a/documentation/src/main/asciidoc/userguide/Preface.adoc +++ b/documentation/src/main/asciidoc/userguide/Preface.adoc @@ -1,10 +1,10 @@ [[preface]] == Preface -Working with both Object-Oriented software and Relational Databases can be cumbersome and time consuming. +Working with both Object-Oriented software and Relational Databases can be cumbersome and time-consuming. Development costs are significantly higher due to a paradigm mismatch between how data is represented in objects versus relational databases. Hibernate is an Object/Relational Mapping solution for Java environments. -The term http://en.wikipedia.org/wiki/Object-relational_mapping[Object/Relational Mapping] refers to the technique of mapping data from an object model representation to a relational data model representation (and visa versa). +The term http://en.wikipedia.org/wiki/Object-relational_mapping[Object/Relational Mapping] refers to the technique of mapping data from an object model representation to a relational data model representation (and vice versa). Hibernate not only takes care of the mapping from Java classes to database tables (and from Java data types to SQL data types), but also provides data query and retrieval facilities. It can significantly reduce development time otherwise spent with manual data handling in SQL and JDBC. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index 6911c1afcf17..8c0e4e99e7f6 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -56,7 +56,7 @@ See the <> chapter for more info. [[annotations-jpa-collectiontable]] ==== `@CollectionTable` -The http://docs.oracle.com/javaee/7/api/javax/persistence/CollectionTable.html[`@CollectionTable`] annotation is used to specify the database table that stores the values of a basic or an embeddable type collection. +The http://docs.oracle.com/javaee/7/api/javax/persistence/CollectionTable.html[`@CollectionTable`] annotation is used to specify the database table that stores the values of basic or an embeddable type collection. See the <> section for more info. @@ -84,7 +84,7 @@ See the <> section for more info. @@ -159,7 +159,7 @@ See the <> section for more info. [[annotations-jpa-entitylisteners]] ==== `@EntityListeners` -The http://docs.oracle.com/javaee/7/api/javax/persistence/EntityListeners.html[`@EntityListeners`] annotation is used to specify an array of callback listener classes that are used by the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/EntityListeners.html[`@EntityListeners`] annotation is used to specify an array of callback listener classes that are used by the currently annotated entity. See the <> section for more info. @@ -180,14 +180,14 @@ See the <> section for more info. [[annotations-jpa-excludesuperclasslisteners]] ==== `@ExcludeSuperclassListeners` -The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] annotation is used to specify that the current annotated entity skips the invocation of listeners declared by its superclass. +The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] annotation is used to specify that the currently annotated entity skips the invocation of listeners declared by its superclass. See the <> section for more info. @@ -266,7 +266,7 @@ See the <> section for more info. @@ -303,7 +303,7 @@ See the <> section for an example of `@MapKeyColumn` annotation usage. +See the <> for an example of `@MapKeyColumn` annotation usage. [[annotations-jpa-mapkeyenumerated]] ==== `@MapKeyEnumerated` @@ -335,14 +335,14 @@ See the <> section for more info. [[annotations-jpa-mapsid]] ==== `@MapsId` -The http://docs.oracle.com/javaee/7/api/javax/persistence/MapsId.html[`@MapsId`] annotation is used to specify that the entity identifier is mapped by the current annotated `@ManyToOne` or `@OneToOne` association. +The http://docs.oracle.com/javaee/7/api/javax/persistence/MapsId.html[`@MapsId`] annotation is used to specify that the entity identifier is mapped by the currently annotated `@ManyToOne` or `@OneToOne` association. See the <> section for more info. @@ -427,7 +427,7 @@ See the <> section for more info. @@ -521,7 +521,7 @@ See the <> section for more info. @@ -541,7 +541,7 @@ See the <> section for more info. @@ -553,7 +553,7 @@ The http://docs.oracle.com/javaee/7/api/javax/persistence/SecondaryTables.html[` [[annotations-jpa-sequencegenerator]] ==== `@SequenceGenerator` -The http://docs.oracle.com/javaee/7/api/javax/persistence/SequenceGenerator.html[`@SequenceGenerator`] annotation is used to specify the database sequence used by the identifier generator of the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/SequenceGenerator.html[`@SequenceGenerator`] annotation is used to specify the database sequence used by the identifier generator of the currently annotated entity. See the <> section for more info. @@ -579,21 +579,21 @@ See the <> section for more info. [[annotations-jpa-tablegenerator]] ==== `@TableGenerator` -The http://docs.oracle.com/javaee/7/api/javax/persistence/TableGenerator.html[`@TableGenerator`] annotation is used to specify the database table used by the identity generator of the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/TableGenerator.html[`@TableGenerator`] annotation is used to specify the database table used by the identity generator of the currently annotated entity. See the <> section for more info. [[annotations-jpa-temporal]] ==== `@Temporal` -The http://docs.oracle.com/javaee/7/api/javax/persistence/Temporal.html[`@Temporal`] annotation is used to specify the `TemporalType` of the current annotated `java.util.Date` or `java.util.Calendar` entity attribute. +The http://docs.oracle.com/javaee/7/api/javax/persistence/Temporal.html[`@Temporal`] annotation is used to specify the `TemporalType` of the currently annotated `java.util.Date` or `java.util.Calendar` entity attribute. See the <> chapter for more info. @@ -607,7 +607,7 @@ See the <> chapter for more info. @@ -712,7 +712,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern The same behavior can be achieved using the `definition` attribute of the JPA <> annotation. -See the <> chapter for more info. +See the <> chapter for more info. [[annotations-hibernate-columns]] ==== `@Columns` @@ -736,7 +736,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-creationtimestamp]] ==== `@CreationTimestamp` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/CreationTimestamp.html[`@CreationTimestamp`] annotation is used to specify that the current annotated temporal type must be initialized with the current JVM timestamp value. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/CreationTimestamp.html[`@CreationTimestamp`] annotation is used to specify that the currently annotated temporal type must be initialized with the current JVM timestamp value. See the <> section for more info. @@ -787,7 +787,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-fetch]] ==== `@Fetch` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Fetch.html[`@Fetch`] annotation is used to specify the Hibernate specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/FetchMode.html[`FetchMode`] (e.g. `JOIN`, `SELECT`, `SUBSELECT`) used for the current annotated association: +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Fetch.html[`@Fetch`] annotation is used to specify the Hibernate specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/FetchMode.html[`FetchMode`] (e.g. `JOIN`, `SELECT`, `SUBSELECT`) used for the currently annotated association: See the <> section for more info. @@ -861,7 +861,7 @@ See the <> section for more info. @@ -869,7 +869,7 @@ See the <> section for more info. @@ -1044,14 +1044,14 @@ See the <> section for more info. [[annotations-hibernate-naturalid]] ==== `@NaturalId` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NaturalId.html[`@NaturalId`] annotation is used to specify that the current annotated attribute is part of the natural id of the entity. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NaturalId.html[`@NaturalId`] annotation is used to specify that the currently annotated attribute is part of the natural id of the entity. See the <> section for more info. @@ -1077,7 +1077,7 @@ See the <> section for more info. [[annotations-hibernate-optimisticlocking]] ==== `@OptimisticLocking` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] annotation is used to specify the current annotated an entity optimistic locking strategy. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] annotation is used to specify the currently annotated an entity optimistic locking strategy. The four possible strategies are defined by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLockType.html[`OptimisticLockType`] enumeration: @@ -1111,7 +1111,7 @@ See the <> annotation because the JPA annotation expects a JPQL order-by fragment, not an SQL directive. @@ -1127,13 +1127,13 @@ See the <>, <>, and <>, <>. [[annotations-hibernate-parent]] ==== `@Parent` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Parent.html[`@Parent`] annotation is used to specify that the current annotated embeddable attribute references back the owning entity. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Parent.html[`@Parent`] annotation is used to specify that the currently annotated embeddable attribute references back the owning entity. See the <> section for more info. @@ -1155,15 +1155,15 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern There are two possible `PolymorphismType` options: -EXPLICIT:: The current annotated entity is retrieved only if explicitly asked. -IMPLICIT:: The current annotated entity is retrieved if any of its super entity are retrieved. This is the default option. +EXPLICIT:: The currently annotated entity is retrieved only if explicitly asked. +IMPLICIT:: The currently annotated entity is retrieved if any of its super entity are retrieved. This is the default option. See the <> section for more info. [[annotations-hibernate-proxy]] ==== `@Proxy` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Proxy.html[`@Proxy`] annotation is used to specify a custom proxy implementation for the current annotated entity. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Proxy.html[`@Proxy`] annotation is used to specify a custom proxy implementation for the currently annotated entity. See the <> section for more info. @@ -1180,7 +1180,7 @@ See the <> [[annotations-hibernate-selectbeforeupdate]] ==== `@SelectBeforeUpdate` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SelectBeforeUpdate.html[`@SelectBeforeUpdate`] annotation is used to specify that the current annotated entity state be selected from the database when determining whether to perform an update when the detached entity is reattached. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SelectBeforeUpdate.html[`@SelectBeforeUpdate`] annotation is used to specify that the currently annotated entity state be selected from the database when determining whether to perform an update when the detached entity is reattached. See the <> section for more info on how `@SelectBeforeUpdate` works. @@ -1219,14 +1219,14 @@ See the <> section for more info. [[annotations-hibernate-sqldeleteall]] ==== `@SQLDeleteAll` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLDeleteAll.html[`@SQLDeleteAll`] annotation is used to specify a custom SQL `DELETE` statement when removing all elements of the current annotated collection. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLDeleteAll.html[`@SQLDeleteAll`] annotation is used to specify a custom SQL `DELETE` statement when removing all elements of the currently annotated collection. See the <> section for more info. @@ -1242,14 +1242,14 @@ See the <> section for more info. [[annotations-hibernate-sqlupdate]] ==== `@SQLUpdate` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLUpdate.html[`@SQLUpdate`] annotation is used to specify a custom SQL `UPDATE` statement for the current annotated entity or collection. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLUpdate.html[`@SQLUpdate`] annotation is used to specify a custom SQL `UPDATE` statement for the currently annotated entity or collection. See the <> section for more info. @@ -1285,14 +1285,14 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-target]] ==== `@Target` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Target.html[`@Target`] annotation is used to specify an explicit target implementation when the current annotated association is using an interface type. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Target.html[`@Target`] annotation is used to specify an explicit target implementation when the currently annotated association is using an interface type. See the <> section for more info. [[annotations-hibernate-tuplizer]] ==== `@Tuplizer` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tuplizer.html[`@Tuplizer`] annotation is used to specify a custom tuplizer for the current annotated entity or embeddable. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tuplizer.html[`@Tuplizer`] annotation is used to specify a custom tuplizer for the currently annotated entity or embeddable. For entities, the tupelizer must implement the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tuple/entity/EntityTuplizer.html[`EntityTuplizer`] interface. @@ -1308,7 +1308,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-type]] ==== `@Type` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Type.html[`@Type`] annotation is used to specify the Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/type/Type.html[`@Type`] used by the current annotated basic attribute. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Type.html[`@Type`] annotation is used to specify the Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/type/Type.html[`@Type`] used by the currently annotated basic attribute. See the <> section for more info. @@ -1327,7 +1327,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-updatetimestamp]] ==== `@UpdateTimestamp` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/UpdateTimestamp.html[`@UpdateTimestamp`] annotation is used to specify that the current annotated timestamp attribute should be updated with the current JVM timestamp whenever the owning entity gets modified. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/UpdateTimestamp.html[`@UpdateTimestamp`] annotation is used to specify that the currently annotated timestamp attribute should be updated with the current JVM timestamp whenever the owning entity gets modified. - `java.util.Date` - `java.util.Calendar` diff --git a/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc b/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc index d48160e721b0..b59113f9a232 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc @@ -215,12 +215,12 @@ If you need to fetch multiple collections, to avoid a Cartesian Product, you sho Hibernate has two caching layers: -- the first-level cache (Persistence Context) which is a application-level repeatable reads. +- the first-level cache (Persistence Context) which provides application-level repeatable reads. - the second-level cache which, unlike application-level caches, it doesn't store entity aggregates but normalized dehydrated entity entries. The first-level cache is not a caching solution "per se", being more useful for ensuring `READ COMMITTED` isolation level. -While the first-level cache is short lived, being cleared when the underlying `EntityManager` is closed, the second-level cache is tied to an `EntityManagerFactory`. +While the first-level cache is short-lived, being cleared when the underlying `EntityManager` is closed, the second-level cache is tied to an `EntityManagerFactory`. Some second-level caching providers offer support for clusters. Therefore, a node needs only to store a subset of the whole cached data. Although the second-level cache can reduce transaction response time since entities are retrieved from the cache rather than from the database, @@ -233,8 +233,8 @@ and you should consider these alternatives prior to jumping to a second-level ca After properly tuning the database, to further reduce the average response time and increase the system throughput, application-level caching becomes inevitable. -Topically, a key-value application-level cache like https://memcached.org/[Memcached] or http://redis.io/[Redis] is a common choice to store data aggregates. -If you can duplicate all data in the key-value store, you have the option of taking down the database system for maintenance without completely loosing availability since read-only traffic can still be served from the cache. +Typically, a key-value application-level cache like https://memcached.org/[Memcached] or http://redis.io/[Redis] is a common choice to store data aggregates. +If you can duplicate all data in the key-value store, you have the option of taking down the database system for maintenance without completely losing availability since read-only traffic can still be served from the cache. One of the main challenges of using an application-level cache is ensuring data consistency across entity aggregates. That's where the second-level cache comes to the rescue. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index e81686bf34f0..46ea863deeef 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -5,8 +5,8 @@ === Strategy configurations Many configuration settings define pluggable strategies that Hibernate uses for various purposes. -The configuration of many of these strategy type settings accept definition in various forms. -The documentation of such configuration settings refer here. +The configurations of many of these strategy type settings accept definition in various forms. +The documentation of such configuration settings refers here. The types of forms available in such cases include: short name (if defined):: @@ -22,9 +22,9 @@ strategy Class name:: === General configuration `*hibernate.dialect*` (e.g. `org.hibernate.dialect.PostgreSQL94Dialect`):: -The classname of a Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] from which Hibernate can generate SQL optimized for a particular relational database. +The class name of a Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] from which Hibernate can generate SQL optimized for a particular relational database. + -In most cases Hibernate can choose the correct https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] implementation based on the JDBC metadata returned by the JDBC driver. +In most cases, Hibernate can choose the correct https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] implementation based on the JDBC metadata returned by the JDBC driver. + `*hibernate.current_session_context_class*` (e.g. `jta`, `thread`, `managed`, or a custom class implementing `org.hibernate.context.spi.CurrentSessionContext`):: + @@ -32,7 +32,7 @@ Supply a custom strategy for the scoping of the _current_ `Session`. + The definition of what exactly _current_ means is controlled by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentSessionContext.html[`CurrentSessionContext`] implementation in use. + -Note that for backwards compatibility, if a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentSessionContext.html[`CurrentSessionContext`] is not configured but JTA is configured this will default to the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/JTASessionContext.html[`JTASessionContext`]. +Note that for backward compatibility, if a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentSessionContext.html[`CurrentSessionContext`] is not configured but JTA is configured this will default to the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/JTASessionContext.html[`JTASessionContext`]. [[configurations-jpa-compliance]] === JPA compliance @@ -45,7 +45,7 @@ since it extends the JPA one. Controls whether Hibernate's handling of `javax.persistence.Query` (JPQL, Criteria and native-query) should strictly follow the JPA spec. + This includes both in terms of parsing or translating a query as well as calls to the `javax.persistence.Query` methods throwing spec -defined exceptions where as Hibernate might not. +defined exceptions whereas Hibernate might not. `*hibernate.jpa.compliance.list*` (e.g. `true` or `false` (default value)):: Controls whether Hibernate should recognize what it considers a "bag" (`org.hibernate.collection.internal.PersistentBag`) @@ -58,7 +58,7 @@ is just missing (and its defaults will apply). JPA defines specific exceptions upon calling specific methods on `javax.persistence.EntityManager` and `javax.persistence.EntityManagerFactory` objects which have been closed previously. + -This setting controls whether the JPA spec defined behavior or the Hibernate behavior will be used. +This setting controls whether the JPA spec-defined behavior or the Hibernate behavior will be used. + If enabled, Hibernate will operate in the JPA specified way, throwing exceptions when the spec says it should. @@ -105,13 +105,13 @@ See discussion of `hibernate.connection.provider_disables_autocommit` as well. `*hibernate.connection.provider_disables_autocommit*` (e.g. `true` or `false` (default value)):: Indicates a promise by the user that Connections that Hibernate obtains from the configured ConnectionProvider have auto-commit disabled when they are obtained from that provider, whether that provider is backed by -a DataSource or some other Connection pooling mechanism. Generally this occurs when: +a DataSource or some other Connection pooling mechanism. Generally, this occurs when: * Hibernate is configured to get Connections from an underlying DataSource, and that DataSource is already configured to disable auto-commit on its managed Connections * Hibernate is configured to get Connections from a non-DataSource connection pool and that connection pool is already configured to disable auto-commit. For the Hibernate provided implementation this will depend on the value of `hibernate.connection.autocommit` setting. + Hibernate uses this assurance as an opportunity to opt-out of certain operations that may have a performance -impact (although this impact is general negligible). Specifically, when a transaction is started via the +impact (although this impact is generally negligible). Specifically, when a transaction is started via the Hibernate or JPA transaction APIs Hibernate will generally immediately acquire a Connection from the provider and: * check whether the Connection is initially in auto-commit mode via a call to `Connection#getAutocommit` to know how to clean up the Connection when released. @@ -146,7 +146,7 @@ Can reference: ** a fully qualified name of a class implementing `ConnectionProvider` + -The term `class` appears in the setting name due to legacy reasons; however it can accept instances. +The term `class` appears in the setting name due to legacy reasons. However, it can accept instances. `*hibernate.jndi.class*`:: Names the JNDI `javax.naming.InitialContext` class. @@ -196,7 +196,7 @@ The number of seconds between two consecutive pool validations. During validatio Maximum size of C3P0 statement cache. Refers to http://www.mchange.com/projects/c3p0/#maxStatements[c3p0 `maxStatements` setting]. `*hibernate.c3p0.acquire_increment*` (e.g. 2):: - Number of connections acquired at a time when there's no connection available in the pool. Refers to http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 `acquireIncrement` setting]. + The number of connections acquired at a time when there's no connection available in the pool. Refers to http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 `acquireIncrement` setting]. `*hibernate.c3p0.idle_test_period*` (e.g. 5):: Idle time before a C3P0 pooled connection is validated. Refers to http://www.mchange.com/projects/c3p0/#idleConnectionTestPeriod[c3p0 `idleConnectionTestPeriod` setting]. @@ -289,7 +289,7 @@ The following short names are defined for this setting: `legacy-hbm`::: Uses the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/naming/ImplicitNamingStrategyLegacyHbmImpl.html[`ImplicitNamingStrategyLegacyHbmImpl`] `component-path`::: Uses the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/naming/ImplicitNamingStrategyComponentPathImpl.html[`ImplicitNamingStrategyComponentPathImpl`] + -If this property happens to be empty, the fallback is to use `default` strategy. +If this property happens to be empty, the fallback is to use the `default` strategy. `*hibernate.physical_naming_strategy*` (e.g. `org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl` (default value)):: Used to specify the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/naming/PhysicalNamingStrategy.html[`PhysicalNamingStrategy`] class to use. @@ -336,7 +336,7 @@ Therefore, when setting `exclude-unlisted-classes` to true, only the classes tha Used to specify the order in which metadata sources should be processed. Value is a delimited-list whose elements are defined by https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/cfg/MetadataSourceType.html[`MetadataSourceType`]. + -Default is `hbm,class"`, therefore `hbm.xml` files are processed first, followed by annotations (combined with `orm.xml` mappings). +The default is `hbm,class"`, therefore `hbm.xml` files are processed first, followed by annotations (combined with `orm.xml` mappings). + When using JPA, the XML mapping overrides a conflicting annotation mapping that targets the same entity attribute. @@ -460,7 +460,7 @@ Can reference a `StatementInspector` implementation class name (fully-qualified class name). `*hibernate.query.validate_parameters*` (e.g. `true` (default value) or `false`):: -This configuration property can be used to disable parameters validation performed by `org.hibernate.query.Query#setParameter` when the the Session is bootstrapped via JPA +This configuration property can be used to disable parameters validation performed by `org.hibernate.query.Query#setParameter` when the Session is bootstrapped via JPA `javax.persistence.EntityManagerFactory` `*hibernate.criteria.literal_handling_mode*` (e.g. `AUTO` (default value), `BIND` or `INLINE`):: @@ -491,19 +491,19 @@ Provide a custom https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javado `*hibernate.hql.bulk_id_strategy.persistent.drop_tables*` (e.g. `true` or `false` (default value)):: This configuration property is used by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.html[`PersistentTableBulkIdStrategy`], that mimics temporary tables for databases which do not support temporary tables. -It follows a pattern similar to the ANSI SQL definition of global temporary table using a "session id" column to segment rows from the various sessions. +It follows a pattern similar to the ANSI SQL definition of the global temporary table using a "session id" column to segment rows from the various sessions. + This configuration property allows you to DROP the tables used for multi-table bulk HQL operations when the `SessionFactory` or the `EntityManagerFactory` is closed. `*hibernate.hql.bulk_id_strategy.persistent.schema*` (e.g. Database schema name. By default, the `hibernate.default_schema` is used.):: This configuration property is used by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.html[`PersistentTableBulkIdStrategy`], that mimics temporary tables for databases which do not support temporary tables. -It follows a pattern similar to the ANSI SQL definition of global temporary table using a "session id" column to segment rows from the various sessions. +It follows a pattern similar to the ANSI SQL definition of the global temporary table using a "session id" column to segment rows from the various sessions. + This configuration property defines the database schema used for storing the temporary tables used for bulk HQL operations. `*hibernate.hql.bulk_id_strategy.persistent.catalog*` (e.g. Database catalog name. By default, the `hibernate.default_catalog` is used.):: This configuration property is used by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.html[`PersistentTableBulkIdStrategy`], that mimics temporary tables for databases which do not support temporary tables. -It follows a pattern similar to the ANSI SQL definition of global temporary table using a "session id" column to segment rows from the various sessions. +It follows a pattern similar to the ANSI SQL definition of the global temporary table using a "session id" column to segment rows from the various sessions. + This configuration property defines the database catalog used for storing the temporary tables used for bulk HQL operations. @@ -515,9 +515,9 @@ Legacy 4.x behavior favored performing pagination in-memory by avoiding the use In 5.x, the limit handler behavior favors performance, thus, if the dialect doesn't support offsets, an exception is thrown instead. `*hibernate.query.conventional_java_constants*` (e.g. `true` (default value) or `false`):: -Setting which indicates whether or not Java constant follow the https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html[Java Naming conventions]. +Setting which indicates whether or not Java constants follow the https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html[Java Naming conventions]. + -Default is `true`. +The default is `true`. Existing applications may want to disable this (set it `false`) if non-conventional Java constants are used. However, there is a significant performance overhead for using non-conventional Java constants since Hibernate cannot determine if aliases should be treated as Java constants or not. @@ -546,17 +546,17 @@ Names the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/ + Can specify either the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/loader/BatchFetchStyle.html[`BatchFetchStyle`] name (insensitively), or a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/loader/BatchFetchStyle.html[`BatchFetchStyle`] instance. `LEGACY}` is the default value. -`*hibernate.jdbc.batch.builder*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/batch/spi/BatchBuilder.html[`BatchBuilder`] implementation class type or an actual object instance):: +`*hibernate.jdbc.batch.builder*` (e.g. The fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/batch/spi/BatchBuilder.html[`BatchBuilder`] implementation class type or an actual object instance):: Names the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/batch/spi/BatchBuilder.html[`BatchBuilder`] implementation to use. [[configurations-database-fetch]] ==== Fetching properties `*hibernate.max_fetch_depth*` (e.g. A value between `0` and `3`):: -Sets a maximum depth for the outer join fetch tree for single-ended associations. A single-ended association is a one-to-one or many-to-one assocation. A value of `0` disables default outer join fetching. +Sets a maximum depth for the outer join fetch tree for single-ended associations. A single-ended association is a one-to-one or many-to-one association. A value of `0` disables default outer join fetching. `*hibernate.default_batch_fetch_size*` (e.g. `4`,`8`, or `16`):: -Default size for Hibernate Batch fetching of associations (lazily fetched associations can be fetched in batches to prevent N+1 query problems). +The default size for Hibernate Batch fetching of associations (lazily fetched associations can be fetched in batches to prevent N+1 query problems). `*hibernate.jdbc.fetch_size*` (e.g. `0` or an integer):: A non-zero value determines the JDBC fetch size, by calling `Statement.setFetchSize()`. @@ -576,7 +576,7 @@ Enable wrapping of JDBC result sets in order to speed up column name lookups for `*hibernate.enable_lazy_load_no_trans*` (e.g. `true` or `false` (default value)):: Initialize Lazy Proxies or Collections outside a given Transactional Persistence Context. + -Although enabling this configuration can make `LazyInitializationException` go away, it's better to use a fetch plan that guarantees that all properties are properly initialised before the Session is closed. +Although enabling this configuration can make `LazyInitializationException` go away, it's better to use a fetch plan that guarantees that all properties are properly initialized before the Session is closed. + In reality, you shouldn't probably enable this setting anyway. @@ -599,7 +599,7 @@ If true, Hibernate generates comments inside the SQL, for easier debugging. `*hibernate.generate_statistics*` (e.g. `true` or `false`):: Causes Hibernate to collect statistics for performance tuning. -`*hibernate.stats.factory*` (e.g. the fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/stat/spi/StatisticsFactory.html[`StatisticsFactory`] implementation or an actual instance):: +`*hibernate.stats.factory*` (e.g. the fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/stat/spi/StatisticsFactory.html[`StatisticsFactory`] implementation or an actual instance):: The `StatisticsFactory` allow you to customize how the Hibernate Statistics are being collected. `*hibernate.session.events.log*` (e.g. `true` or `false`):: @@ -626,7 +626,7 @@ Enables the query cache. You still need to set individual queries to be cachable `*hibernate.cache.use_second_level_cache*` (e.g. `true` (default value) or `false`):: Enable/disable the second level cache, which is enabled by default, although the default `RegionFactor` is `NoCachingRegionFactory` (meaning there is no actual caching implementation). -`*hibernate.cache.query_cache_factory*` (e.g. Fully-qualified classname):: +`*hibernate.cache.query_cache_factory*` (e.g. Fully-qualified class name):: A custom https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/cache/spi/QueryCacheFactory.html[`QueryCacheFactory`] interface. The default is the built-in `StandardQueryCacheFactory`. `*hibernate.cache.region_prefix*` (e.g. A string):: @@ -639,7 +639,7 @@ Forces Hibernate to store data in the second-level cache in a more human-readabl Enables the automatic eviction of a bi-directional association's collection cache when an element in the `ManyToOne` collection is added/updated/removed without properly managing the change on the `OneToMany` side. `*hibernate.cache.use_reference_entries*` (e.g. `true` or `false`):: -Optimizes second-level cache operation to store immutable entities (aka "reference") which do not have associations into cache directly, this case, lots of disasseble and deep copy operations can be avoid. Default value of this property is `false`. +Optimizes second-level cache operation to store immutable entities (aka "reference") which do not have associations into cache directly, this case, disassembling and deep copy operations can be avoided. The default value of this property is `false`. `*hibernate.ejb.classcache*` (e.g. `hibernate.ejb.classcache.org.hibernate.ejb.test.Item` = `read-write`):: Sets the associated entity class cache concurrency strategy for the designated region. Caching configuration should follow the following pattern `hibernate.ejb.classcache.` usage[, region] where usage is the cache strategy used and region the cache region name. @@ -788,21 +788,21 @@ Specifies the minor version of the underlying database, as would be returned by This value is used to help more precisely determine how to perform schema generation tasks for the underlying database in cases where `javax.persistence.database-product-name` and `javax.persistence.database-major-version` does not provide enough distinction. `*javax.persistence.schema-generation.create-source*`:: -Specifies whether schema generation commands for schema creation are to be determine based on object/relational mapping metadata, DDL scripts, or a combination of the two. +Specifies whether schema generation commands for schema creation are to be determined based on object/relational mapping metadata, DDL scripts, or a combination of the two. See https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/SourceType.html[`SourceType`] for valid set of values. + If no value is specified, a default is assumed as follows: + -* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then `scripts` is assumed +* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then `script` is assumed * otherwise, `metadata` is assumed `*javax.persistence.schema-generation.drop-source*`:: -Specifies whether schema generation commands for schema dropping are to be determine based on object/relational mapping metadata, DDL scripts, or a combination of the two. +Specifies whether schema generation commands for schema dropping are to be determined based on object/relational mapping metadata, DDL scripts, or a combination of the two. See https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/SourceType.html[`SourceType`] for valid set of values. + If no value is specified, a default is assumed as follows: + -* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then `scripts` is assumed +* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then the `script` option is assumed * otherwise, `metadata` is assumed `*javax.persistence.schema-generation.create-script-source*`:: @@ -821,7 +821,7 @@ For cases where the `javax.persistence.schema-generation.scripts.action` value i `*javax.persistence.hibernate.hbm2ddl.import_files*` (e.g. `import.sql` (default value)):: Comma-separated names of the optional files containing SQL DML statements executed during the `SessionFactory` creation. -File order matters, the statements of a give file are executed before the statements of the following one. +File order matters, the statements of a given file are executed before the statements of the following one. + These statements are only executed if the schema is created, meaning that `hibernate.hbm2ddl.auto` is set to `create`, `create-drop`, or `update`. `javax.persistence.schema-generation.create-script-source` / `javax.persistence.schema-generation.drop-script-source` should be preferred. @@ -999,7 +999,7 @@ Names the `ClassLoader` used to load user application classes. Names the `ClassLoader` Hibernate should use to perform resource loading. `*hibernate.classLoader.hibernate*`:: -Names the `ClassLoader` responsible for loading Hibernate classes. By default this is the `ClassLoader` that loaded this class. +Names the `ClassLoader` responsible for loading Hibernate classes. By default, this is the `ClassLoader` that loaded this class. `*hibernate.classLoader.environment*`:: Names the `ClassLoader` used when Hibernate is unable to locates classes on the `hibernate.classLoader.application` or `hibernate.classLoader.hibernate`. @@ -1008,13 +1008,13 @@ Names the `ClassLoader` used when Hibernate is unable to locates classes on the === Bootstrap properties `*hibernate.integrator_provider*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/IntegratorProvider.html[`IntegratorProvider`]):: -Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/integrator/spi/Integrator.html[`Integrator`] which are used during bootstrap process to integrate various services. +Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/integrator/spi/Integrator.html[`Integrator`] which is used during the bootstrap process to integrate various services. `*hibernate.strategy_registration_provider*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/StrategyRegistrationProviderList.html[`StrategyRegistrationProviderList`]):: -Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/registry/selector/StrategyRegistrationProvider.html[`StrategyRegistrationProvider`] which are used during bootstrap process to provide registrations of strategy selector(s). +Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/registry/selector/StrategyRegistrationProvider.html[`StrategyRegistrationProvider`] which is used during the bootstrap process to provide registrations of strategy selector(s). `*hibernate.type_contributors*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/TypeContributorList.html[`TypeContributorList`]):: -Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/TypeContributor.html[`TypeContributor`] which are used during bootstrap process to contribute types. +Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/TypeContributor.html[`TypeContributor`] which is used during the bootstrap process to contribute types. `*hibernate.persister.resolver*` (e.g. The fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/persister/spi/PersisterClassResolver.html[`PersisterClassResolver`] or a `PersisterClassResolver` instance):: Used to define an implementation of the `PersisterClassResolver` interface which can be used to customize how an entity or a collection is being persisted. @@ -1025,8 +1025,8 @@ Like a `PersisterClassResolver`, the `PersisterFactory` can be used to customize `*hibernate.service.allow_crawling*` (e.g. `true` (default value) or `false`):: Crawl all available service bindings for an alternate registration of a given Hibernate `Service`. -`*hibernate.metadata_builder_contributor*` (e.g. The instance, the class or the fully qualified class name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`]):: -Used to define a instance, the class or the fully qualified class name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`] which can be used to configure the `MetadataBuilder` when bootstrapping via the JPA `EntityManagerFactory`. +`*hibernate.metadata_builder_contributor*` (e.g. The instance, the class or the fully qualified class name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`]):: +Used to define an instance, the class or the fully qualified class name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`] which can be used to configure the `MetadataBuilder` when bootstrapping via the JPA `EntityManagerFactory`. [[configurations-misc]] === Miscellaneous properties @@ -1043,7 +1043,7 @@ If `hibernate.session_factory_name_is_jndi` is set to `true`, this is also the n `*hibernate.session_factory_name_is_jndi*` (e.g. `true` (default value) or `false`):: Does the value defined by `hibernate.session_factory_name` represent a JNDI namespace into which the `org.hibernate.SessionFactory` should be bound and made accessible? + -Defaults to `true` for backwards compatibility. Set this to `false` if naming a SessionFactory is needed for serialization purposes, but no writable JNDI context exists in the runtime environment or if the user simply does not want JNDI to be used. +Defaults to `true` for backward compatibility. Set this to `false` if naming a SessionFactory is needed for serialization purposes, but no writable JNDI context exists in the runtime environment or if the user simply does not want JNDI to be used. `*hibernate.ejb.entitymanager_factory_name*` (e.g. By default, the persistence unit name is used, otherwise a randomly generated UUID):: Internally, Hibernate keeps track of all `EntityManagerFactory` instances using the `EntityManagerFactoryRegistry`. The name is used as a key to identify a given `EntityManagerFactory` reference. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc index 58a5edcfc308..350d36062c63 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc @@ -41,7 +41,7 @@ There are other ways to specify Configuration information, including: * Place a file named hibernate.properties in a root directory of the classpath * Pass an instance of java.util.Properties to `Configuration#setProperties` * Via a `hibernate.cfg.xml` file -* System properties using java `-Dproperty=value` +* System properties using Java `-Dproperty=value` == Migration diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc index 76b13178e6f5..95e12ea910e6 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc @@ -26,7 +26,7 @@ List cats = crit.list(); ---- [[criteria-entity-name]] -=== JPA vs Hibernate entity name +=== JPA vs. Hibernate entity name When using the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/SharedSessionContract.html#createCriteria-java.lang.String-[`Session#createCriteria(String entityName)` or `StatelessSession#createCriteria(String entityName)`], the *entityName* means the fully-qualified name of the underlying entity and not the name denoted by the `name` attribute of the JPA `@Entity` annotation. @@ -228,7 +228,7 @@ List cats = session.createCriteria( Cat.class ) This will return all of the `Cat`s with a mate whose name starts with "good" ordered by their mate's age, and all cats who do not have a mate. This is useful when there is a need to order or limit in the database prior to returning complex/large result sets, -and removes many instances where multiple queries would have to be performed and the results unioned by java in memory. +and removes many instances where multiple queries would have to be performed and the results unioned by Java in memory. Without this feature, first all of the cats without a mate would need to be loaded in one query. @@ -275,7 +275,7 @@ When using criteria against collections, there are two distinct cases. One is if the collection contains entities (eg. `` or ``) or components (`` ), and the second is if the collection contains scalar values (``). In the first case, the syntax is as given above in the section <> where we restrict the `kittens` collection. -Essentially we create a `Criteria` object against the collection property and restrict the entity or component properties using that instance. +Essentially, we create a `Criteria` object against the collection property and restrict the entity or component properties using that instance. For querying a collection of basic values, we still create the `Criteria` object against the collection, but to reference the value, we use the special property "elements". diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc index 300b44a5c33c..43f92dc654d5 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc @@ -37,7 +37,7 @@ include::{sourcedir}/timestamp_version.xml[] |column |The name of the column which holds the timestamp. Optional, defaults to the property name |name |The name of a JavaBeans style property of Java type `Date` or `Timestamp` of the persistent class. |access |The strategy Hibernate uses to access the property value. Optional, defaults to `property`. -|unsaved-value |A version property which indicates than instance is newly instantiated, and unsaved. +|unsaved-value |A version property which indicates that the instance is newly instantiated and unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value of `undefined` indicates that Hibernate uses the identifier property value. |source |Whether Hibernate retrieves the timestamp from the database or the current JVM. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc index 6d6c3c885e7c..955911a3ae6f 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc @@ -102,7 +102,7 @@ You can externalize the resultset mapping information in a `` element ---- ==== -You can, alternatively, use the resultset mapping information in your hbm files directly in java code. +You can, alternatively, use the resultset mapping information in your hbm files directly in Java code. .Programmatically specifying the result mapping information ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc b/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc index dbe41e9a0fa6..58123d50b8e6 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc @@ -16,7 +16,7 @@ As a JPA provider, Hibernate implements the Java Persistence API specifications image:images/architecture/JPA_Hibernate.svg[image] SessionFactory (`org.hibernate.SessionFactory`):: A thread-safe (and immutable) representation of the mapping of the application domain model to a database. -Acts as a factory for `org.hibernate.Session` instances. The `EntityManagerFactory` is the JPA equivalent of a `SessionFactory` and basically those two converge into the same `SessionFactory` implementation. +Acts as a factory for `org.hibernate.Session` instances. The `EntityManagerFactory` is the JPA equivalent of a `SessionFactory` and basically, those two converge into the same `SessionFactory` implementation. + A `SessionFactory` is very expensive to create, so, for any given database, the application should have only one associated `SessionFactory`. The `SessionFactory` maintains services that Hibernate uses across all `Session(s)` such as second level caches, connection pools, transaction system integrations, etc. diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc index cb495bfbf040..13404f0d6972 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc @@ -67,7 +67,7 @@ include::{sourcedir}/BatchTest.java[tags=batch-session-batch-example] There are several problems associated with this example: . Hibernate caches all the newly inserted `Customer` instances in the session-level c1ache, so, when the transaction ends, 100 000 entities are managed by the persistence context. - If the maximum memory allocated to the JVM is rather low, this example could fails with an `OutOfMemoryException`. + If the maximum memory allocated to the JVM is rather low, this example could fail with an `OutOfMemoryException`. The Java 1.8 JVM allocated either 1/4 of available RAM or 1Gb, which can easily accommodate 100 000 objects on the heap. . long-running transactions can deplete a connection pool so other transactions don't get a chance to proceed. . JDBC batching is not enabled by default, so every insert statement requires a database roundtrip. @@ -118,7 +118,7 @@ However, it is good practice to close the `ScrollableResults` explicitly. `StatelessSession` is a command-oriented API provided by Hibernate. Use it to stream data to and from the database in the form of detached objects. -A `StatelessSession` has no persistence context associated with it and does not provide many of the higher-level life cycle semantics. +A `StatelessSession` has no persistence context associated with it and does not provide many of the higher-level lifecycle semantics. Some of the things not provided by a `StatelessSession` include: @@ -243,8 +243,8 @@ include::{sourcedir}/BatchTest.java[tags=batch-bulk-hql-delete-example] ---- ==== -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. +Method `Query.executeUpdate()` returns an `int` value, which indicates the number of entities affected by the operation. +This may or may not correlate to the number of rows affected in the database. A 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. @@ -282,7 +282,7 @@ Otherwise, Hibernate throws an exception during parsing. Available in-database generators are `org.hibernate.id.SequenceGenerator` and its subclasses, and objects which implement `org.hibernate.id.PostInsertIdentifierGenerator`. For properties mapped as either version or timestamp, the insert statement gives you two options. -You can either specify the property in the properties_list, in which case its value is taken from the corresponding select expressions, or omit it from the properties_list, +You can either specify the property in the properties_list, in which case its value is taken from the corresponding select expressions or omit it from the properties_list, in which case the seed value defined by the org.hibernate.type.VersionType is used. [[batch-bulk-hql-insert-example]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc index cd042975c972..ce7f360f4f45 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc @@ -26,7 +26,7 @@ During the bootstrap process, you might want to customize Hibernate behavior so === Native Bootstrapping This section discusses the process of bootstrapping a Hibernate `SessionFactory`. -Specifically it discusses the bootstrapping APIs as redesigned in 5.0. +Specifically, it addresses the bootstrapping APIs as redesigned in 5.0. For a discussion of the legacy bootstrapping API, see <> [[bootstrap-native-registry]] @@ -110,7 +110,7 @@ include::{sourcedir}/BootstrapTest.java[tags=bootstrap-event-listener-registrati [[bootstrap-native-metadata]] ==== Building the Metadata -The second step in native bootstrapping is the building of a `org.hibernate.boot.Metadata` object containing the parsed representations of an application domain model and its mapping to a database. +The second step in native bootstrapping is the building of an `org.hibernate.boot.Metadata` object containing the parsed representations of an application domain model and its mapping to a database. The first thing we obviously need to build a parsed representation is the source information to be parsed (annotated classes, `hbm.xml` files, `orm.xml` files). This is the purpose of `org.hibernate.boot.MetadataSources`: @@ -133,7 +133,7 @@ If you are ok with the default behavior in building the Metadata then you can si ==== Notice that a `ServiceRegistry` can be passed at a number of points in this bootstrapping process. The suggested approach is to build a `StandardServiceRegistry` yourself and pass that along to the `MetadataSources` constructor. -From there, `MetadataBuilder`, `Metadata`, `SessionFactoryBuilder` and `SessionFactory` will all pick up that same `StandardServiceRegistry`. +From there, `MetadataBuilder`, `Metadata`, `SessionFactoryBuilder`, and `SessionFactory` will all pick up that same `StandardServiceRegistry`. ==== However, if you wish to adjust the process of building `Metadata` from `MetadataSources`, @@ -156,7 +156,7 @@ include::{sourcedir}/BootstrapTest.java[tags=bootstrap-native-metadata-builder-e The final step in native bootstrapping is to build the `SessionFactory` itself. Much like discussed above, if you are ok with the default behavior of building a `SessionFactory` from a `Metadata` reference, you can simply call the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/Metadata.html#buildSessionFactory--[`buildSessionFactory`] method on the `Metadata` object. -However, if you would like to adjust that building process you will need to use `SessionFactoryBuilder` as obtained via [`Metadata#getSessionFactoryBuilder`. Again, see its https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/Metadata.html#getSessionFactoryBuilder--[Javadocs] for more details. +However, if you would like to adjust that building process, you will need to use `SessionFactoryBuilder` as obtained via [`Metadata#getSessionFactoryBuilder`. Again, see its https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/Metadata.html#getSessionFactoryBuilder--[Javadocs] for more details. [[bootstrap-native-SessionFactory-example]] .Native Bootstrapping - Putting it all together @@ -291,22 +291,22 @@ JPA offers two mapping options: - annotations - XML mappings -Although annotations are much more common, there are projects were XML mappings are preferred. +Although annotations are much more common, there are projects where XML mappings are preferred. You can even mix annotations and XML mappings so that you can override annotation mappings with XML configurations that can be easily changed without recompiling the project source code. -This is possible because if there are two conflicting mappings, the XML mappings takes precedence over its annotation counterpart. +This is possible because if there are two conflicting mappings, the XML mappings take precedence over its annotation counterpart. -The JPA specifications requires the XML mappings to be located on the class path: +The JPA specification requires the XML mappings to be located on the classpath: [quote, Section 8.2.1.6.2 of the JPA 2.1 Specification] ____ An object/relational mapping XML file named `orm.xml` may be specified in the `META-INF` directory in the root of the persistence unit or in the `META-INF` directory of any jar file referenced by the `persistence.xml`. -Alternatively, or in addition, one or more mapping files may be referenced by the mapping-file elements of the persistence-unit element. These mapping files may be present anywhere on the class path. +Alternatively, or in addition, one or more mapping files may be referenced by the mapping-file elements of the persistence-unit element. These mapping files may be present anywhere on the classpath. ____ -Therefore, the mapping files can reside in the application jar artifacts, or they can be stored in an external folder location with the cogitation that that location be included in the class path. +Therefore, the mapping files can reside in the application jar artifacts, or they can be stored in an external folder location with the cogitation that that location be included in the classpath. -Hibernate is more lenient in this regard so you can use any external location even outside of the application configured class path. +Hibernate is more lenient in this regard so you can use any external location even outside of the application configured classpath. [[bootstrap-jpa-compliant-persistence-xml-external-mappings-example]] .META-INF/persistence.xml configuration file for external XML mappings diff --git a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc index ecdca35c772a..ff1b66f6b775 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc @@ -35,9 +35,9 @@ Detailed information is provided later in this chapter. Besides specific provider configuration, there are a number of configurations options on the Hibernate side of the integration that control various caching behaviors: `hibernate.cache.use_second_level_cache`:: - Enable or disable second level caching overall. Default is true, although the default region factory is `NoCachingRegionFactory`. + Enable or disable second level caching overall. The default is true, although the default region factory is `NoCachingRegionFactory`. `hibernate.cache.use_query_cache`:: - Enable or disable second level caching of query results. Default is false. + Enable or disable second level caching of query results. The default is false. `hibernate.cache.query_cache_factory`:: Query result caching is handled by a special contract that deals with staleness-based invalidation of the results. The default implementation does not allow stale results at all. Use this for applications that would like to relax that. @@ -48,7 +48,7 @@ Besides specific provider configuration, there are a number of configurations op Defines a name to be used as a prefix to all second-level cache region names. `hibernate.cache.default_cache_concurrency_strategy`:: In Hibernate second-level caching, all regions can be configured differently including the concurrency strategy to use when accessing that particular region. - This setting allows to define a default strategy to be used. + This setting allows defining a default strategy to be used. This setting is very rarely required as the pluggable providers do specify the default strategy to use. Valid values include: * read-only, @@ -61,12 +61,12 @@ Besides specific provider configuration, there are a number of configurations op `hibernate.cache.auto_evict_collection_cache`:: Enables or disables the automatic eviction of a bidirectional association's collection cache entry when the association is changed just from the owning side. This is disabled by default, as it has a performance impact to track this state. - However if your application does not manage both sides of bidirectional association where the collection side is cached, + However, if your application does not manage both sides of bidirectional association where the collection side is cached, the alternative is to have stale data in that collection cache. `hibernate.cache.use_reference_entries`:: Enable direct storage of entity references into the second level cache for read-only or immutable entities. `hibernate.cache.keys_factory`:: - When storing entries into second-level cache as key-value pair, the identifiers can be wrapped into tuples + When storing entries into the second-level cache as a key-value pair, the identifiers can be wrapped into tuples to guarantee uniqueness in case that second-level cache stores all entities in single space. These tuples are then used as keys in the cache. When the second-level cache implementation (incl. its configuration) guarantees that different entity types are stored separately and multi-tenancy is not @@ -380,7 +380,7 @@ When using http://docs.oracle.com/javaee/7/api/javax/persistence/CacheStoreMode. Hibernate will selectively force the results cached in that particular region to be refreshed. This is particularly useful in cases where underlying data may have been updated via a separate process -and is a far more efficient alternative to bulk eviction of the region via `SessionFactory` eviction which looks as follows: +and is a far more efficient alternative to the bulk eviction of the region via `SessionFactory` eviction which looks as follows: [source, JAVA, indent=0] ---- @@ -402,11 +402,11 @@ The relationship between Hibernate and JPA cache modes can be seen in the follow [cols=",,",options="header",] |====================================== |Hibernate | JPA | Description -|`CacheMode.NORMAL` |`CacheStoreMode.USE` and `CacheRetrieveMode.USE` | Default. Reads/writes data from/into cache +|`CacheMode.NORMAL` |`CacheStoreMode.USE` and `CacheRetrieveMode.USE` | Default. Reads/writes data from/into the cache |`CacheMode.REFRESH` |`CacheStoreMode.REFRESH` and `CacheRetrieveMode.BYPASS` | Doesn't read from cache, but writes to the cache upon loading from the database |`CacheMode.PUT` |`CacheStoreMode.USE` and `CacheRetrieveMode.BYPASS` | Doesn't read from cache, but writes to the cache as it reads from the database |`CacheMode.GET` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.USE` | Read from the cache, but doesn't write to cache -|`CacheMode.IGNORE` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.BYPASS` | Doesn't read/write data from/into cache +|`CacheMode.IGNORE` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.BYPASS` | Doesn't read/write data from/into the cache |====================================== Setting the cache mode can be done either when loading entities directly or when executing a query. @@ -507,7 +507,7 @@ include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-statistics-example] [NOTE] ==== -Use of the build-in integration for https://jcp.org/en/jsr/detail?id=107[JCache] requires that the `hibernate-jcache` module jar (and all of its dependencies) are on the classpath. +Use of the built-in integration for https://jcp.org/en/jsr/detail?id=107[JCache] requires that the `hibernate-jcache` module jar (and all of its dependencies) are on the classpath. In addition a JCache implementation needs to be added as well. A list of compatible implementations can be found https://jcp.org/aboutJava/communityprocess/implementations/jsr107/index.html[on the JCP website]. An alternative source of compatible implementations can be found through https://github.com/cruftex/jsr107-test-zoo[the JSR-107 test zoo]. @@ -585,7 +585,7 @@ and also log a warning about the missing cache. Note that caches created this way may be very badly configured (unlimited size and no eviction in particular) unless the cache provider was explicitly configured to use a specific configuration for default caches. -Ehcache in particular allows to set such default configuration using cache templates, +Ehcache, in particular, allows to set such default configuration using cache templates, see http://www.ehcache.org/documentation/3.0/107.html#supplement-jsr-107-configurations ==== @@ -596,7 +596,7 @@ This integration covers Ehcache 2.x, in order to use Ehcache 3.x as second level [NOTE] ==== -Use of the build-in integration for http://www.ehcache.org/[Ehcache] requires that the `hibernate-ehcache` module jar (and all of its dependencies) are on the classpath. +Use of the built-in integration for http://www.ehcache.org/[Ehcache] requires that the `hibernate-ehcache` module jar (and all of its dependencies) are on the classpath. ==== [[caching-provider-ehcache-region-factory]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc index a15aa7ceb082..a8d83ab014d8 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc @@ -123,7 +123,7 @@ include::{extrasdir}/associations-one-to-many-bidirectional-example.sql[] [IMPORTANT] ==== Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times. -The `addPhone()` and `removePhone()` are utilities methods that synchronize both ends whenever a child element is added or removed. +The `addPhone()` and `removePhone()` are utility methods that synchronize both ends whenever a child element is added or removed. ==== Because the `Phone` class has a `@NaturalId` column (the phone number being unique), @@ -146,7 +146,7 @@ include::{extrasdir}/associations-one-to-many-bidirectional-lifecycle-example.sq Unlike the unidirectional `@OneToMany`, the bidirectional association is much more efficient when managing the collection persistence state. Every element removal only requires a single update (in which the foreign key column is set to `NULL`), and, if the child entity lifecycle is bound to its owning parent so that the child cannot exist without its parent, -then we can annotate the association with the `orphan-removal` attribute and disassociating the child will trigger a delete statement on the actual child table row as well. +then we can annotate the association with the `orphan-removal` attribute and dissociate the child will trigger a delete statement on the actual child table row as well. [[associations-one-to-one]] ==== `@OneToOne` @@ -254,7 +254,7 @@ see the <> for additional information on fetching and on bytecode enhancement. @@ -188,7 +188,7 @@ or its `org.hibernate.type.IntegerType` for mapping `java.lang.Integer` attribut The answer lies in a service inside Hibernate called the `org.hibernate.type.BasicTypeRegistry`, which essentially maintains a map of `org.hibernate.type.BasicType` (a `org.hibernate.type.Type` specialization) instances keyed by a name. We will see later, in the <> section, that we can explicitly tell Hibernate which BasicType to use for a particular attribute. -But first let's explore how implicit resolution works and how applications can adjust implicit resolution. +But first, let's explore how implicit resolution works and how applications can adjust the implicit resolution. [NOTE] ==== @@ -214,7 +214,7 @@ For more details, see <> section. Sometimes you want a particular attribute to be handled differently. Occasionally Hibernate will implicitly pick a `BasicType` that you do not want (and for some reason you do not want to adjust the `BasicTypeRegistry`). -In these cases you must explicitly tell Hibernate the `BasicType` to use, via the `org.hibernate.annotations.Type` annotation. +In these cases, you must explicitly tell Hibernate the `BasicType` to use, via the `org.hibernate.annotations.Type` annotation. [[basic-type-annotation-example]] .Using `@org.hibernate.annotations.Type` @@ -315,7 +315,7 @@ include::{sourcedir}/basic/BitSetTypeTest.java[tags=basic-custom-type-BitSetType ---- ==== -Alternatively, use can use a `@TypeDef` ans skip the registration phase: +Alternatively, you can use the `@TypeDef` and skip the registration phase: [[basic-custom-type-BitSetTypeDef-mapping-example]] .Using `@TypeDef` to register a custom Type @@ -424,7 +424,7 @@ Hibernate supports the mapping of Java enums as basic value types in a number of [[basic-enums-Enumerated]] ===== `@Enumerated` -The original JPA-compliant way to map enums was via the `@Enumerated` and `@MapKeyEnumerated` for map keys annotations which works on the principle that the enum values are stored according to one of 2 strategies indicated by `javax.persistence.EnumType`: +The original JPA-compliant way to map enums was via the `@Enumerated` or `@MapKeyEnumerated` for map keys annotations, working on the principle that the enum values are stored according to one of 2 strategies indicated by `javax.persistence.EnumType`: `ORDINAL`:: stored according to the enum value's ordinal position within the enum class, as indicated by `java.lang.Enum#ordinal` @@ -487,7 +487,7 @@ include::{sourcedir}/basic/PhoneTypeEnumeratedStringTest.java[tags=basic-enums-E ---- ==== -Persisting the same entity like in the `@Enumerated(ORDINAL)` example, Hibernate generates the following SQL statement: +Persisting the same entity as in the `@Enumerated(ORDINAL)` example, Hibernate generates the following SQL statement: [[basic-enums-Enumerated-string-persistence-example]] .Persisting an entity with an `@Enumerated(STRING)` mapping @@ -504,7 +504,7 @@ include::{extrasdir}/basic/basic-enums-Enumerated-string-persistence-example.sql Let's consider the following `Gender` enum which stores its values using the `'M'` and `'F'` codes. [[basic-enums-converter-example]] -.Enum with custom constructor +.Enum with a custom constructor ==== [source, JAVA, indent=0] ---- @@ -684,7 +684,7 @@ Mapping LOBs (database Large Objects) come in 2 forms, those using the JDBC loca JDBC LOB locators exist to allow efficient access to the LOB data. They allow the JDBC driver to stream parts of the LOB data as needed, potentially freeing up memory space. -However they can be unnatural to deal with and have certain limitations. +However, they can be unnatural to deal with and have certain limitations. For example, a LOB locator is only portably valid during the duration of the transaction in which it was obtained. The idea of materialized LOBs is to trade-off the potential efficiency (not all drivers handle LOB data efficiently) for a more natural programming paradigm using familiar Java types such as `String` or `byte[]`, etc for these LOBs. @@ -698,7 +698,7 @@ The JDBC LOB locator types include: * `java.sql.NClob` Mapping materialized forms of these LOB values would use more familiar Java types such as `String`, `char[]`, `byte[]`, etc. -The trade off for _more familiar_ is usually performance. +The trade-off for _more familiar_ is usually performance. [[basic-clob]] ===== Mapping CLOB @@ -843,7 +843,7 @@ include::{sourcedir}/basic/BlobByteArrayTest.java[tags=basic-blob-byte-array-exa ==== Mapping Nationalized Character Data JDBC 4 added the ability to explicitly handle nationalized character data. -To this end it added specific nationalized character data types. +To this end, it added specific nationalized character data types: * `NCHAR` * `NVARCHAR` @@ -894,7 +894,7 @@ include::{sourcedir}/basic/NClobTest.java[tags=basic-nclob-example] ---- ==== -To persist such an entity, you have to create a `NClob` using the `NClobProxy` Hibernate utility: +To persist such an entity, you have to create an `NClob` using the `NClobProxy` Hibernate utility: [[basic-nclob-persist-example]] .Persisting a `java.sql.NClob` entity @@ -952,7 +952,7 @@ Hibernate also allows you to map UUID values, again in a number of ways. [NOTE] ==== The default UUID mapping is as binary because it represents more efficient storage. -However many applications prefer the readability of character storage. +However, many applications prefer the readability of character storage. To switch the default mapping, simply call `MetadataBuilder.applyBasicType( UUIDCharType.INSTANCE, UUID.class.getName() )`. ==== @@ -961,7 +961,7 @@ To switch the default mapping, simply call `MetadataBuilder.applyBasicType( UUID As mentioned, the default mapping for UUID attributes. Maps the UUID to a `byte[]` using `java.util.UUID#getMostSignificantBits` and `java.util.UUID#getLeastSignificantBits` and stores that as `BINARY` data. -Chosen as the default simply because it is generally more efficient from storage perspective. +Chosen as the default simply because it is generally more efficient from a storage perspective. ==== UUID as (var)char @@ -980,7 +980,7 @@ 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's behalf. +Hibernate supports using UUID values as identifiers, and they can even be generated on the user's behalf. For details, see the discussion of generators in <>. [[basic-datetime]] @@ -1127,7 +1127,7 @@ Programmatically:: TimeZone.setDefault( TimeZone.getTimeZone( "UTC" ) ); ---- -However, as explained in http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/[this article], this is not always practical especially for front-end nodes. +However, as explained in http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/[this article], this is not always practical, especially for front-end nodes. For this reason, Hibernate offers the `hibernate.jdbc.time_zone` configuration property which can be configured: Declaratively, at the `SessionFactory` level:: @@ -1200,7 +1200,7 @@ include::{extrasdir}/basic/basic-jpa-convert-period-string-converter-sql-example In cases when the Java type specified for the "database side" of the conversion (the second `AttributeConverter` bind parameter) is not known, Hibernate will fallback to a `java.io.Serializable` type. -If the Java type is not know to Hibernate, you will encounter the following message: +If the Java type is not known to Hibernate, you will encounter the following message: > HHH000481: Encountered Java type for which we could not locate a JavaTypeDescriptor and which does not appear to implement equals and/or hashCode. > This can lead to significant performance problems when performing equality/dirty checking involving this Java type. @@ -1291,7 +1291,7 @@ include::{sourcedir}/basic/JpaQuotingTest.java[tags=basic-jpa-quoting-example] ---- ==== -Because `name` and `number` are reserved words, the `Product` entity mapping uses backtricks to quote these column names. +Because `name` and `number` are reserved words, the `Product` entity mapping uses backticks to quote these column names. When saving the following `Product entity`, Hibernate generates the following SQL insert statement: @@ -1360,8 +1360,8 @@ Properties marked as generated must additionally be _non-insertable_ and _non-up Only `@Version` and `@Basic` types can be marked as generated. `NEVER` (the default):: the given property value is not generated within the database. -`INSERT`:: the given property value is generated on insert, but is not regenerated on subsequent updates. Properties like _creationTimestamp_ fall into this category. -`ALWAYS`:: the property value is generated both on insert and on update. +`INSERT`:: the given property value is generated on insert but is not regenerated on subsequent updates. Properties like _creationTimestamp_ fall into this category. +`ALWAYS`:: the property value is generated both on insert and update. To mark a property as generated, use The Hibernate specific `@Generated` annotation. @@ -1682,7 +1682,7 @@ include::{extrasdir}/basic/mapping-column-read-and-write-composite-type-persiste ==== `@Formula` Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. -You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment) +You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read-only (its value is calculated by your formula fragment) [NOTE] ==== @@ -1847,7 +1847,7 @@ include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-Account-example] ==== Notice that the `active` property is mapped to the `active_status` column. -This mapping was done to show you that the `@Filter` condition uses a SQL condition, and not a JPQL filtering criteria. +This mapping was done to show you that the `@Filter` condition uses a SQL condition and not a JPQL filtering predicate. ==== As already explained, we can also apply the `@Filter` annotation for collections as illustrated by the `Client` entity: @@ -2033,7 +2033,7 @@ include::{extrasdir}/basic/mapping-no-filter-join-table-collection-query-example ---- ==== -If we enable the filter and set the `maxOrderId` to `1`, when fetching the `accounts` collections, Hibernate is going to apply the `@FilterJoinTable` clause filtering criteria, and we will get just +If we enable the filter and set the `maxOrderId` to `1` when fetching the `accounts` collections, Hibernate is going to apply the `@FilterJoinTable` clause filtering criteria, and we will get just `2` `Account` entities, with the `order_id` values of `0` and `1`. [[mapping-filter-join-table-collection-query-example]] @@ -2366,7 +2366,7 @@ http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToOne.html[`@ManyToOne http://docs.oracle.com/javaee/7/api/javax/persistence/OneToOne.html[`@OneToOne`], http://docs.oracle.com/javaee/7/api/javax/persistence/OneToMany.html[`@OneToMany`], and http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToMany.html[`@ManyToMany`] -feature a http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToOne.html#targetEntity--[`targetEntity`] attribute to specify the actual class of the entiity association when an interface is used for the mapping. +feature a http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToOne.html#targetEntity--[`targetEntity`] attribute to specify the actual class of the entity association when an interface is used for the mapping. The http://docs.oracle.com/javaee/7/api/javax/persistence/ElementCollection.html[`@ElementCollection`] association has a http://docs.oracle.com/javaee/7/api/javax/persistence/ElementCollection.html#targetClass--[`targetClass`] attribute for the same purpose. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc index 6680b9e63bbd..11b4e695b24c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc @@ -3,11 +3,11 @@ :sourcedir: ../../../../../test/java/org/hibernate/userguide/collections :extrasdir: extras/collections -Naturally Hibernate also allows to persist collections. -These persistent collections can contain almost any other Hibernate type, including: basic types, custom types, embeddables and references to other entities. +Naturally Hibernate also allows persisting collections. +These persistent collections can contain almost any other Hibernate type, including basic types, custom types, embeddables, and references to other entities. In this context, the distinction between value and reference semantics is very important. -An object in a collection might be handled with _value_ semantics (its life cycle being fully depends on the collection owner), -or it might be a reference to another entity with its own life cycle. +An object in a collection might be handled with _value_ semantics (its lifecycle being fully dependant on the collection owner), +or it might be a reference to another entity with its own lifecycle. In the latter case, only the _link_ between the two objects is considered to be a state held by the collection. The owner of the collection is always an entity, even if the collection is defined by an embeddable type. @@ -46,7 +46,7 @@ The persistent collections injected by Hibernate behave like `ArrayList`, `HashS [[collections-synopsis]] ==== Collections as a value type -Value and embeddable type collections have a similar behavior as simple value types because they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. +Value and embeddable type collections have similar behavior as simple value types because they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. If a collection is passed from one persistent object to another, its elements might be moved from one table to another. [IMPORTANT] @@ -170,7 +170,7 @@ In the following sections, we will go through all these collection types and dis [[collections-bag]] ==== Bags -Bags are unordered lists and we can have unidirectional bags or bidirectional ones. +Bags are unordered lists, and we can have unidirectional bags or bidirectional ones. [[collections-unidirectional-bag]] ===== Unidirectional bags @@ -270,7 +270,7 @@ include::{extrasdir}/collections-bidirectional-bag-orphan-removal-example.sql[] ---- ==== -When rerunning the previous example, the child will get removed because the parent-side propagates the removal upon disassociating the child entity reference. +When rerunning the previous example, the child will get removed because the parent-side propagates the removal upon dissociating the child entity reference. [[collections-list]] ==== Ordered Lists @@ -418,7 +418,7 @@ http://docs.oracle.com/javaee/7/api/javax/persistence/OrderBy.html[`@OrderBy`] a when fetching the current annotated collection, the Hibernate specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OrderBy.html[`@OrderBy`] annotation is used to specify a *SQL* clause instead. -In the following example, the `@OrderBy` annotations uses the `CHAR_LENGTH` SQL function to order the `Article` entities +In the following example, the `@OrderBy` annotation uses the `CHAR_LENGTH` SQL function to order the `Article` entities by the number of characters of the `name` attribute. [[collections-customizing-ordered-by-sql-clause-mapping-example]] @@ -541,7 +541,7 @@ include::{sourcedir}/UnidirectionalComparatorSortedSetTest.java[lines=75..77,ind [[collections-map]] ==== Maps -A `java.util.Map` is a ternary association because it requires a parent entity, a map key and a value. +A `java.util.Map` is a ternary association because it requires a parent entity, a map key, and a value. An entity can either be a map key or a map value, depending on the mapping. Hibernate allows using the following map keys: @@ -601,7 +601,7 @@ include::{extrasdir}/collections-map-custom-key-type-sql-example.sql[] ---- The `call_register` records the call history for every `person`. -The `call_timestamp_epoch` column stores the phone call timestamp as a Unix timestamp since epoch. +The `call_timestamp_epoch` column stores the phone call timestamp as a Unix timestamp since the Unix epoch. [NOTE] ==== @@ -700,7 +700,7 @@ include::{extrasdir}/collections-map-key-class-fetch-example.sql[] A unidirectional map exposes a parent-child association from the parent-side only. The following example shows a unidirectional map which also uses a `@MapKeyTemporal` annotation. -The map key is a timestamp and it's taken from the child entity table. +The map key is a timestamp, and it's taken from the child entity table. [NOTE] ==== @@ -851,7 +851,7 @@ The reason why the `Queue` interface is not used for the entity attribute is bec - `java.util.SortedSet` - `java.util.SortedMap` -However, the custom collection type can still be customized as long as the base type is one of the aformentioned persistent types. +However, the custom collection type can still be customized as long as the base type is one of the aforementioned persistent types. ==== This way, the `Phone` collection can be used as a `java.util.Queue`: diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc index 20a3e2497ddd..b7517cb8ddeb 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc @@ -19,7 +19,7 @@ With this approach, you do not write persistent classes, only mapping files. A given entity has just one entity mode within a given SessionFactory. This is a change from previous versions which allowed to define multiple entity modes for an entity and to select which to load. -Entity modes can now be mixed within a domain model; a dynamic entity might reference a POJO entity, and vice versa. +Entity modes can now be mixed within a domain model; a dynamic entity might reference a POJO entity and vice versa. [[mapping-model-dynamic-example]] .Dynamic domain model Hibernate mapping @@ -60,8 +60,8 @@ include::{extrasdir}/dynamic/mapping-model-dynamic-example.sql[indent=0] [NOTE] ==== -The main advantage of dynamic models is quick turnaround time for prototyping without the need for entity class implementation. -The main down-fall is that you lose compile-time type checking and will likely deal with many exceptions at runtime. +The main advantage of dynamic models is the quick turnaround time for prototyping without the need for entity class implementation. +The main downfall is that you lose compile-time type checking and will likely deal with many exceptions at runtime. However, as a result of the Hibernate mapping, the database schema can easily be normalized and sound, allowing to add a proper domain model implementation on top later on. It is also interesting to note that dynamic models are great for certain integration use cases as well. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc index c00e72f05025..b9e50148ce6c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc @@ -5,17 +5,17 @@ Historically Hibernate called these components. JPA calls them embeddables. -Either way the concept is the same: a composition of values. +Either way, the concept is the same: a composition of values. -For example we might have a `Publisher` class that is a composition of `name` and `country`, +For example, we might have a `Publisher` class that is a composition of `name` and `country`, or a `Location` class that is a composition of `country` and `city`. .Usage of the word _embeddable_ [NOTE] ==== -To avoid any confusion with the annotation that marks a given embeddable type, the annotation will be further referred as `@Embeddable`. +To avoid any confusion with the annotation that marks a given embeddable type, the annotation will be further referred to as `@Embeddable`. -Throughout this chapter and thereafter, for brevity sake, embeddable types may also be referred as _embeddable_. +Throughout this chapter and thereafter, for brevity sake, embeddable types may also be referred to as _embeddable_. ==== [[embeddable-type-mapping-example]] @@ -27,7 +27,7 @@ include::{sourcedir}/NestedEmbeddableTest.java[tag=embeddable-type-mapping-examp ---- ==== -An embeddable type is another form of value type, and its lifecycle is bound to a parent entity type, therefore inheriting the attribute access from its parent (for details on attribute access, see <>). +An embeddable type is another form of a value type, and its lifecycle is bound to a parent entity type, therefore inheriting the attribute access from its parent (for details on attribute access, see <>). Embeddable types can be made up of basic values as well as associations, with the caveat that, when used as collection elements, they cannot define collections themselves. @@ -36,7 +36,7 @@ Embeddable types can be made up of basic values as well as associations, with th Most often, embeddable types are used to group multiple basic type mappings and reuse them across several entities. [[simple-embeddable-type-mapping-example]] -.Simple Embeddedable +.Simple Embeddable ==== [source,java] ---- @@ -62,7 +62,7 @@ So, the embeddable type is represented by the `Publisher` class and the parent entity makes use of it through the `book#publisher` object composition. The composed values are mapped to the same table as the parent table. -Composition is part of good Object-oriented data modeling (idiomatic Java). +Composition is part of good object-oriented data modeling (idiomatic Java). In fact, that table could also be mapped by the following entity type instead. [[alternative-to-embeddable-type-mapping-example]] @@ -74,13 +74,13 @@ include::{sourcedir}/SimpleEmbeddableEquivalentTest.java[tag=embeddable-type-map ---- ==== -The composition form is certainly more Object-oriented, and that becomes more evident as we work with multiple embeddable types. +The composition form is certainly more object-oriented, and that becomes more evident as we work with multiple embeddable types. [[embeddable-multiple]] ==== Multiple embeddable types Although from an object-oriented perspective, it's much more convenient to work with embeddable types, this example doesn't work as-is. -When the same embeddable type is included multiple times in the same parent entity type, the JPA specification demands setting the associated column names explicitly. +When the same embeddable type is included multiple times in the same parent entity type, the JPA specification demands to set the associated column names explicitly. This requirement is due to how object properties are mapped to database columns. By default, JPA expects a database column having the same name with its associated object property. @@ -94,10 +94,10 @@ We have a few options to handle this issue. JPA defines the `@AttributeOverride` annotation to handle this scenario. This way, the mapping conflict is resolved by setting up explicit name-based property-column type mappings. -If an Embeddabe type is used multiple times in some entity, you need to use the +If an Embeddable type is used multiple times in some entity, you need to use the http://docs.oracle.com/javaee/7/api/javax/persistence/AttributeOverride.html[`@AttributeOverride`] and http://docs.oracle.com/javaee/7/api/javax/persistence/AssociationOverride.html[`@AssociationOverride`] annotations -to override the default column names definied by the Embeddable. +to override the default column names defined by the Embeddable. Considering you have the following `Publisher` embeddable type which defines a `@ManyToOne` association with the `Country` entity: @@ -179,17 +179,17 @@ You could even develop your own naming strategy to do other types of implicit na [[embeddable-collections]] ==== Collections of embeddable types -Collections of embeddable types are specifically value collections (as embeddable types are a value type). +Collections of embeddable types are specifically valued collections (as embeddable types are a value type). Value collections are covered in detail in <>. [[embeddable-mapkey]] -==== Embeddable types as Map key +==== Embeddable type as a Map key Embeddable types can also be used as `Map` keys. This topic is converted in detail in <>. [[embeddable-identifier]] -==== Embeddable types as identifiers +==== Embeddable type as identifier Embeddable types can also be used as entity type identifiers. This usage is covered in detail in <>. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc index e0c54be2af17..5a0ff41dd556 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc @@ -10,9 +10,9 @@ [NOTE] ==== The entity type describes the mapping between the actual persistable domain model object and a database table row. -To avoid any confusion with the annotation that marks a given entity type, the annotation will be further referred as `@Entity`. +To avoid any confusion with the annotation that marks a given entity type, the annotation will be further referred to as `@Entity`. -Throughout this chapter and thereafter, entity types will be simply referred as _entity_. +Throughout this chapter and thereafter, entity types will be simply referred to as _entity_. ==== [[entity-pojo]] @@ -71,17 +71,17 @@ That said, the constructor should be defined with at least package visibility if [[entity-pojo-accessors]] ==== Declare getters and setters for persistent attributes -The JPA specification requires this, otherwise the model would prevent accessing the entity persistent state fields directly from outside the entity itself. +The JPA specification requires this, otherwise, the model would prevent accessing the entity persistent state fields directly from outside the entity itself. Although Hibernate does not require it, it is recommended to follow the JavaBean conventions and define getters and setters for entity persistent attributes. Nevertheless, you can still tell Hibernate to directly access the entity fields. Attributes (whether fields or getters/setters) need not be declared public. -Hibernate can deal with attributes declared with public, protected, package or private visibility. +Hibernate can deal with attributes declared with the public, protected, package or private visibility. Again, if wanting to use runtime proxy generation for lazy loading, the getter/setter should grant access to at least package visibility. [[entity-pojo-identifier]] -==== Provide identifier attribute(s) +==== Providing identifier attribute(s) [IMPORTANT] ==== @@ -210,11 +210,11 @@ include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-mu ---- ==== -Specifically the outcome in this last example will depend on whether the `Book` class +Specifically, the outcome in this last example will depend on whether the `Book` class implemented equals/hashCode, and, if so, how. If the `Book` class did not override the default equals/hashCode, -then the two `Book` object reference are not going to be equal since their references are different. +then the two `Book` object references are not going to be equal since their references are different. Consider yet another case: @@ -253,7 +253,7 @@ include::{sourcedir-mapping}/identifier/NaiveEqualsHashCodeEntityTest.java[tag=e ---- ==== -The issue here is a conflict between the use of generated identifier, the contract of `Set` and the equals/hashCode implementations. +The issue here is a conflict between the use of the generated identifier, the contract of `Set`, and the equals/hashCode implementations. `Set` says that the equals/hashCode value for an object should not change while the object is part of the `Set`. But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the JPA transaction is committed. @@ -328,7 +328,7 @@ To find the `Account` balance, we need to query the `AccountSummary` which share However, the `AccountSummary` is not mapped to a physical table, but to an SQL query. -So, if we have the following `AccountTransaction` record, the `AccountSummary` balance will mach the proper amount of money in this `Account`. +So, if we have the following `AccountTransaction` record, the `AccountSummary` balance will match the proper amount of money in this `Account`. [[mapping-Subselect-entity-find-example]] .Finding a `@Subselect` entity @@ -356,7 +356,7 @@ The goal of the `@Synchronize` annotation in the `AccountSummary` entity mapping underlying `@Subselect` SQL query. This is because, unlike JPQL and HQL queries, Hibernate cannot parse the underlying native SQL query. With the `@Synchronize` annotation in place, -when executing a HQL or JPQL which selects from the `AccountSummary` entity, +when executing an HQL or JPQL which selects from the `AccountSummary` entity, Hibernate will trigger a Persistence Context flush if there are pending `Account`, `Client` or `AccountTransaction` entity state transitions. ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc index 9b0eb74eb21e..79d66216f992 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc @@ -21,7 +21,7 @@ See <>. ==== Technically the identifier does not have to map to the column(s) physically defined as the table primary key. They just need to map to column(s) that uniquely identify each row. -However this documentation will continue to use the terms identifier and primary key interchangeably. +However, this documentation will continue to use the terms identifier and primary key interchangeably. ==== Every entity must define an identifier. For entity inheritance hierarchies, the identifier must be defined just on the entity that is the root of the hierarchy. @@ -219,7 +219,7 @@ For discussion of generated values for non-identifier attributes, see <>. @@ -249,7 +249,7 @@ If the identifier type is numerical (e.g. `Long`, `Integer`), then Hibernate is The `IdGeneratorStrategyInterpreter` has two implementations: `FallbackInterpreter`:: - This is the default strategy since Hibernate 5.0. For older versions, this strategy is enabled through the <> configuration property . + This is the default strategy since Hibernate 5.0. For older versions, this strategy is enabled through the <> configuration property. When using this strategy, `AUTO` always resolves to `SequenceStyleGenerator`. If the underlying database supports sequences, then a SEQUENCE generator is used. Otherwise, a TABLE generator is going to be used instead. `LegacyFallbackInterpreter`:: @@ -288,7 +288,7 @@ include::{sourcedir}/SequenceGeneratorNamedTest.java[tag=identifiers-generators- ---- ==== -The `javax.persistence.SequenceGenerator` annotataion allows you to specify additional configurations as well. +The `javax.persistence.SequenceGenerator` annotation allows you to specify additional configurations as well. [[identifiers-generators-sequence-configured]] .Configured sequence @@ -303,7 +303,7 @@ include::{sourcedir}/SequenceGeneratorConfiguredTest.java[tag=identifiers-genera ==== Using IDENTITY columns For implementing identifier value generation based on IDENTITY columns, -Hibernate makes use of its `org.hibernate.id.IdentityGenerator` id generator which expects the identifier to generated by INSERT into the table. +Hibernate makes use of its `org.hibernate.id.IdentityGenerator` id generator which expects the identifier to be generated by INSERT into the table. IdentityGenerator understands 3 different ways that the INSERT-generated value might be retrieved: * If Hibernate believes the JDBC environment supports `java.sql.Statement#getGeneratedKeys`, then that approach will be used for extracting the IDENTITY generated keys. @@ -314,18 +314,18 @@ IdentityGenerator understands 3 different ways that the INSERT-generated value m ==== It is important to realize that this imposes a runtime behavior where the entity row *must* be physically inserted prior to the identifier value being known. This can mess up extended persistence contexts (conversations). -Because of the runtime imposition/inconsistency Hibernate suggest other forms of identifier value generation be used. +Because of the runtime imposition/inconsistency, Hibernate suggests other forms of identifier value generation be used. ==== [NOTE] ==== There is yet another important runtime impact of choosing IDENTITY generation: Hibernate will not be able to JDBC batching for inserts of the entities that use IDENTITY generation. -The importance of this depends on the application specific use cases. +The importance of this depends on the application-specific use cases. If the application is not usually creating many new instances of a given type of entity that uses IDENTITY generation, then this is not an important impact since batching would not have been helpful anyway. ==== [[identifiers-generators-table]] -==== Using table identifier generator +==== Using the table identifier generator Hibernate achieves table-based identifier generation based on its `org.hibernate.id.enhanced.TableGenerator` which defines a table capable of holding multiple named value segments for any number of entities. @@ -392,7 +392,7 @@ This is supported through its `org.hibernate.id.UUIDGenerator` id generator. `UUIDGenerator` supports pluggable strategies for exactly how the UUID is generated. These strategies are defined by the `org.hibernate.id.UUIDGenerationStrategy` contract. The default strategy is a version 4 (random) strategy according to IETF RFC 4122. -Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using ip address rather than mac address). +Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using IP address rather than mac address). [[identifiers-generators-uuid-mapping-example]] .Implicitly using the random UUID strategy @@ -427,7 +427,7 @@ Which is, in fact, the role of these optimizers. none:: No optimization is performed. We communicate with the database each and every time an identifier value is needed from the generator. pooled-lo:: The pooled-lo optimizer works on the principle that the increment-value is encoded into the database table/sequence structure. -In sequence-terms this means that the sequence is defined with a greater-that-1 increment size. +In sequence-terms, this means that the sequence is defined with a greater-than-1 increment size. + For example, consider a brand new sequence defined as `create sequence m_sequence start with 1 increment by 20`. This sequence essentially defines a "pool" of 20 usable id values each and every time we ask it for its next-value. @@ -483,7 +483,7 @@ include::{extrasdir}/id/identifiers-generators-pooled-lo-optimizer-persist-examp ---- ==== -As you can see from the list of generated SQL statements, you can insert 3 entities for one database sequence call. +As you can see from the list of generated SQL statements, you can insert 3 entities with just one database sequence call. This way, the pooled and the pooled-lo optimizers allow you to reduce the number of database roundtrips, therefore reducing the overall transaction response time. [[identifiers-derived]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc index 3a4d5cd954da..9e7de6c950d7 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc @@ -5,7 +5,7 @@ Although relational database systems don't provide support for inheritance, Hibernate provides several strategies to leverage this object-oriented trait onto domain model entities: -MappedSuperclass:: Inheritance is implemented in domain model only without reflecting it in the database schema. See <>. +MappedSuperclass:: Inheritance is implemented in the domain model only without reflecting it in the database schema. See <>. Single table:: The domain model class hierarchy is materialized into a single table which contains entities belonging to different class types. See <>. Joined table:: The base class and all the subclasses have their own database tables and fetching a subclass entity requires a join with the parent table as well. See <>. Table per class:: Each subclass has its own table containing both the subclass and the base class properties. See <>. @@ -13,11 +13,11 @@ Table per class:: Each subclass has its own table containing both the subclass a [[entity-inheritance-mapped-superclass]] ==== MappedSuperclass -In the following domain model class hierarchy, a 'DebitAccount' and a 'CreditAccount' share the same 'Account' base class. +In the following domain model class hierarchy, a `DebitAccount` and a `CreditAccount` share the same `Account` base class. image:images/domain/inheritance/inheritance_class_diagram.svg[Inheritance class diagram] -When using `MappedSuperclass`, the inheritance is visible in the domain model only and each database table contains both the base class and the subclass properties. +When using `MappedSuperclass`, the inheritance is visible in the domain model only, and each database table contains both the base class and the subclass properties. [[entity-inheritance-mapped-superclass-example]] .`@MappedSuperclass` inheritance @@ -35,7 +35,7 @@ include::{extrasdir}/entity-inheritance-mapped-superclass-example.sql[] [NOTE] ==== -Because the `@MappedSuperclass` inheritance model is not mirrored at database level, +Because the `@MappedSuperclass` inheritance model is not mirrored at the database level, it's not possible to use polymorphic queries (fetching subclasses by their base class). ==== @@ -123,7 +123,7 @@ Both `@DiscriminatorColumn` and `@DiscriminatorFormula` are to be set on the roo The available options are `force` and `insert`. The `force` attribute is useful if the table contains rows with _extra_ discriminator values that are not mapped to a persistent class. -This could for example occur when working with a legacy database. +This could, for example, occur when working with a legacy database. If `force` is set to true Hibernate will specify the allowed discriminator values in the SELECT query, even when retrieving all instances of the root class. The second option, `insert`, tells Hibernate whether or not to include the discriminator column in SQL INSERTs. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc index ff67cb51c37c..7826a95c7444 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc @@ -83,7 +83,7 @@ to specify the ImplicitNamingStrategy to use. See [[PhysicalNamingStrategy]] ==== PhysicalNamingStrategy -Many organizations define rules around the naming of database objects (tables, columns, foreign-keys, etc). +Many organizations define rules around the naming of database objects (tables, columns, foreign keys, etc). The idea of a PhysicalNamingStrategy is to help implement such naming rules without having to hard-code them into the mapping via explicit names. @@ -94,8 +94,8 @@ would be, for example, to say that the physical column name should instead be ab [NOTE] ==== It is true that the resolution to `acct_num` could have been handled in an ImplicitNamingStrategy in this case. -But the point is separation of concerns. The PhysicalNamingStrategy will be applied regardless of whether -the attribute explicitly specified the column name or whether we determined that implicitly. The +But the point is separation of concerns. The PhysicalNamingStrategy will be applied regardless of whether +the attribute explicitly specified the column name or whether we determined that implicitly. The ImplicitNamingStrategy would only be applied if an explicit name was not given. So it depends on needs and intent. ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc index 35bc3600a91c..7d00d39e7e49 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc @@ -10,8 +10,7 @@ As we will see later, Hibernate provides a dedicated, efficient API for loading [[naturalid-mapping]] ==== Natural Id Mapping -Natural ids are defined in terms of on -e or more persistent attributes. +Natural ids are defined in terms of one or more persistent attributes. [[naturalid-simple-basic-attribute-mapping-example]] .Natural id using single basic attribute diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc index 8c0a3f6ee4ca..1d58ca3ccda3 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc @@ -6,7 +6,7 @@ Hibernate understands both the Java and JDBC representations of application data. The ability to read/write this data from/to the database is the function of a Hibernate _type_. A type, in this usage, is an implementation of the `org.hibernate.type.Type` interface. -This Hibernate type also describes various aspects of behavior of the Java type such as how to check for equality, how to clone values, etc. +This Hibernate type also describes various behavioral aspects of the Java type such as how to check for equality, how to clone values, etc. .Usage of the word _type_ [NOTE] @@ -20,7 +20,7 @@ When you encounter the term type in discussions of Hibernate, it may refer to th To help understand the type categorizations, let's look at a simple table and domain model that we wish to map. [[mapping-types-basic-example]] -.Simple table and domain model +.A simple table and domain model ==== [source, SQL, indent=0] ---- diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index aa83e0a162b4..5b360b551228 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -113,7 +113,7 @@ The `REVTYPE` column value is taken from the https://docs.jboss.org/hibernate/or |2 | `DEL` |A database table row was deleted. |================================= -The audit (history) of an entity can be accessed using the `AuditReader` interface, which can be obtained having an open `EntityManager` or `Session` via the `AuditReaderFactory`. +The audit (history) of an entity can be accessed using the `AuditReader` interface, which can be obtained by having an open `EntityManager` or `Session` via the `AuditReaderFactory`. [[envers-audited-revisions-example]] .Getting a list of revisions for the `Customer` entity @@ -148,11 +148,11 @@ include::{extrasdir}/envers-audited-rev1-example.sql[] When executing the aforementioned SQL query, there are two parameters: revision_number:: -The first parameter marks the revision number we are interested in or the latest one that exist up to this particular revision. +The first parameter marks the revision number we are interested in or the latest one that exists up to this particular revision. revision_type:: The second parameter specifies that we are not interested in `DEL` `RevisionType` so that deleted entries are filtered out. -The same goes for the second revision associated to the `UPDATE` statement. +The same goes for the second revision associated with the `UPDATE` statement. [[envers-audited-rev2-example]] .Getting the second revision for the `Customer` entity @@ -210,7 +210,7 @@ Name of a field in the audit entity that will hold the revision number. Name of a field in the audit entity that will hold the type of the revision (currently, this can be: `add`, `mod`, `del`). `*org.hibernate.envers.revision_on_collection_change*` (default: `true` ):: -Should a revision be generated when a not-owned relation field changes (this can be either a collection in a one-to-many relation, or the field using `mappedBy` attribute in a one-to-one relation). +Should a revision be generated when a not-owned relation field changes (this can be either a collection in a one-to-many relation or the field using `mappedBy` attribute in a one-to-one relation). `*org.hibernate.envers.do_not_audit_optimistic_locking_field*` (default: `true` ):: When true, properties to be used for optimistic locking, annotated with `@Version`, will not be automatically audited (their history won't be stored; it normally doesn't make sense to store it). @@ -221,14 +221,14 @@ Should the entity data be stored in the revision when the entity is deleted (ins This is not normally needed, as the data is present in the last-but-one revision. Sometimes, however, it is easier and more efficient to access it in the last revision (then the data that the entity contained before deletion is stored twice). -`*org.hibernate.envers.default_schema*` (default: `null` - same schema as table being audited):: +`*org.hibernate.envers.default_schema*` (default: `null` - same schema as the table being audited):: The default schema name that should be used for audit tables. + Can be overridden using the `@AuditTable( schema="..." )` annotation. + If not present, the schema will be the same as the schema of the table being audited. -`*org.hibernate.envers.default_catalog*` (default: `null` - same catalog as table being audited):: +`*org.hibernate.envers.default_catalog*` (default: `null` - same catalog as the table being audited):: The default catalog name that should be used for audit tables. + Can be overridden using the `@AuditTable( catalog="..." )` annotation. @@ -261,7 +261,7 @@ Only used if the `ValidityAuditStrategy` is used, and `org.hibernate.envers.audi Boolean flag that determines the strategy of revision number generation. Default implementation of revision entity uses native identifier generator. + -If current database engine does not support identity columns, users are advised to set this property to false. +If the current database engine does not support identity columns, users are advised to set this property to false. + In this case revision numbers are created by preconfigured `org.hibernate.id.enhanced.SequenceStyleGenerator`. See: `org.hibernate.envers.DefaultRevisionEntity` and `org.hibernate.envers.enhanced.SequenceIdRevisionEntity`. @@ -284,7 +284,7 @@ For more information, refer to <> and <>. -Users are also allowed to implement custom mechanism of tracking modified entity types. +Users are also allowed to implement custom mechanisms of tracking modified entity types. In this case, they shall pass their own implementation of `org.hibernate.envers.EntityTrackingRevisionListener` interface as the value of `@org.hibernate.envers.RevisionEntity` annotation. @@ -657,10 +657,10 @@ include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags ==== [[envers-tracking-properties-changes]] -=== Tracking entity changes at property level +=== Tracking entity changes at the property level By default, the only information stored by Envers are revisions of modified entities. -This approach lets user create audit queries based on historical values of entity properties. +This approach lets users create audit queries based on historical values of entity properties. Sometimes it is useful to store additional metadata for each revision, when you are interested also in the type of changes, not only about the resulting values. The feature described in <> makes it possible to tell which entities were modified in a given revision. @@ -668,7 +668,7 @@ The feature described in <> makes The feature described here takes it one step further. _Modification Flags_ enable Envers to track which properties of audited entities were modified in a given revision. -Tracking entity changes at property level can be enabled by: +Tracking entity changes at the property level can be enabled by: . setting `org.hibernate.envers.global_with_modified_flag` configuration property to `true`. This global switch will cause adding modification flags to be stored for all audited properties of all audited entities. @@ -677,11 +677,11 @@ Tracking entity changes at property level can be enabled by: The trade-off coming with this functionality is an increased size of audit tables and a very little, almost negligible, performance drop during audit writes. This is due to the fact that every tracked property has to have an accompanying boolean column in the schema that stores information about the property modifications. -Of course it is Envers job to fill these columns accordingly - no additional work by the developer is required. +Of course, it is Enver's job to fill these columns accordingly - no additional work by the developer is required. Because of costs mentioned, it is recommended to enable the feature selectively, when needed with use of the granular configuration means described above. [[envers-tracking-properties-changes-mapping-example]] -.Mapping for tracking entity changes at property level +.Mapping for tracking entity changes at the property level ==== [source, JAVA, indent=0] ---- @@ -697,7 +697,7 @@ include::{extrasdir}/envers-tracking-properties-changes-mapping-example.sql[] As you can see, every property features a `_MOD` column (e.g. `createdOn_MOD`) in the audit log. [[envers-tracking-properties-changes-example]] -.Tracking entity changes at property level example +.Tracking entity changes at the property level example ==== [source, JAVA, indent=0] ---- @@ -724,14 +724,14 @@ The queries in Envers are similar to Hibernate Criteria queries, so if you are c The main limitation of the current queries implementation is that you cannot traverse relations. You can only specify constraints on the ids of the related entities, and only on the "owning" side of the relation. -This however will be changed in future releases. +This, however, will be changed in future releases. [NOTE] ==== The queries on the audited data will be in many cases much slower than corresponding queries on "live" data, as, especially for the default audit strategy, they involve correlated subselects. -Queries are improved both in terms of speed and possibilities, when using the validity audit strategy, +Queries are improved both in terms of speed and possibilities when using the validity audit strategy, which stores both start and end revisions for entities. See <>. ==== @@ -907,7 +907,7 @@ In other words, the result set would contain a list of `Customer` instances, one hold the audited property data at the _maximum_ revision number for each `Customer` primary key. [[envers-tracking-properties-changes-queries]] -=== Querying for revisions of entity that modified a given property +=== Querying for entity revisions that modified a given property For the two types of queries described above it's possible to use special `Audit` criteria called `hasChanged()` and `hasNotChanged()` that makes use of the functionality described in <>. @@ -946,7 +946,7 @@ Using this query we won't get all other revisions in which `lastName` wasn't tou From the SQL query you can see that the `lastName_MOD` column is being used in the WHERE clause, hence the aforementioned requirement for tracking modification flags. -Of course, nothing prevents user from combining `hasChanged` condition with some additional criteria. +Of course, nothing prevents users from combining `hasChanged` condition with some additional criteria. [[envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example]] .Getting all `Customer` revisions for which the `lastName` attribute has changed and the `firstName` attribute has not changed @@ -1196,7 +1196,7 @@ include::{extrasdir}/envers-querying-entity-relation-nested-join-multiple-restri [[envers-querying-revision-entities]] === Querying for revision information without loading entities -It may sometimes be useful to load information about revisions to find out who performed specific revisions or +Sometimes, it may be useful to load information about revisions to find out who performed specific revisions or to know what entity names were modified but the change log about the related audited entities isn't needed. This API allows an efficient way to get the revision information entity log without instantiating the actual entities themselves. @@ -1213,7 +1213,7 @@ AuditQuery query = getAuditReader().createQuery() This query will return all revision information entities for revisions between 1 and 25 including those which are related to deletions. If deletions are not of interest, you would pass `false` as the second argument. -Note this this query uses the `DefaultRevisionEntity` class type. The class provided will vary depending on the +Note that this query uses the `DefaultRevisionEntity` class type. The class provided will vary depending on the configuration properties used to configure Envers or if you supply your own revision entity. Typically users who will use this API will likely be providing a custom revision entity implementation to obtain custom information being maintained per revision. @@ -1257,24 +1257,24 @@ The audit table contains the following columns: id:: `id` of the original entity (this can be more then one column in the case of composite primary keys) revision number:: an integer, which matches to the revision number in the revision entity table. -revision type:: The `org.hibernate.envers.RevisionType` enumeration ordinal stating if the change represent an INSERT, UPDATE or DELETE. +revision type:: The `org.hibernate.envers.RevisionType` enumeration ordinal stating if the change represents an INSERT, UPDATE or DELETE. audited fields:: properties from the original entity being audited The primary key of the audit table is the combination of the original id of the entity and the revision number, so there can be at most one historic entry for a given entity instance at a given revision. The current entity data is stored in the original table and in the audit table. -This is a duplication of data, however as this solution makes the query system much more powerful, and as memory is cheap, hopefully this won't be a major drawback for the users. +This is a duplication of data, however as this solution makes the query system much more powerful, and as memory is cheap, hopefully, this won't be a major drawback for the users. -A row in the audit table with entity id `ID`, revision `N` and data `D` means: entity with id `ID` has data `D` from revision `N` upwards. +A row in the audit table with entity id `ID`, revision `N`, and data `D` means: entity with id `ID` has data `D` from revision `N` upwards. Hence, if we want to find an entity at revision `M`, we have to search for a row in the audit table, which has the revision number smaller or equal to `M`, but as large as possible. If no such row is found, or a row with a "deleted" marker is found, it means that the entity didn't exist at that revision. -The "revision type" field can currently have three values: `0`, `1` and `2`, which means `ADD`, `MOD` and `DEL`, respectively. +The "revision type" field can currently have three values: `0`, `1` and `2`, which means `ADD`, `MOD`, and `DEL`, respectively. A row with a revision of type `DEL` will only contain the id of the entity and no data (all fields `NULL`), as it only serves as a marker saying "this entity was deleted at that revision". Additionally, there is a revision entity table which contains the information about the global revision. -By default the generated table is named `REVINFO` and contains just two columns: `ID` and `TIMESTAMP`. +By default, the generated table is named `REVINFO` and contains just two columns: `ID` and `TIMESTAMP`. A row is inserted into this table on each new revision, that is, on each commit of a transaction, which changes audited data. The name of this table can be configured, the name of its columns as well as adding additional columns can be achieved as discussed in <>. @@ -1283,7 +1283,7 @@ The name of this table can be configured, the name of its columns as well as add While global revisions are a good way to provide correct auditing of relations, some people have pointed out that this may be a bottleneck in systems, where data is very often modified. One viable solution is to introduce an option to have an entity "locally revisioned", that is revisions would be created for it independently. -This woulld not enable correct versioning of relations, but it would work without the `REVINFO` table. +This would not enable correct versioning of relations, but it would work without the `REVINFO` table. Another possibility is to introduce a notion of "revisioning groups", which would group entities sharing the same revision numbering. Each such group would have to consist of one or more strongly connected components belonging to the entity graph induced by relations between entities. @@ -1326,7 +1326,7 @@ Bags are not supported because they can contain non-unique elements. Persisting, a bag of `String`s violates the relational database principle that each table is a set of tuples. In case of bags, however (which require a join table), if there is a duplicate element, the two tuples corresponding to the elements will be the same. -Hibernate allows this, however Envers (or more precisely: the database connector) will throw an exception when trying to persist two identical elements because of a unique constraint violation. +Although Hibernate allows this, Envers (or more precisely: the database connector) will throw an exception when trying to persist two identical elements because of a unique constraint violation. There are at least two ways out if you need bag semantics: @@ -1344,7 +1344,7 @@ Envers, however, has to do this so that when you read the revisions in which the To be able to name the additional join table, there is a special annotation: `@AuditJoinTable`, which has similar semantics to JPA `@JoinTable`. -One special case are relations mapped with `@OneToMany` with `@JoinColumn` on the one side, and `@ManyToOne` and `@JoinColumn( insertable=false, updatable=false`) on the many side. +One special case is to have relations mapped with `@OneToMany` with `@JoinColumn` on the one side, and `@ManyToOne` and `@JoinColumn( insertable=false, updatable=false`) on the many side. Such relations are, in fact, bidirectional, but the owning side is the collection. To properly audit such relations with Envers, you can use the `@AuditMappedBy` annotation. @@ -1370,7 +1370,7 @@ SQL table partitioning offers a lot of advantages including, but certainly not l === Suitable columns for audit table partitioning Generally, SQL tables must be partitioned on a column that exists within the table. -As a rule it makes sense to use either the _end revision_ or the _end revision timestamp_ column for partitioning of audit tables. +As a rule, it makes sense to use either the _end revision_ or the _end revision timestamp_ column for partitioning of audit tables. [NOTE] ==== @@ -1442,14 +1442,14 @@ The following audit information is available, sorted on in order of occurrence: To partition this data, the _level of relevancy_ must be defined. Consider the following: -. For fiscal year 2006 there is only one revision. +. For the fiscal year 2006, there is only one revision. It has the oldest _revision timestamp_ of all audit rows, but should still be regarded as relevant because it's the latest modification for this fiscal year in the salary table (its _end revision timestamp_ is null). + -Also, note that it would be very unfortunate if in 2011 there would be an update of the salary for fiscal year 2006 (which is possible in until at least 10 years after the fiscal year), +Also, note that it would be very unfortunate if in 2011 there would be an update of the salary for the fiscal year 2006 (which is possible until at least 10 years after the fiscal year), and the audit information would have been moved to a slow disk (based on the age of the __revision timestamp__). Remember that, in this case, Envers will have to update the _end revision timestamp_ of the most recent audit row. -. There are two revisions in the salary of fiscal year 2007 which both have nearly the same _revision timestamp_ and a different __end revision timestamp__. +. There are two revisions in the salary of the fiscal year 2007 which both have nearly the same _revision timestamp_ and a different __end revision timestamp__. On first sight, it is evident that the first revision was a mistake and probably not relevant. The only relevant revision for 2007 is the one with _end revision timestamp_ null. diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc b/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc index 6a94ea8b4cfc..75333e08f718 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc @@ -42,7 +42,7 @@ include::{sourcedir}/InterceptorTest.java[tags=events-interceptors-session-scope A `SessionFactory`-scoped interceptor is registered with the `Configuration` object prior to building the `SessionFactory`. Unless a session is opened explicitly specifying the interceptor to use, the `SessionFactory`-scoped interceptor will be applied to all sessions opened from that `SessionFactory`. -`SessionFactory`-scoped interceptors must be thread safe. +`SessionFactory`-scoped interceptors must be thread-safe. Ensure that you do not store session-specific states since multiple sessions will use this interceptor potentially concurrently. [[events-interceptors-session-factory-scope-example]] @@ -63,8 +63,8 @@ Many methods of the `Session` interface correlate to an event type. The full range of defined event types is declared as enum values on `org.hibernate.event.spi.EventType`. When a request is made of one of these methods, the Session generates an appropriate event and passes it to the configured event listener(s) for that type. -Applications are free to implement a customization of one of the listener interfaces (i.e., the `LoadEvent` is processed by the registered implementation of the `LoadEventListener` interface), in which case their implementation would -be responsible for processing any `load()` requests made of the `Session`. +Applications can customize the listener interfaces (i.e., the `LoadEvent` is processed by the registered implementation of the `LoadEventListener` interface), in which case their implementations would +be responsible for processing the `load()` requests made of the `Session`. [NOTE] ==== @@ -94,7 +94,7 @@ When you want to customize the entity state transition behavior, you have to opt For example, the `Interceptor#onSave()` method is invoked by Hibernate `AbstractSaveEventListener`. Or, the `Interceptor#onLoad()` is called by the `DefaultPreLoadEventListener`. . you can replace any given default event listener with your own implementation. -When doing this, you should probably extend the default listeners because otherwise you'd have to take care of all the low-level entity state transition logic. +When doing this, you should probably extend the default listeners because otherwise, you'd have to take care of all the low-level entity state transition logic. For example, if you replace the `DefaultPreLoadEventListener` with your own implementation, then, only if you call the `Interceptor#onLoad()` method explicitly, you can mix the custom load event listener with a custom Hibernate interceptor. [[events-declarative-security]] @@ -140,7 +140,7 @@ JPA also defines a more limited set of callbacks through annotations. There are two available approaches defined for specifying callback handling: -* The first approach is to annotate methods on the entity itself to receive notification of particular entity life cycle event(s). +* The first approach is to annotate methods on the entity itself to receive notifications of a particular entity lifecycle event(s). * The second is to use a separate entity listener class. An entity listener is a stateless class with a no-arg constructor. The callback annotations are placed on a method of this class instead of the entity class. diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc index 04a2d8e67bd9..c6a423f1c60a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc @@ -8,7 +8,7 @@ Tuning how an application does fetching is one of the biggest factors in determi Fetching too much data, in terms of width (values/columns) and/or depth (results/rows), adds unnecessary overhead in terms of both JDBC communication and ResultSet processing. Fetching too little data might cause additional fetching to be needed. -Tuning how an application fetches data presents a great opportunity to influence the application overall performance. +Tuning how an application fetches data presents a great opportunity to influence the overall application performance. [[fetching-basics]] === The basics @@ -27,7 +27,7 @@ There are a number of scopes for defining fetching: _static_:: Static definition of fetching strategies is done in the mappings. - The statically-defined fetch strategies is used in the absence of any dynamically defined strategies + The statically-defined fetch strategies are used in the absence of any dynamically defined strategies SELECT::: Performs a separate SQL select to load the data. This can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed). This is the strategy generally termed N+1. @@ -40,13 +40,13 @@ _static_:: Performs a separate SQL select to load associated data based on the SQL restriction used to load the owner. Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed). _dynamic_ (sometimes referred to as runtime):: - Dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching: + The dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching: _fetch profiles_::: defined in mappings, but can be enabled/disabled on the `Session`. HQL/JPQL::: and both Hibernate and JPA Criteria queries have the ability to specify fetching, specific to said query. entity graphs::: Starting in Hibernate 4.2 (JPA 2.1) this is also an option. [[fetching-direct-vs-query]] -=== Direct fetching vs entity queries +=== Direct fetching vs. entity queries To see the difference between direct fetching and entity queries in regard to eagerly fetched associations, consider the following entities: @@ -308,7 +308,7 @@ include::{extrasdir}/fetching-batch-fetching-example.sql[] ---- ==== -As you can see in the example above, there are only two SQL statements used to fetch the `Employee` entities associated to multiple `Department` entities. +As you can see in the example above, there are only two SQL statements used to fetch the `Employee` entities associated with multiple `Department` entities. [TIP] ==== @@ -388,7 +388,7 @@ include::{sourcedir}/FetchModeSubselectTest.java[tags=fetching-strategies-fetch- ---- ==== -Now, we are going to fetch all `Department` entities that match a given filtering criteria +Now, we are going to fetch all `Department` entities that match a given filtering predicate and then navigate their `employees` collections. Hibernate is going to avoid the N+1 query issue by generating a single SQL statement to initialize all `employees` collections diff --git a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc index 52ac549780cc..4876321cc870 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc @@ -7,7 +7,7 @@ Flushing is the process of synchronizing the state of the persistence context wi The `EntityManager` and the Hibernate `Session` expose a set of methods, through which the application developer can change the persistent state of an entity. The persistence context acts as a transactional write-behind cache, queuing any entity state change. -Like any write-behind cache, changes are first applied in-memory and synchronized with the database during flush time. +Like any write-behind cache, changes are first applied in-memory and synchronized with the database during the flush time. The flush operation takes every entity state change and translates it to an `INSERT`, `UPDATE` or `DELETE` statement. [NOTE] @@ -21,7 +21,7 @@ Although JPA defines only two flushing strategies (https://javaee.github.io/java 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. +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. @@ -36,7 +36,7 @@ By default, Hibernate uses the `AUTO` flush mode which triggers a flush in the f ==== `AUTO` flush on commit -In the following example, an entity is persisted and then the transaction is committed. +In the following example, an entity is persisted, and then the transaction is committed. [[flushing-auto-flush-commit-example]] .Automatic flushing on commit @@ -79,7 +79,7 @@ include::{extrasdir}/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: +The reason why the `Advertisement` entity query didn't trigger a flush is that there's no overlapping between the `Advertisement` and the `Person` tables: [[flushing-auto-flush-jpql-entity-example]] .Automatic flushing on JPQL/HQL entities @@ -106,7 +106,7 @@ include::{extrasdir}/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. +This time, the flush was triggered by a JPQL query because the pending entity persists action overlaps with the query being executed. ==== `AUTO` flush on native SQL query @@ -214,7 +214,7 @@ include::{extrasdir}/flushing-always-flush-sql-example.sql[] === `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. +Hibernate also provides a `MANUAL` flush mode so the persistence context can only be flushed manually. [[flushing-manual-flush-example]] .`MANUAL` flushing @@ -234,14 +234,14 @@ The `INSERT` statement was not executed because the persistence context because [NOTE] ==== -This mode is useful when using multi-request logical transactions and only the last request should flush the persistence context. +This mode is useful when using multi-request logical transactions, and only the last request should flush the persistence context. ==== [[flushing-order]] === Flush operation order From a database perspective, a row state can be altered using either an `INSERT`, an `UPDATE` or a `DELETE` statement. -Because entity state changes are automatically converted to SQL statements, it's important to know which entity actions are associated to a given SQL statement. +Because entity state changes are automatically converted to SQL statements, it's important to know which entity actions are associated with a given SQL statement. `INSERT`:: The `INSERT` statement is generated either by the `EntityInsertAction` or `EntityIdentityInsertAction`. These actions are scheduled by the `persist` operation, either explicitly or through cascading the `PersistEvent` from a parent to a child entity. `DELETE`:: The `DELETE` statement is generated by the `EntityDeleteAction` or `OrphanRemovalAction`. diff --git a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc index 2a3b59b08a10..b6ea92ca12ed 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc @@ -59,7 +59,7 @@ Any settings prefixed with `hibernate.connection.` (other than the "special ones `hibernate.c3p0.max_size` or `c3p0.maxPoolSize`:: The maximum size of the c3p0 pool. See http://www.mchange.com/projects/c3p0/#maxPoolSize[c3p0 maxPoolSize] `hibernate.c3p0.timeout` or `c3p0.maxIdleTime`:: The Connection idle time. See http://www.mchange.com/projects/c3p0/#maxIdleTime[c3p0 maxIdleTime] `hibernate.c3p0.max_statements` or `c3p0.maxStatements`:: Controls the c3p0 PreparedStatement cache size (if using). See http://www.mchange.com/projects/c3p0/#maxStatements[c3p0 maxStatements] -`hibernate.c3p0.acquire_increment` or `c3p0.acquireIncrement`:: Number of connections c3p0 should acquire at a time when pool is exhausted. See http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 acquireIncrement] +`hibernate.c3p0.acquire_increment` or `c3p0.acquireIncrement`:: Number of connections c3p0 should acquire at a time when the pool is exhausted. See http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 acquireIncrement] `hibernate.c3p0.idle_test_period` or `c3p0.idleConnectionTestPeriod`:: Idle time before a c3p0 pooled connection is validated. See http://www.mchange.com/projects/c3p0/#idleConnectionTestPeriod[c3p0 idleConnectionTestPeriod] `hibernate.c3p0.initialPoolSize`:: The initial c3p0 pool size. If not specified, default is to use the min pool size. See http://www.mchange.com/projects/c3p0/#initialPoolSize[c3p0 initialPoolSize] Any other settings prefixed with `hibernate.c3p0.`:: Will have the `hibernate.` portion stripped and be passed to c3p0. @@ -194,7 +194,7 @@ Although SQL is relatively standardized, each database vendor uses a subset and This is referred to as the database's dialect. Hibernate handles variations across these dialects through its `org.hibernate.dialect.Dialect` class and the various subclasses for each database vendor. -In most cases Hibernate will be able to determine the proper Dialect to use by asking some questions of the JDBC Connection during bootstrap. +In most cases, Hibernate will be able to determine the proper Dialect to use by asking some questions of the JDBC Connection during bootstrap. For information on Hibernate's ability to determine the proper Dialect to use (and your ability to influence that resolution), see <>. If for some reason it is not able to determine the proper one or you want to use a custom Dialect, you will need to set the `hibernate.dialect` setting. @@ -229,8 +229,8 @@ If for some reason it is not able to determine the proper one or you want to use |MySQL5 |Support for the MySQL database, version 5.x |MySQL5InnoDB |Support for the MySQL database, version 5.x preferring the InnoDB storage engine when exporting tables. |MySQL57InnoDB |Support for the MySQL database, version 5.7 preferring the InnoDB storage engine when exporting tables. May work with newer versions -|MariaDB |Support for the MariadB database. May work with newer versions -|MariaDB53 |Support for the MariadB database, version 5.3 and newer. +|MariaDB |Support for the MariaDB database. May work with newer versions +|MariaDB53 |Support for the MariaDB database, version 5.3 and newer. |Oracle8i |Support for the Oracle database, version 8i |Oracle9i |Support for the Oracle database, version 9i |Oracle10g |Support for the Oracle database, version 10g diff --git a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc index 00c665632d5b..b3133032417b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc @@ -100,7 +100,7 @@ If the version number is generated by the database, such as a trigger, use the a [[locking-optimistic-timestamp]] ===== Timestamp -Timestamps are a less reliable way of optimistic locking than version numbers, but can be used by applications for other purposes as well. +Timestamps are a less reliable way of optimistic locking than version numbers but can be used by applications for other purposes as well. Timestamping is automatically used if you the `@Version` annotation on a `Date` or `Calendar` property type. [[locking-optimistic-version-timestamp-example]] @@ -114,7 +114,7 @@ include::{sourcedir}/OptimisticLockingTest.java[tags=locking-optimistic-version- Hibernate can retrieve the timestamp value from the database or the JVM, by reading the value you specify for the `@org.hibernate.annotations.Source` annotation. The value can be either `org.hibernate.annotations.SourceType.DB` or `org.hibernate.annotations.SourceType.VM`. -The default behavior is to use the database, and is also used if you don't specify the annotation at all. +The default behavior is to use the database and is also used if you don't specify the annotation at all. The timestamp can also be generated by the database instead of Hibernate if you use the `@org.hibernate.annotations.Generated(GenerationTime.ALWAYS)` or the `@Source` annotation. @@ -161,7 +161,7 @@ include::{sourcedir}/OptimisticLockTest.java[tags=locking-optimistic-exclude-att ---- ==== -This way, if one tread modifies the `Phone` number while a second thread increments the `callCount` attribute, +This way, if one thread modifies the `Phone` number while a second thread increments the `callCount` attribute, the two concurrent transactions are not going to conflict as illustrated by the following example. [[locking-optimistic-exclude-attribute-example]] @@ -198,7 +198,7 @@ sometimes, you need rely on the actual database row column values to prevent *lo Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute". This is also useful for use with modeling legacy schemas. -The idea is that you can get Hibernate to perform "version checks" using either all of the entity's attributes, or just the attributes that have changed. +The idea is that you can get Hibernate to perform "version checks" using either all of the entity's attributes or just the attributes that have changed. This is achieved through the use of the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] annotation which defines a single attribute of type @@ -322,7 +322,7 @@ JPA comes with its own http://docs.oracle.com/javaee/7/api/javax/persistence/Loc |`READ` and `OPTIMISTIC`|`READ` | The entity version is checked towards the end of the currently running transaction. |`WRITE` and `OPTIMISTIC_FORCE_INCREMENT`|`WRITE` | The entity version is incremented automatically even if the entity has not changed. |`PESSIMISTIC_FORCE_INCREMENT`|`PESSIMISTIC_FORCE_INCREMENT` | The entity is locked pessimistically and its version is incremented automatically even if the entity has not changed. -|`PESSIMISTIC_READ`|`PESSIMISTIC_READ` | The entity is locked pessimistically using a shared lock, if the database supports such a feature. Otherwise, an explicit lock is used. +|`PESSIMISTIC_READ`|`PESSIMISTIC_READ` | The entity is locked pessimistically using a shared lock if the database supports such a feature. Otherwise, an explicit lock is used. |`PESSIMISTIC_WRITE`|`PESSIMISTIC_WRITE`, `UPGRADE` | The entity is locked using an explicit lock. |`PESSIMISTIC_WRITE` with a `javax.persistence.lock.timeout` setting of 0 |`UPGRADE_NOWAIT` | The lock acquisition request fails fast if the row s already locked. |`PESSIMISTIC_WRITE` with a `javax.persistence.lock.timeout` setting of -2 |`UPGRADE_SKIPLOCKED` | The lock acquisition request skips the already locked rows. It uses a `SELECT ... FOR UPDATE SKIP LOCKED` in Oracle and PostgreSQL 9.5, or `SELECT ... with (rowlock, updlock, readpast) in SQL Server`. @@ -385,7 +385,7 @@ The `javax.persistence.lock.scope` is https://hibernate.atlassian.net/browse/HHH Traditionally, Hibernate offered the `Session#lock()` method for acquiring an optimistic or a pessimistic lock on a given entity. Because varying the locking options was difficult when using a single `LockMode` parameter, Hibernate has added the `Session#buildLockRequest()` method API. -The following example shows how to obtain shared database lock without waiting for the lock acquisition request. +The following example shows how to obtain a shared database lock without waiting for the lock acquisition request. [[locking-buildLockRequest-example]] .`buildLockRequest` example @@ -448,8 +448,8 @@ include::{extrasdir}/locking-follow-on-secondary-query-example.sql[] The lock request was moved from the original query to a secondary one which takes the previously fetched entities to lock their associated database records. -Prior to Hibernate 5.2.1, the the follow-on-locking mechanism was applied uniformly to any locking query executing on Oracle. -Since 5.2.1, the Oracle Dialect tries to figure out if the current query demand the follow-on-locking mechanism. +Prior to Hibernate 5.2.1, the follow-on-locking mechanism was applied uniformly to any locking query executing on Oracle. +Since 5.2.1, the Oracle Dialect tries to figure out if the current query demands the follow-on-locking mechanism. Even more important is that you can overrule the default follow-on-locking detection logic and explicitly enable or disable it on a per query basis. @@ -469,6 +469,6 @@ include::{extrasdir}/locking-follow-on-explicit-example.sql[] [NOTE] ==== -The follow-on-locking mechanism should be explicitly enabled only if the current executing query fails because the `FOR UPDATE` clause cannot be applied, meaning that the Dialect resolving mechanism needs to be further improved. +The follow-on-locking mechanism should be explicitly enabled only if the currently executing query fails because the `FOR UPDATE` clause cannot be applied, meaning that the Dialect resolving mechanism needs to be further improved. ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc index 69b90ae42526..a081b7091e69 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc @@ -75,7 +75,7 @@ include::{sourcedir}/AbstractMultiTenancyTest.java[tags=multitenacy-hibernate-se Additionally, when specifying the configuration, an `org.hibernate.MultiTenancyStrategy` should be named using the `hibernate.multiTenancy` setting. Hibernate will perform validations based on the type of strategy you specify. -The strategy here correlates to the isolation approach discussed above. +The strategy here correlates with the isolation approach discussed above. NONE:: (the default) No multitenancy is expected. @@ -111,7 +111,7 @@ The `MultiTenantConnectionProvider` to use can be specified in a number of ways: * Use the `hibernate.multi_tenant_connection_provider` setting. It could name a `MultiTenantConnectionProvider` instance, a `MultiTenantConnectionProvider` implementation class reference or a `MultiTenantConnectionProvider` implementation class name. * Passed directly to the `org.hibernate.boot.registry.StandardServiceRegistryBuilder`. -* If none of the above options match, but the settings do specify a `hibernate.connection.datasource` value, +* If none of the above options matches, but the settings do specify a `hibernate.connection.datasource` value, Hibernate will assume it should use the specific `DataSourceBasedMultiTenantConnectionProviderImpl` implementation which works on a number of pretty reasonable assumptions when running inside of an app server and using one `javax.sql.DataSource` per tenant. See its https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.html[Javadocs] for more details. diff --git a/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc b/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc index e56095d98947..5bfb5ceea03d 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc @@ -53,10 +53,10 @@ In order to utilize container-managed JPA, an Enterprise OSGi JPA container must In Karaf, this means Aries JPA, which is included out-of-the-box (simply activate the `jpa` and `transaction` features). Originally, we intended to include those dependencies within our own `features.xml`. However, after guidance from the Karaf and Aries teams, it was pulled out. -This allows Hibernate OSGi to be portable and not be directly tied to Aries versions, instead having the user choose which to use. +This allows Hibernate OSGi to be portable and not be directly tied to Aries versions, instead of having the user choose which to use. That being said, the QuickStart/Demo projects include a sample https://github.com/hibernate/hibernate-demos/tree/master/hibernate-orm/osgi/managed-jpa/features.xml[features.xml] -showing which features need activated in Karaf in order to support this environment. +showing which features need to be activated in Karaf in order to support this environment. As mentioned, use this purely as a reference! === persistence.xml @@ -186,7 +186,7 @@ include::{sourcedir}/_native/HibernateUtil.java[tag=osgi-discover-SessionFactory The https://github.com/hibernate/hibernate-demos/tree/master/hibernate-orm/osgi/unmanaged-native[unmanaged-native] demo project displays the use of optional Hibernate modules. Each module adds additional dependency bundles that must first be activated, either manually or through an additional feature. As of ORM 4.2, Envers is fully supported. -Support for C3P0, Proxool, EhCache, and Infinispan were added in 4.3, however none of their 3rd party libraries currently work in OSGi (lots of `ClassLoader` problems, etc.). +Support for C3P0, Proxool, EhCache, and Infinispan were added in 4.3. However, none of their 3rd party libraries currently work in OSGi (lots of `ClassLoader` problems, etc.). We're tracking the issues in JIRA. === Extension Points @@ -201,7 +201,7 @@ The specified interface should be used during service registration. `org.hibernate.integrator.spi.Integrator`:: (as of 4.2) `org.hibernate.boot.registry.selector.StrategyRegistrationProvider`:: (as of 4.3) `org.hibernate.boot.model.TypeContributor`:: (as of 4.3) -JTA's:: `javax.transaction.TransactionManager` and `javax.transaction.UserTransaction` (as of 4.2), however these are typically provided by the OSGi container. +JTA's:: `javax.transaction.TransactionManager` and `javax.transaction.UserTransaction` (as of 4.2). However, these are typically provided by the OSGi container. The easiest way to register extension point implementations is through a `blueprint.xml` file. Add `OSGI-INF/blueprint/blueprint.xml` to your classpath. Envers' blueprint is a great example: @@ -225,10 +225,10 @@ Extension points can also be registered programmatically with `BundleContext#reg * Scanning is supported to find non-explicitly listed entities and mappings. However, they MUST be in the same bundle as your persistence unit (fairly typical anyway). Our OSGi `ClassLoader` only considers the "requesting bundle" (hence the requirement on using services to create `EntityManagerFactory`/`SessionFactory`), rather than attempting to scan all available bundles. - This is primarily for versioning considerations, collision protections, etc. + This is primarily for versioning considerations, collision protection, etc. * Some containers (ex: Aries) always return true for `PersistenceUnitInfo#excludeUnlistedClasses`, even if your `persistence.xml` explicitly has `exclude-unlisted-classes` set to `false`. They claim it's to protect JPA providers from having to implement scanning ("we handle it for you"), even though we still want to support it in many cases. - The work around is to set `hibernate.archive.autodetection` to, for example, `hbm,class`. + The workaround is to set `hibernate.archive.autodetection` to, for example, `hbm,class`. This tells hibernate to ignore the `excludeUnlistedClasses` value and scan for `*.hbm.xml` and entities regardless. * Scanning does not currently support annotated packages on `package-info.java`. * Currently, Hibernate OSGi is primarily tested using Apache Karaf and Apache Aries JPA. Additional testing is needed with Equinox, Gemini, and other container providers. diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc index 83b9b39af8e5..70b52470701f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc @@ -17,11 +17,11 @@ Hibernate supports the enhancement of an application Java domain model for the p ===== Lazy attribute loading Think of this as partial loading support. -Essentially you can tell Hibernate that only part(s) of an entity should be loaded upon fetching from the database and when the other part(s) should be loaded as well. -Note that this is very much different from proxy-based idea of lazy loading which is entity-centric where the entity's state is loaded at once as needed. +Essentially, you can tell Hibernate that only part(s) of an entity should be loaded upon fetching from the database and when the other part(s) should be loaded as well. +Note that this is very much different from the proxy-based idea of lazy loading which is entity-centric where the entity's state is loaded at once as needed. With bytecode enhancement, individual attributes or groups of attributes are loaded as needed. -Lazy attributes can be designated to be loaded together and this is called a "lazy group". +Lazy attributes can be designated to be loaded together, and this is called a "lazy group". By default, all singular attributes are part of a single group, meaning that when one lazy singular attribute is accessed all lazy singular attributes are loaded. Lazy plural attributes, by default, are each a lazy group by themselves. This behavior is explicitly controllable through the `@org.hibernate.annotations.LazyGroup` annotation. @@ -35,9 +35,9 @@ include::{sourcedir}/BytecodeEnhancementTest.java[tags=BytecodeEnhancement-lazy- ---- ==== -In the above example we have 2 lazy attributes: `accountsPayableXrefId` and `image`. +In the above example, we have 2 lazy attributes: `accountsPayableXrefId` and `image`. Each is part of a different fetch group (accountsPayableXrefId is part of the default fetch group), -which means that accessing `accountsPayableXrefId` will not force the loading of image, and vice-versa. +which means that accessing `accountsPayableXrefId` will not force the loading of the `image` attribute, and vice-versa. [NOTE] ==== @@ -52,11 +52,11 @@ Historically Hibernate only supported diff-based dirty calculation for determini This essentially means that Hibernate would keep track of the last known state of an entity in regards to the database (typically the last read or write). Then, as part of flushing the persistence context, Hibernate would walk every entity associated with the persistence context and check its current state against that "last known database state". This is by far the most thorough approach to dirty checking because it accounts for data-types that can change their internal state (`java.util.Date` is the prime example of this). -However, in a persistence context with a large number of associated entities it can also be a performance-inhibiting approach. +However, in a persistence context with a large number of associated entities, it can also be a performance-inhibiting approach. If your application does not need to care about "internal state changing data-type" use cases, bytecode-enhanced dirty tracking might be a worthwhile alternative to consider, especially in terms of performance. In this approach Hibernate will manipulate the bytecode of your classes to add "dirty tracking" directly to the entity, allowing the entity itself to keep track of which of its attributes have changed. -During flush time, Hibernate simply asks your entity what has changed rather that having to perform the state-diff calculations. +During the flush time, Hibernate asks your entity what has changed rather than having to perform the state-diff calculations. [[BytecodeEnhancement-dirty-tracking-bidirectional]] ===== Bidirectional association management @@ -105,11 +105,11 @@ These are hard to discuss without diving into a discussion of Hibernate internal ==== Performing enhancement [[BytecodeEnhancement-enhancement-runtime]] -===== Run-time enhancement +===== Runtime enhancement -Currently, run-time enhancement of the domain model is only supported in managed JPA environments following the JPA-defined SPI for performing class transformations. +Currently, runtime enhancement of the domain model is only supported in managed JPA environments following the JPA-defined SPI for performing class transformations. -Even then, this support is disabled by default. To enable run-time enhancement, specify one of the following configuration properties: +Even then, this support is disabled by default. To enable runtime enhancement, specify one of the following configuration properties: `*hibernate.enhancer.enableDirtyTracking*` (e.g. `true` or `false` (default value)):: Enable dirty tracking feature in runtime bytecode enhancement. @@ -122,14 +122,14 @@ Enable association management feature in runtime bytecode enhancement which auto [NOTE] ==== -Also, at the moment, only annotated classes are supported for run-time enhancement. +Also, at the moment, only annotated classes are supported for runtime enhancement. ==== [[BytecodeEnhancement-enhancement-gradle]] ===== Gradle plugin Hibernate provides a Gradle plugin that is capable of providing build-time enhancement of the domain model as they are compiled as part of a Gradle build. -To use the plugin a project would first need to apply it: +To use the plugin, a project would first need to apply it: .Apply the Gradle plugin ==== @@ -157,7 +157,7 @@ Hibernate provides a Maven plugin capable of providing build-time enhancement of See the section on the <> for details on the configuration settings. Again, the default for those 3 is `false`. The Maven plugin supports one additional configuration settings: failOnError, which controls what happens in case of error. -Default behavior is to fail the build, but it can be set so that only a warning is issued. +The default behavior is to fail the build, but it can be set so that only a warning is issued. .Apply the Maven plugin ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc index 00b79e0bd934..2a812aa36e4d 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc @@ -12,8 +12,8 @@ Persistent data has a state in relation to both a persistence context and the un It has no persistent representation in the database and typically no identifier value has been assigned (unless the _assigned_ generator was used). `managed`, or `persistent`:: the entity has an associated identifier and is associated with a persistence context. It may or may not physically exist in the database yet. -`detached`:: the entity has an associated identifier, but is no longer associated with a persistence context (usually because the persistence context was closed or the instance was evicted from the context) -`removed`:: the entity has an associated identifier and is associated with a persistence context, however it is scheduled for removal from the database. +`detached`:: the entity has an associated identifier but is no longer associated with a persistence context (usually because the persistence context was closed or the instance was evicted from the context) +`removed`:: the entity has an associated identifier and is associated with a persistence context, however, it is scheduled for removal from the database. Much of the `org.hibernate.Session` and `javax.persistence.EntityManager` methods deal with moving entities between these states. @@ -78,7 +78,7 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-remove-jpa-example] ==== [[pc-remove-native-example]] -.Deleting an entity with Hibernate API +.Deleting an entity with the Hibernate API ==== [source, JAVA, indent=0] ---- @@ -91,7 +91,7 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-remove-native-example] Hibernate itself can handle deleting detached state. JPA, however, disallows it. The implication here is that the entity instance passed to the `org.hibernate.Session` delete method can be either in managed or detached state, -while the entity instance passed to remove on `javax.persistence.EntityManager` must be in managed state. +while the entity instance passed to remove on `javax.persistence.EntityManager` must be in the managed state. ==== [[pc-get-reference]] @@ -177,7 +177,7 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-find-optional-by-id-nat [[pc-find-natural-id]] === Obtain an entity by natural-id -In addition to allowing to load by identifier, Hibernate allows applications to load by declared natural identifier. +In addition to allowing to load the entity by its identifier, Hibernate allows applications to load entities by the declared natural identifier. [[pc-find-by-natural-id-entity-example]] .Natural-id mapping @@ -219,23 +219,23 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-find-optional-by-simple ---- ==== -Hibernate offer a consistent API for accessing persistent data by identifier or by the natural-id. Each of these defines the same two data access methods: +Hibernate offers a consistent API for accessing persistent data by identifier or by the natural-id. Each of these defines the same two data access methods: getReference:: Should be used in cases where the identifier is assumed to exist, where non-existence would be an actual error. Should never be used to test existence. That is because this method will prefer to create and return a proxy if the data is not already associated with the Session rather than hit the database. - The quintessential use-case for using this method is to create foreign-key based associations. + The quintessential use-case for using this method is to create foreign key based associations. load:: Will return the persistent data associated with the given identifier value or null if that identifier does not exist. -Each of these two methods define an overloading variant accepting a `org.hibernate.LockOptions` argument. +Each of these two methods defines an overloading variant accepting a `org.hibernate.LockOptions` argument. Locking is discussed in a separate <>. [[pc-managed-state]] === Modifying managed/persistent state -Entities in managed/persistent state may be manipulated by the application and any changes will be automatically detected and persisted when the persistence context is flushed. +Entities in managed/persistent state may be manipulated by the application, and any changes will be automatically detected and persisted when the persistence context is flushed. There is no need to call a particular method to make your modifications persistent. [[pc-managed-state-jpa-example]] @@ -320,7 +320,7 @@ include::{sourcedir}/DynamicUpdateTest.java[tags=pc-managed-state-dynamic-update ---- ==== -This time, when reruning the previous test case, Hibernate generates the following SQL UPDATE statement: +This time, when rerunning the previous test case, Hibernate generates the following SQL UPDATE statement: [[pc-managed-state-dynamic-update-example]] .Modifying the `Product` entity with a dynamic update @@ -416,12 +416,12 @@ Clearing the persistence context has the same effect. Evicting a particular entity from the persistence context makes it detached. And finally, serialization will make the deserialized form be detached (the original instance is still managed). -Detached data can still be manipulated, however the persistence context will no longer automatically know about these modification and the application will need to intervene to make the changes persistent again. +Detached data can still be manipulated, however, the persistence context will no longer automatically know about these modifications, and the application will need to intervene to make the changes persistent again. [[pc-detach-reattach]] ==== Reattaching detached data -Reattachment is the process of taking an incoming entity instance that is in detached state and re-associating it with the current persistence context. +Reattachment is the process of taking an incoming entity instance that is in the detached state and re-associating it with the current persistence context. [IMPORTANT] ==== @@ -459,7 +459,7 @@ Provided the entity is detached, `update` and `saveOrUpdate` operate exactly the [[pc-merge]] ==== Merging detached data -Merging is the process of taking an incoming entity instance that is in detached state and copying its data over onto a new managed instance. +Merging is the process of taking an incoming entity instance that is in the detached state and copying its data over onto a new managed instance. Although not exactly per se, the following example is a good visualization of the `merge` operation internals. diff --git a/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc b/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc index 8a96fa7d6210..503c19a7c238 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc @@ -24,7 +24,7 @@ Originally, Hibernate would always require that users specify which dialect to u Generally, this required their users to configure the Hibernate dialect or defining their own method of setting that value. Starting with version 3.2, Hibernate introduced the notion of automatically detecting the dialect to use based on the `java.sql.DatabaseMetaData` obtained from a `java.sql.Connection` to that database. -This was much better, expect that this resolution was limited to databases Hibernate know about ahead of time and was in no way configurable or overrideable. +This was much better, except that this resolution was limited to databases Hibernate know about ahead of time and was in no way configurable or overrideable. Starting with version 3.3, Hibernate has a fare more powerful way to automatically determine which dialect to should be used by relying on a series of delegates which implement the `org.hibernate.dialect.resolver.DialectResolver` which defines only a single method: @@ -35,7 +35,7 @@ public Dialect resolveDialect(DatabaseMetaData metaData) throws JDBCConnectionEx The basic contract here is that if the resolver 'understands' the given database metadata then it returns the corresponding Dialect; if not it returns null and the process continues to the next resolver. The signature also identifies `org.hibernate.exception.JDBCConnectionException` as possibly being thrown. -A `JDBCConnectionException` here is interpreted to imply a "non transient" (aka non-recoverable) connection problem and is used to indicate an immediate stop to resolution attempts. +A `JDBCConnectionException` here is interpreted to imply a __non-transient__ (aka non-recoverable) connection problem and is used to indicate an immediate stop to resolution attempts. All other exceptions result in a warning and continuing on to the next resolver. The cool part about these resolvers is that users can also register their own custom resolvers which will be processed ahead of the built-in Hibernate ones. @@ -50,14 +50,14 @@ To register one or more resolvers, simply specify them (separated by commas, tab === Identifier generation When considering portability between databases, another important decision is selecting the identifier generation strategy you want to use. -Originally Hibernate provided the _native_ generator for this purpose, which was intended to select between a __sequence__, __identity__, or _table_ strategy depending on the capability of the underlying database. +Originally, Hibernate provided the _native_ generator for this purpose, which was intended to select between a __sequence__, __identity__, or _table_ strategy depending on the capability of the underlying database. However, an insidious implication of this approach comes about when targeting some databases which support _identity_ generation and some which do not. _identity_ generation relies on the SQL definition of an IDENTITY (or auto-increment) column to manage the identifier value. It is what is known as a _post-insert_ generation strategy because the insert must actually happen before we can know the identifier value. Because Hibernate relies on this identifier value to uniquely reference entities within a persistence context, -it must then issue the insert immediately when the users requests that the entity be associated with the session (e.g. like via `save()` or `persist()`) , regardless of current transactional semantics. +it must then issue the insert immediately when the user requests that the entity be associated with the session (e.g. like via `save()` or `persist()`), regardless of current transactional semantics. [NOTE] ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc index bfbfdbad3efa..cc86b78d9a89 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc @@ -18,9 +18,9 @@ They are type-safe in terms of using interfaces and classes to represent various They can also be type-safe in terms of referencing attributes as we will see in a bit. Users of the older Hibernate `org.hibernate.Criteria` query API will recognize the general approach, though we believe the JPA API to be superior as it represents a clean look at the lessons learned from that API. -Criteria queries are essentially an object graph, where each part of the graph represents an increasing (as we navigate down this graph) more atomic part of query. +Criteria queries are essentially an object graph, where each part of the graph represents an increasing (as we navigate down this graph) more atomic part of the query. The first step in performing a criteria query is building this graph. -The `javax.persistence.criteria.CriteriaBuilder` interface is the first thing with which you need to become acquainted to begin using criteria queries. +The `javax.persistence.criteria.CriteriaBuilder` interface is the first thing with which you need to become acquainted with begin using criteria queries. Its role is that of a factory for all the individual pieces of the criteria. You obtain a `javax.persistence.criteria.CriteriaBuilder` instance by calling the `getCriteriaBuilder()` method of either `javax.persistence.EntityManagerFactory` or `javax.persistence.EntityManager`. @@ -148,7 +148,7 @@ Specifically, notice the constructor and its argument types. Since we will be returning `PersonWrapper` objects, we use `PersonWrapper` as the type of our criteria query. This example illustrates the use of the `javax.persistence.criteria.CriteriaBuilder` method construct which is used to build a wrapper expression. -For every row in the result we are saying we would like a `PersonWrapper` instantiated with the remaining arguments by the matching constructor. +For every row in the result, we are saying we would like a `PersonWrapper` instantiated with the remaining arguments by the matching constructor. This wrapper expression is then passed as the select. [[criteria-tuple]] @@ -273,7 +273,7 @@ include::{sourcedir}/CriteriaTest.java[tags=criteria-from-fetch-example] [NOTE] ==== Technically speaking, embedded attributes are always fetched with their owner. -However in order to define the fetching of _Phone#addresses_ we needed a `javax.persistence.criteria.Fetch` because element collections are `LAZY` by default. +However, in order to define the fetching of _Phone#addresses_ we needed a `javax.persistence.criteria.Fetch` because element collections are `LAZY` by default. ==== [[criteria-path]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc index 3e25f6b17b9b..3047cce411ae 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc @@ -6,7 +6,7 @@ The Hibernate Query Language (HQL) and Java Persistence Query Language (JPQL) are both object model focused query languages similar in nature to SQL. JPQL is a heavily-inspired-by subset of HQL. -A JPQL query is always a valid HQL query, the reverse is not true however. +A JPQL query is always a valid HQL query, the reverse is not true, however. Both HQL and JPQL are non-type-safe ways to perform query operations. Criteria queries offer a type-safe approach to querying. See <> for more information. @@ -15,7 +15,7 @@ Criteria queries offer a type-safe approach to querying. See <> for additional details on collection related expressions. +See <> for additional details on collection-related expressions. [[hql-polymorphism]] === Polymorphism @@ -987,7 +987,7 @@ It returns every object of every entity type defined by your application mapping [[hql-expressions]] === Expressions -Essentially expressions are references that resolve to basic or tuple values. +Essentially, expressions are references that resolve to basic or tuple values. [[hql-identification-variable]] === Identification variable @@ -1036,7 +1036,7 @@ The actual suffix is case-insensitive. The boolean literals are `TRUE` and `FALSE`, again case-insensitive. Enums can even be referenced as literals. The fully-qualified enum class name must be used. -HQL can also handle constants in the same manner, though JPQL does not define that as supported. +HQL can also handle constants in the same manner, though JPQL does not define that as being supported. Entity names can also be used as literal. See <>. @@ -1046,7 +1046,7 @@ Date/time literals can be specified using the JDBC escape syntax: * `{t 'hh:mm:ss'}` for times * `{ts 'yyyy-mm-dd hh:mm:ss[.millis]'}` (millis optional) for timestamps. -These Date/time literals only work if you JDBC drivers supports them. +These Date/time literals only work if the underlying JDBC driver supports them. ==== [[hql-numeric-arithmetic]] @@ -1073,13 +1073,13 @@ The following rules apply to the result of arithmetic operations: * else, (the assumption being that both operands are of integral type) the result is `Integer` (except for division, in which case the result type is not further defined) Date arithmetic is also supported, albeit in a more limited fashion. -This is due partially to differences in database support and partially to the lack of support for `INTERVAL` definition in the query language itself. +This is due to differences in database support and partly to the lack of support for `INTERVAL` definition in the query language itself. [[hql-concatenation]] === Concatenation (operation) HQL defines a concatenation operator in addition to supporting the concatenation (`CONCAT`) function. -This is not defined by JPQL, so portable applications should avoid it use. +This is not defined by JPQL, so portable applications should avoid its use. The concatenation operator is taken from the SQL concatenation operator (e.g `||`). [[hql-concatenation-example]] @@ -1378,7 +1378,7 @@ ELEMENTS:: Only allowed in the where clause. Often used in conjunction with `ALL`, `ANY` or `SOME` restrictions. INDICES:: - Similar to `elements` except that `indices` refers to the collections indices (keys/positions) as a whole. + Similar to `elements` except that the `indices` expression refers to the collections indices (keys/positions) as a whole. [[hql-collection-expressions-example]] .Collection-related expressions examples @@ -1407,7 +1407,7 @@ See also <> as there is a good deal of overlap. We can also refer to the type of an entity as an expression. This is mainly useful when dealing with entity inheritance hierarchies. -The type can expressed using a `TYPE` function used to refer to the type of an identification variable representing an entity. +The type can be expressed using a `TYPE` function used to refer to the type of an identification variable representing an entity. The name of the entity also serves as a way to refer to an entity type. Additionally, the entity type can be parameterized, in which case the entity's Java Class reference would be bound as the parameter value. @@ -1501,7 +1501,7 @@ There is a particular expression type that is only valid in the select clause. Hibernate calls this "dynamic instantiation". JPQL supports some of that feature and calls it a "constructor expression". -So rather than dealing with the `Object[]` (again, see <>) here we are wrapping the values in a type-safe java object that will be returned as the results of the query. +So rather than dealing with the `Object[]` (again, see <>) here, we are wrapping the values in a type-safe Java object that will be returned as the results of the query. [[hql-select-clause-dynamic-instantiation-example]] .Dynamic HQL and JPQL instantiation example @@ -1558,7 +1558,7 @@ If the user doesn't assign aliases, the key will be the index of each particular === Predicates Predicates form the basis of the where clause, the having clause and searched case expressions. -They are expressions which resolve to a truth value, generally `TRUE` or `FALSE`, although boolean comparisons involving `NULL` generally resolve to `UNKNOWN`. +They are expressions which resolve to a truth value, generally `TRUE` or `FALSE`, although boolean comparisons involving `NULL` resolve typically to `UNKNOWN`. [[hql-relational-comparisons]] === Relational comparisons @@ -1596,8 +1596,8 @@ It resolves to false if the subquery result is empty. [[hql-null-predicate]] === Nullness predicate -Check a value for nullness. -Can be applied to basic attribute references, entity references and parameters. +It check a value for nullness. +It can be applied to basic attribute references, entity references, and parameters. HQL additionally allows it to be applied to component/embeddable types. [[hql-null-predicate-example]] @@ -1678,9 +1678,9 @@ include::{extrasdir}/predicate_in_bnf.txt[] The types of the `single_valued_expression` and the individual values in the `single_valued_list` must be consistent. -JPQL limits the valid types here to string, numeric, date, time, timestamp, and enum types, and , in JPQL, `single_valued_expression` can only refer to: +JPQL limits the valid types here to string, numeric, date, time, timestamp, and enum types, and, in JPQL, `single_valued_expression` can only refer to: -* "state fields", which is its term for simple attributes. Specifically this excludes association and component/embedded attributes. +* "state fields", which is its term for simple attributes. Specifically, this excludes association and component/embedded attributes. * entity type expressions. See <> In HQL, `single_valued_expression` can refer to a far more broad set of expression types. @@ -1752,7 +1752,7 @@ If the predicate is true, NOT resolves to false. If the predicate is unknown (e. The `AND` operator is used to combine 2 predicate expressions. The result of the AND expression is true if and only if both predicates resolve to true. -If either predicates resolves to unknown, the AND expression resolves to unknown as well. Otherwise, the result is false. +If either predicate resolves to unknown, the AND expression resolves to unknown as well. Otherwise, the result is false. [[hql-or-predicate]] === OR predicate operator @@ -1817,7 +1817,7 @@ The types of expressions considered valid as part of the `ORDER BY` clause inclu Additionally, JPQL says that all values referenced in the `ORDER BY` clause must be named in the `SELECT` clause. HQL does not mandate that restriction, but applications desiring database portability should be aware that not all databases support referencing values in the `ORDER BY` clause that are not referenced in the select clause. -Individual expressions in the order-by can be qualified with either `ASC` (ascending) or `DESC` (descending) to indicated the desired ordering direction. +Individual expressions in the order-by can be qualified with either `ASC` (ascending) or `DESC` (descending) to indicate the desired ordering direction. Null values can be placed in front or at the end of the sorted set using `NULLS FIRST` or `NULLS LAST` clause respectively. [[hql-order-by-example]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc index 95f2d0da9607..3214cba9f912 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc @@ -5,7 +5,7 @@ :extrasdir: extras You may also express queries in the native SQL dialect of your database. -This is useful if you want to utilize database specific features such as window functions, Common Table Expressions (CTE) or the `CONNECT BY` option in Oracle. +This is useful if you want to utilize database-specific features such as window functions, Common Table Expressions (CTE) or the `CONNECT BY` option in Oracle. It also provides a clean migration path from a direct SQL/JDBC based application to Hibernate/JPA. Hibernate also allows you to specify handwritten SQL (including stored procedures) for all create, update, delete, and retrieve operations. @@ -84,7 +84,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-scalar-query-partial-explic ---- ==== -This is essentially the same query as before, but now `ResultSetMetaData` is used to determine the type of `name`, where as the type of `id` is explicitly specified. +This is essentially the same query as before, but now `ResultSetMetaData` is used to determine the type of `name`, whereas the type of `id` is explicitly specified. How the `java.sql.Types` returned from `ResultSetMetaData` is mapped to Hibernate types is controlled by the `Dialect`. If a specific type is not mapped, or does not result in the expected type, it is possible to customize it via calls to `registerHibernateType` in the Dialect. @@ -112,7 +112,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-entity-query-example] ---- ==== -Assuming that `Person` is mapped as a class with the columns `id`, `name`, `nickName`, `address`, `createdOn` and `version`, +Assuming that `Person` is mapped as a class with the columns `id`, `name`, `nickName`, `address`, `createdOn`, and `version`, the following query will also return a `List` where each element is a `Person` entity. [[sql-jpa-entity-query-explicit-result-set-example]] @@ -138,7 +138,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-entity-query-explicit-resul If the entity is mapped with a `many-to-one` or a child-side `one-to-one` to another entity, it is required to also return this when performing the native query, -otherwise a database specific _column not found_ error will occur. +otherwise, a database-specific _column not found_ error will occur. [[sql-jpa-entity-associations-query-many-to-one-example]] .JPA native query selecting entities with many-to-one association @@ -230,7 +230,7 @@ include::{extrasdir}/sql-hibernate-entity-associations-query-one-to-many-join-ex ---- ==== -At this stage you are reaching the limits of what is possible with native queries, without starting to enhance the sql queries to make them usable in Hibernate. +At this stage, you are reaching the limits of what is possible with native queries, without starting to enhance the sql queries to make them usable in Hibernate. Problems can arise when returning multiple entities of the same type or when the default alias/column names are not enough. [[sql-multi-entity-query]] @@ -261,7 +261,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-multi-entity-query-example] The query was intended to return all `Person` and `Partner` instances with the same name. The query fails because there is a conflict of names since the two entities are mapped to the same column names (e.g. `id`, `name`, `version`). -Also, on some databases the returned column aliases will most likely be on the form `pr.id`, `pr.name`, etc. +Also, on some databases, the returned column aliases will most likely be on the form `pr.id`, `pr.name`, etc. which are not equal to the columns specified in the mappings (`id` and `name`). The following form is not vulnerable to column name duplication: @@ -281,13 +281,13 @@ There's no such equivalent in JPA because the `Query` interface doesn't define a ==== The `{pr.*}` and `{pt.*}` notation used above is shorthand for "all properties". -Alternatively, you can list the columns explicitly, but even in this case Hibernate injects the SQL column aliases for each property. +Alternatively, you can list the columns explicitly, but even in this case, Hibernate injects the SQL column aliases for each property. The placeholder for a column alias is just the property name qualified by the table alias. [[sql-alias-references]] === Alias and property references -In most cases the above alias injection is needed. +In most cases, the above alias injection is needed. For queries relating to more complex mappings, like composite properties, inheritance discriminators, collections etc., you can use specific aliases that allow Hibernate to inject the proper aliases. The following table shows the different ways you can use the alias injection. @@ -409,7 +409,7 @@ and the Hibernate `org.hibernate.annotations.NamedNativeQuery` annotation extend `timeout()`:: The query timeout (in seconds). By default, there's no timeout. `callable()`:: - Does the SQL query represent a call to a procedure/function? Default is false. + Does the SQL query represent a call to a procedure/function? The default is false. `comment()`:: A comment added to the SQL query for tuning the execution plan. `cacheMode()`:: @@ -670,7 +670,7 @@ Fortunately, Hibernate allows you to resolve the current global catalog and sche {h-schema}:: resolves the current `hibernate.default_schema` configuration property value. {h-domain}:: resolves the current `hibernate.default_catalog` and `hibernate.default_schema` configuration property values (e.g. catalog.schema). -Withe these placeholders, you can imply the catalog, schema, or both catalog and schema for every native query. +With these placeholders, you can imply the catalog, schema, or both catalog and schema for every native query. So, when running the following native query: @@ -863,15 +863,15 @@ include::{sourcedir}/OracleStoredProcedureTest.java[tags=sql-jpa-call-sp-ref-cur ==== [[sql-crud]] -=== Custom SQL for create, update, and delete +=== Custom SQL for CRUD (Create, Read, Update and Delete) -Hibernate can use custom SQL for create, update, and delete operations. +Hibernate can use custom SQL for CRUD operations. The SQL can be overridden at the statement level or individual column level. This section describes statement overrides. For columns, see <>. The following example shows how to define custom SQL operations using annotations. -`@SQLInsert`, `@SQLUpdate` and `@SQLDelete` override the INSERT, UPDATE, DELETE statements of a given entity. +`@SQLInsert`, `@SQLUpdate`, and `@SQLDelete` override the INSERT, UPDATE, DELETE statements of a given entity. For the SELECT clause, a `@Loader` must be defined along with a `@NamedNativeQuery` used for loading the underlying table record. For collections, Hibernate allows defining a custom `@SQLDeleteAll` which is used for removing all child records associated with a given parent entity. @@ -894,7 +894,8 @@ The same is done for the `phones` collection. The `@SQLDeleteAll` and the `SQLIn [NOTE] ==== -You also call a store procedure using the custom CRUD statements; the only requirement is to set the `callable` attribute to `true`. +You can also call a store procedure using the custom CRUD statements. +The only requirement is to set the `callable` attribute to `true`. ==== To check that the execution happens correctly, Hibernate allows you to define one of those three strategies: @@ -927,7 +928,7 @@ include::{sourcedir}/CustomSQLSecondaryTableTest.java[tags=sql-custom-crud-secon [TIP] ==== The SQL is directly executed in your database, so you can use any dialect you like. -This will, however, reduce the portability of your mapping if you use database specific SQL. +This will, however, reduce the portability of your mapping if you use database-specific SQL. ==== You can also use stored procedures for customizing the CRUD statements. diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc index 36e8d9bf8a1b..226c0793435a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc @@ -11,8 +11,8 @@ Since 5.0, Hibernate Spatial is now part of the Hibernate ORM project, and it allows you to deal with geographic data in a standardized way. Hibernate Spatial provides a standardized, cross-database interface to geographic data storage and query functions. -It supports most of the functions described by the OGC Simple Feature Specification. Supported databases are: Oracle 10g/11g, -PostgreSql/PostGIS, MySQL, Microsoft SQL Server and H2/GeoDB. +It supports most of the functions described by the OGC Simple Feature Specification. Supported databases are Oracle 10g/11g, +PostgreSQL/PostGIS, MySQL, Microsoft SQL Server and H2/GeoDB. Spatial data types are not part of the Java standard library, and they are absent from the JDBC specification. Over the years http://tsusiatsoftware.net/jts/main.html[JTS] has emerged the _de facto_ standard to fill this gap. JTS is @@ -148,10 +148,10 @@ There are several dialects for MySQL: MySQL versions before 5.6.1 had only limited support for spatial operators. Most operators only took account of the minimum bounding rectangles (MBR) of the geometries, and not the geometries themselves. -This changed in version 5.6.1 were MySQL introduced `ST_*` spatial operators. +This changed in version 5.6.1, when MySQL introduced `ST_*` spatial operators. The dialect `MySQLSpatial56Dialect` uses these newer, more precise operators. -These dialects may therefore produce results that differ from that of the other spatial dialects. +These dialects may, therefore, produce results that differ from that of the other spatial dialects. For more information, see this page in the MySQL reference guide (esp. the section https://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions.html[Functions That Test Spatial Relations Between Geometry Objects]) ==== @@ -164,21 +164,22 @@ This dialect has been tested on both Oracle 10g and Oracle 11g with the `SDO_GEO This dialect can be configured using the Hibernate property: + `hibernate.spatial.connection_finder`::: -the fully-qualified classname for the implementation of the `ConnectionFinder` to use (see below). +the fully-qualified class name for the implementation of the `ConnectionFinder` to use (see below). .The `ConnectionFinder` interface [NOTE] ==== -The `SDOGeometryType` requires access to an `OracleConnection` object wehen converting a geometry to SDO_GEOMETRY. +The `SDOGeometryType` requires access to an `OracleConnection` object when converting a geometry to SDO_GEOMETRY. In some environments, however, the `OracleConnection` is not available (e.g. because a Java EE container or connection pool proxy wraps the connection object in its own `Connection` implementation). A `ConnectionFinder` knows how to retrieve the `OracleConnection` from the wrapper or proxy Connection object that is passed into prepared statements. -The default implementation will, when the passed object is not already an `OracleConnection`, attempt to retrieve the `OracleConnection` by recursive reflection. +When the passed object is not already an `OracleConnection`, the default implementation will attempt to retrieve the `OracleConnection` by recursive reflection. It will search for methods that return `Connection` objects, execute these methods and check the result. -If the result is of type `OracleConnection` the object is returned, otherwise it recurses on it. +If the result is of type `OracleConnection` the object is returned. +Otherwise, it recurses on it. -In may cases this strategy will suffice. -If not, you can provide your own implementation of this interface on the class path, and configure it in the `hibernate.spatial.connection_finder` property. +In may cases, this strategy will suffice. +If not, you can provide your own implementation of this interface on the classpath, and configure it in the `hibernate.spatial.connection_finder` property. Note that implementations must be thread-safe and have a default no-args constructor. ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc b/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc index fa5d49b0d83b..0f2bdfffb0e1 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc @@ -119,7 +119,7 @@ include::{extrasdir}/schema-generation-database-checks-persist-example.sql[] ==== [[schema-generation-column-default-value]] -=== Default value for database column +=== Default value for a database column With Hibernate, you can specify a default value for a given database column using the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/ColumnDefault.html[`@ColumnDefault`] annotation. diff --git a/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc b/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc index 082988573c9d..1a0b4712ac7c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc @@ -17,10 +17,10 @@ This documentation largely treats the physical and logic notions of a transactio [[transactions-physical]] === Physical Transactions -Hibernate uses the JDBC API for persistence. In the world of Java there are two well-defined mechanism for dealing with transactions in JDBC: JDBC itself and JTA. +Hibernate uses the JDBC API for persistence. In the world of Java, there are two well-defined mechanisms for dealing with transactions in JDBC: JDBC itself and JTA. Hibernate supports both mechanisms for integrating with transactions and allowing applications to manage physical transactions. -Transaction handling per `Session` is handled by the `org.hibernate.resource.transaction.spi.TransactionCoordinator` contract, +The transaction handling per `Session` is handled by the `org.hibernate.resource.transaction.spi.TransactionCoordinator` contract, which are built by the `org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder` service. `TransactionCoordinatorBuilder` represents a strategy for dealing with transactions whereas TransactionCoordinator represents one instance of that strategy related to a Session. Which `TransactionCoordinatorBuilder` implementation to use is defined by the `hibernate.transaction.coordinator_class` setting. @@ -50,9 +50,9 @@ The Hibernate `Session` acts as a transaction-scoped cache providing repeatable [IMPORTANT] ==== To reduce lock contention in the database, the physical database transaction needs to be as short as possible. -Long database transactions prevent your application from scaling to a highly-concurrent load. +Long-running database transactions prevent your application from scaling to a highly-concurrent load. Do not hold a database transaction open during end-user-level work, but open it after the end-user-level work is finished. -This is concept is referred to as `transactional write-behind`. +This concept is referred to as `transactional write-behind`. ==== [[transactions-physical-jtaplatform]] @@ -99,11 +99,11 @@ To use this API, you would obtain the `org.hibernate.Transaction` from the Sessi `markRollbackOnly`:: that works in both JTA and JDBC `getTimeout` and `setTimeout`:: that again work in both JTA and JDBC `registerSynchronization`:: that allows you to register JTA Synchronizations even in non-JTA environments. -In fact in both JTA and JDBC environments, these `Synchronizations` are kept locally by Hibernate. +In fact, in both JTA and JDBC environments, these `Synchronizations` are kept locally by Hibernate. In JTA environments, Hibernate will only ever register one single `Synchronization` with the `TransactionManager` to avoid ordering problems. Additionally, it exposes a getStatus method that returns an `org.hibernate.resource.transaction.spi.TransactionStatus` enum. -This method checks with the underlying transaction system if needed, so care should be taken to minimize its use; it can have a big performance impact in certain JTA set ups. +This method checks with the underlying transaction system if needed, so care should be taken to minimize its use; it can have a big performance impact in certain JTA setups. Let's take a look at using the Transaction API in the various environments. @@ -134,7 +134,7 @@ include::{sourcedir}/TransactionsTest.java[tags=transactions-api-bmt-example] ---- ==== -In the CMT case we really could have omitted all of the Transaction calls. +In the CMT case, we really could have omitted all of the Transaction calls. But the point of the examples was to show that the Transaction API really does insulate your code from the underlying transaction mechanism. In fact, if you strip away the comments and the single configuration setting supplied at bootstrap, the code is exactly the same in all 3 examples. In other words, we could develop that code and drop it, as-is, in any of the 3 transaction environments. @@ -184,7 +184,7 @@ If you use JTA, you can utilize the JTA interfaces to demarcate transactions. If you execute in an EJB container that supports CMT, transaction boundaries are defined declaratively and you do not need any transaction or session demarcation operations in your code. Refer to <> for more information and code examples. The `hibernate.current_session_context_class` configuration parameter defines which `org.hibernate.context.spi.CurrentSessionContext` implementation should be used. -For backwards compatibility, if this configuration parameter is not set but a `org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform` is configured, Hibernate will use the `org.hibernate.context.internal.JTASessionContext`. +For backward compatibility, if this configuration parameter is not set but a `org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform` is configured, Hibernate will use the `org.hibernate.context.internal.JTASessionContext`. === Transactional patterns (and anti-patterns) @@ -195,7 +195,7 @@ This is an anti-pattern of opening and closing a `Session` for each database cal It is also an anti-pattern in terms of database transactions. Group your database calls into a planned sequence. In the same way, do not auto-commit after every SQL statement in your application. -Hibernate disables, or expects the application server to disable, auto-commit mode immediately. +Hibernate disables or expects the application server to disable, auto-commit mode immediately. Database transactions are never optional. All communication with a database must be encapsulated by a transaction. Avoid auto-commit behavior for reading data because many small transactions are unlikely to perform better than one clearly-defined unit of work, and are more difficult to maintain and extend. @@ -216,7 +216,7 @@ Web applications are a prime example of this type of system, though certainly no At the beginning of handling such a request, the application opens a Hibernate Session, starts a transaction, performs all data related work, ends the transaction and closes the Session. The crux of the pattern is the one-to-one relationship between the transaction and the Session. -Within this pattern there is a common technique of defining a current session to simplify the need of passing this `Session` around to all the application components that may need access to it. +Within this pattern, there is a common technique of defining a current session to simplify the need of passing this `Session` around to all the application components that may need access to it. Hibernate provides support for this technique through the `getCurrentSession` method of the `SessionFactory`. The concept of a _current_ session has to have a scope that defines the bounds in which the notion of _current_ is valid. This is the purpose of the `org.hibernate.context.spi.CurrentSessionContext` contract. @@ -230,7 +230,7 @@ Using this implementation, a `Session` will be opened the first time `getCurrent This is best represented with the `org.hibernate.context.internal.ManagedSessionContext` implementation of the `org.hibernate.context.spi.CurrentSessionContext` contract. Here an external component is responsible for managing the lifecycle and scoping of a _current_ session. At the start of such a scope, `ManagedSessionContext#bind()` method is called passing in the `Session`. -At the end, its `unbind()` method is called. +In the end, its `unbind()` method is called. Some common examples of such _external components_ include: ** `javax.servlet.Filter` implementation ** AOP interceptor with a pointcut on the service methods @@ -285,7 +285,7 @@ Automatic versioning is used to isolate concurrent modifications. |Extended `Session` |The Hibernate `Session` can be disconnected from the underlying JDBC connection after the database transaction has been committed and reconnected when a new client request occurs. This pattern is known as session-per-conversation and makes even reattachment unnecessary. -Automatic versioning is used to isolate concurrent modifications and the `Session` will not be allowed to flush automatically, only explicitly. +Automatic versioning is used to isolate concurrent modifications, and the `Session` will not be allowed to flush automatically, only explicitly. |======================================================================= Session-per-request-with-detached-objects and session-per-conversation each have advantages and disadvantages. @@ -295,7 +295,7 @@ Session-per-request-with-detached-objects and session-per-conversation each have The _session-per-application_ is also considered an anti-pattern. The Hibernate `Session`, like the JPA `EntityManager`, is not a thread-safe object and it is intended to be confined to a single thread at once. -If the `Session` is shared among multiple threads, there will be race conditions as well as visibility issues , so beware of this. +If the `Session` is shared among multiple threads, there will be race conditions as well as visibility issues, so beware of this. An exception thrown by Hibernate means you have to rollback your database transaction and close the `Session` immediately. If your `Session` is bound to the application, you have to stop the application. diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java b/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java index 757693c023d3..34c1ff4a6680 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java @@ -16,14 +16,14 @@ public class CallStatistics { private final long total; private final int min; private final int max; - private final double abg; + private final double avg; - public CallStatistics(long count, long total, int min, int max, double abg) { + public CallStatistics(long count, long total, int min, int max, double avg) { this.count = count; this.total = total; this.min = min; this.max = max; - this.abg = abg; + this.avg = avg; } //Getters and setters omitted for brevity diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java index e642addd209f..a0c7fd7293fe 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java @@ -120,7 +120,7 @@ public static class Product { @Type( type = "bitset" ) private BitSet bitSet; - //Constructors, getters and setters are omitted for brevity + //Constructors, getters, and setters are omitted for brevity //end::basic-custom-type-BitSetUserType-mapping-example[] public Product() { } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java index 0d8291ebb492..4da5ae8b2008 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java @@ -53,7 +53,7 @@ public static class Event { @CreationTimestamp private Date timestamp; - //Constructors, getters and setters are omitted for brevity + //Constructors, getters, and setters are omitted for brevity //end::mapping-generated-CreationTimestamp-example[] public Event() {} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java index c88983b86ed9..870997a9b4d3 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java @@ -56,7 +56,7 @@ public static class Event { @FunctionCreationTimestamp private Date timestamp; - //Constructors, getters and setters are omitted for brevity + //Constructors, getters, and setters are omitted for brevity //end::mapping-database-generated-value-example[] public Event() {} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java index 705dd6fd0050..7457a5137343 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java @@ -56,7 +56,7 @@ public static class Event { @FunctionCreationTimestamp private Date timestamp; - //Constructors, getters and setters are omitted for brevity + //Constructors, getters, and setters are omitted for brevity //end::mapping-in-memory-generated-value-example[] public Event() {} From 59607a988dc3911cdf61b3d8f3f8784045296cf5 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Mon, 13 Aug 2018 17:41:18 +0300 Subject: [PATCH 141/772] HHH-12892 - Fix spelling issues in the User Guide --- .../src/main/asciidoc/userguide/appendices/Annotations.adoc | 2 +- .../main/asciidoc/userguide/chapters/domain/collections.adoc | 2 +- .../src/main/asciidoc/userguide/chapters/envers/Envers.adoc | 2 +- .../asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index 8c0e4e99e7f6..214362104ecf 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -56,7 +56,7 @@ See the <> chapter for more info. [[annotations-jpa-collectiontable]] ==== `@CollectionTable` -The http://docs.oracle.com/javaee/7/api/javax/persistence/CollectionTable.html[`@CollectionTable`] annotation is used to specify the database table that stores the values of basic or an embeddable type collection. +The http://docs.oracle.com/javaee/7/api/javax/persistence/CollectionTable.html[`@CollectionTable`] annotation is used to specify the database table that stores the values of a basic or an embeddable type collection. See the <> section for more info. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc index 11b4e695b24c..ae6e9663c76c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc @@ -46,7 +46,7 @@ The persistent collections injected by Hibernate behave like `ArrayList`, `HashS [[collections-synopsis]] ==== Collections as a value type -Value and embeddable type collections have similar behavior as simple value types because they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. +Value and embeddable type collections have a similar behavior to basic types since they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. If a collection is passed from one persistent object to another, its elements might be moved from one table to another. [IMPORTANT] diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index 5b360b551228..a381bfd11145 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -459,7 +459,7 @@ If your `RevisionListener` class is inaccessible from `@RevisionEntity` (e.g. it set `org.hibernate.envers.revision_listener` property to its fully qualified class name. Class name defined by the configuration parameter overrides the revision entity's value attribute. -Considering we have a `CurrentUser` utility which stores the currenty logged user: +Considering we have a `CurrentUser` utility which stores the currently logged user: [[envers-revisionlog-CurrentUser-example]] .`CurrentUser` utility diff --git a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc index a081b7091e69..16fb19f5de33 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc @@ -111,7 +111,7 @@ The `MultiTenantConnectionProvider` to use can be specified in a number of ways: * Use the `hibernate.multi_tenant_connection_provider` setting. It could name a `MultiTenantConnectionProvider` instance, a `MultiTenantConnectionProvider` implementation class reference or a `MultiTenantConnectionProvider` implementation class name. * Passed directly to the `org.hibernate.boot.registry.StandardServiceRegistryBuilder`. -* If none of the above options matches, but the settings do specify a `hibernate.connection.datasource` value, +* If none of the above options match, but the settings do specify a `hibernate.connection.datasource` value, Hibernate will assume it should use the specific `DataSourceBasedMultiTenantConnectionProviderImpl` implementation which works on a number of pretty reasonable assumptions when running inside of an app server and using one `javax.sql.DataSource` per tenant. See its https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.html[Javadocs] for more details. From f062abe72a0f43eb4d816eca520980970449e5c3 Mon Sep 17 00:00:00 2001 From: Roland Kurucz Date: Sat, 11 Aug 2018 20:27:51 +0200 Subject: [PATCH 142/772] HHH-12905 - Bind value [null] was not of specified type in StoredProcedureQuery --- .../procedure/internal/ParameterBindImpl.java | 15 +- .../procedure/MySQLStoredProcedureTest.java | 83 ++++++++ .../PostgreSQLStoredProcedureTest.java | 184 +++++++++++++----- .../StoredProcedureParameterTypeTest.java | 67 ++++++- 4 files changed, 297 insertions(+), 52 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java index 4d9dd0b8600f..6b7d99bb5133 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java @@ -79,8 +79,19 @@ private void internalSetValue(T value) { } if ( procedureParameter.getParameterType() != null ) { - if ( !procedureParameter.getParameterType().isInstance( value ) && !procedureParameter.getHibernateType().getReturnedClass().isInstance( value ) ) { - throw new IllegalArgumentException( "Bind value [" + value + "] was not of specified type [" + procedureParameter.getParameterType() ); + if ( value == null ) { + if ( !procedureParameter.isPassNullsEnabled() ) { + throw new IllegalArgumentException( "The parameter with the [" + + ( procedureParameter.getName() != null + ? procedureParameter.getName() + "] name" + : procedureParameter.getPosition() + "] position" ) + + " was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters." ); + } + } + else if ( !procedureParameter.getParameterType().isInstance( value ) && + !procedureParameter.getHibernateType().getReturnedClass().isInstance( value ) ) { + throw new IllegalArgumentException( "Bind value [" + value + "] was not of specified type [" + procedureParameter + .getParameterType() ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java index cd231086c06e..4e554590548b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java @@ -28,14 +28,19 @@ import org.hibernate.procedure.ProcedureCall; import org.hibernate.result.Output; import org.hibernate.result.ResultSetOutput; +import org.hibernate.type.StringType; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; 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.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Vlad Mihalcea @@ -98,6 +103,18 @@ public void init() { " RETURN phoneCount; " + "END" ); + + statement.executeUpdate( + "CREATE PROCEDURE sp_is_null (" + + " IN param varchar(255), " + + " OUT result BIT(1) " + + ") " + + "BEGIN " + + " IF (param IS NULL) THEN SET result = 1; " + + " ELSE SET result = 0; " + + " END IF; " + + "END" + ); } finally { if ( statement != null ) { statement.close(); @@ -193,6 +210,24 @@ public void destroy() { entityManager.getTransaction().rollback(); entityManager.close(); } + + entityManager = createEntityManager(); + entityManager.getTransaction().begin(); + + try { + Session session = entityManager.unwrap( Session.class ); + session.doWork( connection -> { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate( "DROP PROCEDURE IF EXISTS sp_is_null" ); + } + catch (SQLException ignore) { + } + } ); + } + finally { + entityManager.getTransaction().rollback(); + entityManager.close(); + } } @Test @@ -332,4 +367,52 @@ public void testFunctionWithJDBC() { entityManager.close(); } } + + @Test + @TestForIssue( jiraKey = "HHH-12905") + public void testStoredProcedureNullParameter() { + + doInJPA( this::entityManagerFactory, entityManager -> { + ProcedureCall procedureCall = entityManager.unwrap( Session.class ).createStoredProcedureCall("sp_is_null"); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN).enablePassingNulls( true); + procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT); + procedureCall.setParameter(1, null); + + Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 ); + + assertTrue( result ); + }); + + doInJPA( this::entityManagerFactory, entityManager -> { + ProcedureCall procedureCall = entityManager.unwrap( Session.class ).createStoredProcedureCall("sp_is_null"); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN).enablePassingNulls( true); + procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT); + procedureCall.setParameter(1, "test"); + + Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 ); + + assertFalse( result ); + }); + } + + @Test + @TestForIssue( jiraKey = "HHH-12905") + public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls() { + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + ProcedureCall procedureCall = entityManager.unwrap( Session.class ).createStoredProcedureCall("sp_is_null"); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN); + procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT); + procedureCall.setParameter(1, null); + + procedureCall.getOutputParameterValue( 2 ); + + fail("Should have thrown exception"); + } + catch (IllegalArgumentException e) { + assertEquals( "The parameter on the [1] position was null. You need to call ParameterRegistration#enablePassingNulls in order to pass null parameters.", e.getMessage() ); + } + }); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java index 0515969d21f7..73a7e77f4c0b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java @@ -16,12 +16,16 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; +import java.util.Properties; import javax.persistence.ParameterMode; import javax.persistence.StoredProcedureQuery; import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.type.StringType; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; @@ -31,6 +35,7 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -42,8 +47,8 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe @Override protected Class[] getAnnotatedClasses() { return new Class[] { - Person.class, - Phone.class + Person.class, + Phone.class }; } @@ -106,6 +111,25 @@ public void init() { } ); } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + + session.doWork( connection -> { + Statement statement = null; + try { + statement = connection.createStatement(); + statement.executeUpdate( "DROP FUNCTION sp_is_null()" ); + } + catch (SQLException ignore) { + } + finally { + if ( statement != null ) { + statement.close(); + } + } + } ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); @@ -114,49 +138,62 @@ public void init() { try { statement = connection.createStatement(); statement.executeUpdate( - "CREATE OR REPLACE FUNCTION sp_count_phones( " + - " IN personId bigint, " + - " OUT phoneCount bigint) " + - " RETURNS bigint AS " + - "$BODY$ " + - " BEGIN " + - " SELECT COUNT(*) INTO phoneCount " + - " FROM phone " + - " WHERE person_id = personId; " + - " END; " + - "$BODY$ " + - "LANGUAGE plpgsql;" + "CREATE OR REPLACE FUNCTION sp_count_phones( " + + " IN personId bigint, " + + " OUT phoneCount bigint) " + + " RETURNS bigint AS " + + "$BODY$ " + + " BEGIN " + + " SELECT COUNT(*) INTO phoneCount " + + " FROM phone " + + " WHERE person_id = personId; " + + " END; " + + "$BODY$ " + + "LANGUAGE plpgsql;" ); statement.executeUpdate( - "CREATE OR REPLACE FUNCTION fn_phones(personId BIGINT) " + - " RETURNS REFCURSOR AS " + - "$BODY$ " + - " DECLARE " + - " phones REFCURSOR; " + - " BEGIN " + - " OPEN phones FOR " + - " SELECT * " + - " FROM phone " + - " WHERE person_id = personId; " + - " RETURN phones; " + - " END; " + - "$BODY$ " + - "LANGUAGE plpgsql" + "CREATE OR REPLACE FUNCTION fn_phones(personId BIGINT) " + + " RETURNS REFCURSOR AS " + + "$BODY$ " + + " DECLARE " + + " phones REFCURSOR; " + + " BEGIN " + + " OPEN phones FOR " + + " SELECT * " + + " FROM phone " + + " WHERE person_id = personId; " + + " RETURN phones; " + + " END; " + + "$BODY$ " + + "LANGUAGE plpgsql" ); statement.executeUpdate( - "CREATE OR REPLACE FUNCTION singleRefCursor() " + - " RETURNS REFCURSOR AS " + - "$BODY$ " + - " DECLARE " + - " p_recordset REFCURSOR; " + - " BEGIN " + - " OPEN p_recordset FOR SELECT 1; " + - " RETURN p_recordset; " + - " END; " + - "$BODY$ " + - "LANGUAGE plpgsql;" + "CREATE OR REPLACE FUNCTION singleRefCursor() " + + " RETURNS REFCURSOR AS " + + "$BODY$ " + + " DECLARE " + + " p_recordset REFCURSOR; " + + " BEGIN " + + " OPEN p_recordset FOR SELECT 1; " + + " RETURN p_recordset; " + + " END; " + + "$BODY$ " + + "LANGUAGE plpgsql;" + ); + + statement.executeUpdate( + "CREATE OR REPLACE FUNCTION sp_is_null( " + + " IN param varchar(255), " + + " OUT result boolean) " + + " RETURNS boolean AS " + + "$BODY$ " + + " BEGIN " + + " select param is null into result; " + + " END; " + + "$BODY$ " + + "LANGUAGE plpgsql;" ); } finally { @@ -171,7 +208,8 @@ public void init() { Person person1 = new Person( "John Doe" ); person1.setNickName( "JD" ); person1.setAddress( "Earth" ); - person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ).toInstant( ZoneOffset.UTC ) ) ); + person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ) + .toInstant( ZoneOffset.UTC ) ) ); entityManager.persist( person1 ); @@ -185,7 +223,7 @@ public void init() { person1.addPhone( phone2 ); } ); - } + } @Test public void testStoredProcedureOutParameter() { @@ -260,14 +298,15 @@ public void testFunctionWithJDBCByName() { } } ); assertEquals( Long.valueOf( 2 ), phoneCount ); - } catch (Exception e) { + } + catch (Exception e) { assertEquals( SQLFeatureNotSupportedException.class, e.getCause().getClass() ); } } ); } @Test - @TestForIssue( jiraKey = "HHH-11863") + @TestForIssue(jiraKey = "HHH-11863") public void testSysRefCursorAsOutParameter() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -275,7 +314,7 @@ public void testSysRefCursorAsOutParameter() { Session session = entityManager.unwrap( Session.class ); - try(ResultSet resultSet = session.doReturningWork( connection -> { + try (ResultSet resultSet = session.doReturningWork( connection -> { CallableStatement function = null; try { function = connection.prepareCall( "{ ? = call singleRefCursor() }" ); @@ -294,7 +333,7 @@ public void testSysRefCursorAsOutParameter() { } } catch (Exception e) { - fail(e.getMessage()); + fail( e.getMessage() ); } assertEquals( Long.valueOf( 1 ), value ); @@ -307,16 +346,67 @@ public void testSysRefCursorAsOutParameter() { assertFalse( function.hasMoreResults() ); value = null; - try ( ResultSet resultSet = (ResultSet) function.getOutputParameterValue( 1 ) ) { + try (ResultSet resultSet = (ResultSet) function.getOutputParameterValue( 1 )) { while ( resultSet.next() ) { value = resultSet.getLong( 1 ); } } catch (SQLException e) { - fail(e.getMessage()); + fail( e.getMessage() ); } assertEquals( Long.valueOf( 1 ), value ); } ); } + + @Test + @TestForIssue(jiraKey = "HHH-12905") + public void testStoredProcedureNullParameterHibernate() { + + doInJPA( this::entityManagerFactory, entityManager -> { + ProcedureCall procedureCall = entityManager.unwrap( Session.class ) + .createStoredProcedureCall( "sp_is_null" ); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN ).enablePassingNulls( true ); + procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT ); + procedureCall.setParameter( 1, null ); + + Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 ); + + assertTrue( result ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + ProcedureCall procedureCall = entityManager.unwrap( Session.class ) + .createStoredProcedureCall( "sp_is_null" ); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN ).enablePassingNulls( true ); + procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT ); + procedureCall.setParameter( 1, "test" ); + + Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 ); + + assertFalse( result ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12905") + public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls() { + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + ProcedureCall procedureCall = entityManager.unwrap( Session.class ) + .createStoredProcedureCall( "sp_is_null" ); + procedureCall.registerParameter( "param", StringType.class, ParameterMode.IN ); + procedureCall.registerParameter( "result", Boolean.class, ParameterMode.OUT ); + procedureCall.setParameter( "param", null ); + + procedureCall.getOutputParameterValue( "result" ); + + fail("Should have thrown exception"); + } + catch (IllegalArgumentException e) { + assertEquals( "The parameter with the [param] position was null. You need to call ParameterRegistration#enablePassingNulls in order to pass null parameters.", e.getMessage() ); + } + } ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/procedure/StoredProcedureParameterTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/procedure/StoredProcedureParameterTypeTest.java index e630182ac7c3..d02c2d54673d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/procedure/StoredProcedureParameterTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/procedure/StoredProcedureParameterTypeTest.java @@ -29,7 +29,7 @@ import javax.sql.rowset.serial.SerialBlob; import javax.sql.rowset.serial.SerialClob; -import org.hibernate.dialect.H2Dialect; +import org.hibernate.procedure.ProcedureCall; import org.hibernate.type.BigDecimalType; import org.hibernate.type.BigIntegerType; import org.hibernate.type.BinaryType; @@ -61,17 +61,17 @@ import org.hibernate.type.UrlType; import org.hibernate.type.YesNoType; -import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Vlad Mihalcea */ -@TestForIssue( jiraKey = "HHH-12661" ) public class StoredProcedureParameterTypeTest extends BaseNonConfigCoreFunctionalTestCase { private static final String TEST_STRING = "test_string"; @@ -79,6 +79,7 @@ public class StoredProcedureParameterTypeTest extends BaseNonConfigCoreFunctiona private static final byte[] TEST_BYTE_ARRAY = TEST_STRING.getBytes(); @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testNumericBooleanTypeInParameter() { doInHibernate( this::sessionFactory, session -> { session.createStoredProcedureQuery( "test" ) @@ -89,6 +90,7 @@ public void testNumericBooleanTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testYesNoTypeInParameter() { doInHibernate( this::sessionFactory, session -> { session.createStoredProcedureQuery( "test" ) @@ -99,6 +101,7 @@ public void testYesNoTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testStringTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -108,6 +111,7 @@ public void testStringTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testMaterializedClobTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -117,6 +121,7 @@ public void testMaterializedClobTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testTextTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -126,6 +131,7 @@ public void testTextTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testCharacterTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -135,6 +141,7 @@ public void testCharacterTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testTrueFalseTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -144,6 +151,7 @@ public void testTrueFalseTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testBooleanTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -153,6 +161,7 @@ public void testBooleanTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testByteTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -162,6 +171,7 @@ public void testByteTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testShortTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -171,6 +181,7 @@ public void testShortTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testIntegerTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -180,6 +191,7 @@ public void testIntegerTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testLongTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -198,6 +210,7 @@ public void testFloatTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testDoubleTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -207,6 +220,7 @@ public void testDoubleTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testBigIntegerTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -216,6 +230,7 @@ public void testBigIntegerTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testBigDecimalTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -225,6 +240,7 @@ public void testBigDecimalTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testTimestampTypeDateInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -234,6 +250,7 @@ public void testTimestampTypeDateInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testTimestampTypeTimestampInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -243,6 +260,7 @@ public void testTimestampTypeTimestampInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testTimeTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -252,6 +270,7 @@ public void testTimeTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testDateTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -261,6 +280,7 @@ public void testDateTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testCalendarTypeCalendarInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -270,6 +290,7 @@ public void testCalendarTypeCalendarInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testCurrencyTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -279,6 +300,7 @@ public void testCurrencyTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testLocaleTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -288,6 +310,7 @@ public void testLocaleTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testTimeZoneTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -297,6 +320,7 @@ public void testTimeZoneTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testUrlTypeInParameter() throws MalformedURLException { final URL url = new URL( "http://example.com"); inTransaction( @@ -307,6 +331,7 @@ public void testUrlTypeInParameter() throws MalformedURLException { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testClassTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -316,6 +341,7 @@ public void testClassTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testBlobTypeInParameter() throws SQLException { final Blob blob = new SerialBlob( TEST_BYTE_ARRAY); inTransaction( @@ -326,6 +352,7 @@ public void testBlobTypeInParameter() throws SQLException { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testClobTypeInParameter() throws SQLException { final Clob clob = new SerialClob( TEST_CHAR_ARRAY); inTransaction( @@ -336,6 +363,7 @@ public void testClobTypeInParameter() throws SQLException { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testBinaryTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -345,6 +373,7 @@ public void testBinaryTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testCharArrayTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -354,6 +383,7 @@ public void testCharArrayTypeInParameter() { } @Test + @TestForIssue( jiraKey = "HHH-12661" ) public void testUUIDBinaryTypeInParameter() { inTransaction( session -> session.createStoredProcedureQuery("test") @@ -361,4 +391,35 @@ public void testUUIDBinaryTypeInParameter() { .setParameter( 1, UUID.randomUUID()) ); } + + @Test + @TestForIssue(jiraKey = "HHH-12905") + public void testStringTypeInParameterIsNull() { + inTransaction( + session -> { + ProcedureCall procedureCall = session.createStoredProcedureCall( "test" ); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN ).enablePassingNulls( true ); + procedureCall.setParameter( 1, null ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12905") + public void testStringTypeInParameterIsNullWithoutEnablePassingNulls() { + inTransaction( + session -> { + try { + ProcedureCall procedureCall = session.createStoredProcedureCall( "test" ); + procedureCall.registerParameter( 1, StringType.class, ParameterMode.IN ); + procedureCall.setParameter( 1, null ); + + fail("Should have thrown exception"); + } + catch (IllegalArgumentException e) { + assertTrue( e.getMessage().endsWith( "You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters." ) ); + } + } + ); + } } \ No newline at end of file From 01243a9b0e168646086f5e2e06f377541b3ea3f1 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 1 Aug 2018 14:57:51 -0700 Subject: [PATCH 143/772] HHH-12875 : test cases (cherry picked from commit eef897487733c18fc96d7f534e0c97fb19aadd7c) --- ...ToManyNonUniqueIdNotFoundWhereTest.hbm.xml | 77 ++++ ...anyToManyNonUniqueIdNotFoundWhereTest.java | 342 ++++++++++++++++++ ...LazyManyToManyNonUniqueIdWhereTest.hbm.xml | 77 ++++ .../LazyManyToManyNonUniqueIdWhereTest.java | 308 ++++++++++++++++ 4 files changed, 804 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml new file mode 100644 index 000000000000..662121aa21da --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.java new file mode 100644 index 000000000000..4d91d796113e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.java @@ -0,0 +1,342 @@ +/* + * 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.where; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class LazyManyToManyNonUniqueIdNotFoundWhereTest extends BaseCoreFunctionalTestCase { + + protected String[] getMappings() { + return new String[] { "where/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml" }; + } + + @Before + public void setup() { + doInHibernate( + this::sessionFactory, session -> { + session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE ASSOCIATION_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE BUILDING_RATINGS" ).executeUpdate(); + + session.createSQLQuery( + "create table MAIN_TABLE( " + + "ID integer not null, NAME varchar(255) not null, CODE varchar(10) not null, " + + "primary key (ID, CODE) )" + ).executeUpdate(); + + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'high', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'small', 'SIZE' )" ) + .executeUpdate(); + + session.createSQLQuery( + "create table ASSOCIATION_TABLE( " + + "MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " + + "ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " + + "primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 1, 'RATING' )" + ).executeUpdate(); + + // add a collection element that won't be found + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 2, 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 1, 'SIZE' )" + ).executeUpdate(); + + // add a collection element that won't be found + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 2, 'SIZE' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'RATING' )" + ).executeUpdate(); + + // add a collection element that won't be found + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 2, 'RATING' )" + ).executeUpdate(); + + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'SIZE' )" + ).executeUpdate(); + + // add a collection element that won't be found + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 2, 'SIZE' )" + ).executeUpdate(); + + session.createSQLQuery( + "create table MATERIAL_RATINGS( " + + "MATERIAL_ID integer not null, RATING_ID integer not null," + + " primary key (MATERIAL_ID, RATING_ID))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 1 )" + ).executeUpdate(); + + // add a collection element that won't be found + session.createSQLQuery( + "insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 2 )" + ).executeUpdate(); + + + session.createSQLQuery( + "create table BUILDING_RATINGS( " + + "BUILDING_ID integer not null, RATING_ID integer not null," + + " primary key (BUILDING_ID, RATING_ID))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 1 )" + ).executeUpdate(); + + // add a collection element that won't be found + session.createSQLQuery( + "insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 2 )" + ).executeUpdate(); + } + ); + } + + @After + public void cleanup() { + doInHibernate( + this::sessionFactory, session -> { + session.createSQLQuery( "delete from MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from BUILDING_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from ASSOCIATION_TABLE" ).executeUpdate(); + session.createSQLQuery( "delete from MAIN_TABLE" ).executeUpdate(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12875") + public void testInitializeFromUniqueAssociationTable() { + doInHibernate( + this::sessionFactory, session -> { + + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getRatings() ) ); + assertEquals( 1, material.getRatings().size() ); + assertTrue( Hibernate.isInitialized( material.getRatings() ) ); + + final Rating rating = material.getRatings().iterator().next(); + assertEquals( "high", rating.getName() ); + + Building building = session.get( Building.class, 1 ); + assertEquals( "house", building.getName() ); + + // Building#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getRatings() ) ); + assertEquals( 1, building.getRatings().size() ); + assertTrue( Hibernate.isInitialized( building.getRatings() ) ); + assertSame( rating, building.getRatings().iterator().next() ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12875") + public void testInitializeFromNonUniqueAssociationTable() { + doInHibernate( + this::sessionFactory, session -> { + + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getRatingsFromCombined() ) ); + assertEquals( 1, material.getRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getRatingsFromCombined() ) ); + + final Rating rating = material.getRatingsFromCombined().iterator().next(); + assertEquals( "high", rating.getName() ); + + // Material#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + assertEquals( 1, material.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + + final Size size = material.getSizesFromCombined().iterator().next(); + assertEquals( "small", size.getName() ); + + Building building = session.get( Building.class, 1 ); + + // building.ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertEquals( 1, building.getRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertSame( rating, building.getRatingsFromCombined().iterator().next() ); + + // Building#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( 1, building.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertSame( size, building.getSizesFromCombined().iterator().next() ); + } + ); + } + + public static class Material { + private int id; + + private String name; + private Set sizesFromCombined = new HashSet<>(); + private Set ratingsFromCombined = new HashSet<>(); + private Set ratings = new HashSet<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + public Set getRatingsFromCombined() { + return ratingsFromCombined; + } + public void setRatingsFromCombined(Set ratingsFromCombined) { + this.ratingsFromCombined = ratingsFromCombined; + } + public Set getRatings() { + return ratings; + } + public void setRatings(Set ratings) { + this.ratings = ratings; + } + } + + public static class Building { + private int id; + private String name; + private Set sizesFromCombined = new HashSet<>(); + private Set ratingsFromCombined = new HashSet<>(); + private Set ratings = new HashSet<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + public Set getRatingsFromCombined() { + return ratingsFromCombined; + } + public void setRatingsFromCombined(Set ratingsFromCombined) { + this.ratingsFromCombined = ratingsFromCombined; + } + public Set getRatings() { + return ratings; + } + public void setRatings(Set ratings) { + this.ratings = ratings; + } + } + + public static class Size { + private int id; + private String name; + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + public static class Rating { + private int id; + private String name; + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.hbm.xml new file mode 100644 index 000000000000..29bf2a85ef67 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.hbm.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.java new file mode 100644 index 000000000000..d218918870c5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.java @@ -0,0 +1,308 @@ +/* + * 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.where; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class LazyManyToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + + protected String[] getMappings() { + return new String[] { "where/LazyManyToManyNonUniqueIdWhereTest.hbm.xml" }; + } + + @Before + public void setup() { + doInHibernate( + this::sessionFactory, session -> { + + session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE ASSOCIATION_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE BUILDING_RATINGS" ).executeUpdate(); + + session.createSQLQuery( + "create table MAIN_TABLE( " + + "ID integer not null, NAME varchar(255) not null, CODE varchar(10) not null, " + + "primary key (ID, CODE) )" + ).executeUpdate(); + + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'high', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'small', 'SIZE' )" ) + .executeUpdate(); + + session.createSQLQuery( + "create table ASSOCIATION_TABLE( " + + "MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " + + "ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " + + "primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 1, 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 1, 'SIZE' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'SIZE' )" + ).executeUpdate(); + + + session.createSQLQuery( + "create table MATERIAL_RATINGS( " + + "MATERIAL_ID integer not null, RATING_ID integer not null," + + " primary key (MATERIAL_ID, RATING_ID))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 1 )" + ).executeUpdate(); + + session.createSQLQuery( + "create table BUILDING_RATINGS( " + + "BUILDING_ID integer not null, RATING_ID integer not null," + + " primary key (BUILDING_ID, RATING_ID))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 1 )" + ).executeUpdate(); + } + ); + } + + @After + public void cleanup() { + doInHibernate( + this::sessionFactory, session -> { + session.createSQLQuery( "delete from MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from BUILDING_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from ASSOCIATION_TABLE" ).executeUpdate(); + session.createSQLQuery( "delete from MAIN_TABLE" ).executeUpdate(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12875") + public void testInitializeFromUniqueAssociationTable() { + doInHibernate( + this::sessionFactory, session -> { + + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getRatings() ) ); + assertEquals( 1, material.getRatings().size() ); + assertTrue( Hibernate.isInitialized( material.getRatings() ) ); + + final Rating rating = material.getRatings().iterator().next(); + assertEquals( "high", rating.getName() ); + + Building building = session.get( Building.class, 1 ); + assertEquals( "house", building.getName() ); + + // Building#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getRatings() ) ); + assertEquals( 1, building.getRatings().size() ); + assertTrue( Hibernate.isInitialized( building.getRatings() ) ); + assertSame( rating, building.getRatings().iterator().next() ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12875") + public void testInitializeFromNonUniqueAssociationTable() { + doInHibernate( + this::sessionFactory, session -> { + + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getRatingsFromCombined() ) ); + assertEquals( 1, material.getRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getRatingsFromCombined() ) ); + + final Rating rating = material.getRatingsFromCombined().iterator().next(); + assertEquals( "high", rating.getName() ); + + // Material#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + assertEquals( 1, material.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + + final Size size = material.getSizesFromCombined().iterator().next(); + assertEquals( "small", size.getName() ); + + Building building = session.get( Building.class, 1 ); + + // building.ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertEquals( 1, building.getRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertSame( rating, building.getRatingsFromCombined().iterator().next() ); + + // Building#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( 1, building.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertSame( size, building.getSizesFromCombined().iterator().next() ); + } + ); + } + + public static class Material { + private int id; + + private String name; + private Set sizesFromCombined = new HashSet<>(); + private Set ratingsFromCombined = new HashSet<>(); + private Set ratings = new HashSet<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + public Set getRatingsFromCombined() { + return ratingsFromCombined; + } + public void setRatingsFromCombined(Set ratingsFromCombined) { + this.ratingsFromCombined = ratingsFromCombined; + } + public Set getRatings() { + return ratings; + } + public void setRatings(Set ratings) { + this.ratings = ratings; + } + } + + public static class Building { + private int id; + private String name; + private Set sizesFromCombined = new HashSet<>(); + private Set ratingsFromCombined = new HashSet<>(); + private Set ratings = new HashSet<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + public Set getRatingsFromCombined() { + return ratingsFromCombined; + } + public void setRatingsFromCombined(Set ratingsFromCombined) { + this.ratingsFromCombined = ratingsFromCombined; + } + public Set getRatings() { + return ratings; + } + public void setRatings(Set ratings) { + this.ratings = ratings; + } + } + + public static class Size { + private int id; + private String name; + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + public static class Rating { + private int id; + private String name; + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } +} From 22c9b2ba0a485dd9d053f800f2cbfc40bd89a171 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 1 Aug 2018 15:01:25 -0700 Subject: [PATCH 144/772] HHH-12875 : Class level where="..." clause in hbm.xml mappings is not enforced on collections of that class (cherry picked from commit 10826d8f3aa6b4136f798771034f4c1ee832ddd8) --- .../model/source/internal/hbm/ModelBinder.java | 15 ++++++++++++++- .../hibernate/test/legacy/ParentChildTest.java | 14 +++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index b79be28cbbf1..91f134fbfe7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -3494,7 +3494,20 @@ public Identifier determineImplicitName(final LocalMetadataBuildingContext conte getCollectionBinding().setElement( elementBinding ); - getCollectionBinding().setManyToManyWhere( elementSource.getWhere() ); + final StringBuilder whereBuffer = new StringBuilder(); + final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector() + .getEntityBinding( elementSource.getReferencedEntityName() ); + if ( StringHelper.isNotEmpty( referencedEntityBinding.getWhere() ) ) { + whereBuffer.append( referencedEntityBinding.getWhere() ); + } + if ( StringHelper.isNotEmpty( elementSource.getWhere() ) ) { + if ( whereBuffer.length() > 0 ) { + whereBuffer.append( " and " ); + } + whereBuffer.append( elementSource.getWhere() ); + } + getCollectionBinding().setManyToManyWhere( whereBuffer.toString() ); + getCollectionBinding().setManyToManyOrdering( elementSource.getOrder() ); if ( !CollectionHelper.isEmpty( elementSource.getFilterSources() ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java index 395ea2e4dea7..587f57ef503b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java @@ -514,12 +514,20 @@ public void testClassWhereManyToMany() throws Exception { assertTrue( s.createCriteria(Part.class).list().size()==1 ); //there is a where condition on Part mapping assertTrue( s.createCriteria(Part.class).add( Restrictions.eq( "id", p1.getId() ) ).list().size()==1 ); assertTrue( s.createQuery("from Part").list().size()==1 ); - assertTrue( s.createQuery("from Baz baz join baz.moreParts").list().size()==2 ); + // only Part entities that satisfy the where condition on Part mapping should be included in the collection + assertTrue( s.createQuery("from Baz baz join baz.moreParts").list().size()==1 ); baz = (Baz) s.createCriteria(Baz.class).uniqueResult(); - assertTrue( s.createFilter( baz.getMoreParts(), "" ).list().size()==2 ); + // only Part entities that satisfy the where condition on Part mapping should be included in the collection + assertTrue( s.createFilter( baz.getMoreParts(), "" ).list().size()==1 ); //assertTrue( baz.getParts().size()==1 ); s.delete( s.get( Part.class, p1.getId() )); - s.delete( s.get( Part.class, p2.getId() )); + // p2.description does not satisfy the condition on Part mapping, so it's not possible to retrieve it + // using Session#get; instead just delete using a native query + s.createNativeQuery( "delete from baz_moreParts where baz = :baz AND part = :part" ) + .setParameter( "baz", baz.getCode() ) + .setParameter( "part", p2.getId() ) + .executeUpdate(); + s.createNativeQuery( "delete from Part where id = :id" ).setParameter( "id", p2.getId() ).executeUpdate(); s.delete(baz); t.commit(); s.close(); From 70a39d91cbfaff47bed6bb2180477b8897d9d585 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 3 Aug 2018 01:22:48 -0700 Subject: [PATCH 145/772] HHH-12875 HHH-12882 : test cases (cherry picked from commit cdf5e45514e348f6eae196e3abe23ac60d899fd7) --- .../LazyManyToManyNonUniqueIdWhereTest.java | 444 ++++++++++++++++++ .../LazyOneToManyNonUniqueIdWhereTest.java | 302 ++++++++++++ .../test/where/{ => hbm}/File.hbm.xml | 2 +- .../hibernate/test/where/{ => hbm}/File.java | 2 +- ...ToManyNonUniqueIdNotFoundWhereTest.hbm.xml | 2 +- ...anyToManyNonUniqueIdNotFoundWhereTest.java | 4 +- ...LazyManyToManyNonUniqueIdWhereTest.hbm.xml | 16 +- .../LazyManyToManyNonUniqueIdWhereTest.java | 97 ++-- .../LazyOneToManyNonUniqueIdWhereTest.hbm.xml | 64 +++ .../LazyOneToManyNonUniqueIdWhereTest.java | 257 ++++++++++ .../test/where/{ => hbm}/WhereTest.java | 7 +- 11 files changed, 1155 insertions(+), 42 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java rename hibernate-core/src/test/java/org/hibernate/test/where/{ => hbm}/File.hbm.xml (93%) rename hibernate-core/src/test/java/org/hibernate/test/where/{ => hbm}/File.java (96%) rename hibernate-core/src/test/java/org/hibernate/test/where/{ => hbm}/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml (97%) rename hibernate-core/src/test/java/org/hibernate/test/where/{ => hbm}/LazyManyToManyNonUniqueIdNotFoundWhereTest.java (98%) rename hibernate-core/src/test/java/org/hibernate/test/where/{ => hbm}/LazyManyToManyNonUniqueIdWhereTest.hbm.xml (84%) rename hibernate-core/src/test/java/org/hibernate/test/where/{ => hbm}/LazyManyToManyNonUniqueIdWhereTest.java (71%) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.java rename hibernate-core/src/test/java/org/hibernate/test/where/{ => hbm}/WhereTest.java (95%) diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java new file mode 100644 index 000000000000..c21c0ddaf21b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java @@ -0,0 +1,444 @@ +/* + * 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.where.annotations; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Where; +import org.hibernate.annotations.WhereJoinTable; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Gail Badner + */ +public class LazyManyToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + + protected Class[] getAnnotatedClasses() { + return new Class[] { Material.class, Building.class, Rating.class, Size.class }; + } + + @Before + public void setup() { + doInHibernate( + this::sessionFactory, session -> { + + session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE ASSOCIATION_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE BUILDING_RATINGS" ).executeUpdate(); + + session.createSQLQuery( + "create table MAIN_TABLE( " + + "ID integer not null, NAME varchar(255) not null, CODE varchar(10) not null, " + + "primary key (ID, CODE) )" + ).executeUpdate(); + + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'high', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 3, 'low', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'small', 'SIZE' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'SIZE' )" ) + .executeUpdate(); + + session.createSQLQuery( + "create table ASSOCIATION_TABLE( " + + "MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " + + "ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " + + "primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 1, 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 2, 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 3, 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 2, 'SIZE' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'SIZE' )" + ).executeUpdate(); + + + session.createSQLQuery( + "create table MATERIAL_RATINGS( " + + "MATERIAL_ID integer not null, RATING_ID integer not null," + + " primary key (MATERIAL_ID, RATING_ID))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 1 )" + ).executeUpdate(); + + session.createSQLQuery( + "create table BUILDING_RATINGS( " + + "BUILDING_ID integer not null, RATING_ID integer not null," + + " primary key (BUILDING_ID, RATING_ID))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 1 )" + ).executeUpdate(); + session.createSQLQuery( + "insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 2 )" + ).executeUpdate(); + session.createSQLQuery( + "insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 3 )" + ).executeUpdate(); + } + ); + } + + @After + public void cleanup() { + doInHibernate( + this::sessionFactory, session -> { + session.createSQLQuery( "delete from MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from BUILDING_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from ASSOCIATION_TABLE" ).executeUpdate(); + session.createSQLQuery( "delete from MAIN_TABLE" ).executeUpdate(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12875") + public void testInitializeFromUniqueAssociationTable() { + doInHibernate( + this::sessionFactory, session -> { + + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getRatings() ) ); + assertEquals( 1, material.getRatings().size() ); + assertTrue( Hibernate.isInitialized( material.getRatings() ) ); + + final Rating rating = material.getRatings().iterator().next(); + assertEquals( "high", rating.getName() ); + + Building building = session.get( Building.class, 1 ); + assertEquals( "house", building.getName() ); + + // Building#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getMediumOrHighRatings() ) ); + checkMediumOrHighRatings( building.getMediumOrHighRatings() ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12875") + public void testInitializeFromNonUniqueAssociationTable() { + doInHibernate( + this::sessionFactory, session -> { + + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#mediumOrHighRatingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getMediumOrHighRatingsFromCombined() ) ); + checkMediumOrHighRatings( material.getMediumOrHighRatingsFromCombined() ); + Rating highRating = null; + for ( Rating rating : material.getMediumOrHighRatingsFromCombined() ) { + if ( "high".equals( rating.getName() ) ) { + highRating = rating; + } + } + assertNotNull( highRating ); + + // Material#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + assertEquals( 1, material.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + + final Size size = material.getSizesFromCombined().iterator().next(); + assertEquals( "medium", size.getName() ); + + Building building = session.get( Building.class, 1 ); + + // building.ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertEquals( 1, building.getRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertSame( highRating, building.getRatingsFromCombined().iterator().next() ); + + // Building#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( 1, building.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( "small", building.getSizesFromCombined().iterator().next().getName() ); + } + ); + } + + private void checkMediumOrHighRatings(List mediumOrHighRatings) { + assertEquals( 2, mediumOrHighRatings.size() ); + + final Iterator iterator = mediumOrHighRatings.iterator(); + final Rating firstRating = iterator.next(); + final Rating secondRating = iterator.next(); + if ( "high".equals( firstRating.getName() ) ) { + assertEquals( "medium", secondRating.getName() ); + } + else if ( "medium".equals( firstRating.getName() ) ) { + assertEquals( "high", secondRating.getName() ); + } + else { + fail( "unexpected rating" ); + } + } + + @Entity( name = "Material" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'MATERIAL'" ) + public static class Material { + private int id; + + private String name; + private Set sizesFromCombined = new HashSet<>(); + private List mediumOrHighRatingsFromCombined = new ArrayList<>(); + private Set ratings = new HashSet<>(); + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + @ManyToMany + @JoinTable( + name = "ASSOCIATION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) }, + inverseJoinColumns = { @JoinColumn( name = "ASSOCIATION_ID" ) } + ) + @WhereJoinTable( clause = "MAIN_CODE='MATERIAL' AND ASSOCIATION_CODE='SIZE'") + @Immutable + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + @ManyToMany + @JoinTable( + name = "ASSOCIATION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) }, + inverseJoinColumns = { @JoinColumn( name = "ASSOCIATION_ID" ) } + ) + @WhereJoinTable( clause = "MAIN_CODE='MATERIAL' AND ASSOCIATION_CODE='RATING'" ) + @Where( clause = "name = 'high' or name = 'medium'" ) + @Immutable + public List getMediumOrHighRatingsFromCombined() { + return mediumOrHighRatingsFromCombined; + } + public void setMediumOrHighRatingsFromCombined(List mediumOrHighRatingsFromCombined) { + this.mediumOrHighRatingsFromCombined = mediumOrHighRatingsFromCombined; + } + + @ManyToMany + @JoinTable( + name = "MATERIAL_RATINGS", + joinColumns = { @JoinColumn( name = "MATERIAL_ID") }, + inverseJoinColumns = { @JoinColumn( name = "RATING_ID" ) } + ) + @Immutable + public Set getRatings() { + return ratings; + } + public void setRatings(Set ratings) { + this.ratings = ratings; + } + } + + @Entity( name = "Building" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'BUILDING'" ) + public static class Building { + private int id; + private String name; + private Set sizesFromCombined = new HashSet<>(); + private Set ratingsFromCombined = new HashSet<>(); + private List mediumOrHighRatings = new ArrayList<>(); + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + @ManyToMany + @JoinTable( + name = "ASSOCIATION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) }, + inverseJoinColumns = { @JoinColumn( name = "ASSOCIATION_ID" ) } + ) + @WhereJoinTable( clause = "MAIN_CODE='BUILDING' AND ASSOCIATION_CODE='RATING'") + @Immutable + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + @ManyToMany + @JoinTable( + name = "ASSOCIATION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) }, + inverseJoinColumns = { @JoinColumn( name = "ASSOCIATION_ID" ) } + ) + @WhereJoinTable( clause = "MAIN_CODE='BUILDING' AND ASSOCIATION_CODE='RATING'" ) + @Immutable + public Set getRatingsFromCombined() { + return ratingsFromCombined; + } + public void setRatingsFromCombined(Set ratingsFromCombined) { + this.ratingsFromCombined = ratingsFromCombined; + } + + @ManyToMany + @JoinTable( + name = "BUILDING_RATINGS", + joinColumns = { @JoinColumn( name = "BUILDING_ID") }, + inverseJoinColumns = { @JoinColumn( name = "RATING_ID" ) } + ) + @Where( clause = "name = 'high' or name = 'medium'" ) + @Immutable + public List getMediumOrHighRatings() { + return mediumOrHighRatings; + } + public void setMediumOrHighRatings(List mediumOrHighRatings) { + this.mediumOrHighRatings = mediumOrHighRatings; + } + } + + @Entity( name = "Size" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'SIZE'" ) + public static class Size { + private int id; + private String name; + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + @Entity( name = "Rating" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'RATING'" ) + public static class Rating { + private int id; + private String name; + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java new file mode 100644 index 000000000000..5ce2b86daffb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java @@ -0,0 +1,302 @@ +/* + * 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.where.annotations; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Where; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Gail Badner + */ +public class LazyOneToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + + protected Class[] getAnnotatedClasses() { + return new Class[] { Material.class, Building.class, Rating.class, Size.class }; + } + + @Before + public void setup() { + doInHibernate( + this::sessionFactory, session -> { + + session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); + + session.createSQLQuery( + "create table MAIN_TABLE( " + + "ID integer not null, NAME varchar(255) not null, CODE varchar(10) not null, " + + "MATERIAL_OWNER_ID integer, BUILDING_OWNER_ID integer, " + + "primary key (ID, CODE) )" + ).executeUpdate(); + + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 1, 'high', 'RATING', 1, 1 )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 2, 'medium', 'RATING', 1, null )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 3, 'low', 'RATING', 1, null )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 1, 'small', 'SIZE', null, 1 )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 2, 'medium', 'SIZE', 1, null )" ) + .executeUpdate(); + } + ); + } + + @After + public void cleanup() { + doInHibernate( + this::sessionFactory, session -> { + session.createSQLQuery( "delete from MAIN_TABLE" ).executeUpdate(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12875") + public void testInitializeFromNonUniqueAssociationTable() { + doInHibernate( + this::sessionFactory, session -> { + + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#mediumOrHighRatingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getMediumOrHighRatingsFromCombined() ) ); + checkMediumOrHighRatings( material.getMediumOrHighRatingsFromCombined() ); + Rating highRating = null; + for ( Rating rating : material.getMediumOrHighRatingsFromCombined() ) { + if ( "high".equals( rating.getName() ) ) { + highRating = rating; + } + } + assertNotNull( highRating ); + + // Material#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + assertEquals( 1, material.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + + final Size size = material.getSizesFromCombined().iterator().next(); + assertEquals( "medium", size.getName() ); + + Building building = session.get( Building.class, 1 ); + + // building.ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertEquals( 1, building.getRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertSame( highRating, building.getRatingsFromCombined().iterator().next() ); + + // Building#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( 1, building.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( "small", building.getSizesFromCombined().iterator().next().getName() ); + } + ); + } + + private void checkMediumOrHighRatings(List mediumOrHighRatings) { + assertEquals( 2, mediumOrHighRatings.size() ); + + final Iterator iterator = mediumOrHighRatings.iterator(); + final Rating firstRating = iterator.next(); + final Rating secondRating = iterator.next(); + if ( "high".equals( firstRating.getName() ) ) { + assertEquals( "medium", secondRating.getName() ); + } + else if ( "medium".equals( firstRating.getName() ) ) { + assertEquals( "high", secondRating.getName() ); + } + else { + fail( "unexpected rating" ); + } + } + + @Entity( name = "Material" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'MATERIAL'" ) + public static class Material { + private int id; + + private String name; + private Set sizesFromCombined = new HashSet<>(); + private List mediumOrHighRatingsFromCombined = new ArrayList<>(); + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + @OneToMany + @JoinColumn( name = "MATERIAL_OWNER_ID" ) + @Immutable + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + @OneToMany + @JoinColumn( name = "MATERIAL_OWNER_ID") + @Where( clause = "name = 'high' or name = 'medium'" ) + @Immutable + public List getMediumOrHighRatingsFromCombined() { + return mediumOrHighRatingsFromCombined; + } + public void setMediumOrHighRatingsFromCombined(List mediumOrHighRatingsFromCombined) { + this.mediumOrHighRatingsFromCombined = mediumOrHighRatingsFromCombined; + } + } + + @Entity( name = "Building" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'BUILDING'" ) + public static class Building { + private int id; + private String name; + private Set sizesFromCombined = new HashSet<>(); + private Set ratingsFromCombined = new HashSet<>(); + private List mediumOrHighRatings = new ArrayList<>(); + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + @OneToMany + @JoinColumn( name = "BUILDING_OWNER_ID" ) + @Immutable + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + @OneToMany + @JoinColumn( name = "BUILDING_OWNER_ID" ) + @Immutable + public Set getRatingsFromCombined() { + return ratingsFromCombined; + } + public void setRatingsFromCombined(Set ratingsFromCombined) { + this.ratingsFromCombined = ratingsFromCombined; + } + } + + @Entity( name = "Size" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'SIZE'" ) + public static class Size { + private int id; + private String name; + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + @Entity( name = "Rating" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'RATING'" ) + public static class Rating { + private int id; + private String name; + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/File.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/File.hbm.xml similarity index 93% rename from hibernate-core/src/test/java/org/hibernate/test/where/File.hbm.xml rename to hibernate-core/src/test/java/org/hibernate/test/where/hbm/File.hbm.xml index 1c4614a843bc..1ea7cf6b7b6a 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/where/File.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/File.hbm.xml @@ -12,7 +12,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/File.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/File.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/where/File.java rename to hibernate-core/src/test/java/org/hibernate/test/where/hbm/File.java index 899bd58ace0d..6146cd6b446e 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/where/File.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/File.java @@ -6,7 +6,7 @@ */ //$Id: File.java 8043 2005-08-30 15:20:42Z oneovthafew $ -package org.hibernate.test.where; +package org.hibernate.test.where.hbm; import java.util.Set; public class File { diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml rename to hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml index 662121aa21da..ef83684956ef 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml @@ -4,7 +4,7 @@ "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.java similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.java rename to hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.java index 4d91d796113e..17e81e85fd9f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdNotFoundWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.java @@ -4,7 +4,7 @@ * 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.where; +package org.hibernate.test.where.hbm; import java.util.HashSet; import java.util.Set; @@ -29,7 +29,7 @@ public class LazyManyToManyNonUniqueIdNotFoundWhereTest extends BaseCoreFunctionalTestCase { protected String[] getMappings() { - return new String[] { "where/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml" }; + return new String[] { "where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.hbm.xml" }; } @Before diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml similarity index 84% rename from hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.hbm.xml rename to hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml index 29bf2a85ef67..d0b365853c51 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml @@ -4,7 +4,7 @@ "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - + @@ -18,11 +18,12 @@ - - - + + @@ -50,10 +51,11 @@ - + - - + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.java similarity index 71% rename from hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.java rename to hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.java index d218918870c5..06c36d0a579b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/LazyManyToManyNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.java @@ -4,9 +4,12 @@ * 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.where; +package org.hibernate.test.where.hbm; +import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Set; import org.hibernate.Hibernate; @@ -20,8 +23,10 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Gail Badner @@ -29,7 +34,7 @@ public class LazyManyToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { protected String[] getMappings() { - return new String[] { "where/LazyManyToManyNonUniqueIdWhereTest.hbm.xml" }; + return new String[] { "where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml" }; } @Before @@ -54,8 +59,14 @@ public void setup() { .executeUpdate(); session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'high', 'RATING' )" ) .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 3, 'low', 'RATING' )" ) + .executeUpdate(); session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'small', 'SIZE' )" ) .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'SIZE' )" ) + .executeUpdate(); session.createSQLQuery( "create table ASSOCIATION_TABLE( " + @@ -68,10 +79,18 @@ public void setup() { "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + "VALUES( 1, 'MATERIAL', 1, 'RATING' )" ).executeUpdate(); + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 2, 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 3, 'RATING' )" + ).executeUpdate(); session.createSQLQuery( "insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + - "VALUES( 1, 'MATERIAL', 1, 'SIZE' )" + "VALUES( 1, 'MATERIAL', 2, 'SIZE' )" ).executeUpdate(); session.createSQLQuery( @@ -104,6 +123,12 @@ public void setup() { session.createSQLQuery( "insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 1 )" ).executeUpdate(); + session.createSQLQuery( + "insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 2 )" + ).executeUpdate(); + session.createSQLQuery( + "insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 3 )" + ).executeUpdate(); } ); } @@ -141,10 +166,8 @@ public void testInitializeFromUniqueAssociationTable() { assertEquals( "house", building.getName() ); // Building#ratings is mapped with lazy="true" - assertFalse( Hibernate.isInitialized( building.getRatings() ) ); - assertEquals( 1, building.getRatings().size() ); - assertTrue( Hibernate.isInitialized( building.getRatings() ) ); - assertSame( rating, building.getRatings().iterator().next() ); + assertFalse( Hibernate.isInitialized( building.getMediumOrHighRatings() ) ); + checkMediumOrHighRatings( building.getMediumOrHighRatings() ); } ); } @@ -158,13 +181,16 @@ public void testInitializeFromNonUniqueAssociationTable() { Material material = session.get( Material.class, 1 ); assertEquals( "plastic", material.getName() ); - // Material#ratingsFromCombined is mapped with lazy="true" - assertFalse( Hibernate.isInitialized( material.getRatingsFromCombined() ) ); - assertEquals( 1, material.getRatingsFromCombined().size() ); - assertTrue( Hibernate.isInitialized( material.getRatingsFromCombined() ) ); - - final Rating rating = material.getRatingsFromCombined().iterator().next(); - assertEquals( "high", rating.getName() ); + // Material#mediumOrHighRatingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getMediumOrHighRatingsFromCombined() ) ); + checkMediumOrHighRatings( material.getMediumOrHighRatingsFromCombined() ); + Rating highRating = null; + for ( Rating rating : material.getMediumOrHighRatingsFromCombined() ) { + if ( "high".equals( rating.getName() ) ) { + highRating = rating; + } + } + assertNotNull( highRating ); // Material#sizesFromCombined is mapped with lazy="true" assertFalse( Hibernate.isInitialized( material.getSizesFromCombined() ) ); @@ -172,7 +198,7 @@ public void testInitializeFromNonUniqueAssociationTable() { assertTrue( Hibernate.isInitialized( material.getSizesFromCombined() ) ); final Size size = material.getSizesFromCombined().iterator().next(); - assertEquals( "small", size.getName() ); + assertEquals( "medium", size.getName() ); Building building = session.get( Building.class, 1 ); @@ -180,23 +206,40 @@ public void testInitializeFromNonUniqueAssociationTable() { assertFalse( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); assertEquals( 1, building.getRatingsFromCombined().size() ); assertTrue( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); - assertSame( rating, building.getRatingsFromCombined().iterator().next() ); + assertSame( highRating, building.getRatingsFromCombined().iterator().next() ); // Building#sizesFromCombined is mapped with lazy="true" assertFalse( Hibernate.isInitialized( building.getSizesFromCombined() ) ); assertEquals( 1, building.getSizesFromCombined().size() ); assertTrue( Hibernate.isInitialized( building.getSizesFromCombined() ) ); - assertSame( size, building.getSizesFromCombined().iterator().next() ); + assertEquals( "small", building.getSizesFromCombined().iterator().next().getName() ); } ); } + private void checkMediumOrHighRatings(List mediumOrHighRatings) { + assertEquals( 2, mediumOrHighRatings.size() ); + + final Iterator iterator = mediumOrHighRatings.iterator(); + final Rating firstRating = iterator.next(); + final Rating secondRating = iterator.next(); + if ( "high".equals( firstRating.getName() ) ) { + assertEquals( "medium", secondRating.getName() ); + } + else if ( "medium".equals( firstRating.getName() ) ) { + assertEquals( "high", secondRating.getName() ); + } + else { + fail( "unexpected rating" ); + } + } + public static class Material { private int id; private String name; private Set sizesFromCombined = new HashSet<>(); - private Set ratingsFromCombined = new HashSet<>(); + private List mediumOrHighRatingsFromCombined = new ArrayList<>(); private Set ratings = new HashSet<>(); public int getId() { @@ -217,11 +260,11 @@ public Set getSizesFromCombined() { public void setSizesFromCombined(Set sizesFromCombined) { this.sizesFromCombined = sizesFromCombined; } - public Set getRatingsFromCombined() { - return ratingsFromCombined; + public List getMediumOrHighRatingsFromCombined() { + return mediumOrHighRatingsFromCombined; } - public void setRatingsFromCombined(Set ratingsFromCombined) { - this.ratingsFromCombined = ratingsFromCombined; + public void setMediumOrHighRatingsFromCombined(List mediumOrHighRatingsFromCombined) { + this.mediumOrHighRatingsFromCombined = mediumOrHighRatingsFromCombined; } public Set getRatings() { return ratings; @@ -236,7 +279,7 @@ public static class Building { private String name; private Set sizesFromCombined = new HashSet<>(); private Set ratingsFromCombined = new HashSet<>(); - private Set ratings = new HashSet<>(); + private List mediumOrHighRatings = new ArrayList<>(); public int getId() { return id; @@ -262,11 +305,11 @@ public Set getRatingsFromCombined() { public void setRatingsFromCombined(Set ratingsFromCombined) { this.ratingsFromCombined = ratingsFromCombined; } - public Set getRatings() { - return ratings; + public List getMediumOrHighRatings() { + return mediumOrHighRatings; } - public void setRatings(Set ratings) { - this.ratings = ratings; + public void setMediumOrHighRatings(List mediumOrHighRatings) { + this.mediumOrHighRatings = mediumOrHighRatings; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml new file mode 100644 index 000000000000..caf46130696f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.java new file mode 100644 index 000000000000..004f5c4708e8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.java @@ -0,0 +1,257 @@ +/* + * 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.where.hbm; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Gail Badner + */ +public class LazyOneToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + + protected String[] getMappings() { + return new String[] { "where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml" }; + } + + @Before + public void setup() { + doInHibernate( + this::sessionFactory, session -> { + + session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); + + session.createSQLQuery( + "create table MAIN_TABLE( " + + "ID integer not null, NAME varchar(255) not null, CODE varchar(10) not null, " + + "MATERIAL_OWNER_ID integer, BUILDING_OWNER_ID integer, " + + "primary key (ID, CODE) )" + ).executeUpdate(); + + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 1, 'high', 'RATING', 1, 1 )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 2, 'medium', 'RATING', 1, null )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 3, 'low', 'RATING', 1, null )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 1, 'small', 'SIZE', null, 1 )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE, MATERIAL_OWNER_ID, BUILDING_OWNER_ID) " + + "VALUES( 2, 'medium', 'SIZE', 1, null )" ) + .executeUpdate(); + } + ); + } + + @After + public void cleanup() { + doInHibernate( + this::sessionFactory, session -> { + session.createSQLQuery( "delete from MAIN_TABLE" ).executeUpdate(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-12875") + public void testInitializeFromNonUniqueAssociationTable() { + doInHibernate( + this::sessionFactory, session -> { + + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#mediumOrHighRatingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getMediumOrHighRatingsFromCombined() ) ); + checkMediumOrHighRatings( material.getMediumOrHighRatingsFromCombined() ); + Rating highRating = null; + for ( Rating rating : material.getMediumOrHighRatingsFromCombined() ) { + if ( "high".equals( rating.getName() ) ) { + highRating = rating; + } + } + assertNotNull( highRating ); + + // Material#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + assertEquals( 1, material.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + + final Size size = material.getSizesFromCombined().iterator().next(); + assertEquals( "medium", size.getName() ); + + Building building = session.get( Building.class, 1 ); + + // building.ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertEquals( 1, building.getRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertSame( highRating, building.getRatingsFromCombined().iterator().next() ); + + // Building#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( 1, building.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( "small", building.getSizesFromCombined().iterator().next().getName() ); + } + ); + } + + private void checkMediumOrHighRatings(List mediumOrHighRatings) { + assertEquals( 2, mediumOrHighRatings.size() ); + + final Iterator iterator = mediumOrHighRatings.iterator(); + final Rating firstRating = iterator.next(); + final Rating secondRating = iterator.next(); + if ( "high".equals( firstRating.getName() ) ) { + assertEquals( "medium", secondRating.getName() ); + } + else if ( "medium".equals( firstRating.getName() ) ) { + assertEquals( "high", secondRating.getName() ); + } + else { + fail( "unexpected rating" ); + } + } + + public static class Material { + private int id; + + private String name; + private Set sizesFromCombined = new HashSet<>(); + private List mediumOrHighRatingsFromCombined = new ArrayList<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + public List getMediumOrHighRatingsFromCombined() { + return mediumOrHighRatingsFromCombined; + } + public void setMediumOrHighRatingsFromCombined(List mediumOrHighRatingsFromCombined) { + this.mediumOrHighRatingsFromCombined = mediumOrHighRatingsFromCombined; + } + } + + public static class Building { + private int id; + private String name; + private Set sizesFromCombined = new HashSet<>(); + private Set ratingsFromCombined = new HashSet<>(); + private List mediumOrHighRatings = new ArrayList<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + public Set getRatingsFromCombined() { + return ratingsFromCombined; + } + public void setRatingsFromCombined(Set ratingsFromCombined) { + this.ratingsFromCombined = ratingsFromCombined; + } + } + + public static class Size { + private int id; + private String name; + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + public static class Rating { + private int id; + private String name; + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/WhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/WhereTest.java similarity index 95% rename from hibernate-core/src/test/java/org/hibernate/test/where/WhereTest.java rename to hibernate-core/src/test/java/org/hibernate/test/where/hbm/WhereTest.java index 3e4c463260b9..534064197dbe 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/where/WhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/WhereTest.java @@ -4,7 +4,7 @@ * 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.where; +package org.hibernate.test.where.hbm; import java.util.HashSet; import java.util.List; @@ -14,12 +14,13 @@ import org.junit.Test; import org.hibernate.FetchMode; -import org.hibernate.Session; import org.hibernate.criterion.Restrictions; import org.hibernate.query.NativeQuery; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.test.where.hbm.File; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -28,7 +29,7 @@ */ public class WhereTest extends BaseCoreFunctionalTestCase { public String[] getMappings() { - return new String[] { "where/File.hbm.xml" }; + return new String[] { "where/hbm/File.hbm.xml" }; } @Before From f381490a49e0df6e68dcf7b22150e38c2ed86bd0 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 3 Aug 2018 01:25:34 -0700 Subject: [PATCH 146/772] HHH-12875 HHH-12882 : Class level where="..." clause hbm.xml mappings is not enforced on collections of that class; add parentheses when where clauses get combined in a conjunction (hbm and annotations) HHH-12882 : correct assertions in ParentChildTest (cherry picked from commit 27937e56278037fc4e7d0044cd87cc2b2a7c8e10) --- .../source/internal/hbm/ModelBinder.java | 42 +++++++++++-------- .../cfg/annotations/CollectionBinder.java | 35 ++++++---------- .../hibernate/internal/util/StringHelper.java | 34 +++++++++++++++ .../test/legacy/ParentChildTest.java | 12 ++++-- 4 files changed, 79 insertions(+), 44 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index 91f134fbfe7b..7a2c4d98ee65 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -1527,7 +1527,6 @@ private void bindCollectionMetadata(MappingDocument mappingDocument, PluralAttri binding.getSynchronizedTables().add( name ); } - binding.setWhere( source.getWhere() ); binding.setLoaderName( source.getCustomLoaderName() ); if ( source.getCustomSqlInsert() != null ) { binding.setCustomSQLInsert( @@ -3448,9 +3447,15 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector() .getEntityBinding( elementSource.getReferencedEntityName() ); + collectionBinding.setWhere( + StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + getPluralAttributeSource().getWhere() + ) + ); + elementBinding.setReferencedEntityName( referencedEntityBinding.getEntityName() ); elementBinding.setAssociatedClass( referencedEntityBinding ); - elementBinding.setIgnoreNotFound( elementSource.isIgnoreNotFound() ); } else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceManyToMany ) { @@ -3469,12 +3474,17 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu new RelationalObjectBinder.ColumnNamingDelegate() { @Override public Identifier determineImplicitName(final LocalMetadataBuildingContext context) { - return context.getMetadataCollector().getDatabase().toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ); + return context.getMetadataCollector() + .getDatabase() + .toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ); } } ); - elementBinding.setLazy( elementSource.getFetchCharacteristics().getFetchTiming() != FetchTiming.IMMEDIATE ); + elementBinding.setLazy( + elementSource.getFetchCharacteristics() + .getFetchTiming() != FetchTiming.IMMEDIATE + ); elementBinding.setFetchMode( elementSource.getFetchCharacteristics().getFetchStyle() == FetchStyle.SELECT ? FetchMode.SELECT @@ -3494,20 +3504,16 @@ public Identifier determineImplicitName(final LocalMetadataBuildingContext conte getCollectionBinding().setElement( elementBinding ); - final StringBuilder whereBuffer = new StringBuilder(); - final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector() - .getEntityBinding( elementSource.getReferencedEntityName() ); - if ( StringHelper.isNotEmpty( referencedEntityBinding.getWhere() ) ) { - whereBuffer.append( referencedEntityBinding.getWhere() ); - } - if ( StringHelper.isNotEmpty( elementSource.getWhere() ) ) { - if ( whereBuffer.length() > 0 ) { - whereBuffer.append( " and " ); - } - whereBuffer.append( elementSource.getWhere() ); - } - getCollectionBinding().setManyToManyWhere( whereBuffer.toString() ); - + final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector().getEntityBinding( + elementSource.getReferencedEntityName() + ); + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); + getCollectionBinding().setManyToManyWhere( + StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + elementSource.getWhere() + ) + ); getCollectionBinding().setManyToManyOrdering( elementSource.getOrder() ); if ( !CollectionHelper.isEmpty( elementSource.getFilterSources() ) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index 8c566518e923..7ccc8c4ebe4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -953,36 +953,27 @@ private void bindFilters(boolean hasAssociationTable) { } } - StringBuilder whereBuffer = new StringBuilder(); + String whereOnClassClause = null; if ( property.getElementClass() != null ) { Where whereOnClass = property.getElementClass().getAnnotation( Where.class ); if ( whereOnClass != null ) { - String clause = whereOnClass.clause(); - if ( StringHelper.isNotEmpty( clause ) ) { - whereBuffer.append( clause ); - } + whereOnClassClause = whereOnClass.clause(); } } Where whereOnCollection = property.getAnnotation( Where.class ); + String whereOnCollectionClause = null; if ( whereOnCollection != null ) { - String clause = whereOnCollection.clause(); - if ( StringHelper.isNotEmpty( clause ) ) { - if ( whereBuffer.length() > 0 ) { - whereBuffer.append( ' ' ); - whereBuffer.append( Junction.Nature.AND.getOperator() ); - whereBuffer.append( ' ' ); - } - whereBuffer.append( clause ); - } + whereOnCollectionClause = whereOnCollection.clause(); } - if ( whereBuffer.length() > 0 ) { - String whereClause = whereBuffer.toString(); - if ( hasAssociationTable ) { - collection.setManyToManyWhere( whereClause ); - } - else { - collection.setWhere( whereClause ); - } + final String whereClause = StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + whereOnClassClause, + whereOnCollectionClause + ); + if ( hasAssociationTable ) { + collection.setManyToManyWhere( whereClause ); + } + else { + collection.setWhere( whereClause ); } WhereJoinTable whereJoinTable = property.getAnnotation( WhereJoinTable.class ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java index ac6db99f0ff8..acd425617da5 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java @@ -852,4 +852,38 @@ public static String join(T[] values, Renderer renderer) { public interface Renderer { String render(T value); } + + /** + * @param firstExpression the first expression + * @param secondExpression the second expression + * @return if {@code firstExpression} and {@code secondExpression} are both non-empty, + * then "( " + {@code firstExpression} + " ) and ( " + {@code secondExpression} + " )" is returned; + * if {@code firstExpression} is non-empty and {@code secondExpression} is empty, + * then {@code firstExpression} is returned; + * if {@code firstExpression} is empty and {@code secondExpression} is non-empty, + * then {@code secondExpression} is returned; + * if both {@code firstExpression} and {@code secondExpression} are empty, then null is returned. + */ + public static String getNonEmptyOrConjunctionIfBothNonEmpty( String firstExpression, String secondExpression ) { + final boolean isFirstExpressionNonEmpty = StringHelper.isNotEmpty( firstExpression ); + final boolean isSecondExpressionNonEmpty = StringHelper.isNotEmpty( secondExpression ); + if ( isFirstExpressionNonEmpty && isSecondExpressionNonEmpty ) { + final StringBuilder buffer = new StringBuilder(); + buffer.append( "( " ) + .append( firstExpression ) + .append( " ) and ( ") + .append( secondExpression ) + .append( " )" ); + return buffer.toString(); + } + else if ( isFirstExpressionNonEmpty ) { + return firstExpression; + } + else if ( isSecondExpressionNonEmpty ) { + return secondExpression; + } + else { + return null; + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java index 587f57ef503b..0acff0581e28 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java @@ -482,12 +482,16 @@ public void testClassWhere() throws Exception { assertTrue( s.createCriteria(Part.class).list().size()==1 ); //there is a where condition on Part mapping assertTrue( s.createCriteria(Part.class).add( Restrictions.eq( "id", p1.getId() ) ).list().size()==1 ); assertTrue( s.createQuery("from Part").list().size()==1 ); - assertTrue( s.createQuery("from Baz baz join baz.parts").list().size()==2 ); + // only Part entities that satisfy the where condition on Part mapping should be included in the collection + assertTrue( s.createQuery("from Baz baz join baz.parts").list().size()==1 ); baz = (Baz) s.createCriteria(Baz.class).uniqueResult(); - assertTrue( s.createFilter( baz.getParts(), "" ).list().size()==2 ); + // only Part entities that satisfy the where condition on Part mapping should be included in the collection + assertTrue( s.createFilter( baz.getParts(), "" ).list().size()==1 ); //assertTrue( baz.getParts().size()==1 ); - s.delete( s.get( Part.class, p1.getId() )); - s.delete( s.get( Part.class, p2.getId() )); + s.delete( s.get( Part.class, p1.getId() ) ); + // p2.description does not satisfy the condition on Part mapping, so it's not possible to retrieve it + // using Session#get; instead just delete using a native query + s.createNativeQuery( "delete from Part where id = :id" ).setParameter( "id", p2.getId() ).executeUpdate(); s.delete(baz); t.commit(); s.close(); From 69e7132e9107e06869b665c278f32969c179300c Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 6 Aug 2018 22:25:21 -0700 Subject: [PATCH 147/772] HHH-12875 : Add comments to clarify how Collection#setWhere and #setManyToManyWhere are used (cherry picked from commit a3cecf3411c09ccea598fac7761c0a54d39d7993) --- .../model/source/internal/hbm/ModelBinder.java | 16 ++++++++++++++++ .../cfg/annotations/CollectionBinder.java | 14 ++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index 7a2c4d98ee65..27f16af5bb6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -3447,6 +3447,12 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector() .getEntityBinding( elementSource.getReferencedEntityName() ); + // For a one-to-many association, there are 2 possible sources of "where" clauses that apply + // to the associated entity table: + // 1) from the associated entity mapping; i.e., + // 2) from the collection mapping; e.g., + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the associated entity table for a one-to-many association). collectionBinding.setWhere( StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( referencedEntityBinding.getWhere(), @@ -3507,7 +3513,17 @@ public Identifier determineImplicitName(final LocalMetadataBuildingContext conte final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector().getEntityBinding( elementSource.getReferencedEntityName() ); + + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the join table for a many-to-many association). + // This "where" clause comes from the collection mapping; e.g., getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); + // For a many-to-many association, there are 2 possible sources of "where" clauses that apply + // to the associated entity table (not the join table): + // 1) from the associated entity mapping; i.e., + // 2) from the many-to-many mapping; i.e + // Collection#setManytoManyWhere is used to set the "where" clause that applies to + // to the many-to-many associated entity table (not the join table). getCollectionBinding().setManyToManyWhere( StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( referencedEntityBinding.getWhere(), diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index 7ccc8c4ebe4f..dbeeea5ba441 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -953,6 +953,11 @@ private void bindFilters(boolean hasAssociationTable) { } } + // There are 2 possible sources of "where" clauses that apply to the associated entity table: + // 1) from the associated entity mapping; i.e., @Entity @Where(clause="...") + // 2) from the collection mapping; + // for one-to-many, e.g., @OneToMany @JoinColumn @Where(clause="...") public Set getRatings(); + // for many-to-many e.g., @ManyToMany @Where(clause="...") public Set getRatings(); String whereOnClassClause = null; if ( property.getElementClass() != null ) { Where whereOnClass = property.getElementClass().getAnnotation( Where.class ); @@ -970,9 +975,15 @@ private void bindFilters(boolean hasAssociationTable) { whereOnCollectionClause ); if ( hasAssociationTable ) { + // A many-to-many association has an association (join) table + // Collection#setManytoManyWhere is used to set the "where" clause that applies to + // to the many-to-many associated entity table (not the join table). collection.setManyToManyWhere( whereClause ); } else { + // A one-to-many association does not have an association (join) table. + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the associated entity table for a one-to-many association). collection.setWhere( whereClause ); } @@ -980,6 +991,9 @@ private void bindFilters(boolean hasAssociationTable) { String whereJoinTableClause = whereJoinTable == null ? null : whereJoinTable.clause(); if ( StringHelper.isNotEmpty( whereJoinTableClause ) ) { if ( hasAssociationTable ) { + // This is a many-to-many association. + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the join table for a many-to-many association). collection.setWhere( whereJoinTableClause ); } else { From 9c96641d003f2326545a41975bfa62406719e0af Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 13 Aug 2018 21:49:45 -0700 Subject: [PATCH 148/772] HHH-12875 : Remove "where" clause for Top used by MultiTableTest; After HHH-12875 is fixed, the "where" clause causes MultiTableTest to fail due to HHH-12016 (cherry picked from commit a5fa213658b393aa9f671779f5ca1518b40bf54a) --- .../src/test/java/org/hibernate/test/legacy/Multi.hbm.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/Multi.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/legacy/Multi.hbm.xml index 6e1595a27c49..5db6ac0e610b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/Multi.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/Multi.hbm.xml @@ -15,8 +15,7 @@ table="rootclass" dynamic-insert="true" dynamic-update="true" - select-before-update="true" - where="id1_ is not null"> + select-before-update="true"> From 99b93ae949b42d99e397510a6f8c71d56dd39560 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 13 Aug 2018 23:26:37 -0700 Subject: [PATCH 149/772] HHH-12875 HHH-12882 : Drop tables in proper order in test setup methods (cherry picked from commit 770ae50e8b9b84d5e64be3b7f6db78918588cba9) --- .../annotations/LazyManyToManyNonUniqueIdWhereTest.java | 8 ++++---- .../hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.java | 9 +++++---- .../where/hbm/LazyManyToManyNonUniqueIdWhereTest.java | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java index c21c0ddaf21b..0ac7b6a2b803 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java @@ -53,10 +53,10 @@ public void setup() { doInHibernate( this::sessionFactory, session -> { - session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); - session.createSQLQuery( "DROP TABLE ASSOCIATION_TABLE" ).executeUpdate(); - session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); - session.createSQLQuery( "DROP TABLE BUILDING_RATINGS" ).executeUpdate(); + session.createSQLQuery( "drop table MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "drop table BUILDING_RATINGS" ).executeUpdate(); + session.createSQLQuery( "drop table ASSOCIATION_TABLE" ).executeUpdate(); + session.createSQLQuery( "drop table MAIN_TABLE" ).executeUpdate(); session.createSQLQuery( "create table MAIN_TABLE( " + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.java index 17e81e85fd9f..886b17df09e4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdNotFoundWhereTest.java @@ -36,10 +36,11 @@ protected String[] getMappings() { public void setup() { doInHibernate( this::sessionFactory, session -> { - session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); - session.createSQLQuery( "DROP TABLE ASSOCIATION_TABLE" ).executeUpdate(); - session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); - session.createSQLQuery( "DROP TABLE BUILDING_RATINGS" ).executeUpdate(); + + session.createSQLQuery( "drop table MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "drop table BUILDING_RATINGS" ).executeUpdate(); + session.createSQLQuery( "drop table ASSOCIATION_TABLE" ).executeUpdate(); + session.createSQLQuery( "drop table MAIN_TABLE" ).executeUpdate(); session.createSQLQuery( "create table MAIN_TABLE( " + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.java index 06c36d0a579b..231c14a090d0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.java @@ -42,10 +42,10 @@ public void setup() { doInHibernate( this::sessionFactory, session -> { - session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); - session.createSQLQuery( "DROP TABLE ASSOCIATION_TABLE" ).executeUpdate(); - session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); - session.createSQLQuery( "DROP TABLE BUILDING_RATINGS" ).executeUpdate(); + session.createSQLQuery( "drop table MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "drop table BUILDING_RATINGS" ).executeUpdate(); + session.createSQLQuery( "drop table ASSOCIATION_TABLE" ).executeUpdate(); + session.createSQLQuery( "drop table MAIN_TABLE" ).executeUpdate(); session.createSQLQuery( "create table MAIN_TABLE( " + From fdfef4f5ce939de983aff446c8847af4d7993504 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 13 Aug 2018 18:43:25 +0200 Subject: [PATCH 150/772] HHH-12905 Fix error message tested in MySQL and PostgreSQL tests --- .../org/hibernate/test/procedure/MySQLStoredProcedureTest.java | 2 +- .../hibernate/test/procedure/PostgreSQLStoredProcedureTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java index 4e554590548b..9e9261c8f974 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java @@ -411,7 +411,7 @@ public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls() fail("Should have thrown exception"); } catch (IllegalArgumentException e) { - assertEquals( "The parameter on the [1] position was null. You need to call ParameterRegistration#enablePassingNulls in order to pass null parameters.", e.getMessage() ); + assertEquals( "The parameter on the [1] position was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters.", e.getMessage() ); } }); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java index 73a7e77f4c0b..e544c2e34e70 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java @@ -405,7 +405,7 @@ public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls() fail("Should have thrown exception"); } catch (IllegalArgumentException e) { - assertEquals( "The parameter with the [param] position was null. You need to call ParameterRegistration#enablePassingNulls in order to pass null parameters.", e.getMessage() ); + assertEquals( "The parameter with the [param] name was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters.", e.getMessage() ); } } ); } From 14f28df3bb175967a4270fee19fc83119ef64df9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Aug 2018 00:33:55 +0200 Subject: [PATCH 151/772] Uncomment MariaDB dialect in gradle/databases.gradle I don't know why it has been commmented out but it prevents from running the tests with -Pdb=mariadb as no dialect is defined. --- gradle/databases.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle/databases.gradle b/gradle/databases.gradle index 41e200dff57c..8e741bab1706 100644 --- a/gradle/databases.gradle +++ b/gradle/databases.gradle @@ -59,8 +59,7 @@ ext { 'jdbc.url' : 'jdbc:mysql://127.0.0.1/hibernate_orm_test?useSSL=false' ], mariadb : [ - 'db.dialect' : '', -// 'db.dialect' : 'org.hibernate.dialect.MariaDB102Dialect', + 'db.dialect' : 'org.hibernate.dialect.MariaDB102Dialect', 'jdbc.driver': 'org.mariadb.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', From 5b826c90621aefae4d05f64f6de5c73af20d85ca Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Aug 2018 00:41:44 +0200 Subject: [PATCH 152/772] HHH-12905 Improve the error message and update the tests accordingly Also fix a loose end in the MySQL test: at least with MariaDB, using a bit(1) as datatype for boolean does not work: it always return true even if you set it to 0. Using either boolean or tinyint(1) solves the issue. As I'm not sure older versions of MySQL supports a real boolean type I used a tinyint(1). --- .../hibernate/procedure/internal/ParameterBindImpl.java | 6 +++--- .../test/procedure/MySQLStoredProcedureTest.java | 8 ++++---- .../test/procedure/PostgreSQLStoredProcedureTest.java | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java index 6b7d99bb5133..e5536efd3215 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java @@ -81,10 +81,10 @@ private void internalSetValue(T value) { if ( procedureParameter.getParameterType() != null ) { if ( value == null ) { if ( !procedureParameter.isPassNullsEnabled() ) { - throw new IllegalArgumentException( "The parameter with the [" + + throw new IllegalArgumentException( "The parameter " + ( procedureParameter.getName() != null - ? procedureParameter.getName() + "] name" - : procedureParameter.getPosition() + "] position" ) + ? "named [" + procedureParameter.getName() + "]" + : "at position [" + procedureParameter.getPosition() + "]" ) + " was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters." ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java index 9e9261c8f974..69e0d005b6e5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/procedure/MySQLStoredProcedureTest.java @@ -107,11 +107,11 @@ public void init() { statement.executeUpdate( "CREATE PROCEDURE sp_is_null (" + " IN param varchar(255), " + - " OUT result BIT(1) " + + " OUT result tinyint(1) " + ") " + "BEGIN " + - " IF (param IS NULL) THEN SET result = 1; " + - " ELSE SET result = 0; " + + " IF (param IS NULL) THEN SET result = true; " + + " ELSE SET result = false; " + " END IF; " + "END" ); @@ -411,7 +411,7 @@ public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls() fail("Should have thrown exception"); } catch (IllegalArgumentException e) { - assertEquals( "The parameter on the [1] position was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters.", e.getMessage() ); + assertEquals( "The parameter at position [1] was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters.", e.getMessage() ); } }); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java index e544c2e34e70..a4c157ab6af1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/procedure/PostgreSQLStoredProcedureTest.java @@ -405,7 +405,7 @@ public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls() fail("Should have thrown exception"); } catch (IllegalArgumentException e) { - assertEquals( "The parameter with the [param] name was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters.", e.getMessage() ); + assertEquals( "The parameter named [param] was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters.", e.getMessage() ); } } ); } From 0c22ccc1075ef180c8044bbc224cc397056a5b89 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Aug 2018 14:27:47 +0200 Subject: [PATCH 153/772] 5.3.5.Final --- changelog.txt | 28 ++++++++++++++++++++++++++++ gradle/base-information.gradle | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 93f65b1e5682..2daa58968bb5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,34 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.3.5.final (August 14th, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31695/tab/release-report-done + +** Bug + * [HHH-12871] - Metamodel contains managed types related to dynamic-map entities that have been excluded. + * [HHH-12875] - Class level where="..." clause in hbm.xml mappings is not enforced on collections of that class + * [HHH-12882] - Where clauses mapped on collections and entities need parentheses when used in conjunction + * [HHH-12890] - Fix link to JPA Metamodel generator documentation + * [HHH-12903] - CommitFlushCollectionTest fails when running on Oracle. + * [HHH-12905] - Passing null as parameter is not allowed even when enablePassingNulls() has been called + * [HHH-12906] - Statistics.getCollectionRoleNames() reports incorrect value + +** Task + * [HHH-10782] - Add a comment about what you can expect from a query plan cache cleanup + * [HHH-12898] - Enable integration tests for Oracle Standard Edition Two 12.1.0.2.v12 on the AWS build slaves + * [HHH-12899] - Enable integration tests for MS SQL Server on the AWS build slaves + * [HHH-12901] - Enable loading of additional JDBC drivers from a local path + * [HHH-12909] - Upgrade ByteBuddy to 1.8.17 + +** Improvement + * [HHH-12196] - Sybase Dialect not supporting max result - paging + * [HHH-12361] - In the User Guide, omit constructors and equals/hashCode for brevity + * [HHH-12608] - Add the ST_DWithin() function in DB2 Spatial Dialect + * [HHH-12892] - Fix spelling issues in the User Guide + * [HHH-12907] - Avoid garbage collection pressure when creating proxies with ByteBuddy + Changes in 5.3.4.final (August 2nd, 2018) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 5b2765087294..d0c835dcfd57 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.5-SNAPSHOT', project ) + ormVersion = new HibernateVersion( '5.3.5.Final', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From 095f0fb6c6eebc784e9ce1c215ab051bed3f0002 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Aug 2018 15:22:27 +0200 Subject: [PATCH 154/772] Prepare for next development iteration --- gradle/base-information.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index d0c835dcfd57..1d349f10f823 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.5.Final', project ) + ormVersion = new HibernateVersion( '5.3.6-SNAPSHOT', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From 70381b8c8411cd6641577097bca7148dcb6715d6 Mon Sep 17 00:00:00 2001 From: Sande Gilda Date: Tue, 14 Aug 2018 10:47:29 -0400 Subject: [PATCH 155/772] Fix Javadocs comments for Query.getHibernateMaxResults() method --- hibernate-core/src/main/java/org/hibernate/Query.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/Query.java b/hibernate-core/src/main/java/org/hibernate/Query.java index 6c68a3ab5437..e943bc7f1ea3 100644 --- a/hibernate-core/src/main/java/org/hibernate/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/Query.java @@ -135,7 +135,7 @@ default Query setHibernateFirstResult(int firstRow) { * @see #setMaxResults(int) (int) * @see #setHibernateMaxResults(int) (int) * - * @deprecated {@link #setMaxResults(int)} should be used instead. + * @deprecated {@link #getMaxResults(int)} should be used instead. */ @Deprecated default Integer getHibernateMaxResults() { From 5ed5656c29328adcb6c9e7222349deebf79b4a92 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Aug 2018 17:01:21 +0200 Subject: [PATCH 156/772] Fix the previous commit to reference #getMaxResults() --- hibernate-core/src/main/java/org/hibernate/Query.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/Query.java b/hibernate-core/src/main/java/org/hibernate/Query.java index e943bc7f1ea3..5fbf556736e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/Query.java @@ -135,7 +135,7 @@ default Query setHibernateFirstResult(int firstRow) { * @see #setMaxResults(int) (int) * @see #setHibernateMaxResults(int) (int) * - * @deprecated {@link #getMaxResults(int)} should be used instead. + * @deprecated {@link #getMaxResults()} should be used instead. */ @Deprecated default Integer getHibernateMaxResults() { From b8b0fbc13c8507edc727fb4ced67eb20b12e8cc7 Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Wed, 15 Aug 2018 18:07:02 +0300 Subject: [PATCH 157/772] Fix a typo in a variable name --- .../main/java/org/hibernate/engine/spi/CascadingActions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java index 7839d99dd8cd..2903cb6966d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java @@ -375,12 +375,12 @@ public void noCascade( && !isInManagedState( child, session ) && !(child instanceof HibernateProxy) //a proxy cannot be transient and it breaks ForeignKeys.isTransient && ForeignKeys.isTransient( childEntityName, child, null, session ) ) { - String parentEntiytName = persister.getEntityName(); + String parentEntityName = persister.getEntityName(); String propertyName = persister.getPropertyNames()[propertyIndex]; throw new TransientPropertyValueException( "object references an unsaved transient instance - save the transient instance before flushing", childEntityName, - parentEntiytName, + parentEntityName, propertyName ); From 56a29af496dcdc6bd31ac3c6c2ce2286565ffcc9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 27 Aug 2018 17:22:15 +0200 Subject: [PATCH 158/772] HHH-12931 Revert "HHH-12542 - Add necessary privileged action blocks for SecurityManager used on WildFly." This reverts commit d24685de6776d5df9eb7cdb08bbb96c1ca40c60c. --- .../boot/cfgxml/internal/ConfigLoader.java | 47 +++---- .../boot/jaxb/internal/AbstractBinder.java | 14 +- .../internal/ClassLoaderServiceImpl.java | 124 +++++++---------- .../hibernate/internal/util/ConfigHelper.java | 47 +++---- .../internal/util/ReflectHelper.java | 128 +++++------------- .../internal/CallbackBuilderLegacyImpl.java | 17 +-- .../metamodel/internal/MetadataContext.java | 27 +--- .../tuple/entity/PojoEntityTuplizer.java | 45 +++--- .../reader/AuditedPropertiesReader.java | 30 +--- 9 files changed, 151 insertions(+), 328 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java b/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java index 1485347c9f82..ab094f769bc8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java @@ -12,8 +12,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Properties; import org.hibernate.boot.cfgxml.spi.LoadedConfig; @@ -50,34 +48,27 @@ public ConfigLoader(BootstrapServiceRegistry bootstrapServiceRegistry) { } public LoadedConfig loadConfigXmlResource(String cfgXmlResourceName) { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public JaxbCfgHibernateConfiguration run() { - final InputStream stream = bootstrapServiceRegistry.getService( ClassLoaderService.class ).locateResourceStream( cfgXmlResourceName ); - if ( stream == null ) { - throw new ConfigurationException( "Could not locate cfg.xml resource [" + cfgXmlResourceName + "]" ); - } + final InputStream stream = bootstrapServiceRegistry.getService( ClassLoaderService.class ).locateResourceStream( cfgXmlResourceName ); + if ( stream == null ) { + throw new ConfigurationException( "Could not locate cfg.xml resource [" + cfgXmlResourceName + "]" ); + } - try { - return jaxbProcessorHolder.getValue().unmarshal( - stream, - new Origin( SourceType.RESOURCE, cfgXmlResourceName ) - ); - } - finally { - try { - stream.close(); - } - catch ( IOException e ) { - log.debug( "Unable to close cfg.xml resource stream", e ); - } - } - } - }; + try { + final JaxbCfgHibernateConfiguration jaxbCfg = jaxbProcessorHolder.getValue().unmarshal( + stream, + new Origin( SourceType.RESOURCE, cfgXmlResourceName ) + ); - return LoadedConfig.consume( - System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run() - ); + return LoadedConfig.consume( jaxbCfg ); + } + finally { + try { + stream.close(); + } + catch (IOException e) { + log.debug( "Unable to close cfg.xml resource stream", e ); + } + } } public LoadedConfig loadConfigXmlFile(File cfgXmlFile) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java index 8f5ffd919d48..79ef80e06089 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java @@ -7,9 +7,6 @@ package org.hibernate.boot.jaxb.internal; import java.io.InputStream; -import java.security.AccessController; -import java.security.PrivilegedAction; - import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; @@ -101,15 +98,8 @@ protected XMLEventReader createReader(Source source, Origin origin) { private Binding doBind(XMLEventReader eventReader, Origin origin) { try { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public Binding run() { - final StartElement rootElementStartEvent = seekRootElementStartEvent( eventReader, origin ); - return doBind( eventReader, rootElementStartEvent, origin ); - } - }; - - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + final StartElement rootElementStartEvent = seekRootElementStartEvent( eventReader, origin ); + return doBind( eventReader, rootElementStartEvent, origin ); } finally { try { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java index c1f7fa3df1ca..fd4deff3f3e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java @@ -83,16 +83,11 @@ public ClassLoaderServiceImpl(Collection providedClassLoaders, Tccl orderedClassLoaderSet.add( ClassLoaderServiceImpl.class.getClassLoader() ); // now build the aggregated class loader... - final PrivilegedAction action = new PrivilegedAction() { - @Override + this.aggregatedClassLoader = AccessController.doPrivileged( new PrivilegedAction() { public AggregatedClassLoader run() { return new AggregatedClassLoader( orderedClassLoaderSet, lookupPrecedence ); } - }; - - this.aggregatedClassLoader = System.getSecurityManager() != null - ? AccessController.doPrivileged( action ) - : action.run(); + } ); } /** @@ -352,62 +347,49 @@ protected Class findClass(String name) throws ClassNotFoundException { @Override @SuppressWarnings({"unchecked"}) public Class classForName(String className) { - final PrivilegedAction> action = new PrivilegedAction>() { - @Override - public Class run() { - try { - return (Class) Class.forName( className, true, getAggregatedClassLoader() ); - } - catch (Exception e) { - throw new ClassLoadingException( "Unable to load class [" + className + "]", e ); - } - catch (LinkageError e) { - throw new ClassLoadingException( "Unable to load class [" + className + "]", e ); - } - } - }; - - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + try { + return (Class) Class.forName( className, true, getAggregatedClassLoader() ); + } + catch (Exception e) { + throw new ClassLoadingException( "Unable to load class [" + className + "]", e ); + } + catch (LinkageError e) { + throw new ClassLoadingException( "Unable to load class [" + className + "]", e ); + } } @Override - public URL locateResource(final String name) { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public URL run() { - try { - return new URL( name ); - } - catch (Exception ignore) { - } + public URL locateResource(String name) { + // first we try name as a URL + try { + return new URL( name ); + } + catch (Exception ignore) { + } - try { - final URL url = getAggregatedClassLoader().getResource( name ); - if ( url != null ) { - return url; - } - } - catch (Exception ignore) { - } + try { + final URL url = getAggregatedClassLoader().getResource( name ); + if ( url != null ) { + return url; + } + } + catch (Exception ignore) { + } - if ( name.startsWith( "/" ) ) { - final String resourceName = name.substring( 1 ); + if ( name.startsWith( "/" ) ) { + name = name.substring( 1 ); - try { - final URL url = getAggregatedClassLoader().getResource( resourceName ); - if ( url != null ) { - return url; - } - } - catch (Exception ignore) { - } + try { + final URL url = getAggregatedClassLoader().getResource( name ); + if ( url != null ) { + return url; } - - return null; } - }; + catch (Exception ignore) { + } + } - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + return null; } @Override @@ -474,22 +456,16 @@ public List locateResources(String name) { @Override @SuppressWarnings("unchecked") public Collection loadJavaServices(Class serviceContract) { - final PrivilegedAction> action = new PrivilegedAction>() { - @Override - public Collection run() { - ServiceLoader serviceLoader = serviceLoaders.get( serviceContract ); - if ( serviceLoader == null ) { - serviceLoader = ServiceLoader.load( serviceContract, getAggregatedClassLoader() ); - serviceLoaders.put( serviceContract, serviceLoader ); - } - final LinkedHashSet services = new LinkedHashSet(); - for ( S service : serviceLoader ) { - services.add( service ); - } - return services; - } - }; - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + ServiceLoader serviceLoader = serviceLoaders.get( serviceContract ); + if ( serviceLoader == null ) { + serviceLoader = ServiceLoader.load( serviceContract, getAggregatedClassLoader() ); + serviceLoaders.put( serviceContract, serviceLoader ); + } + final LinkedHashSet services = new LinkedHashSet(); + for ( S service : serviceLoader ) { + services.add( service ); + } + return services; } @Override @@ -504,13 +480,7 @@ public T generateProxy(InvocationHandler handler, Class... interfaces) { @Override public T workWithClassLoader(Work work) { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public T run() { - return work.doWork( getAggregatedClassLoader() ); - } - }; - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + return work.doWork( getAggregatedClassLoader() ); } private ClassLoader getAggregatedClassLoader() { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java index a3383bde8696..f87581521cb8 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java @@ -10,8 +10,6 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; -import java.security.AccessController; -import java.security.PrivilegedAction; import org.hibernate.HibernateException; import org.hibernate.cfg.Environment; @@ -115,33 +113,28 @@ private ConfigHelper() { } public static InputStream getResourceAsStream(String resource) { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public InputStream run() { - String stripped = resource.startsWith( "/" ) - ? resource.substring( 1 ) - : resource; - - InputStream stream = null; - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - if ( classLoader != null ) { - stream = classLoader.getResourceAsStream( stripped ); - } - if ( stream == null ) { - stream = Environment.class.getResourceAsStream( resource ); - } - if ( stream == null ) { - stream = Environment.class.getClassLoader().getResourceAsStream( stripped ); - } - if ( stream == null ) { - throw new HibernateException( resource + " not found" ); - } - return stream; - } - }; - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + String stripped = resource.startsWith( "/" ) + ? resource.substring( 1 ) + : resource; + + InputStream stream = null; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if ( classLoader != null ) { + stream = classLoader.getResourceAsStream( stripped ); + } + if ( stream == null ) { + stream = Environment.class.getResourceAsStream( resource ); + } + if ( stream == null ) { + stream = Environment.class.getClassLoader().getResourceAsStream( stripped ); + } + if ( stream == null ) { + throw new HibernateException( resource + " not found" ); + } + return stream; } + public static InputStream getUserResourceAsStream(String resource) { boolean hasLeadingSlash = resource.startsWith( "/" ); String stripped = hasLeadingSlash ? resource.substring( 1 ) : resource; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index b192252ad542..4fbff725c4bf 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -13,8 +13,6 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Locale; import java.util.regex.Pattern; import javax.persistence.Transient; @@ -237,14 +235,7 @@ public static Class reflectedPropertyClass(Class clazz, String name) throws Mapp } private static Getter getter(Class clazz, String name) throws MappingException { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public Getter run() { - return PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess( clazz, name ).getGetter(); - } - }; - - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + return PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess( clazz, name ).getGetter(); } public static Object getConstantValue(String name, SessionFactoryImplementor factory) { @@ -281,23 +272,16 @@ public static Constructor getDefaultConstructor(Class clazz) throws Pr return null; } - final PrivilegedAction action = new PrivilegedAction() { - @Override - public Constructor run() { - try { - Constructor constructor = clazz.getDeclaredConstructor( NO_PARAM_SIGNATURE ); - ensureAccessibility( constructor ); - return constructor; - } - catch (NoSuchMethodException e) { - throw new PropertyNotFoundException( - "Object class [" + clazz.getName() + "] must declare a default (no-argument) constructor" - ); - } - } - }; - - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + try { + Constructor constructor = clazz.getDeclaredConstructor( NO_PARAM_SIGNATURE ); + ensureAccessibility( constructor ); + return constructor; + } + catch ( NoSuchMethodException nme ) { + throw new PropertyNotFoundException( + "Object class [" + clazz.getName() + "] must declare a default (no-argument) constructor" + ); + } } /** @@ -364,19 +348,12 @@ public static Constructor getConstructor(Class clazz, Type[] types) throws Prope } public static Method getMethod(Class clazz, Method method) { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public Method run() { - try { - return clazz.getMethod( method.getName(), method.getParameterTypes() ); - } - catch (Exception e){ - return null; - } - } - }; - - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + try { + return clazz.getMethod( method.getName(), method.getParameterTypes() ); + } + catch (Exception e) { + return null; + } } public static Field findField(Class containerClass, String propertyName) { @@ -387,14 +364,8 @@ else if ( containerClass == Object.class ) { throw new IllegalArgumentException( "Illegal attempt to locate field [" + propertyName + "] on Object.class" ); } - final PrivilegedAction action = new PrivilegedAction() { - @Override - public Field run() { - return locateField( containerClass, propertyName ); - } - }; + Field field = locateField( containerClass, propertyName ); - final Field field = System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); if ( field == null ) { throw new PropertyNotFoundException( String.format( @@ -412,22 +383,11 @@ public Field run() { } public static void ensureAccessibility(AccessibleObject accessibleObject) { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public Object run() { - if ( !accessibleObject.isAccessible() ) { - accessibleObject.setAccessible( true ); - } - return null; - } - }; - - if ( System.getSecurityManager() != null ) { - AccessController.doPrivileged( action ); - } - else { - action.run(); + if ( accessibleObject.isAccessible() ) { + return; } + + accessibleObject.setAccessible( true ); } private static Field locateField(Class clazz, String propertyName) { @@ -502,7 +462,7 @@ private static Method getGetterOrNull(Class[] interfaces, String propertyName) { } private static Method getGetterOrNull(Class containerClass, String propertyName) { - for ( Method method : getDeclaredMethods( containerClass ) ) { + for ( Method method : containerClass.getDeclaredMethods() ) { // if the method has parameters, skip it if ( method.getParameterCount() != 0 ) { continue; @@ -553,39 +513,17 @@ private static void verifyNoIsVariantExists( String propertyName, Method getMethod, String stemName) { - final Method isMethod = getDeclaredMethod( containerClass, "is" + stemName ); - if ( isMethod != null ) { + // verify that the Class does not also define a method with the same stem name with 'is' + try { + final Method isMethod = containerClass.getDeclaredMethod( "is" + stemName ); if ( !Modifier.isStatic( isMethod.getModifiers() ) && isMethod.getAnnotation( Transient.class ) == null ) { // No such method should throw the caught exception. So if we get here, there was // such a method. checkGetAndIsVariants( containerClass, propertyName, getMethod, isMethod ); } } - } - - private static Method getDeclaredMethod(Class containerClass, String methodName) { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public Method run() { - try { - return containerClass.getDeclaredMethod( methodName ); - } - catch (NoSuchMethodException ignore) { - return null; - } - } - }; - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); - } - - private static Method[] getDeclaredMethods(Class containerClass) { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public Method[] run() { - return containerClass.getDeclaredMethods(); - } - }; - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + catch (NoSuchMethodException ignore) { + } } private static void checkGetAndIsVariants( @@ -616,14 +554,16 @@ private static void verifyNoGetVariantExists( Method isMethod, String stemName) { // verify that the Class does not also define a method with the same stem name with 'is' - final Method getMethod = getDeclaredMethod( containerClass, "get" + stemName ); - if ( getMethod != null ) { + try { + final Method getMethod = containerClass.getDeclaredMethod( "get" + stemName ); // No such method should throw the caught exception. So if we get here, there was // such a method. if ( !Modifier.isStatic( getMethod.getModifiers() ) && getMethod.getAnnotation( Transient.class ) == null ) { checkGetAndIsVariants( containerClass, propertyName, getMethod, isMethod ); } } + catch (NoSuchMethodException ignore) { + } } public static Method getterMethodOrNull(Class containerJavaType, String propertyName) { @@ -691,7 +631,7 @@ private static Method setterOrNull(Class[] interfaces, String propertyName, Clas private static Method setterOrNull(Class theClass, String propertyName, Class propertyType) { Method potentialSetter = null; - for ( Method method : getDeclaredMethods( theClass ) ) { + for ( Method method : theClass.getDeclaredMethods() ) { final String methodName = method.getName(); if ( method.getParameterCount() == 1 && methodName.startsWith( "set" ) ) { final String testOldMethod = methodName.substring( 3 ); @@ -716,7 +656,7 @@ private static Method setterOrNull(Class theClass, String propertyName, Class pr * as an abstract - but again, that is such an edge case... */ public static Method findGetterMethodForFieldAccess(Field field, String propertyName) { - for ( Method method : getDeclaredMethods( field.getDeclaringClass() ) ) { + for ( Method method : field.getDeclaringClass().getDeclaredMethods() ) { // if the method has parameters, skip it if ( method.getParameterCount() != 0 ) { continue; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java index 42da63f82d0e..1a6851ba3ce9 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java @@ -10,8 +10,6 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Target; import java.lang.reflect.Method; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.List; import javax.persistence.Entity; @@ -74,7 +72,6 @@ public void buildCallbacksForEntity(String entityClassName, CallbackRegistrar ca } continue; } - final Callback[] callbacks = resolveEntityCallbacks( entityXClass, callbackType, reflectionManager ); callbackRegistrar.registerCallbacks( entityClass, callbacks ); } @@ -122,7 +119,7 @@ public Callback[] resolveEntityCallbacks(XClass beanClass, CallbackType callback final boolean debugEnabled = log.isDebugEnabled(); do { Callback callback = null; - List methods = getDeclaredMethods( currentClazz ); + List methods = currentClazz.getDeclaredMethods(); for ( final XMethod xMethod : methods ) { if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) { Method method = reflectionManager.toMethod( xMethod ); @@ -193,7 +190,7 @@ public Callback[] resolveEntityCallbacks(XClass beanClass, CallbackType callback if ( listener != null ) { XClass xListener = reflectionManager.toXClass( listener ); callbacksMethodNames = new ArrayList<>(); - List methods = getDeclaredMethods( xListener ); + List methods = xListener.getDeclaredMethods(); for ( final XMethod xMethod : methods ) { if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) { final Method method = reflectionManager.toMethod( xMethod ); @@ -341,14 +338,4 @@ private static void getListeners(XClass currentClazz, List orderedListene } } } - - private static List getDeclaredMethods(XClass clazz) { - final PrivilegedAction> action = new PrivilegedAction>() { - @Override - public List run() { - return clazz.getDeclaredMethods(); - } - }; - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index 1012c391c13d..e97fdad69112 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -7,8 +7,6 @@ package org.hibernate.metamodel.internal; import java.lang.reflect.Field; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -378,26 +376,13 @@ private void populateStaticMetamodel(AbstractManagedType managedType) { return; } final String metamodelClassName = managedTypeClass.getName() + '_'; - - final PrivilegedAction action = new PrivilegedAction() { - @Override - public Object run() { - try { - final Class metamodelClass = Class.forName( metamodelClassName, true, managedTypeClass.getClassLoader() ); - // we found the class; so populate it... - registerAttributes( metamodelClass, managedType ); - } - catch (ClassNotFoundException ignore) { - // nothing to do... - } - return null; - } - }; - if ( System.getSecurityManager() != null ) { - AccessController.doPrivileged( action ); + try { + final Class metamodelClass = Class.forName( metamodelClassName, true, managedTypeClass.getClassLoader() ); + // we found the class; so populate it... + registerAttributes( metamodelClass, managedType ); } - else { - action.run(); + catch (ClassNotFoundException ignore) { + // nothing to do... } // todo : this does not account for @MappeSuperclass, mainly because this is not being tracked in our diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java index 6efd27b543e1..f186e8a384c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java @@ -8,8 +8,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -159,31 +157,24 @@ protected ProxyFactory buildProxyFactory(PersistentClass persistentClass, Getter null : ReflectHelper.getMethod( proxyInterface, idSetterMethod ); - final PrivilegedAction action = new PrivilegedAction() { - @Override - public ProxyFactory run() { - ProxyFactory pf = buildProxyFactoryInternal( persistentClass, idGetter, idSetter ); - try { - pf.postInstantiate( - getEntityName(), - mappedClass, - proxyInterfaces, - proxyGetIdentifierMethod, - proxySetIdentifierMethod, - persistentClass.hasEmbeddedIdentifier() ? - (CompositeType) persistentClass.getIdentifier().getType() : - null - ); - } - catch (HibernateException he) { - LOG.unableToCreateProxyFactory( getEntityName(), he ); - pf = null; - } - return pf; - } - }; - - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + ProxyFactory pf = buildProxyFactoryInternal( persistentClass, idGetter, idSetter ); + try { + pf.postInstantiate( + getEntityName(), + mappedClass, + proxyInterfaces, + proxyGetIdentifierMethod, + proxySetIdentifierMethod, + persistentClass.hasEmbeddedIdentifier() ? + (CompositeType) persistentClass.getIdentifier().getType() : + null + ); + } + catch (HibernateException he) { + LOG.unableToCreateProxyFactory( getEntityName(), he ); + pf = null; + } + return pf; } protected ProxyFactory buildProxyFactoryInternal( diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java index a1d28592157a..3ddffe0a4621 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java @@ -7,8 +7,6 @@ package org.hibernate.envers.configuration.internal.metadata.reader; import java.lang.annotation.Annotation; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -49,7 +47,6 @@ import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; import org.hibernate.mapping.Value; - import org.jboss.logging.Logger; import static org.hibernate.envers.internal.tools.Tools.newHashMap; @@ -357,47 +354,26 @@ private void addPropertiesFromClass(XClass clazz) { //look in the class addFromProperties( - getPropertiesFromClassByType( clazz, AccessType.FIELD ), + clazz.getDeclaredProperties( "field" ), it -> "field", fieldAccessedPersistentProperties, allClassAudited ); - addFromProperties( - getPropertiesFromClassByType( clazz, AccessType.PROPERTY ), + clazz.getDeclaredProperties( "property" ), propertyAccessedPersistentProperties::get, propertyAccessedPersistentProperties.keySet(), allClassAudited ); if ( allClassAudited != null || !auditedPropertiesHolder.isEmpty() ) { - final PrivilegedAction action = new PrivilegedAction() { - @Override - public XClass run() { - return clazz.getSuperclass(); - } - }; - - final XClass superclazz = System.getSecurityManager() != null - ? AccessController.doPrivileged( action ) - : action.run(); - + final XClass superclazz = clazz.getSuperclass(); if ( !clazz.isInterface() && !"java.lang.Object".equals( superclazz.getName() ) ) { addPropertiesFromClass( superclazz ); } } } - private Iterable getPropertiesFromClassByType(XClass clazz, AccessType accessType) { - final PrivilegedAction> action = new PrivilegedAction>() { - @Override - public Iterable run() { - return clazz.getDeclaredProperties( accessType.getType() ); - } - }; - return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); - } - private void addFromProperties( Iterable properties, Function accessTypeProvider, From b04de4c9f771c414011d61d423f9d9278a9597d2 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 27 Aug 2018 17:41:57 +0200 Subject: [PATCH 159/772] HHH-12932 Execute ByteBuddy code requiring privileges inside a privileged block --- .../internal/bytebuddy/ByteBuddyState.java | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index 0c464204728b..1cdeecbc8887 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -262,6 +262,7 @@ public static ClassLoadingStrategy resolveClassLoadingStrategy(Clas } private static ForDeclaredMethods getDeclaredMethodMemberSubstitution() { + // this should only be called if the security manager is enabled, thus the privileged calls return MemberSubstitution.relaxed() .method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class, "getDeclaredMethod", String.class, Class[].class ) ) ) ) @@ -272,6 +273,7 @@ private static ForDeclaredMethods getDeclaredMethodMemberSubstitution() { } private static ForDeclaredMethods getMethodMemberSubstitution() { + // this should only be called if the security manager is enabled, thus the privileged calls return MemberSubstitution.relaxed() .method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class, "getMethod", String.class, Class[].class ) ) ) ) @@ -321,10 +323,33 @@ private ProxyDefinitionHelpers() { .and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ); this.virtualNotFinalizerFilter = isVirtual().and( not( isFinalizer() ) ); this.hibernateGeneratedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() ); - this.delegateToInterceptorDispatcherMethodDelegation = MethodDelegation - .to( ProxyConfiguration.InterceptorDispatcher.class ); - this.interceptorFieldAccessor = FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) - .withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ); + + PrivilegedAction delegateToInterceptorDispatcherMethodDelegationPrivilegedAction = + new PrivilegedAction() { + + @Override + public MethodDelegation run() { + return MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ); + } + }; + + this.delegateToInterceptorDispatcherMethodDelegation = System.getSecurityManager() != null + ? AccessController.doPrivileged( delegateToInterceptorDispatcherMethodDelegationPrivilegedAction ) + : delegateToInterceptorDispatcherMethodDelegationPrivilegedAction.run(); + + PrivilegedAction interceptorFieldAccessorPrivilegedAction = + new PrivilegedAction() { + + @Override + public FieldAccessor.PropertyConfigurable run() { + return FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) + .withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ); + } + }; + + this.interceptorFieldAccessor = System.getSecurityManager() != null + ? AccessController.doPrivileged( interceptorFieldAccessorPrivilegedAction ) + : interceptorFieldAccessorPrivilegedAction.run(); } public ElementMatcher getGroovyGetMetaClassFilter() { From 97af7c48118d0181a736228db83bdd0afaf7da16 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 28 Aug 2018 19:08:01 +0200 Subject: [PATCH 160/772] 5.3.6.Final --- changelog.txt | 13 +++++++++++++ gradle/base-information.gradle | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 2daa58968bb5..fcbe6209da10 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,17 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.3.6.final (August 28th, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31704/tab/release-report-done + +** Bug + * [HHH-12931] - Revert HHH-12542 as it introduces some issues with the security manager + * [HHH-12932] - Add privileged blocks in ByteBuddyState initialization + + + Changes in 5.3.5.final (August 14th, 2018) ------------------------------------------------------------------------------------------------------------------------ @@ -32,6 +43,8 @@ https://hibernate.atlassian.net/projects/HHH/versions/31695/tab/release-report-d * [HHH-12892] - Fix spelling issues in the User Guide * [HHH-12907] - Avoid garbage collection pressure when creating proxies with ByteBuddy + + Changes in 5.3.4.final (August 2nd, 2018) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 1d349f10f823..67b3405dcf74 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.6-SNAPSHOT', project ) + ormVersion = new HibernateVersion( '5.3.6.Final', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From c1931f5d8d6e592acef3cb27f52d1eb3dce30448 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 28 Aug 2018 19:38:49 +0200 Subject: [PATCH 161/772] Prepare for next development iteration --- gradle/base-information.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 67b3405dcf74..47d6c5a9c507 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.6.Final', project ) + ormVersion = new HibernateVersion( '5.3.7-SNAPSHOT', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From e8fcbe29c3a2b92f4bb1865e373ef000eaab03fd Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 23 Aug 2018 00:10:27 +0200 Subject: [PATCH 162/772] HHH-12920 Fix a debug message causing an exception at debug level --- .../cache/spi/support/AbstractCachedDomainDataAccess.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java index 7bcd27b28061..e4475a3a5a83 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java @@ -38,9 +38,8 @@ protected DomainDataStorageAccess getStorageAccess() { return storageAccess; } - @SuppressWarnings({"unchecked", "WeakerAccess"}) protected void clearCache() { - log.debugf( "Clearing cache data map [region=`%s`]" ); + log.debugf( "Clearing cache data map [region=`%s`]", region.getName() ); getStorageAccess().evictData(); } From 00ec2e4fde7d71120c859e0f71478c2643f16d7e Mon Sep 17 00:00:00 2001 From: Jonathan Bregler Date: Fri, 7 Sep 2018 10:39:43 +0200 Subject: [PATCH 163/772] HHH-12961 Update HANA dialects Javadoc - Fix the links - Add some more information --- .../org/hibernate/dialect/AbstractHANADialect.java | 13 ++++++++++--- .../hibernate/dialect/HANAColumnStoreDialect.java | 11 +++++++++-- .../org/hibernate/dialect/HANARowStoreDialect.java | 11 +++++++++-- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java index 4b2876ec8030..bf94c4f40382 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -102,11 +102,18 @@ import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; /** - * An abstract base class for HANA dialects.
    - * SAP HANA Reference
    - * NOTE: This dialect is currently configured to create foreign keys with on update cascade. + * An abstract base class for SAP HANA dialects. + *

    + * For more information on interacting with the SAP HANA database, refer to the + * SAP HANA SQL and System Views Reference + * and the SAP + * HANA Client Interface Programming Reference. + *

    + * Note: This dialect is configured to create foreign keys with {@code on update cascade}. * * @author Andrew Clemons + * @author Jonathan Bregler */ public abstract class AbstractHANADialect extends Dialect { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java index c916289b4daa..2cafa3aa1299 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java @@ -12,11 +12,18 @@ import org.hibernate.hql.spi.id.local.AfterUseAction; /** - * An SQL dialect for HANA.
    - * SAP HANA Reference
    + * An SQL dialect for the SAP HANA column store. + *

    + * For more information on interacting with the SAP HANA database, refer to the + * SAP HANA SQL and System Views Reference + * and the SAP + * HANA Client Interface Programming Reference. + *

    * Column tables are created by this dialect when using the auto-ddl feature. * * @author Andrew Clemons + * @author Jonathan Bregler */ public class HANAColumnStoreDialect extends AbstractHANADialect { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java index 009c19401d2f..dc87a926458d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java @@ -12,11 +12,18 @@ import org.hibernate.hql.spi.id.local.AfterUseAction; /** - * An SQL dialect for HANA.
    - * SAP HANA Reference
    + * An SQL dialect for the SAP HANA row store. + *

    + * For more information on interacting with the SAP HANA database, refer to the + * SAP HANA SQL and System Views Reference + * and the SAP + * HANA Client Interface Programming Reference. + *

    * Row tables are created by this dialect when using the auto-ddl feature. * * @author Andrew Clemons + * @author Jonathan Bregler */ public class HANARowStoreDialect extends AbstractHANADialect { From 5f92f028c46c924641c62c3953fd0dd6127438b4 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 10 Jul 2018 13:15:28 +0200 Subject: [PATCH 164/772] HHH-12784 Fix a regression in Javassist support --- .../internal/javassist/AttributeTypeDescriptor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java index ab912c6c946b..4c4d7462bc7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java @@ -8,6 +8,8 @@ import java.util.Collection; import java.util.Locale; +import java.util.Objects; + import javax.persistence.EmbeddedId; import javax.persistence.Id; @@ -20,7 +22,7 @@ /** * utility class to generate interceptor methods * @see org.hibernate.engine.spi.PersistentAttributeInterceptor - * + * * @author Luis Barreiro */ public abstract class AttributeTypeDescriptor { @@ -64,7 +66,8 @@ public String buildInLineDirtyCheckingBodyFragment(JavassistEnhancementContext c } builder.append( String.format( - " if ( !Objects.deepEquals( %s, $1 ) )", + " if ( !%s.deepEquals( %s, $1 ) )", + Objects.class.getName(), readFragment ) ); From faa48ed16801dfe1d4902752efe7281f06148b95 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 13 Sep 2018 11:21:38 +0200 Subject: [PATCH 165/772] HHH-12784 Fix changelog as it hasn't been backported to 5.3.3 --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index fcbe6209da10..6f78615c3757 100644 --- a/changelog.txt +++ b/changelog.txt @@ -97,7 +97,6 @@ https://hibernate.atlassian.net/projects/HHH/versions/31687/tab/release-report-d * [HHH-12776] - NullPointerException when executing native query on an Audited Entity * [HHH-12779] - Revert HHH-12670 - Allows native SQL queries that take a given resultClass to map the result set to the required type * [HHH-12781] - Update Javassist dependency to 3.23.1 - * [HHH-12784] - Javassist support broken by HHH-12760 * [HHH-12786] - Deleting an entity leads to NullPointerException in ByteBuddy proxy * [HHH-12787] - SessionJdbcBatchTest hangs with DB2 * [HHH-12791] - ComponentTuplizer generates a LOT of proxy classes when using Bytebuddy as bytecode provider From 052027291b8a2ce97790f6dc499213fe6a981004 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 29 Aug 2018 12:02:16 +0300 Subject: [PATCH 166/772] HHH-12934 - Exception handling documentation does not apply only to "Session-per-application anti-pattern" --- .../chapters/pc/PersistenceContext.adoc | 63 ++++++++++++++++++- .../chapters/transactions/Transactions.adoc | 2 + 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc index 2a812aa36e4d..6af07c251e9b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc @@ -1,5 +1,5 @@ [[pc]] -== Persistence Contexts +== Persistence Context :sourcedir: ../../../../../test/java/org/hibernate/userguide/pc :sourcedir-caching: ../../../../../test/java/org/hibernate/userguide/caching :extrasdir: extras @@ -857,4 +857,65 @@ include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-example] ---- include::{extrasdir}/pc-cascade-on-delete-example.sql[] ---- +==== + +[[pc-exception-handling]] +=== Exception handling + +If the JPA `EntityManager` or the Hibernate-specific `Session` throws an exception, including any JDBC https://docs.oracle.com/javase/8/docs/api/java/sql/SQLException.html[`SQLException`], you have to immediately rollback the database transaction and close the current `EntityManager` or `Session`. + +Certain methods of the JPA `EntityManager` or the Hibernate `Session` will not leave the Persistence Context in a consistent state. As a rule of thumb, no exception thrown by Hibernate can be treated as recoverable. Ensure that the Session will be closed by calling the `close()` method in a finally block. + +Rolling back the database transaction does not put your business objects back into the state they were at the start of the transaction. This means that the database state and the business objects will be out of sync. Usually, this is not a problem because exceptions are not recoverable and you will have to start over after rollback anyway. + +The JPA https://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceException.html[`PersistenceException`] or the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/HibernateException.html[`HibernateException`] wraps most of the errors that can occur in a Hibernate persistence layer. + +Both the `PersistenceException` and the `HibernateException` are runtime exceptions because, in our opinion, we should not force the application developer to catch an unrecoverable exception at a low layer. In most systems, unchecked and fatal exceptions are handled in one of the first frames of the method call stack (i.e., in higher layers) and either an error message is presented to the application user or some other appropriate action is taken. Note that Hibernate might also throw other unchecked exceptions that are not a `HibernateException`. These are not recoverable either, and appropriate action should be taken. + +Hibernate wraps the JDBC `SQLException`, thrown while interacting with the database, in a +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/JDBCException.html[`JDBCException`]. +In fact, Hibernate will attempt to convert the exception into a more meaningful subclass of `JDBCException`. The underlying `SQLException` is always available via https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/JDBCException.html#getSQLException--[`JDBCException.getSQLException()`]. Hibernate converts the `SQLException` into an appropriate JDBCException subclass using the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/exception/spi/SQLExceptionConverter.html[`SQLExceptionConverter`] +attached to the current `SessionFactory`. + +By default, the `SQLExceptionConverter` is defined by the configured Hibernate `Dialect` via the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html#buildSQLExceptionConversionDelegate--[`buildSQLExceptionConversionDelegate`] method +which is overridden by several database-specific `Dialects`. + +However, it is also possible to plug in a custom implementation. See the +<> configuration property for more details. + +The standard `JDBCException` subtypes are: + +ConstraintViolationException:: + indicates some form of integrity constraint violation. +DataException:: + indicates that evaluation of the valid SQL statement against the given data + resulted in some illegal operation, mismatched types, truncation or incorrect cardinality. +GenericJDBCException:: + a generic exception which did not fall into any of the other categories. +JDBCConnectionException:: + indicates an error with the underlying JDBC communication. +LockAcquisitionException:: + indicates an error acquiring a lock level necessary to perform the requested operation. +LockTimeoutException:: + indicates that the lock acquisition request has timed out. +PessimisticLockException:: + indicates that a lock acquisition request has failed. +QueryTimeoutException:: + indicates that the current executing query has timed out. +SQLGrammarException:: + indicates a grammar or syntax problem with the issued SQL. + +[NOTE] +==== +Starting with Hibernate 5.2, the Hibernate `Session` extends the JPA `EntityManager`. For this reason, when a `SessionFactory` is built via Hibernate's native bootstrapping, +the `HibernateException` or `SQLException` can be wrapped in a JPA https://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceException.html[`PersistenceException`] when thrown +by `Session` methods that implement `EntityManager` methods (e.g., https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#merge-java.lang.Object-[Session.merge(Object object)], +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#flush--[Session.flush()]). + +If your `SessionFactory` is built via Hibernate's native bootstrapping, and you don't want the Hibernate exceptions to be wrapped in the JPA `PersistenceException`, you need to set the +`hibernate.native_exception_handling_51_compliance` configuration property to `true`. See the +<> configuration property for more details. ==== \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc b/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc index 1a0b4712ac7c..6a7cc0090e66 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc @@ -303,6 +303,8 @@ Rolling back the database transaction does not put your business objects back in This means that the database state and the business objects will be out of sync. Usually, this is not a problem because exceptions are not recoverable and you will have to start over after rollback anyway. +For more details, check out the <> chapter. + The `Session` caches every object that is in a persistent state (watched and checked for dirty state by Hibernate). If you keep it open for a long time or simply load too much data, it will grow endlessly until you get an `OutOfMemoryException`. One solution is to call `clear()` and `evict()` to manage the `Session` cache, but you should consider a Stored Procedure if you need mass data operations. From 15924cb466c925628c28a4b360ae13c5ba706e05 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 12 Oct 2018 12:40:28 -0700 Subject: [PATCH 167/772] HHH-13027 : test case --- .../boot/DeprecatedProviderCheckerTest.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java new file mode 100644 index 000000000000..b4f707577505 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java @@ -0,0 +1,50 @@ +/* + * 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.boot; + + +import org.hibernate.internal.HEMLogging; +import org.hibernate.jpa.boot.spi.ProviderChecker; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests that deprecated (and removed) provider, "org.hibernate.ejb.HibernatePersistence", + * is recognized as a Hibernate persistence provider. + * + * @author Gail Badner + */ +public class DeprecatedProviderCheckerTest { + final static String DEPRECATED_PROVIDER_NAME = "org.hibernate.ejb.HibernatePersistence"; + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + HEMLogging.messageLogger( ProviderChecker.class.getName() ) + ); + + @Test + @TestForIssue( jiraKey = "HHH-13027") + public void testDeprecatedProvider() { + Triggerable triggerable = logInspection.watchForLogMessages( "HHH015016" ); + triggerable.reset(); + assertTrue( ProviderChecker.hibernateProviderNamesContain( DEPRECATED_PROVIDER_NAME ) ); + triggerable.wasTriggered(); + assertEquals( + "HHH015016: Encountered a deprecated javax.persistence.spi.PersistenceProvider [org.hibernate.ejb.HibernatePersistence]; [org.hibernate.jpa.HibernatePersistenceProvider] will be used instead.", + triggerable.triggerMessage() + ); + } +} + From 83a9adbdb80c9d4a144cdb316ddc82492a66508a Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 12 Oct 2018 12:41:13 -0700 Subject: [PATCH 168/772] HHH-13027 : org.hibernate.ejb.HibernatePersistence can no longer be used as a persistence provider name --- .../internal/EntityManagerMessageLogger.java | 2 +- .../org/hibernate/jpa/boot/spi/ProviderChecker.java | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java index ee4d301c7a06..085299a517e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java @@ -114,7 +114,7 @@ void unableToLocateStaticMetamodelField( @LogMessage(level = WARN) @Message( id = 15016, - value = "Encountered a deprecated javax.persistence.spi.PersistenceProvider [%s]; use [%s] instead." + value = "Encountered a deprecated javax.persistence.spi.PersistenceProvider [%s]; [%s] will be used instead." ) void deprecatedPersistenceProvider(String deprecated, String replacement); diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java index 81aed3aa1bee..a88d09bc55f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java @@ -9,6 +9,8 @@ import java.util.Map; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.EntityManagerMessageLogger; +import org.hibernate.internal.HEMLogging; import org.hibernate.jpa.HibernatePersistenceProvider; import org.jboss.logging.Logger; @@ -20,6 +22,7 @@ * @author Steve Ebersole */ public final class ProviderChecker { + private static final Logger log = Logger.getLogger( ProviderChecker.class ); /** @@ -49,6 +52,15 @@ public static boolean hibernateProviderNamesContain(String requestedProviderName "Checking requested PersistenceProvider name [%s] against Hibernate provider names", requestedProviderName ); + final String deprecatedPersistenceProvider = "org.hibernate.ejb.HibernatePersistence"; + if ( deprecatedPersistenceProvider.equals( requestedProviderName) ) { + HEMLogging.messageLogger( ProviderChecker.class ) + .deprecatedPersistenceProvider( + deprecatedPersistenceProvider, + HibernatePersistenceProvider.class.getName() + ); + return true; + } return HibernatePersistenceProvider.class.getName().equals( requestedProviderName ); } From 7c4d18404c623b573d15cbc1abc015f801d867a7 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 29 Aug 2018 23:08:46 -0700 Subject: [PATCH 169/772] HHH-12937 : test cases (cherry picked from commit da50076afcc73fa1fb581aa34a4f4f986aafb41f) --- ...ntCollectionBasicNonUniqueIdWhereTest.java | 309 ++++++++++++++ ...WithLazyManyToOneNonUniqueIdWhereTest.java | 402 ++++++++++++++++++ .../LazyManyToManyNonUniqueIdWhereTest.java | 2 +- ...ollectionBasicNonUniqueIdWhereTest.hbm.xml | 52 +++ ...ntCollectionBasicNonUniqueIdWhereTest.java | 257 +++++++++++ ...hLazyManyToOneNonUniqueIdWhereTest.hbm.xml | 71 ++++ ...WithLazyManyToOneNonUniqueIdWhereTest.java | 325 ++++++++++++++ 7 files changed, 1417 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java new file mode 100644 index 000000000000..057c484bb0c7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java @@ -0,0 +1,309 @@ +/* + * 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.where.annotations; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Where; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class LazyElementCollectionBasicNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + + protected Class[] getAnnotatedClasses() { + return new Class[] { Material.class, Building.class }; + } + + @Before + public void setup() { + Session session = openSession(); + session.beginTransaction(); + { + session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE COLLECTION_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); + + session.createSQLQuery( + "create table MAIN_TABLE( " + + "ID integer not null, NAME varchar(255) not null, CODE varchar(10) not null, " + + "primary key (ID, CODE) )" + ).executeUpdate(); + + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'high', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 3, 'low', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'small', 'SIZE' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'SIZE' )" ) + .executeUpdate(); + + session.createSQLQuery( + "create table COLLECTION_TABLE( " + + "MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " + + "VALUE varchar(10) not null, VALUE_CODE varchar(10) not null, " + + "primary key (MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'MATERIAL', 'high', 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'MATERIAL', 'medium', 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'MATERIAL', 'low', 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'MATERIAL', 'medium', 'SIZE' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'BUILDING', 'high', 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'BUILDING', 'small', 'SIZE' )" + ).executeUpdate(); + + + session.createSQLQuery( + "create table MATERIAL_RATINGS( " + + "MATERIAL_ID integer not null, RATING varchar(10) not null," + + " primary key (MATERIAL_ID, RATING))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into MATERIAL_RATINGS(MATERIAL_ID, RATING) VALUES( 1, 'high' )" + ).executeUpdate(); + + } + session.getTransaction().commit(); + session.close(); + } + + @After + public void cleanup() { + Session session = openSession(); + session.beginTransaction(); + { + session.createSQLQuery( "delete from MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from COLLECTION_TABLE" ).executeUpdate(); + session.createSQLQuery( "delete from MAIN_TABLE" ).executeUpdate(); + } + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12937") + public void testInitializeFromUniqueAssociationTable() { + Session session = openSession(); + session.beginTransaction(); + { + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getRatings() ) ); + assertEquals( 1, material.getRatings().size() ); + assertTrue( Hibernate.isInitialized( material.getRatings() ) ); + + assertEquals( "high", material.getRatings().iterator().next() ); + } + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12937") + public void testInitializeFromNonUniqueAssociationTable() { + Session session = openSession(); + session.beginTransaction(); + { + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + assertEquals( 1, material.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + + assertEquals( "medium", material.getSizesFromCombined().iterator().next() ); + + Building building = session.get( Building.class, 1 ); + + // building.ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertEquals( 1, building.getRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertEquals( "high", building.getRatingsFromCombined().iterator().next() ); + + // Building#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( 1, building.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( "small", building.getSizesFromCombined().iterator().next() ); + } + session.getTransaction().commit(); + session.close(); + } + + @Entity( name = "Material" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'MATERIAL'" ) + public static class Material { + private int id; + + private String name; + private Set sizesFromCombined = new HashSet<>(); + private List mediumOrHighRatingsFromCombined = new ArrayList<>(); + private Set ratings = new HashSet<>(); + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + @ElementCollection + @CollectionTable( + name = "COLLECTION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) } + ) + @Column( name="VALUE") + @Where( clause = "MAIN_CODE='MATERIAL' AND VALUE_CODE='SIZE'") + @Immutable + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + @ElementCollection + @CollectionTable( + name = "MATERIAL_RATINGS", + joinColumns = { @JoinColumn( name = "MATERIAL_ID" ) } + ) + @Column( name="RATING") + @Immutable + public Set getRatings() { + return ratings; + } + public void setRatings(Set ratings) { + this.ratings = ratings; + } + } + + @Entity( name = "Building" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'BUILDING'" ) + public static class Building { + private int id; + private String name; + private Set sizesFromCombined = new HashSet<>(); + private Set ratingsFromCombined = new HashSet<>(); + private List mediumOrHighRatings = new ArrayList<>(); + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + @ElementCollection + @CollectionTable( + name = "COLLECTION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) } + ) + @Column( name="VALUE") + @Where( clause = "MAIN_CODE='BUILDING' AND VALUE_CODE='SIZE'") + @Immutable + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + @ElementCollection + @CollectionTable( + name = "COLLECTION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) } + ) + @Column( name="VALUE") + @Where( clause = "MAIN_CODE='BUILDING' AND VALUE_CODE='RATING'" ) + @Immutable + public Set getRatingsFromCombined() { + return ratingsFromCombined; + } + public void setRatingsFromCombined(Set ratingsFromCombined) { + this.ratingsFromCombined = ratingsFromCombined; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java new file mode 100644 index 000000000000..c1af2abc841f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java @@ -0,0 +1,402 @@ +/* + * 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.where.annotations; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.AssociationOverride; +import javax.persistence.AssociationOverrides; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Where; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + + protected Class[] getAnnotatedClasses() { + return new Class[] { Material.class, Building.class, Rating.class, Size.class }; + } + + @Before + public void setup() { + Session session = openSession(); + session.beginTransaction(); + { + session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE COLLECTION_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); + + session.createSQLQuery( + "create table MAIN_TABLE( " + + "ID integer not null, NAME varchar(255) not null, CODE varchar(10) not null, " + + "primary key (ID, CODE) )" + ).executeUpdate(); + + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'high', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 3, 'low', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'small', 'SIZE' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'SIZE' )" ) + .executeUpdate(); + + session.createSQLQuery( + "create table COLLECTION_TABLE( " + + "MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " + + "ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " + + "primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 1, 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 2, 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 3, 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 2, 'SIZE' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'SIZE' )" + ).executeUpdate(); + + + session.createSQLQuery( + "create table MATERIAL_RATINGS( " + + "MATERIAL_ID integer not null, RATING_ID integer not null," + + " primary key (MATERIAL_ID, RATING_ID))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 1 )" + ).executeUpdate(); + } + session.getTransaction().commit(); + session.close(); + } + + @After + public void cleanup() { + Session session = openSession(); + session.beginTransaction(); + { + session.createSQLQuery( "delete from MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from COLLECTION_TABLE" ).executeUpdate(); + session.createSQLQuery( "delete from MAIN_TABLE" ).executeUpdate(); + } + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12937") + public void testInitializeFromUniqueAssociationTable() { + Session session = openSession(); + session.beginTransaction(); + { + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getContainedRatings() ) ); + assertEquals( 1, material.getContainedRatings().size() ); + assertTrue( Hibernate.isInitialized( material.getContainedRatings() ) ); + + final ContainedRating containedRating = material.getContainedRatings().iterator().next(); + assertTrue( Hibernate.isInitialized( containedRating ) ); + assertEquals( "high", containedRating.getRating().getName() ); + + } + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12937") + public void testInitializeFromNonUniqueAssociationTable() { + Session session = openSession(); + session.beginTransaction(); + { + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#containedSizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getContainedSizesFromCombined() ) ); + assertEquals( 1, material.getContainedSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getContainedSizesFromCombined() ) ); + + ContainedSize containedSize = material.getContainedSizesFromCombined().iterator().next(); + assertFalse( Hibernate.isInitialized( containedSize.getSize() ) ); + assertEquals( "medium", containedSize.getSize().getName() ); + + Building building = session.get( Building.class, 1 ); + + // building.ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getContainedRatingsFromCombined() ) ); + assertEquals( 1, building.getContainedRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getContainedRatingsFromCombined() ) ); + ContainedRating containedRating = building.getContainedRatingsFromCombined().iterator().next(); + assertFalse( Hibernate.isInitialized( containedRating.getRating() ) ); + assertEquals( "high", containedRating.getRating().getName() ); + + // Building#containedSizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getContainedSizesFromCombined() ) ); + assertEquals( 1, building.getContainedSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getContainedSizesFromCombined() ) ); + containedSize = building.getContainedSizesFromCombined().iterator().next(); + assertFalse( Hibernate.isInitialized( containedSize.getSize() ) ); + assertEquals( "small", containedSize.getSize().getName() ); + } + session.getTransaction().commit(); + session.close(); + } + + @Entity( name = "Material" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'MATERIAL'" ) + public static class Material { + private int id; + + private String name; + private Set containedSizesFromCombined = new HashSet<>(); + private List mediumOrHighRatingsFromCombined = new ArrayList<>(); + private Set containedRatings = new HashSet<>(); + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + @ElementCollection + @CollectionTable( + name = "COLLECTION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) } + ) + @AssociationOverrides( + value = { @AssociationOverride( name = "size", joinColumns = { @JoinColumn(name = "ASSOCIATION_ID") } ) } + ) + @Where( clause = "MAIN_CODE='MATERIAL' AND ASSOCIATION_CODE='SIZE'") + @Immutable + public Set getContainedSizesFromCombined() { + return containedSizesFromCombined; + } + public void setContainedSizesFromCombined(Set containedSizesFromCombined) { + this.containedSizesFromCombined = containedSizesFromCombined; + } + + @ElementCollection + @CollectionTable( + name = "MATERIAL_RATINGS", + joinColumns = { @JoinColumn( name = "MATERIAL_ID" ) } + ) + @AssociationOverrides( + value = { @AssociationOverride( name = "rating", joinColumns = { @JoinColumn(name = "RATING_ID") } ) } + ) + @Immutable + public Set getContainedRatings() { + return containedRatings; + } + public void setContainedRatings(Set containedRatings) { + this.containedRatings = containedRatings; + } + } + + @Entity( name = "Building" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'BUILDING'" ) + public static class Building { + private int id; + private String name; + private Set containedSizesFromCombined = new HashSet<>(); + private Set containedRatingsFromCombined = new HashSet<>(); + private List mediumOrHighRatings = new ArrayList<>(); + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + @ElementCollection + @CollectionTable( + name = "COLLECTION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) } + ) + @Where( clause = "MAIN_CODE='BUILDING' AND ASSOCIATION_CODE='SIZE'") + @Immutable + public Set getContainedSizesFromCombined() { + return containedSizesFromCombined; + } + public void setContainedSizesFromCombined(Set containedSizesFromCombined) { + this.containedSizesFromCombined = containedSizesFromCombined; + } + + @ElementCollection + @CollectionTable( + name = "COLLECTION_TABLE", + joinColumns = { @JoinColumn( name = "MAIN_ID" ) } + ) + @Where( clause = "MAIN_CODE='BUILDING' AND ASSOCIATION_CODE='RATING'" ) + @Immutable + public Set getContainedRatingsFromCombined() { + return containedRatingsFromCombined; + } + public void setContainedRatingsFromCombined(Set containedRatingsFromCombined) { + this.containedRatingsFromCombined = containedRatingsFromCombined; + } + + } + + @Entity( name = "Size" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'SIZE'" ) + public static class Size { + private int id; + private String name; + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + @Embeddable + public static class ContainedSize { + private Size size; + + @ManyToOne( fetch = FetchType.LAZY ) + @JoinColumn( name = "ASSOCIATION_ID" ) + public Size getSize() { + return size; + } + public void setSize(Size size) { + this.size = size; + } + } + + @Entity( name = "Rating" ) + @Table( name = "MAIN_TABLE" ) + @Where( clause = "CODE = 'RATING'" ) + public static class Rating { + private int id; + private String name; + + @Id + @Column( name = "ID" ) + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Column( name = "NAME") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + @Embeddable + public static class ContainedRating { + private Rating rating; + + @ManyToOne( fetch = FetchType.LAZY ) + @JoinColumn( name = "ASSOCIATION_ID" ) + public Rating getRating() { + return rating; + } + public void setRating(Rating rating) { + this.rating = rating; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java index 0ac7b6a2b803..838212e7b0c3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java @@ -352,7 +352,7 @@ public void setName(String name) { joinColumns = { @JoinColumn( name = "MAIN_ID" ) }, inverseJoinColumns = { @JoinColumn( name = "ASSOCIATION_ID" ) } ) - @WhereJoinTable( clause = "MAIN_CODE='BUILDING' AND ASSOCIATION_CODE='RATING'") + @WhereJoinTable( clause = "MAIN_CODE='BUILDING' AND ASSOCIATION_CODE='SIZE'") @Immutable public Set getSizesFromCombined() { return sizesFromCombined; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml new file mode 100644 index 000000000000..de297ca21b4f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java new file mode 100644 index 000000000000..c683dc7db73d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java @@ -0,0 +1,257 @@ +/* + * 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.where.hbm; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.Hibernate; +import org.hibernate.Session; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class LazyElementCollectionBasicNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + + protected String[] getMappings() { + return new String[] { "where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml" }; + } + + @Before + public void setup() { + Session session = openSession(); + session.beginTransaction(); + { + session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE COLLECTION_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); + + session.createSQLQuery( + "create table MAIN_TABLE( " + + "ID integer not null, NAME varchar(255) not null, CODE varchar(10) not null, " + + "primary key (ID, CODE) )" + ).executeUpdate(); + + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'high', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 3, 'low', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'small', 'SIZE' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'SIZE' )" ) + .executeUpdate(); + + session.createSQLQuery( + "create table COLLECTION_TABLE( " + + "MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " + + "VALUE varchar(10) not null, VALUE_CODE varchar(10) not null, " + + "primary key (MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'MATERIAL', 'high', 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'MATERIAL', 'medium', 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'MATERIAL', 'low', 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'MATERIAL', 'medium', 'SIZE' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'BUILDING', 'high', 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, VALUE, VALUE_CODE) " + + "VALUES( 1, 'BUILDING', 'small', 'SIZE' )" + ).executeUpdate(); + + + session.createSQLQuery( + "create table MATERIAL_RATINGS( " + + "MATERIAL_ID integer not null, RATING varchar(10) not null," + + " primary key (MATERIAL_ID, RATING))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into MATERIAL_RATINGS(MATERIAL_ID, RATING) VALUES( 1, 'high' )" + ).executeUpdate(); + + } + session.getTransaction().commit(); + session.close(); + } + + @After + public void cleanup() { + Session session = openSession(); + session.beginTransaction(); + { + session.createSQLQuery( "delete from MATERIAL_RATINGS" ).executeUpdate(); +// session.createSQLQuery( "delete from BUILDING_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from COLLECTION_TABLE" ).executeUpdate(); + session.createSQLQuery( "delete from MAIN_TABLE" ).executeUpdate(); + } + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12937") + public void testInitializeFromUniqueAssociationTable() { + Session session = openSession(); + session.beginTransaction(); + { + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getRatings() ) ); + assertEquals( 1, material.getRatings().size() ); + assertTrue( Hibernate.isInitialized( material.getRatings() ) ); + + assertEquals( "high", material.getRatings().iterator().next() ); + } + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12937") + public void testInitializeFromNonUniqueAssociationTable() { + Session session = openSession(); + session.beginTransaction(); + { + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + assertEquals( 1, material.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getSizesFromCombined() ) ); + + assertEquals( "medium", material.getSizesFromCombined().iterator().next() ); + + Building building = session.get( Building.class, 1 ); + + // building.ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertEquals( 1, building.getRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getRatingsFromCombined() ) ); + assertEquals( "high", building.getRatingsFromCombined().iterator().next() ); + + // Building#sizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( 1, building.getSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getSizesFromCombined() ) ); + assertEquals( "small", building.getSizesFromCombined().iterator().next() ); + } + session.getTransaction().commit(); + session.close(); + } + + public static class Material { + private int id; + + private String name; + private Set sizesFromCombined = new HashSet<>(); + private List mediumOrHighRatingsFromCombined = new ArrayList<>(); + private Set ratings = new HashSet<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + public Set getRatings() { + return ratings; + } + public void setRatings(Set ratings) { + this.ratings = ratings; + } + } + + public static class Building { + private int id; + private String name; + private Set sizesFromCombined = new HashSet<>(); + private Set ratingsFromCombined = new HashSet<>(); + private List mediumOrHighRatings = new ArrayList<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public Set getSizesFromCombined() { + return sizesFromCombined; + } + public void setSizesFromCombined(Set sizesFromCombined) { + this.sizesFromCombined = sizesFromCombined; + } + + public Set getRatingsFromCombined() { + return ratingsFromCombined; + } + public void setRatingsFromCombined(Set ratingsFromCombined) { + this.ratingsFromCombined = ratingsFromCombined; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.hbm.xml new file mode 100644 index 000000000000..ae850a55cb7c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.hbm.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java new file mode 100644 index 000000000000..47a25d2d116d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java @@ -0,0 +1,325 @@ +/* + * 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.where.hbm; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.Hibernate; +import org.hibernate.Session; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + + protected String[] getMappings() { + return new String[] { "where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.hbm.xml" }; + } + + @Before + public void setup() { + Session session = openSession(); + session.beginTransaction(); + { + session.createSQLQuery( "DROP TABLE MAIN_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE COLLECTION_TABLE" ).executeUpdate(); + session.createSQLQuery( "DROP TABLE MATERIAL_RATINGS" ).executeUpdate(); + + session.createSQLQuery( + "create table MAIN_TABLE( " + + "ID integer not null, NAME varchar(255) not null, CODE varchar(10) not null, " + + "primary key (ID, CODE) )" + ).executeUpdate(); + + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'high', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 3, 'low', 'RATING' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'small', 'SIZE' )" ) + .executeUpdate(); + session.createSQLQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'SIZE' )" ) + .executeUpdate(); + + session.createSQLQuery( + "create table COLLECTION_TABLE( " + + "MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " + + "ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " + + "primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 1, 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 2, 'RATING' )" + ).executeUpdate(); + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 3, 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'MATERIAL', 2, 'SIZE' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'RATING' )" + ).executeUpdate(); + + session.createSQLQuery( + "insert into COLLECTION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " + + "VALUES( 1, 'BUILDING', 1, 'SIZE' )" + ).executeUpdate(); + + + session.createSQLQuery( + "create table MATERIAL_RATINGS( " + + "MATERIAL_ID integer not null, RATING_ID integer not null," + + " primary key (MATERIAL_ID, RATING_ID))" + ).executeUpdate(); + + session.createSQLQuery( + "insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 1 )" + ).executeUpdate(); + + } + session.getTransaction().commit(); + session.close(); + } + + @After + public void cleanup() { + Session session = openSession(); + session.beginTransaction(); + { + session.createSQLQuery( "delete from MATERIAL_RATINGS" ).executeUpdate(); + session.createSQLQuery( "delete from COLLECTION_TABLE" ).executeUpdate(); + session.createSQLQuery( "delete from MAIN_TABLE" ).executeUpdate(); + } + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12937") + public void testInitializeFromUniqueAssociationTable() { + Session session = openSession(); + session.beginTransaction(); + { + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#ratings is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getContainedRatings() ) ); + assertEquals( 1, material.getContainedRatings().size() ); + assertTrue( Hibernate.isInitialized( material.getContainedRatings() ) ); + + final ContainedRating containedRating = material.getContainedRatings().iterator().next(); + assertTrue( Hibernate.isInitialized( containedRating ) ); + assertEquals( "high", containedRating.getRating().getName() ); + } + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-12937") + public void testInitializeFromNonUniqueAssociationTable() { + Session session = openSession(); + session.beginTransaction(); + { + Material material = session.get( Material.class, 1 ); + assertEquals( "plastic", material.getName() ); + + // Material#containedSizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( material.getContainedSizesFromCombined() ) ); + assertEquals( 1, material.getContainedSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( material.getContainedSizesFromCombined() ) ); + + ContainedSize containedSize = material.getContainedSizesFromCombined().iterator().next(); + assertFalse( Hibernate.isInitialized( containedSize.getSize() ) ); + assertEquals( "medium", containedSize.getSize().getName() ); + + Building building = session.get( Building.class, 1 ); + + // building.ratingsFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getContainedRatingsFromCombined() ) ); + assertEquals( 1, building.getContainedRatingsFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getContainedRatingsFromCombined() ) ); + ContainedRating containedRating = building.getContainedRatingsFromCombined().iterator().next(); + assertFalse( Hibernate.isInitialized( containedRating.getRating() ) ); + assertEquals( "high", containedRating.getRating().getName() ); + + // Building#containedSizesFromCombined is mapped with lazy="true" + assertFalse( Hibernate.isInitialized( building.getContainedSizesFromCombined() ) ); + assertEquals( 1, building.getContainedSizesFromCombined().size() ); + assertTrue( Hibernate.isInitialized( building.getContainedSizesFromCombined() ) ); + containedSize = building.getContainedSizesFromCombined().iterator().next(); + assertFalse( Hibernate.isInitialized( containedSize.getSize() ) ); + assertEquals( "small", containedSize.getSize().getName() ); + } + session.getTransaction().commit(); + session.close(); + } + + public static class Material { + private int id; + + private String name; + private Set containedSizesFromCombined = new HashSet<>(); + private List mediumOrHighRatingsFromCombined = new ArrayList<>(); + private Set containedRatings = new HashSet<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public Set getContainedSizesFromCombined() { + return containedSizesFromCombined; + } + public void setContainedSizesFromCombined(Set containedSizesFromCombined) { + this.containedSizesFromCombined = containedSizesFromCombined; + } + + public Set getContainedRatings() { + return containedRatings; + } + public void setContainedRatings(Set containedRatings) { + this.containedRatings = containedRatings; + } + } + + public static class Building { + private int id; + private String name; + private Set containedSizesFromCombined = new HashSet<>(); + private Set containedRatingsFromCombined = new HashSet<>(); + private List mediumOrHighRatings = new ArrayList<>(); + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public Set getContainedSizesFromCombined() { + return containedSizesFromCombined; + } + public void setContainedSizesFromCombined(Set containedSizesFromCombined) { + this.containedSizesFromCombined = containedSizesFromCombined; + } + + public Set getContainedRatingsFromCombined() { + return containedRatingsFromCombined; + } + public void setContainedRatingsFromCombined(Set containedRatingsFromCombined) { + this.containedRatingsFromCombined = containedRatingsFromCombined; + } + + } + + public static class Size { + private int id; + private String name; + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + public static class ContainedSize { + private Size size; + + public Size getSize() { + return size; + } + public void setSize(Size size) { + this.size = size; + } + } + + public static class Rating { + private int id; + private String name; + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } + + public static class ContainedRating { + private Rating rating; + + public Rating getRating() { + return rating; + } + public void setRating(Rating rating) { + this.rating = rating; + } + } +} From faabcafa816d2d132b68c3dd11de8a886223a989 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 29 Aug 2018 23:12:13 -0700 Subject: [PATCH 170/772] HHH-12937 : Where clause for collections of basic, embeddable and "any" elements is ignored when mapped using hbm.xml (cherry picked from commit eb14b8de6ff724b0867e7b848b33e05723fe8c55) --- .../model/source/internal/hbm/ModelBinder.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index 27f16af5bb6d..f3f1d092bb60 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -3386,6 +3386,11 @@ protected void bindCollectionIndex() { } protected void bindCollectionElement() { + log.debugf( + "Binding [%s] element type for a [%s]", + getPluralAttributeSource().getElementSource().getNature(), + getPluralAttributeSource().getNature() + ); if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceBasic ) { final PluralAttributeElementSourceBasic elementSource = (PluralAttributeElementSourceBasic) getPluralAttributeSource().getElementSource(); @@ -3414,6 +3419,10 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { ); getCollectionBinding().setElement( elementBinding ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (the table containing the basic elements) + // This "where" clause comes from the collection mapping; e.g., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceEmbedded ) { final PluralAttributeElementSourceEmbedded elementSource = @@ -3435,6 +3444,10 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu ); getCollectionBinding().setElement( elementBinding ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (the table containing the embeddable elements) + // This "where" clause comes from the collection mapping; e.g., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceOneToMany ) { final PluralAttributeElementSourceOneToMany elementSource = @@ -3603,6 +3616,11 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu ); getCollectionBinding().setElement( elementBinding ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the join table for a many-to-any association). + // This "where" clause comes from the collection mapping; e.g., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } } } From 8da2daa4f1e7cf466f57db9bf3ba15a260e4c6cf Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 30 Aug 2018 11:42:33 -0700 Subject: [PATCH 171/772] HHH-12937 : Remove duplicated line (cherry picked from commit ae2da95f556e08df96be6b630580f8e2522a4683) --- .../hibernate/boot/model/source/internal/hbm/ModelBinder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index f3f1d092bb60..c64d98d3aec3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -3620,7 +3620,6 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu // (which is the join table for a many-to-any association). // This "where" clause comes from the collection mapping; e.g., getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); - getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } } } From 320772e5eeb18c3cb8ececc6a2b80de8195e8a61 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 28 Aug 2018 22:03:21 -0700 Subject: [PATCH 172/772] HHH-12935 : test cases (cherry picked from commit e3726bc4db2775569b9285281b37024abb25f8b1) --- .../schemaupdate/ExportIdentifierTest.java | 175 ++++++++++++++++++ .../test/various/ExportIdentifierTest.java | 70 +++++++ 2 files changed, 245 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java new file mode 100644 index 000000000000..eeb6ca807993 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java @@ -0,0 +1,175 @@ +/* + * 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.schemaupdate; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.boot.internal.MetadataBuilderImpl; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.Exportable; +import org.hibernate.boot.model.relational.NamedAuxiliaryDatabaseObject; +import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.model.relational.Sequence; +import org.hibernate.boot.model.relational.SimpleAuxiliaryDatabaseObject; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataBuildingOptions; +import org.hibernate.mapping.ForeignKey; +import org.hibernate.mapping.Index; +import org.hibernate.mapping.PrimaryKey; +import org.hibernate.mapping.Table; +import org.hibernate.mapping.UniqueKey; + + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ExportIdentifierTest { + + @Test + @TestForIssue( jiraKey = "HHH-12935" ) + public void testUniqueExportableIdentifier() { + final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); + final MetadataBuildingOptions options = new MetadataBuilderImpl.MetadataBuildingOptionsImpl( ssr ); + final Database database = new Database( options ); + + database.locateNamespace( null, null ); + database.locateNamespace( Identifier.toIdentifier( "catalog1" ), null ); + database.locateNamespace( Identifier.toIdentifier( "catalog2" ), null ); + database.locateNamespace( null, Identifier.toIdentifier( "schema1" ) ); + database.locateNamespace( null, Identifier.toIdentifier( "schema2" ) ); + database.locateNamespace( + Identifier.toIdentifier( "catalog_both_1" ), + Identifier.toIdentifier( "schema_both_1" ) + ); + database.locateNamespace( + Identifier.toIdentifier( "catalog_both_2" ), + Identifier.toIdentifier( "schema_both_2" ) + ); + + final List exportIdentifierList = new ArrayList<>(); + final Set exportIdentifierSet = new HashSet<>(); + + try { + addTables( "aTable" , database.getNamespaces(), exportIdentifierList, exportIdentifierSet ); + addSimpleAuxiliaryDatabaseObject( database.getNamespaces(), exportIdentifierList, exportIdentifierSet ); + addNamedAuxiliaryDatabaseObjects( + "aNamedAuxiliaryDatabaseObject", database.getNamespaces(), exportIdentifierList, exportIdentifierSet + ); + addSequences( "aSequence", database.getNamespaces(), exportIdentifierList, exportIdentifierSet ); + + assertEquals( exportIdentifierList.size(), exportIdentifierSet.size() ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + private void addTables( + String name, + Iterable namespaces, + List exportIdentifierList, + Set exportIdentifierSet) { + for ( Namespace namespace : namespaces ) { + + final Table table = new Table( namespace, Identifier.toIdentifier( name ), false ); + addExportIdentifier( table, exportIdentifierList, exportIdentifierSet ); + + final ForeignKey foreignKey = new ForeignKey(); + foreignKey.setName( name ); + foreignKey.setTable( table ); + addExportIdentifier( foreignKey, exportIdentifierList, exportIdentifierSet ); + + final Index index = new Index(); + index.setName( name ); + index.setTable( table ); + addExportIdentifier( index, exportIdentifierList, exportIdentifierSet ); + + final PrimaryKey primaryKey = new PrimaryKey( table ); + primaryKey.setName( name ); + addExportIdentifier( primaryKey, exportIdentifierList, exportIdentifierSet ); + + final UniqueKey uniqueKey = new UniqueKey(); + uniqueKey.setName( name ); + uniqueKey.setTable( table ); + addExportIdentifier( uniqueKey, exportIdentifierList, exportIdentifierSet ); + } + } + + private void addSequences( + String name, + Iterable namespaces, + List exportIdentifierList, + Set exportIdentifierSet) { + + for ( Namespace namespace : namespaces ) { + addExportIdentifier( + new Sequence( + namespace.getName().getCatalog(), + namespace.getName().getSchema(), + Identifier.toIdentifier( name ) + ), + exportIdentifierList, + exportIdentifierSet + ); + } + } + + private void addSimpleAuxiliaryDatabaseObject( + Iterable namespaces, + List exportIdentifierList, + Set exportIdentifierSet) { + for ( Namespace namespace : namespaces ) { + addExportIdentifier( + new SimpleAuxiliaryDatabaseObject( + namespace, + "create", + "drop", + Collections.emptySet() + ), + exportIdentifierList, + exportIdentifierSet + ); + } + } + + private void addNamedAuxiliaryDatabaseObjects( + String name, + Iterable namespaces, + List exportIdentifierList, + Set exportIdentifierSet) { + for ( Namespace namespace : namespaces ) { + addExportIdentifier( + new NamedAuxiliaryDatabaseObject( + name, + namespace, + "create", + "drop", + Collections.emptySet() + ), + exportIdentifierList, + exportIdentifierSet + ); + } + } + + private void addExportIdentifier( + Exportable exportable, + List exportIdentifierList, + Set exportIdentifierSet) { + exportIdentifierList.add( exportable.getExportIdentifier() ); + exportIdentifierSet.add( exportable.getExportIdentifier() ); + assertEquals( exportIdentifierList.size(), exportIdentifierSet.size() ); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java new file mode 100644 index 000000000000..9aaa80f863b1 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java @@ -0,0 +1,70 @@ +/* + * 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.envers.test.various; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.boot.internal.MetadataBuilderImpl; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.model.relational.QualifiedNameImpl; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataBuildingOptions; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.id.enhanced.SequenceStructure; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ExportIdentifierTest { + + @Test + @TestForIssue( jiraKey = "HHH-12935" ) + public void testUniqueExportableIdentifier() { + final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); + final MetadataBuildingOptions options = new MetadataBuilderImpl.MetadataBuildingOptionsImpl( ssr ); + final Database database = new Database( options ); + + database.locateNamespace( null, null ); + database.locateNamespace( Identifier.toIdentifier( "catalog1" ), null ); + database.locateNamespace( Identifier.toIdentifier( "catalog2" ), null ); + database.locateNamespace( null, Identifier.toIdentifier( "schema1" ) ); + database.locateNamespace( null, Identifier.toIdentifier( "schema2" ) ); + database.locateNamespace( Identifier.toIdentifier( "catalog_both_1" ), Identifier.toIdentifier( "schema_both_1" ) ); + database.locateNamespace( Identifier.toIdentifier( "catalog_both_2" ), Identifier.toIdentifier( "schema_both_2" ) ); + + try { + final Set exportIdentifierSet = new HashSet<>(); + int namespaceSize = 0; + for ( Namespace namespace : database.getNamespaces() ) { + final SequenceStructure sequenceStructure = new SequenceStructure( + ssr.getService( JdbcEnvironment.class ), + new QualifiedNameImpl( + namespace.getName(), + Identifier.toIdentifier( "aSequence" ) + ), + 1, + 1, + Integer.class + ); + sequenceStructure.registerExportables( database ); + exportIdentifierSet.add( namespace.getSequences().iterator().next().getExportIdentifier() ); + namespaceSize++; + } + assertEquals( 7, namespaceSize ); + assertEquals( 7, exportIdentifierSet.size() ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } +} From 5ba82d6691ba4229f3c3b575a456a4779b7ecde1 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 28 Aug 2018 22:04:44 -0700 Subject: [PATCH 173/772] HHH-12935 : Constraint and AuxiliaryDatabaseObject export identifiers are not qualified by schema or catalog (cherry picked from commit ba80f0b3dc18f11bbaf1544bdcadd5828ad161df) --- .../model/relational/NamedAuxiliaryDatabaseObject.java | 8 +++++++- .../model/relational/SimpleAuxiliaryDatabaseObject.java | 8 ++++++++ .../src/main/java/org/hibernate/mapping/ForeignKey.java | 2 +- .../src/main/java/org/hibernate/mapping/Index.java | 2 +- .../src/main/java/org/hibernate/mapping/PrimaryKey.java | 2 +- .../src/main/java/org/hibernate/mapping/UniqueKey.java | 2 +- 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java index 887300e3d6a2..9d9eb11c575c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java @@ -8,6 +8,8 @@ import java.util.Set; +import org.hibernate.boot.model.naming.Identifier; + /** * Mainly this is used to support legacy sequence exporting. * @@ -42,6 +44,10 @@ public NamedAuxiliaryDatabaseObject( @Override public String getExportIdentifier() { - return name; + return new QualifiedNameImpl( + Identifier.toIdentifier( getCatalogName() ), + Identifier.toIdentifier( getSchemaName() ), + Identifier.toIdentifier( name ) + ).render(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java index edcaa565fc55..df59ef7466f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java @@ -93,6 +93,14 @@ public String[] sqlDropStrings(Dialect dialect) { return copy; } + protected String getCatalogName() { + return catalogName; + } + + protected String getSchemaName() { + return schemaName; + } + private String injectCatalogAndSchema(String ddlString) { String rtn = StringHelper.replace( ddlString, CATALOG_NAME_PLACEHOLDER, catalogName == null ? "" : catalogName ); rtn = StringHelper.replace( rtn, SCHEMA_NAME_PLACEHOLDER, schemaName == null ? "" : schemaName ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java index 8e671c6cffc6..44c5db907b32 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java @@ -33,7 +33,7 @@ public ForeignKey() { @Override public String getExportIdentifier() { // NOt sure name is always set. Might need some implicit naming - return StringHelper.qualify( getTable().getName(), "FK-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "FK-" + getName() ); } public void disableCreation() { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java index 7781ccceb317..4d7da83ec7cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java @@ -238,6 +238,6 @@ public String toString() { @Override public String getExportIdentifier() { - return StringHelper.qualify( getTable().getName(), "IDX-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "IDX-" + getName() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java index bcc9cd24486b..3c5e9a03b127 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java @@ -89,6 +89,6 @@ public String generatedConstraintNamePrefix() { @Override public String getExportIdentifier() { - return StringHelper.qualify( getTable().getName(), "PK-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "PK-" + getName() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java index c85a6847f74d..b3ff9fdf5d0c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java @@ -72,6 +72,6 @@ public String generatedConstraintNamePrefix() { @Override public String getExportIdentifier() { - return StringHelper.qualify( getTable().getName(), "UK-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "UK-" + getName() ); } } From 1fd1882806b77a76ca61de52191fc8261a5d7d54 Mon Sep 17 00:00:00 2001 From: SirWayne Date: Fri, 7 Sep 2018 16:34:38 +0200 Subject: [PATCH 174/772] HHH-12964 - Upgrade to dom4j 2.1.1 (cherry picked from commit e66da8af0076c2fbca9c190e8e708441be5bfcd3) --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 3f0ed28a6a3d..d1d983edece5 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -52,7 +52,7 @@ ext { woodstox: "org.codehaus.woodstox:woodstox-core-asl:4.3.0", // Dom4J - dom4j: 'dom4j:dom4j:1.6.1@jar', + dom4j: 'org.dom4j:dom4j:2.1.1@jar', // Javassist javassist: "org.javassist:javassist:${javassistVersion}", From d5d1f0781a2436e45302f76ebf8e763e3da4a1e7 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 16 Oct 2018 14:22:31 +0200 Subject: [PATCH 175/772] HHH-12937 Only run the tests with H2Dialect --- .../LazyElementCollectionBasicNonUniqueIdWhereTest.java | 5 ++++- ...ementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java | 5 ++++- .../hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java | 5 ++++- ...ementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java index 057c484bb0c7..bde606c4dbcd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionBasicNonUniqueIdWhereTest.java @@ -22,7 +22,8 @@ import org.hibernate.Session; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Where; - +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.After; @@ -36,8 +37,10 @@ /** * @author Gail Badner */ +@RequiresDialect(H2Dialect.class) public class LazyElementCollectionBasicNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + @Override protected Class[] getAnnotatedClasses() { return new Class[] { Material.class, Building.class }; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java index c1af2abc841f..2b2c8474eaa1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java @@ -27,7 +27,8 @@ import org.hibernate.Session; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Where; - +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.After; @@ -41,8 +42,10 @@ /** * @author Gail Badner */ +@RequiresDialect(H2Dialect.class) public class LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + @Override protected Class[] getAnnotatedClasses() { return new Class[] { Material.class, Building.class, Rating.class, Size.class }; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java index c683dc7db73d..fb4ed65b08b5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.java @@ -13,7 +13,8 @@ import org.hibernate.Hibernate; import org.hibernate.Session; - +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.After; @@ -27,8 +28,10 @@ /** * @author Gail Badner */ +@RequiresDialect(H2Dialect.class) public class LazyElementCollectionBasicNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + @Override protected String[] getMappings() { return new String[] { "where/hbm/LazyElementCollectionBasicNonUniqueIdWhereTest.hbm.xml" }; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java index 47a25d2d116d..afd856ec49e6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.java @@ -13,7 +13,8 @@ import org.hibernate.Hibernate; import org.hibernate.Session; - +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.After; @@ -27,8 +28,10 @@ /** * @author Gail Badner */ +@RequiresDialect(H2Dialect.class) public class LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest extends BaseCoreFunctionalTestCase { + @Override protected String[] getMappings() { return new String[] { "where/hbm/LazyElementCollectionWithLazyManyToOneNonUniqueIdWhereTest.hbm.xml" }; } From 98ae615bbe6cb780711ab641a49198aa4396e7da Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 15 Oct 2018 20:55:16 -0700 Subject: [PATCH 176/772] HHH-12104 : Test cases using annotations and hbm.xml --- .../EagerManyToOneFetchModeJoinWhereTest.java | 160 +++++++++++++++ ...agerManyToOneFetchModeSelectWhereTest.java | 159 +++++++++++++++ ...gerManyToOneFetchModeJoinWhereTest.hbm.xml | 42 ++++ .../EagerManyToOneFetchModeJoinWhereTest.java | 189 ++++++++++++++++++ ...rManyToOneFetchModeSelectWhereTest.hbm.xml | 42 ++++ ...agerManyToOneFetchModeSelectWhereTest.java | 187 +++++++++++++++++ 6 files changed, 779 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java new file mode 100644 index 000000000000..53abe44d9b81 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java @@ -0,0 +1,160 @@ +/* + * 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.where.annotations; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.annotations.Where; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * @author Gail Badner + */ +public class EagerManyToOneFetchModeJoinWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Product.class, Category.class }; + } + + @Test + @TestForIssue( jiraKey = "HHH-12104" ) + @FailureExpected( jiraKey = "HHH-12104") + public void testAssociatedWhereClause() { + Product product = new Product(); + Category category = new Category(); + category.name = "flowers"; + product.category = category; + product.containedCategory = new ContainedCategory(); + product.containedCategory.category = category; + product.containedCategories.add( new ContainedCategory( category ) ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertNotNull( p.category ); + assertNotNull( p.containedCategory.category ); + assertEquals( 1, p.containedCategories.size() ); + assertSame( p.category, p.containedCategory.category ); + assertSame( p.category, p.containedCategories.iterator().next().category ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, category.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, category.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + // Entity's where clause is ignored when to-one associations to that + // association is loaded eagerly using FetchMode.JOIN, so the result + // should be the same as before the Category was made inactive. + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertNull( p.category ); + assertNull( p.containedCategory.category ); + assertEquals( 1, p.containedCategories.size() ); + assertNull( p.containedCategories.iterator().next().category ); + } + ); + } + + @Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue + private int id; + + @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "categoryId") + @Fetch(FetchMode.JOIN) + private Category category; + + private ContainedCategory containedCategory; + + @ElementCollection(fetch = FetchType.EAGER) + private Set containedCategories = new HashSet<>(); + } + + @Entity(name = "Category") + @Table(name = "CATEGORY") + @Where(clause = "inactive = 0") + public static class Category { + @Id + @GeneratedValue + private int id; + + private String name; + + private boolean inactive; + } + + @Embeddable + public static class ContainedCategory { + @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "containedCategoryId") + @Fetch(FetchMode.JOIN) + private Category category; + + public ContainedCategory() { + } + + public ContainedCategory(Category category) { + this.category = category; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java new file mode 100644 index 000000000000..2a3a938f143e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java @@ -0,0 +1,159 @@ +/* + * 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.where.annotations; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.annotations.Where; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * @author Gail Badner + */ +public class EagerManyToOneFetchModeSelectWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Product.class, Category.class }; + } + + @Test + @TestForIssue( jiraKey = "HHH-12104" ) + public void testAssociatedWhereClause() { + Product product = new Product(); + Category category = new Category(); + category.name = "flowers"; + product.category = category; + product.containedCategory = new ContainedCategory(); + product.containedCategory.category = category; + product.containedCategories.add( new ContainedCategory( category ) ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertNotNull( p.category ); + assertNotNull( p.containedCategory.category ); + assertEquals( 1, p.containedCategories.size() ); + assertSame( p.category, p.containedCategory.category ); + assertSame( p.category, p.containedCategories.iterator().next().category ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, category.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, category.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + // Entity's where clause is taken into account when to-one associations + // to that entity is loaded eagerly using FetchMode.SELECT, so Category + // associations will be null. + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertNull( p.category ); + assertNull( p.containedCategory.category ); + assertEquals( 1, p.containedCategories.size() ); + assertNull( p.containedCategories.iterator().next().category ); + } + ); + } + + @Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue + private int id; + + @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "categoryId") + @Fetch(FetchMode.SELECT) + private Category category; + + private ContainedCategory containedCategory; + + @ElementCollection(fetch = FetchType.EAGER) + private Set containedCategories = new HashSet<>(); + } + + @Entity(name = "Category") + @Table(name = "CATEGORY") + @Where(clause = "inactive = 0") + public static class Category { + @Id + @GeneratedValue + private int id; + + private String name; + + private boolean inactive; + } + + @Embeddable + public static class ContainedCategory { + @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "containedCategoryId") + @Fetch(FetchMode.SELECT) + private Category category; + + public ContainedCategory() { + } + + public ContainedCategory(Category category) { + this.category = category; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml new file mode 100644 index 000000000000..7cd816215ca5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.java new file mode 100644 index 000000000000..4412613aa6c4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.java @@ -0,0 +1,189 @@ +/* + * 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.where.hbm; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * @author Gail Badner + */ +public class EagerManyToOneFetchModeJoinWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + protected String[] getMappings() { + return new String[] { "where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml" }; + } + + @Test + @TestForIssue( jiraKey = "HHH-12104" ) + @FailureExpected( jiraKey = "HHH-12104") + public void testAssociatedWhereClause() { + Product product = new Product(); + Category category = new Category(); + category.name = "flowers"; + product.category = category; + product.containedCategory = new ContainedCategory(); + product.containedCategory.category = category; + product.containedCategories.add( new ContainedCategory( category ) ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertNotNull( p.category ); + assertNotNull( p.containedCategory.category ); + assertEquals( 1, p.containedCategories.size() ); + assertSame( p.category, p.containedCategory.category ); + assertSame( p.category, p.containedCategories.iterator().next().category ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, category.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, category.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + // Entity's where clause is ignored when to-one associations to that + // association is loaded eagerly using FetchMode.JOIN, so the result + // should be the same as before the Category was made inactive. + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertNull( p.category ); + assertNull( p.containedCategory.category ); + assertEquals( 1, p.containedCategories.size() ); + assertNull( p.containedCategories.iterator().next().category ); + } + ); + } + + public static class Product { + private int id; + + private Category category; + + private ContainedCategory containedCategory; + + private Set containedCategories = new HashSet<>(); + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public ContainedCategory getContainedCategory() { + return containedCategory; + } + + public void setContainedCategory(ContainedCategory containedCategory) { + this.containedCategory = containedCategory; + } + + public Set getContainedCategories() { + return containedCategories; + } + + public void setContainedCategories(Set containedCategories) { + this.containedCategories = containedCategories; + } + } + + public static class Category { + private int id; + + private String name; + + private boolean inactive; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isInactive() { + return inactive; + } + + public void setInactive(boolean inactive) { + this.inactive = inactive; + } + } + + public static class ContainedCategory { + private Category category; + + public ContainedCategory() { + } + + public ContainedCategory(Category category) { + this.category = category; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml new file mode 100644 index 000000000000..662d16697d16 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.java new file mode 100644 index 000000000000..d2dd8a819b1c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.java @@ -0,0 +1,187 @@ +/* + * 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.where.hbm; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * @author Gail Badner + */ +public class EagerManyToOneFetchModeSelectWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + protected String[] getMappings() { + return new String[] { "where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml" }; + } + + @Test + @TestForIssue( jiraKey = "HHH-12104" ) + public void testAssociatedWhereClause() { + Product product = new Product(); + Category category = new Category(); + category.name = "flowers"; + product.category = category; + product.containedCategory = new ContainedCategory(); + product.containedCategory.category = category; + product.containedCategories.add( new ContainedCategory( category ) ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertNotNull( p.category ); + assertNotNull( p.containedCategory.category ); + assertEquals( 1, p.containedCategories.size() ); + assertSame( p.category, p.containedCategory.category ); + assertSame( p.category, p.containedCategories.iterator().next().category ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, category.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, category.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + // Entity's where clause is taken into account when to-one associations + // to that entity is loaded eagerly using FetchMode.SELECT, so Category + // associations will be null. + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertNull( p.category ); + assertNull( p.containedCategory.category ); + assertEquals( 1, p.containedCategories.size() ); + assertNull( p.containedCategories.iterator().next().category ); + } + ); + } + + public static class Product { + private int id; + + private Category category; + + private ContainedCategory containedCategory; + + private Set containedCategories = new HashSet<>(); + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public ContainedCategory getContainedCategory() { + return containedCategory; + } + + public void setContainedCategory(ContainedCategory containedCategory) { + this.containedCategory = containedCategory; + } + + public Set getContainedCategories() { + return containedCategories; + } + + public void setContainedCategories(Set containedCategories) { + this.containedCategories = containedCategories; + } + } + + public static class Category { + private int id; + + private String name; + + private boolean inactive; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isInactive() { + return inactive; + } + + public void setInactive(boolean inactive) { + this.inactive = inactive; + } + } + + public static class ContainedCategory { + private Category category; + + public ContainedCategory() { + } + + public ContainedCategory(Category category) { + this.category = category; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + } +} From b5d3826604c5cb329aed9fbffe1fe3e19c9bcebf Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 15 Oct 2018 23:13:07 -0700 Subject: [PATCH 177/772] HHH-13011 : Add option enabling/disabling use of an entity's mapped where-clause when loading collections of that entity --- .../source/internal/hbm/ModelBinder.java | 78 +++++++++++++------ .../org/hibernate/cfg/AvailableSettings.java | 16 ++++ .../cfg/annotations/CollectionBinder.java | 16 +++- 3 files changed, 85 insertions(+), 25 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index c64d98d3aec3..2c7ec3ee1f53 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -92,10 +92,12 @@ import org.hibernate.boot.spi.InFlightMetadataCollector.EntityTableXref; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.NaturalIdUniqueKeyBinder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.FkSecondPass; import org.hibernate.cfg.SecondPass; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.id.PersistentIdentifierGenerator; @@ -104,6 +106,7 @@ import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.loader.PropertyPath; import org.hibernate.mapping.Any; import org.hibernate.mapping.Array; @@ -3460,18 +3463,25 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector() .getEntityBinding( elementSource.getReferencedEntityName() ); - // For a one-to-many association, there are 2 possible sources of "where" clauses that apply - // to the associated entity table: - // 1) from the associated entity mapping; i.e., - // 2) from the collection mapping; e.g., - // Collection#setWhere is used to set the "where" clause that applies to the collection table - // (which is the associated entity table for a one-to-many association). - collectionBinding.setWhere( - StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( - referencedEntityBinding.getWhere(), - getPluralAttributeSource().getWhere() - ) - ); + + if ( useEntityWhereClauseForCollections() ) { + // For a one-to-many association, there are 2 possible sources of "where" clauses that apply + // to the associated entity table: + // 1) from the associated entity mapping; i.e., + // 2) from the collection mapping; e.g., + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the associated entity table for a one-to-many association). + collectionBinding.setWhere( + StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + getPluralAttributeSource().getWhere() + ) + ); + } + else { + // ignore entity's where clause + collectionBinding.setWhere( getPluralAttributeSource().getWhere() ); + } elementBinding.setReferencedEntityName( referencedEntityBinding.getEntityName() ); elementBinding.setAssociatedClass( referencedEntityBinding ); @@ -3531,18 +3541,26 @@ public Identifier determineImplicitName(final LocalMetadataBuildingContext conte // (which is the join table for a many-to-many association). // This "where" clause comes from the collection mapping; e.g., getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); - // For a many-to-many association, there are 2 possible sources of "where" clauses that apply - // to the associated entity table (not the join table): - // 1) from the associated entity mapping; i.e., - // 2) from the many-to-many mapping; i.e - // Collection#setManytoManyWhere is used to set the "where" clause that applies to - // to the many-to-many associated entity table (not the join table). - getCollectionBinding().setManyToManyWhere( - StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( - referencedEntityBinding.getWhere(), - elementSource.getWhere() - ) - ); + + if ( useEntityWhereClauseForCollections() ) { + // For a many-to-many association, there are 2 possible sources of "where" clauses that apply + // to the associated entity table (not the join table): + // 1) from the associated entity mapping; i.e., + // 2) from the many-to-many mapping; i.e + // Collection#setManytoManyWhere is used to set the "where" clause that applies to + // to the many-to-many associated entity table (not the join table). + getCollectionBinding().setManyToManyWhere( + StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + elementSource.getWhere() + ) + ); + } + else { + // ignore entity's where clause + getCollectionBinding().setManyToManyWhere( elementSource.getWhere() ); + } + getCollectionBinding().setManyToManyOrdering( elementSource.getOrder() ); if ( !CollectionHelper.isEmpty( elementSource.getFilterSources() ) @@ -3624,6 +3642,18 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu } } + private boolean useEntityWhereClauseForCollections() { + return ConfigurationHelper.getBoolean( + AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + metadataBuildingContext + .getBuildingOptions() + .getServiceRegistry() + .getService( ConfigurationService.class ) + .getSettings(), + true + ); + } + private class PluralAttributeListSecondPass extends AbstractPluralAttributeSecondPass { public PluralAttributeListSecondPass( MappingDocument sourceDocument, diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 4d2712bccfd5..e8634f160f36 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -1480,6 +1480,22 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String CUSTOM_ENTITY_DIRTINESS_STRATEGY = "hibernate.entity_dirtiness_strategy"; + /** + * Controls whether an entity's "where" clause, mapped using @Where(clause="....") + * or <entity ... where="...">, is taken into account when loading one-to-many + * or many-to-many collections of that type of entity. + *

    + * This setting has no affect on collections of embeddable values containing an association to + * that type of entity. + *

    + * When `true` (the default), the entity's "where" clause will be taken into account when loading + * one-to-many or many-to-many collections of that type of entity. + *

    + * `false` indicates that the entity's "where" clause will be ignored when loading one-to-many or + * many-to-many collections of that type of entity. + */ + String USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = "hibernate.use_entity_where_clause_for_collections"; + /** * Strategy for multi-tenancy. diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index dbeeea5ba441..554c13cfc068 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -70,6 +70,7 @@ import org.hibernate.cfg.AccessType; import org.hibernate.cfg.AnnotatedClassType; import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.BinderHelper; import org.hibernate.cfg.CollectionPropertyHolder; import org.hibernate.cfg.CollectionSecondPass; @@ -84,9 +85,11 @@ import org.hibernate.cfg.PropertyPreloadedData; import org.hibernate.cfg.SecondPass; import org.hibernate.criterion.Junction; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.mapping.Any; import org.hibernate.mapping.Backref; import org.hibernate.mapping.Collection; @@ -953,13 +956,24 @@ private void bindFilters(boolean hasAssociationTable) { } } + final boolean useEntityWhereClauseForCollections = ConfigurationHelper.getBoolean( + AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + buildingContext + .getBuildingOptions() + .getServiceRegistry() + .getService( ConfigurationService.class ) + .getSettings(), + true + ); + // There are 2 possible sources of "where" clauses that apply to the associated entity table: // 1) from the associated entity mapping; i.e., @Entity @Where(clause="...") + // (ignored if useEntityWhereClauseForCollections == false) // 2) from the collection mapping; // for one-to-many, e.g., @OneToMany @JoinColumn @Where(clause="...") public Set getRatings(); // for many-to-many e.g., @ManyToMany @Where(clause="...") public Set getRatings(); String whereOnClassClause = null; - if ( property.getElementClass() != null ) { + if ( useEntityWhereClauseForCollections && property.getElementClass() != null ) { Where whereOnClass = property.getElementClass().getAnnotation( Where.class ); if ( whereOnClass != null ) { whereOnClassClause = whereOnClass.clause(); From 55e430dd431edef59e436447a49367f8483aa4e5 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 16 Oct 2018 03:51:34 -0700 Subject: [PATCH 178/772] HHH-13011 : test cases --- ...EagerToManyWhereDontUseClassWhereTest.java | 213 ++++++++++++++++++ .../annotations/EagerToManyWhereTest.java | 208 +++++++++++++++++ .../EagerToManyWhereUseClassWhereTest.java | 213 ++++++++++++++++++ .../LazyToManyWhereDontUseClassWhereTest.java | 213 ++++++++++++++++++ .../annotations/LazyToManyWhereTest.java | 207 +++++++++++++++++ .../LazyToManyWhereUseClassWhereTest.java | 213 ++++++++++++++++++ .../hibernate/test/where/hbm/Category.java | 49 ++++ .../test/where/hbm/EagerToManyWhere.hbm.xml | 53 +++++ ...EagerToManyWhereDontUseClassWhereTest.java | 157 +++++++++++++ .../test/where/hbm/EagerToManyWhereTest.java | 150 ++++++++++++ .../EagerToManyWhereUseClassWhereTest.java | 157 +++++++++++++ .../test/where/hbm/LazyToManyWhere.hbm.xml | 53 +++++ .../LazyToManyWhereDontUseClassWhereTest.java | 157 +++++++++++++ .../test/where/hbm/LazyToManyWhereTest.java | 150 ++++++++++++ .../hbm/LazyToManyWhereUseClassWhereTest.java | 157 +++++++++++++ .../org/hibernate/test/where/hbm/Product.java | 72 ++++++ 16 files changed, 2422 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/Category.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereDontUseClassWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereUseClassWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereDontUseClassWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereUseClassWhereTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/where/hbm/Product.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java new file mode 100644 index 000000000000..060f3758f3cd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java @@ -0,0 +1,213 @@ +/* + * 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.where.annotations; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.Where; +import org.hibernate.annotations.WhereJoinTable; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = false + * + * @author Gail Badner + */ +public class EagerToManyWhereDontUseClassWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Product.class, Category.class }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "false" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.id = 1; + flowers.name = "flowers"; + flowers.description = "FLOWERS"; + product.categoriesOneToMany.add( flowers ); + product.categoriesWithDescOneToMany.add( flowers ); + product.categoriesManyToMany.add( flowers ); + product.categoriesWithDescManyToMany.add( flowers ); + product.categoriesWithDescIdLt4ManyToMany.add( flowers ); + Category vegetables = new Category(); + vegetables.id = 2; + vegetables.name = "vegetables"; + vegetables.description = "VEGETABLES"; + product.categoriesOneToMany.add( vegetables ); + product.categoriesWithDescOneToMany.add( vegetables ); + product.categoriesManyToMany.add( vegetables ); + product.categoriesWithDescManyToMany.add( vegetables ); + product.categoriesWithDescIdLt4ManyToMany.add( vegetables ); + Category dogs = new Category(); + dogs.id = 3; + dogs.name = "dogs"; + dogs.description = null; + product.categoriesOneToMany.add( dogs ); + product.categoriesWithDescOneToMany.add( dogs ); + product.categoriesManyToMany.add( dogs ); + product.categoriesWithDescManyToMany.add( dogs ); + product.categoriesWithDescIdLt4ManyToMany.add( dogs ); + Category building = new Category(); + building.id = 4; + building.name = "building"; + building.description = "BUILDING"; + product.categoriesOneToMany.add( building ); + product.categoriesWithDescOneToMany.add( building ); + product.categoriesManyToMany.add( building ); + product.categoriesWithDescManyToMany.add( building ); + product.categoriesWithDescIdLt4ManyToMany.add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 4, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 4, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 1, 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.id ); + } + assertTrue( expectedIdSet.isEmpty() ); + } + + @Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue + private int id; + + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn + private Set categoriesOneToMany = new HashSet<>(); + + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn + @Where( clause = "description is not null" ) + private Set categoriesWithDescOneToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "categoriesManyToMany") + private Set categoriesManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "categoriesWithDescManyToMany") + @Where( clause = "description is not null" ) + private Set categoriesWithDescManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "categoriesWithDescIdLt4MToM", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) + @Where( clause = "description is not null" ) + @WhereJoinTable( clause = "categoryId < 4") + private Set categoriesWithDescIdLt4ManyToMany = new HashSet<>(); + } + + @Entity(name = "Category") + @Table(name = "CATEGORY") + @Where(clause = "inactive = 0") + public static class Category { + @Id + private int id; + + private String name; + + private String description; + + private boolean inactive; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java new file mode 100644 index 000000000000..fc4074952f70 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java @@ -0,0 +1,208 @@ +/* + * 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.where.annotations; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.Where; +import org.hibernate.annotations.WhereJoinTable; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with default AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + * which is true. + * + * @author Gail Badner + */ +public class EagerToManyWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Product.class, Category.class }; + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.id = 1; + flowers.name = "flowers"; + flowers.description = "FLOWERS"; + product.categoriesOneToMany.add( flowers ); + product.categoriesWithDescOneToMany.add( flowers ); + product.categoriesManyToMany.add( flowers ); + product.categoriesWithDescManyToMany.add( flowers ); + product.categoriesWithDescIdLt4ManyToMany.add( flowers ); + Category vegetables = new Category(); + vegetables.id = 2; + vegetables.name = "vegetables"; + vegetables.description = "VEGETABLES"; + product.categoriesOneToMany.add( vegetables ); + product.categoriesWithDescOneToMany.add( vegetables ); + product.categoriesManyToMany.add( vegetables ); + product.categoriesWithDescManyToMany.add( vegetables ); + product.categoriesWithDescIdLt4ManyToMany.add( vegetables ); + Category dogs = new Category(); + dogs.id = 3; + dogs.name = "dogs"; + dogs.description = null; + product.categoriesOneToMany.add( dogs ); + product.categoriesWithDescOneToMany.add( dogs ); + product.categoriesManyToMany.add( dogs ); + product.categoriesWithDescManyToMany.add( dogs ); + product.categoriesWithDescIdLt4ManyToMany.add( dogs ); + Category building = new Category(); + building.id = 4; + building.name = "building"; + building.description = "BUILDING"; + product.categoriesOneToMany.add( building ); + product.categoriesWithDescOneToMany.add( building ); + product.categoriesManyToMany.add( building ); + product.categoriesWithDescManyToMany.add( building ); + product.categoriesWithDescIdLt4ManyToMany.add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 4, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 3, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 2, 4 } ); + assertEquals( 3, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 2, 4 } ); + assertEquals( 1, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.id ); + } + assertTrue( expectedIdSet.isEmpty() ); + } + + @Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue + private int id; + + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn + private Set categoriesOneToMany = new HashSet<>(); + + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn + @Where( clause = "description is not null" ) + private Set categoriesWithDescOneToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "categoriesManyToMany") + private Set categoriesManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "categoriesWithDescManyToMany") + @Where( clause = "description is not null" ) + private Set categoriesWithDescManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "categoriesWithDescIdLt4MToM", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) + @Where( clause = "description is not null" ) + @WhereJoinTable( clause = "categoryId < 4") + private Set categoriesWithDescIdLt4ManyToMany = new HashSet<>(); + } + + @Entity(name = "Category") + @Table(name = "CATEGORY") + @Where(clause = "inactive = 0") + public static class Category { + @Id + private int id; + + private String name; + + private String description; + + private boolean inactive; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java new file mode 100644 index 000000000000..c584d1a29a22 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java @@ -0,0 +1,213 @@ +/* + * 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.where.annotations; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.Where; +import org.hibernate.annotations.WhereJoinTable; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = true + * + * @author Gail Badner + */ +public class EagerToManyWhereUseClassWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Product.class, Category.class }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "true" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.id = 1; + flowers.name = "flowers"; + flowers.description = "FLOWERS"; + product.categoriesOneToMany.add( flowers ); + product.categoriesWithDescOneToMany.add( flowers ); + product.categoriesManyToMany.add( flowers ); + product.categoriesWithDescManyToMany.add( flowers ); + product.categoriesWithDescIdLt4ManyToMany.add( flowers ); + Category vegetables = new Category(); + vegetables.id = 2; + vegetables.name = "vegetables"; + vegetables.description = "VEGETABLES"; + product.categoriesOneToMany.add( vegetables ); + product.categoriesWithDescOneToMany.add( vegetables ); + product.categoriesManyToMany.add( vegetables ); + product.categoriesWithDescManyToMany.add( vegetables ); + product.categoriesWithDescIdLt4ManyToMany.add( vegetables ); + Category dogs = new Category(); + dogs.id = 3; + dogs.name = "dogs"; + dogs.description = null; + product.categoriesOneToMany.add( dogs ); + product.categoriesWithDescOneToMany.add( dogs ); + product.categoriesManyToMany.add( dogs ); + product.categoriesWithDescManyToMany.add( dogs ); + product.categoriesWithDescIdLt4ManyToMany.add( dogs ); + Category building = new Category(); + building.id = 4; + building.name = "building"; + building.description = "BUILDING"; + product.categoriesOneToMany.add( building ); + product.categoriesWithDescOneToMany.add( building ); + product.categoriesManyToMany.add( building ); + product.categoriesWithDescManyToMany.add( building ); + product.categoriesWithDescIdLt4ManyToMany.add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 4, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 3, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 2, 4 } ); + assertEquals( 3, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 2, 4 } ); + assertEquals( 1, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.id ); + } + assertTrue( expectedIdSet.isEmpty() ); + } + + @Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue + private int id; + + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn + private Set categoriesOneToMany = new HashSet<>(); + + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn + @Where( clause = "description is not null" ) + private Set categoriesWithDescOneToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "categoriesManyToMany") + private Set categoriesManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "categoriesWithDescManyToMany") + @Where( clause = "description is not null" ) + private Set categoriesWithDescManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "categoriesWithDescIdLt4MToM", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) + @Where( clause = "description is not null" ) + @WhereJoinTable( clause = "categoryId < 4") + private Set categoriesWithDescIdLt4ManyToMany = new HashSet<>(); + } + + @Entity(name = "Category") + @Table(name = "CATEGORY") + @Where(clause = "inactive = 0") + public static class Category { + @Id + private int id; + + private String name; + + private String description; + + private boolean inactive; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java new file mode 100644 index 000000000000..e7e8eb4584c4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java @@ -0,0 +1,213 @@ +/* + * 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.where.annotations; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.Where; +import org.hibernate.annotations.WhereJoinTable; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = false + * + * @author Gail Badner + */ +public class LazyToManyWhereDontUseClassWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Product.class, Category.class }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "false" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.id = 1; + flowers.name = "flowers"; + flowers.description = "FLOWERS"; + product.categoriesOneToMany.add( flowers ); + product.categoriesWithDescOneToMany.add( flowers ); + product.categoriesManyToMany.add( flowers ); + product.categoriesWithDescManyToMany.add( flowers ); + product.categoriesWithDescIdLt4ManyToMany.add( flowers ); + Category vegetables = new Category(); + vegetables.id = 2; + vegetables.name = "vegetables"; + vegetables.description = "VEGETABLES"; + product.categoriesOneToMany.add( vegetables ); + product.categoriesWithDescOneToMany.add( vegetables ); + product.categoriesManyToMany.add( vegetables ); + product.categoriesWithDescManyToMany.add( vegetables ); + product.categoriesWithDescIdLt4ManyToMany.add( vegetables ); + Category dogs = new Category(); + dogs.id = 3; + dogs.name = "dogs"; + dogs.description = null; + product.categoriesOneToMany.add( dogs ); + product.categoriesWithDescOneToMany.add( dogs ); + product.categoriesManyToMany.add( dogs ); + product.categoriesWithDescManyToMany.add( dogs ); + product.categoriesWithDescIdLt4ManyToMany.add( dogs ); + Category building = new Category(); + building.id = 4; + building.name = "building"; + building.description = "BUILDING"; + product.categoriesOneToMany.add( building ); + product.categoriesWithDescOneToMany.add( building ); + product.categoriesManyToMany.add( building ); + product.categoriesWithDescManyToMany.add( building ); + product.categoriesWithDescIdLt4ManyToMany.add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 4, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 4, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 1, 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.id ); + } + assertTrue( expectedIdSet.isEmpty() ); + } + + @Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue + private int id; + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn + private Set categoriesOneToMany = new HashSet<>(); + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn + @Where( clause = "description is not null" ) + private Set categoriesWithDescOneToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "categoriesManyToMany") + private Set categoriesManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "categoriesWithDescManyToMany") + @Where( clause = "description is not null" ) + private Set categoriesWithDescManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "categoriesWithDescIdLt4MToM", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) + @Where( clause = "description is not null" ) + @WhereJoinTable( clause = "categoryId < 4") + private Set categoriesWithDescIdLt4ManyToMany = new HashSet<>(); + } + + @Entity(name = "Category") + @Table(name = "CATEGORY") + @Where(clause = "inactive = 0") + public static class Category { + @Id + private int id; + + private String name; + + private String description; + + private boolean inactive; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java new file mode 100644 index 000000000000..f6c08f064930 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java @@ -0,0 +1,207 @@ +/* + * 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.where.annotations; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.Where; +import org.hibernate.annotations.WhereJoinTable; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with default AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + * which is true. + * + * @author Gail Badner + */ +public class LazyToManyWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Product.class, Category.class }; + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.id = 1; + flowers.name = "flowers"; + flowers.description = "FLOWERS"; + product.categoriesOneToMany.add( flowers ); + product.categoriesWithDescOneToMany.add( flowers ); + product.categoriesManyToMany.add( flowers ); + product.categoriesWithDescManyToMany.add( flowers ); + product.categoriesWithDescIdLt4ManyToMany.add( flowers ); + Category vegetables = new Category(); + vegetables.id = 2; + vegetables.name = "vegetables"; + vegetables.description = "VEGETABLES"; + product.categoriesOneToMany.add( vegetables ); + product.categoriesWithDescOneToMany.add( vegetables ); + product.categoriesManyToMany.add( vegetables ); + product.categoriesWithDescManyToMany.add( vegetables ); + product.categoriesWithDescIdLt4ManyToMany.add( vegetables ); + Category dogs = new Category(); + dogs.id = 3; + dogs.name = "dogs"; + dogs.description = null; + product.categoriesOneToMany.add( dogs ); + product.categoriesWithDescOneToMany.add( dogs ); + product.categoriesManyToMany.add( dogs ); + product.categoriesWithDescManyToMany.add( dogs ); + product.categoriesWithDescIdLt4ManyToMany.add( dogs ); + Category building = new Category(); + building.id = 4; + building.name = "building"; + building.description = "BUILDING"; + product.categoriesOneToMany.add( building ); + product.categoriesWithDescOneToMany.add( building ); + product.categoriesManyToMany.add( building ); + product.categoriesWithDescManyToMany.add( building ); + product.categoriesWithDescIdLt4ManyToMany.add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 4, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 3, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 2, 4 } ); + assertEquals( 3, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 2, 4 } ); + assertEquals( 1, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.id ); + } + assertTrue( expectedIdSet.isEmpty() ); + } + + @Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue + private int id; + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn + private Set categoriesOneToMany = new HashSet<>(); + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn + @Where( clause = "description is not null" ) + private Set categoriesWithDescOneToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "categoriesManyToMany") + private Set categoriesManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "categoriesWithDescManyToMany") + @Where( clause = "description is not null" ) + private Set categoriesWithDescManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "categoriesWithDescIdLt4MToM", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) + @Where( clause = "description is not null" ) + @WhereJoinTable( clause = "categoryId < 4") + private Set categoriesWithDescIdLt4ManyToMany = new HashSet<>(); + } + + @Entity(name = "Category") + @Table(name = "CATEGORY") + @Where(clause = "inactive = 0") + public static class Category { + @Id + private int id; + + private String name; + + private String description; + + private boolean inactive; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java new file mode 100644 index 000000000000..d87036901faa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java @@ -0,0 +1,213 @@ +/* + * 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.where.annotations; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.Where; +import org.hibernate.annotations.WhereJoinTable; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = true + * + * @author Gail Badner + */ +public class LazyToManyWhereUseClassWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Product.class, Category.class }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "true" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.id = 1; + flowers.name = "flowers"; + flowers.description = "FLOWERS"; + product.categoriesOneToMany.add( flowers ); + product.categoriesWithDescOneToMany.add( flowers ); + product.categoriesManyToMany.add( flowers ); + product.categoriesWithDescManyToMany.add( flowers ); + product.categoriesWithDescIdLt4ManyToMany.add( flowers ); + Category vegetables = new Category(); + vegetables.id = 2; + vegetables.name = "vegetables"; + vegetables.description = "VEGETABLES"; + product.categoriesOneToMany.add( vegetables ); + product.categoriesWithDescOneToMany.add( vegetables ); + product.categoriesManyToMany.add( vegetables ); + product.categoriesWithDescManyToMany.add( vegetables ); + product.categoriesWithDescIdLt4ManyToMany.add( vegetables ); + Category dogs = new Category(); + dogs.id = 3; + dogs.name = "dogs"; + dogs.description = null; + product.categoriesOneToMany.add( dogs ); + product.categoriesWithDescOneToMany.add( dogs ); + product.categoriesManyToMany.add( dogs ); + product.categoriesWithDescManyToMany.add( dogs ); + product.categoriesWithDescIdLt4ManyToMany.add( dogs ); + Category building = new Category(); + building.id = 4; + building.name = "building"; + building.description = "BUILDING"; + product.categoriesOneToMany.add( building ); + product.categoriesWithDescOneToMany.add( building ); + product.categoriesManyToMany.add( building ); + product.categoriesWithDescManyToMany.add( building ); + product.categoriesWithDescIdLt4ManyToMany.add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 4, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNotNull( c ); + c.inactive = true; + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.id ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.id ); + assertNotNull( p ); + assertEquals( 3, p.categoriesOneToMany.size() ); + checkIds( p.categoriesOneToMany, new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.categoriesWithDescOneToMany.size() ); + checkIds( p.categoriesWithDescOneToMany, new Integer[] { 2, 4 } ); + assertEquals( 3, p.categoriesManyToMany.size() ); + checkIds( p.categoriesManyToMany, new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.categoriesWithDescManyToMany.size() ); + checkIds( p.categoriesWithDescManyToMany, new Integer[] { 2, 4 } ); + assertEquals( 1, p.categoriesWithDescIdLt4ManyToMany.size() ); + checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.id ); + } + assertTrue( expectedIdSet.isEmpty() ); + } + + @Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue + private int id; + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn + private Set categoriesOneToMany = new HashSet<>(); + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn + @Where( clause = "description is not null" ) + private Set categoriesWithDescOneToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "categoriesManyToMany") + private Set categoriesManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "categoriesWithDescManyToMany") + @Where( clause = "description is not null" ) + private Set categoriesWithDescManyToMany = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "categoriesWithDescIdLt4MToM", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) + @Where( clause = "description is not null" ) + @WhereJoinTable( clause = "categoryId < 4") + private Set categoriesWithDescIdLt4ManyToMany = new HashSet<>(); + } + + @Entity(name = "Category") + @Table(name = "CATEGORY") + @Where(clause = "inactive = 0") + public static class Category { + @Id + private int id; + + private String name; + + private String description; + + private boolean inactive; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Category.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Category.java new file mode 100644 index 000000000000..f35ce91ba3e1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Category.java @@ -0,0 +1,49 @@ +/* + * 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.where.hbm; + +public class Category { + private int id; + + private String name; + + private String description; + + private boolean inactive; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isInactive() { + return inactive; + } + + public void setInactive(boolean inactive) { + this.inactive = inactive; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml new file mode 100644 index 000000000000..2e2455d07868 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereDontUseClassWhereTest.java new file mode 100644 index 000000000000..f93c5334165c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereDontUseClassWhereTest.java @@ -0,0 +1,157 @@ +/* + * 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.where.hbm; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = false + * + * @author Gail Badner + */ +public class EagerToManyWhereDontUseClassWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "where/hbm/EagerToManyWhere.hbm.xml" }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "false" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.setId( 1 ); + flowers.setName( "flowers" ); + flowers.setDescription( "FLOWERS" ); + product.getCategoriesOneToMany().add( flowers ); + product.getCategoriesWithDescOneToMany().add( flowers ); + product.getCategoriesManyToMany().add( flowers ); + product.getCategoriesWithDescManyToMany().add( flowers ); + product.getCategoriesWithDescIdLt4ManyToMany().add( flowers ); + Category vegetables = new Category(); + vegetables.setId( 2 ); + vegetables.setName( "vegetables" ); + vegetables.setDescription( "VEGETABLES" ); + product.getCategoriesOneToMany().add( vegetables ); + product.getCategoriesWithDescOneToMany().add( vegetables ); + product.getCategoriesManyToMany().add( vegetables ); + product.getCategoriesWithDescManyToMany().add( vegetables ); + product.getCategoriesWithDescIdLt4ManyToMany().add( vegetables ); + Category dogs = new Category(); + dogs.setId( 3 ); + dogs.setName( "dogs" ); + dogs.setDescription( null ); + product.getCategoriesOneToMany().add( dogs ); + product.getCategoriesWithDescOneToMany().add( dogs ); + product.getCategoriesManyToMany().add( dogs ); + product.getCategoriesWithDescManyToMany().add( dogs ); + product.getCategoriesWithDescIdLt4ManyToMany().add( dogs ); + Category building = new Category(); + building.setId( 4 ); + building.setName( "building" ); + building.setDescription( "BUILDING" ); + product.getCategoriesOneToMany().add( building ); + product.getCategoriesWithDescOneToMany().add( building ); + product.getCategoriesManyToMany().add( building ); + product.getCategoriesWithDescManyToMany().add( building ); + product.getCategoriesWithDescIdLt4ManyToMany().add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 4, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNotNull( c ); + c.setInactive( true ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 4, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 1, 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.getId() ); + } + assertTrue( expectedIdSet.isEmpty() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereTest.java new file mode 100644 index 000000000000..053320234650 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereTest.java @@ -0,0 +1,150 @@ +/* + * 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.where.hbm; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with default AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + * which is true. + * + * @author Gail Badner + */ +public class EagerToManyWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "where/hbm/EagerToManyWhere.hbm.xml" }; + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.setId( 1 ); + flowers.setName( "flowers" ); + flowers.setDescription( "FLOWERS" ); + product.getCategoriesOneToMany().add( flowers ); + product.getCategoriesWithDescOneToMany().add( flowers ); + product.getCategoriesManyToMany().add( flowers ); + product.getCategoriesWithDescManyToMany().add( flowers ); + product.getCategoriesWithDescIdLt4ManyToMany().add( flowers ); + Category vegetables = new Category(); + vegetables.setId( 2 ); + vegetables.setName( "vegetables" ); + vegetables.setDescription( "VEGETABLES" ); + product.getCategoriesOneToMany().add( vegetables ); + product.getCategoriesWithDescOneToMany().add( vegetables ); + product.getCategoriesManyToMany().add( vegetables ); + product.getCategoriesWithDescManyToMany().add( vegetables ); + product.getCategoriesWithDescIdLt4ManyToMany().add( vegetables ); + Category dogs = new Category(); + dogs.setId( 3 ); + dogs.setName( "dogs" ); + dogs.setDescription( null ); + product.getCategoriesOneToMany().add( dogs ); + product.getCategoriesWithDescOneToMany().add( dogs ); + product.getCategoriesManyToMany().add( dogs ); + product.getCategoriesWithDescManyToMany().add( dogs ); + product.getCategoriesWithDescIdLt4ManyToMany().add( dogs ); + Category building = new Category(); + building.setId( 4 ); + building.setName( "building" ); + building.setDescription( "BUILDING" ); + product.getCategoriesOneToMany().add( building ); + product.getCategoriesWithDescOneToMany().add( building ); + product.getCategoriesManyToMany().add( building ); + product.getCategoriesWithDescManyToMany().add( building ); + product.getCategoriesWithDescIdLt4ManyToMany().add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 4, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNotNull( c ); + c.setInactive( true ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 3, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 2, 4 } ); + assertEquals( 3, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 2, 4 } ); + assertEquals( 1, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.getId() ); + } + assertTrue( expectedIdSet.isEmpty() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereUseClassWhereTest.java new file mode 100644 index 000000000000..cabcb22ba217 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereUseClassWhereTest.java @@ -0,0 +1,157 @@ +/* + * 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.where.hbm; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = true + * + * @author Gail Badner + */ +public class EagerToManyWhereUseClassWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "where/hbm/EagerToManyWhere.hbm.xml" }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "true" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.setId( 1 ); + flowers.setName( "flowers" ); + flowers.setDescription( "FLOWERS" ); + product.getCategoriesOneToMany().add( flowers ); + product.getCategoriesWithDescOneToMany().add( flowers ); + product.getCategoriesManyToMany().add( flowers ); + product.getCategoriesWithDescManyToMany().add( flowers ); + product.getCategoriesWithDescIdLt4ManyToMany().add( flowers ); + Category vegetables = new Category(); + vegetables.setId( 2 ); + vegetables.setName( "vegetables" ); + vegetables.setDescription( "VEGETABLES" ); + product.getCategoriesOneToMany().add( vegetables ); + product.getCategoriesWithDescOneToMany().add( vegetables ); + product.getCategoriesManyToMany().add( vegetables ); + product.getCategoriesWithDescManyToMany().add( vegetables ); + product.getCategoriesWithDescIdLt4ManyToMany().add( vegetables ); + Category dogs = new Category(); + dogs.setId( 3 ); + dogs.setName( "dogs" ); + dogs.setDescription( null ); + product.getCategoriesOneToMany().add( dogs ); + product.getCategoriesWithDescOneToMany().add( dogs ); + product.getCategoriesManyToMany().add( dogs ); + product.getCategoriesWithDescManyToMany().add( dogs ); + product.getCategoriesWithDescIdLt4ManyToMany().add( dogs ); + Category building = new Category(); + building.setId( 4 ); + building.setName( "building" ); + building.setDescription( "BUILDING" ); + product.getCategoriesOneToMany().add( building ); + product.getCategoriesWithDescOneToMany().add( building ); + product.getCategoriesManyToMany().add( building ); + product.getCategoriesWithDescManyToMany().add( building ); + product.getCategoriesWithDescIdLt4ManyToMany().add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 4, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNotNull( c ); + c.setInactive( true ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 3, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 2, 4 } ); + assertEquals( 3, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 2, 4 } ); + assertEquals( 1, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.getId() ); + } + assertTrue( expectedIdSet.isEmpty() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml new file mode 100644 index 000000000000..a76c02c34cd1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereDontUseClassWhereTest.java new file mode 100644 index 000000000000..86ca4ff631bd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereDontUseClassWhereTest.java @@ -0,0 +1,157 @@ +/* + * 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.where.hbm; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = false + * + * @author Gail Badner + */ +public class LazyToManyWhereDontUseClassWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "where/hbm/LazyToManyWhere.hbm.xml" }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "false" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.setId( 1 ); + flowers.setName( "flowers" ); + flowers.setDescription( "FLOWERS" ); + product.getCategoriesOneToMany().add( flowers ); + product.getCategoriesWithDescOneToMany().add( flowers ); + product.getCategoriesManyToMany().add( flowers ); + product.getCategoriesWithDescManyToMany().add( flowers ); + product.getCategoriesWithDescIdLt4ManyToMany().add( flowers ); + Category vegetables = new Category(); + vegetables.setId( 2 ); + vegetables.setName( "vegetables" ); + vegetables.setDescription( "VEGETABLES" ); + product.getCategoriesOneToMany().add( vegetables ); + product.getCategoriesWithDescOneToMany().add( vegetables ); + product.getCategoriesManyToMany().add( vegetables ); + product.getCategoriesWithDescManyToMany().add( vegetables ); + product.getCategoriesWithDescIdLt4ManyToMany().add( vegetables ); + Category dogs = new Category(); + dogs.setId( 3 ); + dogs.setName( "dogs" ); + dogs.setDescription( null ); + product.getCategoriesOneToMany().add( dogs ); + product.getCategoriesWithDescOneToMany().add( dogs ); + product.getCategoriesManyToMany().add( dogs ); + product.getCategoriesWithDescManyToMany().add( dogs ); + product.getCategoriesWithDescIdLt4ManyToMany().add( dogs ); + Category building = new Category(); + building.setId( 4 ); + building.setName( "building" ); + building.setDescription( "BUILDING" ); + product.getCategoriesOneToMany().add( building ); + product.getCategoriesWithDescOneToMany().add( building ); + product.getCategoriesManyToMany().add( building ); + product.getCategoriesWithDescManyToMany().add( building ); + product.getCategoriesWithDescIdLt4ManyToMany().add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 4, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNotNull( c ); + c.setInactive( true ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 4, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 1, 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.getId() ); + } + assertTrue( expectedIdSet.isEmpty() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereTest.java new file mode 100644 index 000000000000..2b25200f1f12 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereTest.java @@ -0,0 +1,150 @@ +/* + * 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.where.hbm; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with default AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + * which is true. + * + * @author Gail Badner + */ +public class LazyToManyWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "where/hbm/LazyToManyWhere.hbm.xml" }; + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.setId( 1 ); + flowers.setName( "flowers" ); + flowers.setDescription( "FLOWERS" ); + product.getCategoriesOneToMany().add( flowers ); + product.getCategoriesWithDescOneToMany().add( flowers ); + product.getCategoriesManyToMany().add( flowers ); + product.getCategoriesWithDescManyToMany().add( flowers ); + product.getCategoriesWithDescIdLt4ManyToMany().add( flowers ); + Category vegetables = new Category(); + vegetables.setId( 2 ); + vegetables.setName( "vegetables" ); + vegetables.setDescription( "VEGETABLES" ); + product.getCategoriesOneToMany().add( vegetables ); + product.getCategoriesWithDescOneToMany().add( vegetables ); + product.getCategoriesManyToMany().add( vegetables ); + product.getCategoriesWithDescManyToMany().add( vegetables ); + product.getCategoriesWithDescIdLt4ManyToMany().add( vegetables ); + Category dogs = new Category(); + dogs.setId( 3 ); + dogs.setName( "dogs" ); + dogs.setDescription( null ); + product.getCategoriesOneToMany().add( dogs ); + product.getCategoriesWithDescOneToMany().add( dogs ); + product.getCategoriesManyToMany().add( dogs ); + product.getCategoriesWithDescManyToMany().add( dogs ); + product.getCategoriesWithDescIdLt4ManyToMany().add( dogs ); + Category building = new Category(); + building.setId( 4 ); + building.setName( "building" ); + building.setDescription( "BUILDING" ); + product.getCategoriesOneToMany().add( building ); + product.getCategoriesWithDescOneToMany().add( building ); + product.getCategoriesManyToMany().add( building ); + product.getCategoriesWithDescManyToMany().add( building ); + product.getCategoriesWithDescIdLt4ManyToMany().add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 4, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNotNull( c ); + c.setInactive( true ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 3, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 2, 4 } ); + assertEquals( 3, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 2, 4 } ); + assertEquals( 1, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.getId() ); + } + assertTrue( expectedIdSet.isEmpty() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereUseClassWhereTest.java new file mode 100644 index 000000000000..3984200cbf90 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereUseClassWhereTest.java @@ -0,0 +1,157 @@ +/* + * 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.where.hbm; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests association collections with AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = true + * + * @author Gail Badner + */ +public class LazyToManyWhereUseClassWhereTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "where/hbm/LazyToManyWhere.hbm.xml" }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "true" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13011" ) + public void testAssociatedWhereClause() { + + Product product = new Product(); + Category flowers = new Category(); + flowers.setId( 1 ); + flowers.setName( "flowers" ); + flowers.setDescription( "FLOWERS" ); + product.getCategoriesOneToMany().add( flowers ); + product.getCategoriesWithDescOneToMany().add( flowers ); + product.getCategoriesManyToMany().add( flowers ); + product.getCategoriesWithDescManyToMany().add( flowers ); + product.getCategoriesWithDescIdLt4ManyToMany().add( flowers ); + Category vegetables = new Category(); + vegetables.setId( 2 ); + vegetables.setName( "vegetables" ); + vegetables.setDescription( "VEGETABLES" ); + product.getCategoriesOneToMany().add( vegetables ); + product.getCategoriesWithDescOneToMany().add( vegetables ); + product.getCategoriesManyToMany().add( vegetables ); + product.getCategoriesWithDescManyToMany().add( vegetables ); + product.getCategoriesWithDescIdLt4ManyToMany().add( vegetables ); + Category dogs = new Category(); + dogs.setId( 3 ); + dogs.setName( "dogs" ); + dogs.setDescription( null ); + product.getCategoriesOneToMany().add( dogs ); + product.getCategoriesWithDescOneToMany().add( dogs ); + product.getCategoriesManyToMany().add( dogs ); + product.getCategoriesWithDescManyToMany().add( dogs ); + product.getCategoriesWithDescIdLt4ManyToMany().add( dogs ); + Category building = new Category(); + building.setId( 4 ); + building.setName( "building" ); + building.setDescription( "BUILDING" ); + product.getCategoriesOneToMany().add( building ); + product.getCategoriesWithDescOneToMany().add( building ); + product.getCategoriesManyToMany().add( building ); + product.getCategoriesWithDescManyToMany().add( building ); + product.getCategoriesWithDescIdLt4ManyToMany().add( building ); + + doInHibernate( + this::sessionFactory, + session -> { + session.persist( flowers ); + session.persist( vegetables ); + session.persist( dogs ); + session.persist( building ); + session.persist( product ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 4, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 4, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 1, 2, 3, 4 } ); + assertEquals( 3, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 1, 2, 4 } ); + assertEquals( 2, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 1, 2 } ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNotNull( c ); + c.setInactive( true ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Category c = session.get( Category.class, flowers.getId() ); + assertNull( c ); + } + ); + + doInHibernate( + this::sessionFactory, + session -> { + Product p = session.get( Product.class, product.getId() ); + assertNotNull( p ); + assertEquals( 3, p.getCategoriesOneToMany().size() ); + checkIds( p.getCategoriesOneToMany(), new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.getCategoriesWithDescOneToMany().size() ); + checkIds( p.getCategoriesWithDescOneToMany(), new Integer[] { 2, 4 } ); + assertEquals( 3, p.getCategoriesManyToMany().size() ); + checkIds( p.getCategoriesManyToMany(), new Integer[] { 2, 3, 4 } ); + assertEquals( 2, p.getCategoriesWithDescManyToMany().size() ); + checkIds( p.getCategoriesWithDescManyToMany(), new Integer[] { 2, 4 } ); + assertEquals( 1, p.getCategoriesWithDescIdLt4ManyToMany().size() ); + checkIds( p.getCategoriesWithDescIdLt4ManyToMany(), new Integer[] { 2 } ); + } + ); + } + + private void checkIds(Set categories, Integer[] expectedIds) { + final Set expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) ); + for ( Category category : categories ) { + expectedIdSet.remove( category.getId() ); + } + assertTrue( expectedIdSet.isEmpty() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Product.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Product.java new file mode 100644 index 000000000000..dc87c568e849 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Product.java @@ -0,0 +1,72 @@ +/* + * 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.where.hbm; + +import java.util.HashSet; +import java.util.Set; + +public class Product { + private int id; + + private Set categoriesOneToMany = new HashSet<>(); + + private Set categoriesWithDescOneToMany = new HashSet<>(); + + private Set categoriesManyToMany = new HashSet<>(); + + private Set categoriesWithDescManyToMany = new HashSet<>(); + + private Set categoriesWithDescIdLt4ManyToMany = new HashSet<>(); + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Set getCategoriesOneToMany() { + return categoriesOneToMany; + } + + public void setCategoriesOneToMany(Set categoriesOneToMany) { + this.categoriesOneToMany = categoriesOneToMany; + } + + public Set getCategoriesWithDescOneToMany() { + return categoriesWithDescOneToMany; + } + + public void setCategoriesWithDescOneToMany(Set categoriesWithDescOneToMany) { + this.categoriesWithDescOneToMany = categoriesWithDescOneToMany; + } + + public Set getCategoriesManyToMany() { + return categoriesManyToMany; + } + + public void setCategoriesManyToMany(Set categoriesManyToMany) { + this.categoriesManyToMany = categoriesManyToMany; + } + + public Set getCategoriesWithDescManyToMany() { + return categoriesWithDescManyToMany; + } + + public void setCategoriesWithDescManyToMany(Set categoriesWithDescManyToMany) { + this.categoriesWithDescManyToMany = categoriesWithDescManyToMany; + } + + public Set getCategoriesWithDescIdLt4ManyToMany() { + return categoriesWithDescIdLt4ManyToMany; + } + + public void setCategoriesWithDescIdLt4ManyToMany(Set categoriesWithDescIdLt4ManyToMany) { + this.categoriesWithDescIdLt4ManyToMany = categoriesWithDescIdLt4ManyToMany; + } +} From 4135ddc4277663670dfb923a1456c67fb91e19c7 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 16 Oct 2018 05:02:14 -0700 Subject: [PATCH 179/772] HHH-13011 : Document hibernate.use_entity_where_clause_for_collections in the user guide --- .../src/main/asciidoc/userguide/appendices/Configurations.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index 46ea863deeef..ce012faedb82 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -1098,6 +1098,9 @@ Setting which indicates whether or not the new JOINS over collection tables shou `*hibernate.allow_refresh_detached_entity*` (e.g. `true` (default value when using Hibernate native bootstrapping) or `false` (default value when using JPA bootstrapping)):: Setting that allows to call `javax.persistence.EntityManager#refresh(entity)` or `Session#refresh(entity)` on a detached instance even when the `org.hibernate.Session` is obtained from a JPA `javax.persistence.EntityManager`. +`*hibernate.use_entity_where_clause_for_collections*` (e.g., `true` (default) or `false`):: +Setting controls whether an entity's "where" clause, mapped using `@Where(clause="...")` or `, is taken into account when loading one-to-many or many-to-many collections of that type of entity. + `*hibernate.event.merge.entity_copy_observer*` (e.g. `disallow` (default value), `allow`, `log` (testing purpose only) or fully-qualified class name):: Setting that specifies how Hibernate will respond when multiple representations of the same persistent entity ("entity copy") is detected while merging. + From 4d38494d38872209eee5825af662b8bf06c88a94 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 16 Oct 2018 05:52:43 -0700 Subject: [PATCH 180/772] HHH-13011 : Document hibernate.use_entity_where_clause_for_collections in 5.3 migration notes. --- migration-guide.adoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/migration-guide.adoc b/migration-guide.adoc index cf0aabe0ae4a..abc3a584b291 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -188,6 +188,20 @@ should behave the same as native exception handling in Hibernate ORM 5.1. When s `HibernateException` will not be wrapped or converted according to the JPA specification. This setting will be ignored for a `SessionFactory` built via JPA bootstrapping. +== 5.1 -> 5.3 entity class "where" mapping changes + +Starting in 5.2.0, when an entity class uses annotations to map a "where" clause (i.e., `@Where(clause="...")`), +that "where" clause is taken into account when loading one-to-many and many-to-many associations. + +Starting in 5.3.5, the same functionality applies to an entity's where clause mapped using hbm.xml +(e.g., `) + +In 5.3.7, a new property was added, `hibernate.use_entity_where_clause_for_collections`, that provides +control over whether the entity's "where" clause is taken into account when loading one-to-many or +many-to-many collections of that type of entity. The property is set to `true` by default. You can go +back to the previous behavior (ignoring the entity's mapped where clause) by setting +`hibernate.use_entity_where_clause_for_collections` to false. + === 5.3 -> 6.0 compatibility changes The original driving force behind these series of changes is an effort to be as proactive as possible From b964aae339b867e38a524ed24c19325ecdc46b08 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 16 Oct 2018 18:24:29 +0200 Subject: [PATCH 181/772] HHH-13011 Fix the tests: use a boolean query --- .../where/annotations/EagerManyToOneFetchModeJoinWhereTest.java | 2 +- .../annotations/EagerToManyWhereDontUseClassWhereTest.java | 2 +- .../hibernate/test/where/annotations/EagerToManyWhereTest.java | 2 +- .../where/annotations/EagerToManyWhereUseClassWhereTest.java | 2 +- .../where/annotations/LazyToManyWhereDontUseClassWhereTest.java | 2 +- .../hibernate/test/where/annotations/LazyToManyWhereTest.java | 2 +- .../where/annotations/LazyToManyWhereUseClassWhereTest.java | 2 +- .../test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml | 2 +- .../where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml | 2 +- .../java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml | 2 +- .../java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java index 53abe44d9b81..170ba0ef77c8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java @@ -131,7 +131,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "inactive = 0") + @Where(clause = "not inactive") public static class Category { @Id @GeneratedValue diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java index 060f3758f3cd..99cb3567ba31 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java @@ -199,7 +199,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "inactive = 0") + @Where(clause = "not inactive") public static class Category { @Id private int id; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java index fc4074952f70..ec224461399f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java @@ -194,7 +194,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "inactive = 0") + @Where(clause = "not inactive") public static class Category { @Id private int id; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java index c584d1a29a22..18738095eaf3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java @@ -199,7 +199,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "inactive = 0") + @Where(clause = "not inactive") public static class Category { @Id private int id; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java index e7e8eb4584c4..641cde432bef 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java @@ -199,7 +199,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "inactive = 0") + @Where(clause = "not inactive") public static class Category { @Id private int id; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java index f6c08f064930..eaf0b782316e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java @@ -193,7 +193,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "inactive = 0") + @Where(clause = "not inactive") public static class Category { @Id private int id; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java index d87036901faa..4326498ea53b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java @@ -199,7 +199,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "inactive = 0") + @Where(clause = "not inactive") public static class Category { @Id private int id; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml index 7cd816215ca5..46c7ccfe9efa 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml @@ -28,7 +28,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml index 662d16697d16..af77975d6ac0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml @@ -28,7 +28,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml index 2e2455d07868..d8bc42a6459c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml @@ -39,7 +39,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml index a76c02c34cd1..4c65649a7247 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml @@ -39,7 +39,7 @@ - + From 91d94c7e69025c5a80a938a77f63ec2da2b94e81 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 16 Oct 2018 19:18:03 +0200 Subject: [PATCH 182/772] HHH-13011 Fix the tests: use a boolean query --- .../annotations/EagerManyToOneFetchModeSelectWhereTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java index 2a3a938f143e..56110516613c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java @@ -130,7 +130,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "inactive = 0") + @Where(clause = "not inactive") public static class Category { @Id @GeneratedValue From 8f13d226a4bf1f5951c8b3b82092eaa29e31e2ea Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 16 Oct 2018 19:23:41 +0200 Subject: [PATCH 183/772] HHH-12935 Require sequence support in ExportIdentifierTests --- .../hibernate/test/schemaupdate/ExportIdentifierTest.java | 5 +++-- .../hibernate/envers/test/various/ExportIdentifierTest.java | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java index eeb6ca807993..f592e95441ec 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java @@ -28,13 +28,14 @@ import org.hibernate.mapping.PrimaryKey; import org.hibernate.mapping.Table; import org.hibernate.mapping.UniqueKey; - - +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.assertEquals; +@RequiresDialectFeature(DialectChecks.SupportsSequences.class) public class ExportIdentifierTest { @Test diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java index 9aaa80f863b1..5944c9960b77 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java @@ -19,12 +19,14 @@ import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.id.enhanced.SequenceStructure; - +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.assertEquals; +@RequiresDialectFeature(DialectChecks.SupportsSequences.class) public class ExportIdentifierTest { @Test From 625784694399e1fa048bf932d2bc6b0611a2d1db Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 16 Oct 2018 14:17:04 -0700 Subject: [PATCH 184/772] HHH-13011 Fix the tests: use int column in where clause --- .../EagerManyToOneFetchModeJoinWhereTest.java | 6 +++--- .../EagerManyToOneFetchModeSelectWhereTest.java | 6 +++--- .../EagerToManyWhereDontUseClassWhereTest.java | 8 ++++---- .../test/where/annotations/EagerToManyWhereTest.java | 9 ++++----- .../annotations/EagerToManyWhereUseClassWhereTest.java | 8 ++++---- .../LazyToManyWhereDontUseClassWhereTest.java | 8 ++++---- .../test/where/annotations/LazyToManyWhereTest.java | 8 ++++---- .../annotations/LazyToManyWhereUseClassWhereTest.java | 8 ++++---- .../test/java/org/hibernate/test/where/hbm/Category.java | 6 +++--- .../hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml | 2 +- .../where/hbm/EagerManyToOneFetchModeJoinWhereTest.java | 8 ++++---- .../hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml | 2 +- .../hbm/EagerManyToOneFetchModeSelectWhereTest.java | 8 ++++---- .../hibernate/test/where/hbm/EagerToManyWhere.hbm.xml | 2 +- .../where/hbm/EagerToManyWhereDontUseClassWhereTest.java | 2 +- .../hibernate/test/where/hbm/EagerToManyWhereTest.java | 2 +- .../where/hbm/EagerToManyWhereUseClassWhereTest.java | 2 +- .../org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml | 2 +- .../where/hbm/LazyToManyWhereDontUseClassWhereTest.java | 2 +- .../hibernate/test/where/hbm/LazyToManyWhereTest.java | 2 +- .../test/where/hbm/LazyToManyWhereUseClassWhereTest.java | 2 +- 21 files changed, 51 insertions(+), 52 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java index 170ba0ef77c8..d076a4d3ba67 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeJoinWhereTest.java @@ -83,7 +83,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, category.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -131,7 +131,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "not inactive") + @Where(clause = "inactive = 0") public static class Category { @Id @GeneratedValue @@ -139,7 +139,7 @@ public static class Category { private String name; - private boolean inactive; + private int inactive; } @Embeddable diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java index 56110516613c..0791c761f6d3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerManyToOneFetchModeSelectWhereTest.java @@ -82,7 +82,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, category.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -130,7 +130,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "not inactive") + @Where(clause = "inactive = 0") public static class Category { @Id @GeneratedValue @@ -138,7 +138,7 @@ public static class Category { private String name; - private boolean inactive; + private int inactive; } @Embeddable diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java index 99cb3567ba31..e03a34c6dd9b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereDontUseClassWhereTest.java @@ -127,7 +127,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -186,7 +186,7 @@ public static class Product { private Set categoriesManyToMany = new HashSet<>(); @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "categoriesWithDescManyToMany") + @JoinTable(name = "categoriesWithDescManyToMany", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) @Where( clause = "description is not null" ) private Set categoriesWithDescManyToMany = new HashSet<>(); @@ -199,7 +199,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "not inactive") + @Where(clause = "inactive = 0") public static class Category { @Id private int id; @@ -208,6 +208,6 @@ public static class Category { private String description; - private boolean inactive; + private int inactive; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java index ec224461399f..9417345891ec 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereTest.java @@ -24,7 +24,6 @@ import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.junit.After; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @@ -122,7 +121,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -181,7 +180,7 @@ public static class Product { private Set categoriesManyToMany = new HashSet<>(); @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "categoriesWithDescManyToMany") + @JoinTable(name = "categoriesWithDescManyToMany", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) @Where( clause = "description is not null" ) private Set categoriesWithDescManyToMany = new HashSet<>(); @@ -194,7 +193,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "not inactive") + @Where(clause = "inactive = 0") public static class Category { @Id private int id; @@ -203,6 +202,6 @@ public static class Category { private String description; - private boolean inactive; + private int inactive; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java index 18738095eaf3..f7d4462b64c5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/EagerToManyWhereUseClassWhereTest.java @@ -127,7 +127,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -186,7 +186,7 @@ public static class Product { private Set categoriesManyToMany = new HashSet<>(); @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "categoriesWithDescManyToMany") + @JoinTable(name = "categoriesWithDescManyToMany", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) @Where( clause = "description is not null" ) private Set categoriesWithDescManyToMany = new HashSet<>(); @@ -199,7 +199,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "not inactive") + @Where(clause = "inactive = 0") public static class Category { @Id private int id; @@ -208,6 +208,6 @@ public static class Category { private String description; - private boolean inactive; + private int inactive; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java index 641cde432bef..3856ad071065 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereDontUseClassWhereTest.java @@ -127,7 +127,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -186,7 +186,7 @@ public static class Product { private Set categoriesManyToMany = new HashSet<>(); @ManyToMany(fetch = FetchType.LAZY) - @JoinTable(name = "categoriesWithDescManyToMany") + @JoinTable(name = "categoriesWithDescManyToMany", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) @Where( clause = "description is not null" ) private Set categoriesWithDescManyToMany = new HashSet<>(); @@ -199,7 +199,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "not inactive") + @Where(clause = "inactive = 0") public static class Category { @Id private int id; @@ -208,6 +208,6 @@ public static class Category { private String description; - private boolean inactive; + private int inactive; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java index eaf0b782316e..e2ceeddf20da 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereTest.java @@ -121,7 +121,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -180,7 +180,7 @@ public static class Product { private Set categoriesManyToMany = new HashSet<>(); @ManyToMany(fetch = FetchType.LAZY) - @JoinTable(name = "categoriesWithDescManyToMany") + @JoinTable(name = "categoriesWithDescManyToMany", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) @Where( clause = "description is not null" ) private Set categoriesWithDescManyToMany = new HashSet<>(); @@ -193,7 +193,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "not inactive") + @Where(clause = "inactive = 0") public static class Category { @Id private int id; @@ -202,6 +202,6 @@ public static class Category { private String description; - private boolean inactive; + private int inactive; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java index 4326498ea53b..2e12f923b44b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyToManyWhereUseClassWhereTest.java @@ -127,7 +127,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -186,7 +186,7 @@ public static class Product { private Set categoriesManyToMany = new HashSet<>(); @ManyToMany(fetch = FetchType.LAZY) - @JoinTable(name = "categoriesWithDescManyToMany") + @JoinTable(name = "categoriesWithDescManyToMany", inverseJoinColumns = { @JoinColumn( name = "categoryId" )}) @Where( clause = "description is not null" ) private Set categoriesWithDescManyToMany = new HashSet<>(); @@ -199,7 +199,7 @@ public static class Product { @Entity(name = "Category") @Table(name = "CATEGORY") - @Where(clause = "not inactive") + @Where(clause = "inactive = 0") public static class Category { @Id private int id; @@ -208,6 +208,6 @@ public static class Category { private String description; - private boolean inactive; + private int inactive; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Category.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Category.java index f35ce91ba3e1..ce088399816e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Category.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/Category.java @@ -13,7 +13,7 @@ public class Category { private String description; - private boolean inactive; + private int inactive; public int getId() { return id; @@ -39,11 +39,11 @@ public void setDescription(String description) { this.description = description; } - public boolean isInactive() { + public int getInactive() { return inactive; } - public void setInactive(boolean inactive) { + public void setInactive(int inactive) { this.inactive = inactive; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml index 46c7ccfe9efa..7cd816215ca5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.hbm.xml @@ -28,7 +28,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.java index 4412613aa6c4..ef96d0cc5ba4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeJoinWhereTest.java @@ -66,7 +66,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, category.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -141,7 +141,7 @@ public static class Category { private String name; - private boolean inactive; + private int inactive; public int getId() { return id; @@ -159,11 +159,11 @@ public void setName(String name) { this.name = name; } - public boolean isInactive() { + public int getInactive() { return inactive; } - public void setInactive(boolean inactive) { + public void setInactive(int inactive) { this.inactive = inactive; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml index af77975d6ac0..662d16697d16 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.hbm.xml @@ -28,7 +28,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.java index d2dd8a819b1c..12b165b5ff3d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerManyToOneFetchModeSelectWhereTest.java @@ -64,7 +64,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, category.id ); assertNotNull( c ); - c.inactive = true; + c.inactive = 1; } ); @@ -139,7 +139,7 @@ public static class Category { private String name; - private boolean inactive; + private int inactive; public int getId() { return id; @@ -157,11 +157,11 @@ public void setName(String name) { this.name = name; } - public boolean isInactive() { + public int getInactive() { return inactive; } - public void setInactive(boolean inactive) { + public void setInactive(int inactive) { this.inactive = inactive; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml index d8bc42a6459c..2e2455d07868 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhere.hbm.xml @@ -39,7 +39,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereDontUseClassWhereTest.java index f93c5334165c..af9db17de63f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereDontUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereDontUseClassWhereTest.java @@ -116,7 +116,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.getId() ); assertNotNull( c ); - c.setInactive( true ); + c.setInactive( 1 ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereTest.java index 053320234650..a2a9884747d8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereTest.java @@ -109,7 +109,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.getId() ); assertNotNull( c ); - c.setInactive( true ); + c.setInactive( 1 ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereUseClassWhereTest.java index cabcb22ba217..29001f05abb2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/EagerToManyWhereUseClassWhereTest.java @@ -116,7 +116,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.getId() ); assertNotNull( c ); - c.setInactive( true ); + c.setInactive( 1 ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml index 4c65649a7247..a76c02c34cd1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhere.hbm.xml @@ -39,7 +39,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereDontUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereDontUseClassWhereTest.java index 86ca4ff631bd..570308a7ae95 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereDontUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereDontUseClassWhereTest.java @@ -116,7 +116,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.getId() ); assertNotNull( c ); - c.setInactive( true ); + c.setInactive( 1 ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereTest.java index 2b25200f1f12..f85ce6229062 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereTest.java @@ -109,7 +109,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.getId() ); assertNotNull( c ); - c.setInactive( true ); + c.setInactive( 1 ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereUseClassWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereUseClassWhereTest.java index 3984200cbf90..dc02774160a1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereUseClassWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyToManyWhereUseClassWhereTest.java @@ -116,7 +116,7 @@ public void testAssociatedWhereClause() { session -> { Category c = session.get( Category.class, flowers.getId() ); assertNotNull( c ); - c.setInactive( true ); + c.setInactive( 1 ); } ); From 023db8de0ea513e246c58fa1dc24e022fda58a28 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 16 Oct 2018 23:54:51 +0200 Subject: [PATCH 185/772] HHH-12935 Make ExportIdentifierTests extend BaseUnitTestCase --- .../org/hibernate/test/schemaupdate/ExportIdentifierTest.java | 3 ++- .../hibernate/envers/test/various/ExportIdentifierTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java index f592e95441ec..86c60ecc11c7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ExportIdentifierTest.java @@ -31,12 +31,13 @@ import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; import static org.junit.Assert.assertEquals; @RequiresDialectFeature(DialectChecks.SupportsSequences.class) -public class ExportIdentifierTest { +public class ExportIdentifierTest extends BaseUnitTestCase { @Test @TestForIssue( jiraKey = "HHH-12935" ) diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java index 5944c9960b77..bcd33f7b4939 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/various/ExportIdentifierTest.java @@ -22,12 +22,13 @@ import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; import static org.junit.Assert.assertEquals; @RequiresDialectFeature(DialectChecks.SupportsSequences.class) -public class ExportIdentifierTest { +public class ExportIdentifierTest extends BaseUnitTestCase { @Test @TestForIssue( jiraKey = "HHH-12935" ) From 890a6409a637da6f9fb64e2e11a9b87e01270ee0 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 17 Oct 2018 00:00:09 +0200 Subject: [PATCH 186/772] 5.3.7.Final --- changelog.txt | 20 ++++++++++++++++++++ gradle/base-information.gradle | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 6f78615c3757..fe1a5dbd0442 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,26 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.3.7.final (October 16th, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31714/tab/release-report-done + +** Bug + * [HHH-12784] - Javassist support broken by HHH-12760 + * [HHH-12920] - AbstractCachedDomainDataAccess.clearCache() throws MissingFormatArgumentException at DEBUG level + * [HHH-12934] - Exception handling documentation does not apply only to "Session-per-application anti-pattern" + * [HHH-12935] - Constraint and AuxiliaryDatabaseObject export identifiers are not qualified by schema or catalog + * [HHH-12937] - Where clause for collections of basic, embeddable and "any" elements is ignored when mapped using hbm.xml + * [HHH-12964] - Upgrade to dom4j 2.1.1 + * [HHH-13027] - org.hibernate.ejb.HibernatePersistence can no longer be used as a persistence provider name + +** Improvement + * [HHH-12961] - The links in the Javadoc of the SAP HANA dialects don't work + * [HHH-13011] - Add option enabling/disabling use of an entity's mapped where-clause when loading collections of that entity + + + Changes in 5.3.6.final (August 28th, 2018) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 47d6c5a9c507..73b7197602e2 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.7-SNAPSHOT', project ) + ormVersion = new HibernateVersion( '5.3.7.Final', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From 9d0fe33bf0b51c26ac8b089578289d9966fad266 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 17 Oct 2018 02:14:34 +0200 Subject: [PATCH 187/772] Prepare for next development iteration --- gradle/base-information.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 73b7197602e2..16e8367d1891 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.7.Final', project ) + ormVersion = new HibernateVersion( '5.3.8-SNAPSHOT', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From c091738bea3bce255b6b4188a69a7fb30cf8e462 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 25 Oct 2018 19:10:41 +0200 Subject: [PATCH 188/772] HHH-13064 Fix Lock and LockModeType table --- .../src/main/asciidoc/userguide/chapters/locking/Locking.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc index b3133032417b..faa602627fc2 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc @@ -314,7 +314,7 @@ Hibernate always uses the locking mechanism of the database, and never lock obje Long before JPA 1.0, Hibernate already defined various explicit locking strategies through its `LockMode` enumeration. JPA comes with its own http://docs.oracle.com/javaee/7/api/javax/persistence/LockModeType.html[`LockModeType`] enumeration which defines similar strategies as the Hibernate-native `LockMode`. -[cols=",",, options="header"] +[cols=",,",, options="header"] |======================================================================= |`LockModeType`|`LockMode`|Description From 6f636a8c0b7ddf81c0249d7a6dac68311a77ff3f Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 1 Nov 2018 15:20:39 +0000 Subject: [PATCH 189/772] Add 'Use {@link #getTransactionStartTimestamp()}' to SharedSessionContractImplementor#getTimestamp deprecated comment --- .../hibernate/engine/spi/SharedSessionContractImplementor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java index c6b1a5c4f7e3..0c3fe2ea2ceb 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java @@ -161,7 +161,7 @@ default void checkOpen() { long getTransactionStartTimestamp(); /** - * @deprecated (since 5.3) Use + * @deprecated (since 5.3) Use {@link #getTransactionStartTimestamp()} instead. */ @Deprecated default long getTimestamp() { From 91ede032c9e4560dee9bffed6972422e1e0468ac Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 28 May 2018 12:35:37 +0200 Subject: [PATCH 190/772] HHH-12555 Fix merging of lazy loaded blobs/clobs/nclobs It's better to avoid pushing UNFETCHED_PROPERTY to the types as it requires all the types to take it into account. TypeHelper looks like the only sensible caller that needs change. (cherry picked from commit 1af878166fe0883ceb3d9130afa2790850492624) HHH-12555 Add tests (cherry picked from commit 4e05953240264d32fe67f6e0f1810d8168c2725f) HHH-12555 : Update test to check results (cherry picked from commit ca6dc226eb53a0f40978847d2d251b64c5e49f41) HHH-12555 : Remove code in LobMergeStrategy implementations that copied original Lob when target is null (cherry picked from commit 4d0b5dc184f7b48a493da31edfe4acea30c8e975) HHH-12555 Add a DialectCheck for NClob support (cherry picked from commit 855f34c77181fd9ff103a8d2bf9c9bfb37360f2c) HHH-12555 Disable NClob test for dialects not supporting NClob (cherry picked from commit 98249af058a1f6408fcfca5a61d675af192458a1) --- .../type/AbstractStandardBasicType.java | 10 +- .../java/org/hibernate/type/ClobType.java | 3 +- .../java/org/hibernate/type/NClobType.java | 3 +- .../java/org/hibernate/type/TypeHelper.java | 6 + .../NationalizedLobFieldTest.java | 23 +- .../test/type/LobUnfetchedPropertyTest.java | 221 ++++++++++++++++++ .../org/hibernate/test/type/TypeTest.java | 43 ++-- .../org/hibernate/testing/DialectChecks.java | 19 +- 8 files changed, 283 insertions(+), 45 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java index d45e85d65384..962545ef860c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -71,11 +71,7 @@ protected MutabilityPlan getMutabilityPlan() { } protected T getReplacement(T original, T target, SharedSessionContractImplementor session) { - if ( original == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { - return target; - } - else if ( !isMutable() || - ( target != LazyPropertyInitializer.UNFETCHED_PROPERTY && isEqual( original, target ) ) ) { + if ( !isMutable() || ( target != null && isEqual( original, target ) ) ) { return original; } else { @@ -351,6 +347,10 @@ public final Type getSemiResolvedType(SessionFactoryImplementor factory) { @Override @SuppressWarnings({ "unchecked" }) public final Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) { + if ( original == null && target == null ) { + return null; + } + return getReplacement( (T) original, (T) target, session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ClobType.java b/hibernate-core/src/main/java/org/hibernate/type/ClobType.java index c6caf5934921..e731c47b8c5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ClobType.java @@ -36,7 +36,6 @@ protected boolean registerUnderJavaType() { @Override protected Clob getReplacement(Clob original, Clob target, SharedSessionContractImplementor session) { - return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeClob( original, target, session ); + return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeClob( (Clob) original, (Clob) target, session ); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/type/NClobType.java b/hibernate-core/src/main/java/org/hibernate/type/NClobType.java index 71f5921f9761..bf736ce37376 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/NClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/NClobType.java @@ -12,7 +12,7 @@ import org.hibernate.type.descriptor.java.NClobTypeDescriptor; /** - * A type that maps between {@link java.sql.Types#CLOB CLOB} and {@link java.sql.Clob} + * A type that maps between {@link java.sql.Types#NCLOB NCLOB} and {@link java.sql.NClob} * * @author Gavin King * @author Steve Ebersole @@ -38,5 +38,4 @@ protected boolean registerUnderJavaType() { protected NClob getReplacement(NClob original, NClob target, SharedSessionContractImplementor session) { return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeNClob( original, target, session ); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java b/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java index a151592e5050..295cfba36d26 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java @@ -159,6 +159,9 @@ public static Object[] replace( if ( original[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY || original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) { copied[i] = target[i]; } + else if ( target[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + copied[i] = types[i].replace( original[i], null, session, owner, copyCache ); + } else { copied[i] = types[i].replace( original[i], target[i], session, owner, copyCache ); } @@ -193,6 +196,9 @@ public static Object[] replace( || original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) { copied[i] = target[i]; } + else if ( target[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + copied[i] = types[i].replace( original[i], null, session, owner, copyCache, foreignKeyDirection ); + } else { copied[i] = types[i].replace( original[i], target[i], session, owner, copyCache, foreignKeyDirection ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/nationalized/NationalizedLobFieldTest.java b/hibernate-core/src/test/java/org/hibernate/test/nationalized/NationalizedLobFieldTest.java index fbabab693724..aa23fac5385b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/nationalized/NationalizedLobFieldTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/nationalized/NationalizedLobFieldTest.java @@ -6,6 +6,11 @@ */ package org.hibernate.test.nationalized; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.fail; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Lob; @@ -15,30 +20,18 @@ import org.hibernate.annotations.Nationalized; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; -import org.hibernate.dialect.DB2Dialect; -import org.hibernate.dialect.MySQLDialect; -import org.hibernate.dialect.PostgreSQL81Dialect; -import org.hibernate.dialect.SybaseDialect; import org.hibernate.resource.transaction.spi.TransactionStatus; - -import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; -import static junit.framework.TestCase.assertNotNull; -import static junit.framework.TestCase.fail; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - /** * @author Andrea Boriero */ @TestForIssue(jiraKey = "HHH-10364") -@SkipForDialect(value = DB2Dialect.class, comment = "DB2 jdbc driver doesn't support getNClob") -@SkipForDialect(value = MySQLDialect.class, comment = "MySQL/MariadB doesn't support nclob") -@SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL doesn't support nclob") -@SkipForDialect(value = SybaseDialect.class, comment = "Sybase doesn't support nclob") +@RequiresDialectFeature(DialectChecks.SupportsNClob.class) public class NationalizedLobFieldTest extends BaseCoreFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java new file mode 100644 index 000000000000..25ce2fcade6d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java @@ -0,0 +1,221 @@ +/* + * 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.type; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.SQLException; +import java.util.Arrays; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +@TestForIssue(jiraKey = "HHH-12555") +@RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class) +@RunWith(BytecodeEnhancerRunner.class) +public class LobUnfetchedPropertyTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ FileBlob.class, FileClob.class, FileNClob.class }; + } + + @Test + public void testBlob() throws SQLException { + final int id = doInHibernate( this::sessionFactory, s -> { + FileBlob file = new FileBlob(); + file.setBlob( s.getLobHelper().createBlob( "TEST CASE".getBytes() ) ); + // merge transient entity + file = (FileBlob) s.merge( file ); + return file.getId(); + } ); + + doInHibernate( this::sessionFactory, s -> { + FileBlob file = s.get( FileBlob.class, id ); + assertFalse( Hibernate.isPropertyInitialized( file, "blob" ) ); + Blob blob = file.getBlob(); + try { + assertTrue( + Arrays.equals( "TEST CASE".getBytes(), blob.getBytes( 1, (int) file.getBlob().length() ) ) + ); + } + catch (SQLException ex) { + fail( "could not determine Lob length" ); + } + }); + } + + @Test + public void testClob() { + final int id = doInHibernate( this::sessionFactory, s -> { + FileClob file = new FileClob(); + file.setClob( s.getLobHelper().createClob( "TEST CASE" ) ); + // merge transient entity + file = (FileClob) s.merge( file ); + return file.getId(); + } ); + + doInHibernate( this::sessionFactory, s -> { + FileClob file = s.get( FileClob.class, id ); + assertFalse( Hibernate.isPropertyInitialized( file, "clob" ) ); + Clob clob = file.getClob(); + try { + final char[] chars = new char[(int) file.getClob().length()]; + clob.getCharacterStream().read( chars ); + assertTrue( Arrays.equals( "TEST CASE".toCharArray(), chars ) ); + } + catch (SQLException ex ) { + fail( "could not determine Lob length" ); + } + catch (IOException ex) { + fail( "could not read Lob" ); + } + }); + } + + @Test + @RequiresDialectFeature(DialectChecks.SupportsNClob.class) + public void testNClob() { + final int id = doInHibernate( this::sessionFactory, s -> { + FileNClob file = new FileNClob(); + file.setClob( s.getLobHelper().createNClob( "TEST CASE" ) ); + // merge transient entity + file = (FileNClob) s.merge( file ); + return file.getId(); + }); + + doInHibernate( this::sessionFactory, s -> { + FileNClob file = s.get( FileNClob.class, id ); + assertFalse( Hibernate.isPropertyInitialized( file, "clob" ) ); + NClob nClob = file.getClob(); + try { + final char[] chars = new char[(int) file.getClob().length()]; + nClob.getCharacterStream().read( chars ); + assertTrue( Arrays.equals( "TEST CASE".toCharArray(), chars ) ); + } + catch (SQLException ex ) { + fail( "could not determine Lob length" ); + } + catch (IOException ex) { + fail( "could not read Lob" ); + } + }); + } + + @Entity(name = "FileBlob") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") + public static class FileBlob { + + private int id; + + private Blob blob; + + @Id + @GeneratedValue + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Column(name = "filedata", length = 1024 * 1024) + @Lob + @Basic(fetch = FetchType.LAZY) + public Blob getBlob() { + return blob; + } + + public void setBlob(Blob blob) { + this.blob = blob; + } + } + + @Entity(name = "FileClob") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") + public static class FileClob { + + private int id; + + private Clob clob; + + @Id + @GeneratedValue + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Column(name = "filedata", length = 1024 * 1024) + @Lob + @Basic(fetch = FetchType.LAZY) + public Clob getClob() { + return clob; + } + + public void setClob(Clob clob) { + this.clob = clob; + } + } + + @Entity(name = "FileNClob") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") + public static class FileNClob { + + private int id; + + private NClob clob; + + @Id + @GeneratedValue + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Column(name = "filedata", length = 1024 * 1024) + @Lob + @Basic(fetch = FetchType.LAZY) + public NClob getClob() { + return clob; + } + + public void setClob(NClob clob) { + this.clob = clob; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/TypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/TypeTest.java index d0df6cd7c7dd..11f09b596d8d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/TypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/TypeTest.java @@ -6,6 +6,23 @@ */ package org.hibernate.test.type; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Currency; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.SimpleTimeZone; +import java.util.TimeZone; + import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.internal.SessionImpl; import org.hibernate.internal.util.SerializationHelper; @@ -42,26 +59,11 @@ import org.hibernate.type.TimeZoneType; import org.hibernate.type.TimestampType; import org.hibernate.type.TrueFalseType; +import org.hibernate.type.Type; +import org.hibernate.type.TypeHelper; import org.hibernate.type.YesNoType; import org.junit.Test; -import java.io.Serializable; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.Calendar; -import java.util.Currency; -import java.util.GregorianCalendar; -import java.util.Locale; -import java.util.SimpleTimeZone; -import java.util.TimeZone; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - /** * @author Steve Ebersole */ @@ -344,8 +346,11 @@ protected void runBasicTests(AbstractSingleColumnStandardBasicType type, assertTrue( original == type.replace( original, copy, null, null, null ) ); // following tests assert that types work with properties not yet loaded in bytecode enhanced entities - assertSame( copy, type.replace( LazyPropertyInitializer.UNFETCHED_PROPERTY, copy, null, null, null ) ); - assertNotEquals( LazyPropertyInitializer.UNFETCHED_PROPERTY, type.replace( original, LazyPropertyInitializer.UNFETCHED_PROPERTY, null, null, null ) ); + Type[] types = new Type[]{ type }; + assertSame( copy, TypeHelper.replace( new Object[]{ LazyPropertyInitializer.UNFETCHED_PROPERTY }, + new Object[]{ copy }, types, null, null, null )[0] ); + assertNotEquals( LazyPropertyInitializer.UNFETCHED_PROPERTY, TypeHelper.replace( new Object[]{ original }, + new Object[]{ LazyPropertyInitializer.UNFETCHED_PROPERTY }, types, null, null, null )[0] ); assertTrue( type.isSame( original, copy ) ); assertTrue( type.isEqual( original, copy ) ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java index b5b747663004..aee3bd6b84e9 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java @@ -8,6 +8,9 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseDialect; /** * Container class for different implementation of the {@link DialectCheck} interface. @@ -153,13 +156,13 @@ public boolean isMatch(Dialect dialect) { return dialect.supportsExistsInSelect(); } } - + public static class SupportsLobValueChangePropogation implements DialectCheck { public boolean isMatch(Dialect dialect) { return dialect.supportsLobValueChangePropogation(); } } - + public static class SupportsLockTimeouts implements DialectCheck { public boolean isMatch(Dialect dialect) { return dialect.supportsLockTimeouts(); @@ -256,4 +259,16 @@ public boolean isMatch(Dialect dialect) { ); } } + + public static class SupportsNClob implements DialectCheck { + @Override + public boolean isMatch(Dialect dialect) { + return !( + dialect instanceof DB2Dialect || + dialect instanceof PostgreSQL81Dialect || + dialect instanceof SybaseDialect || + dialect instanceof MySQLDialect + ); + } + } } From b198a42db12e2e6ddeed12fbd6149096b9f4433f Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 5 Nov 2018 19:00:57 -0800 Subject: [PATCH 191/772] HHH-13084 HHH-13114 : Add test cases HHH-13084 HHH-13114 : Refactor test cases HHH-13084 HHH-13114 : Add test cases HHH-13084 HHH-13114 : Refactor test cases HHH-13084 HHH-13114 : Add failing test case (cherry picked from commit 346b3c7f3ed2c923f17967b6d88dd3dcd735c9e4) HHH-13084 HHH-13114 : Bugs querying entity with non-ID property named 'id' HHH-13084 HHH-13114 : Query 'select count(h) from Human h' fails if a subclass has a non-Id property named 'id' HHH-13084 HHH-13114 : Update hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java Co-Authored-By: dreab8 HHH-13084 HHH-13114 - Fix FromElementType method name HHH-13084 HHH-13114 : Correct generated column names (cherry picked from commit 7316307d0e10772b10b7dba94ac278ed1cf96c0f) --- .../hql/internal/ast/tree/FromElement.java | 22 ++- .../internal/ast/tree/FromElementType.java | 55 +++--- .../entity/AbstractEntityPersister.java | 2 +- ...sociationsWithEmbeddedCompositeIdTest.java | 160 ++++++++++++++++++ .../IdPropertyInJoinedSubclassTest.java | 117 +++++++++++++ .../IdPropertyInSingleTableSubclassTest.java | 116 +++++++++++++ ...rtyInSubclassIdInMappedSuperclassTest.java | 123 ++++++++++++++ ...IdPropertyInTablePerClassSubclassTest.java | 116 +++++++++++++ .../PropertyNamedIdInEmbeddedIdTest.java | 136 +++++++++++++++ .../idprops/PropertyNamedIdInIdClassTest.java | 139 +++++++++++++++ ...ropertyNamedIdInNonJpaCompositeIdTest.java | 84 +++++++++ .../PropertyNamedIdOutOfEmbeddedIdTest.java | 137 +++++++++++++++ .../PropertyNamedIdOutOfIdClassTest.java | 157 +++++++++++++++++ ...ertyNamedIdOutOfNonJpaCompositeIdTest.java | 153 +++++++++++++++++ 14 files changed, 1488 insertions(+), 29 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/ecid/CompositeIdAssociationsWithEmbeddedCompositeIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInJoinedSubclassTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInSingleTableSubclassTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInSubclassIdInMappedSuperclassTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInTablePerClassSubclassTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInEmbeddedIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInIdClassTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInNonJpaCompositeIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfEmbeddedIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfIdClassTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfNonJpaCompositeIdTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java index 1b0affe72665..77d7dbcb025f 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java @@ -339,12 +339,18 @@ public String[] getIdentityColumns() { throw new IllegalStateException( "No table alias for node " + this ); } - final String propertyName = getIdentifierPropertyName(); - - return toColumns( - table, propertyName, - getWalker().getStatementType() == HqlSqlTokenTypes.SELECT - ); + final String[] propertyNames = getIdentifierPropertyNames(); + List columns = new ArrayList<>(); + for ( int i = 0; i < propertyNames.length; i++ ) { + String[] propertyNameColumns = toColumns( + table, propertyNames[i], + getWalker().getStatementType() == HqlSqlTokenTypes.SELECT + ); + for ( int j = 0; j < propertyNameColumns.length; j++ ) { + columns.add( propertyNameColumns[j] ); + } + } + return columns.toArray( new String[columns.size()] ); } public void setCollectionJoin(boolean collectionJoin) { @@ -525,8 +531,8 @@ public CollectionPropertyReference getCollectionPropertyReference(String propert return elementType.getCollectionPropertyReference( propertyName ); } - public String getIdentifierPropertyName() { - return elementType.getIdentifierPropertyName(); + public String[] getIdentifierPropertyNames() { + return elementType.getIdentifierPropertyNames(); } public void setFetch(boolean fetch) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java index 7c815b0e0206..872e6dcb1e8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java @@ -14,17 +14,14 @@ import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.engine.internal.JoinSequence; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.CollectionProperties; import org.hibernate.hql.internal.CollectionSubqueryFactory; import org.hibernate.hql.internal.NameGenerator; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; -import org.hibernate.hql.internal.ast.HqlSqlWalker; -import org.hibernate.hql.internal.ast.util.SessionFactoryHelper; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.loader.PropertyPath; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.CollectionPropertyMapping; import org.hibernate.persister.collection.CollectionPropertyNames; @@ -33,6 +30,8 @@ import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.PropertyMapping; import org.hibernate.persister.entity.Queryable; +import org.hibernate.tuple.IdentifierProperty; +import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -130,18 +129,22 @@ public Queryable getQueryable() { String renderScalarIdentifierSelect(int i) { checkInitialized(); - final String idPropertyName = getIdentifierPropertyName(); - String[] cols = getPropertyMapping( idPropertyName ).toColumns( getTableAlias(), idPropertyName ); - + final String[] idPropertyName = getIdentifierPropertyNames(); StringBuilder buf = new StringBuilder(); - // For property references generate . as - for ( int j = 0; j < cols.length; j++ ) { - String column = cols[j]; - if ( j > 0 ) { - buf.append( ", " ); + int counter = 0; + for ( int j = 0; j < idPropertyName.length; j++ ) { + String propertyName = idPropertyName[j]; + String[] toColumns = getPropertyMapping( propertyName ).toColumns( getTableAlias(), propertyName ); + for ( int h = 0; h < toColumns.length; h++, counter++ ) { + String column = toColumns[h]; + if ( j + h > 0 ) { + buf.append( ", " ); + } + buf.append( column ).append( " as " ).append( NameGenerator.scalarName( i, counter ) ); } - buf.append( column ).append( " as " ).append( NameGenerator.scalarName( i, j ) ); } + + LOG.debug( "Rendered scalar ID select column(s): " + buf ); return buf.toString(); } @@ -682,13 +685,25 @@ public String[] toColumns(String propertyName) throws QueryException, Unsupporte } } - public String getIdentifierPropertyName() { - if ( getEntityPersister() != null && getEntityPersister().getEntityMetamodel() != null - && getEntityPersister().getEntityMetamodel().hasNonIdentifierPropertyNamedId() ) { - return getEntityPersister().getIdentifierPropertyName(); - } - else { - return EntityPersister.ENTITY_ID; + public String[] getIdentifierPropertyNames() { + if ( getEntityPersister() != null ) { + String identifierPropertyName = getEntityPersister().getIdentifierPropertyName(); + if ( identifierPropertyName != null ) { + return new String[] { identifierPropertyName }; + } + else { + final IdentifierProperty identifierProperty = getEntityPersister().getEntityMetamodel() + .getIdentifierProperty(); + if ( identifierProperty.hasIdentifierMapper() && !identifierProperty.isEmbedded() ) { + return new String[] { PropertyPath.IDENTIFIER_MAPPER_PROPERTY }; + } + else { + if ( EmbeddedComponentType.class.isInstance( identifierProperty.getType() ) ) { + return ( (EmbeddedComponentType) identifierProperty.getType() ).getPropertyNames(); + } + } + } } + return new String[] { EntityPersister.ENTITY_ID }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index dd87b91f25c5..34dc8572c93c 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -2289,7 +2289,7 @@ protected void initSubclassPropertyAliasesMap(PersistentClass model) throws Mapp new String[] {idColumnNames[i]} ); } -// if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyName() ) ) { +// if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyNames() ) ) { if ( hasIdentifierProperty() ) { subclassPropertyAliases.put( getIdentifierPropertyName() + "." + idPropertyNames[i], diff --git a/hibernate-core/src/test/java/org/hibernate/test/ecid/CompositeIdAssociationsWithEmbeddedCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/ecid/CompositeIdAssociationsWithEmbeddedCompositeIdTest.java new file mode 100644 index 000000000000..0fa8d59cbdfb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/ecid/CompositeIdAssociationsWithEmbeddedCompositeIdTest.java @@ -0,0 +1,160 @@ +/* + * 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.ecid; + +import java.io.Serializable; +import java.util.Iterator; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class CompositeIdAssociationsWithEmbeddedCompositeIdTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Person.class }; + } + + @Test + @TestForIssue( jiraKey = "HHH-13114") + public void testQueries() { + Parent parent1 = new Parent( "Jane", 0 ); + Parent parent2 = new Parent( "Jim", 1 ); + Person person = doInHibernate( + this::sessionFactory, session -> { + Person p = new Person(); + p.setParent1( parent1 ); + p.setParent2( parent2 ); + p.setBirthOrder( 0 ); + session.persist( parent1 ); + session.persist( parent2 ); + session.persist( p ); + return p; + }); + + doInHibernate( + this::sessionFactory, session -> { + checkResult( session.get( Person.class, person ) ); + }); + + + doInHibernate( + this::sessionFactory, session -> { + checkResult( session.createQuery( "from Person p", Person.class ).getSingleResult() ); + }); + + doInHibernate( + this::sessionFactory, session -> { + Iterator iterator = session.createQuery( "from Person p", Person.class ).iterate(); + assertTrue( iterator.hasNext() ); + Person p = iterator.next(); + checkResult( p ); + assertFalse( iterator.hasNext() ); + }); + } + + private void checkResult(Person p) { + assertEquals( "Jane", p.getParent1().name ); + assertEquals( 0, p.getParent1().index ); + assertEquals( "Jim", p.getParent2().name ); + assertEquals( 1, p.getParent2().index ); + } + + @Entity(name = "Person") + public static class Person implements Serializable { + @Id + @JoinColumns( value = { + @JoinColumn(name = "p1Name"), + @JoinColumn(name = "p1Index") + }) + @ManyToOne + private Parent parent1; + + @Id + @JoinColumns( value = { + @JoinColumn(name = "p2Name"), + @JoinColumn(name = "p2Index") + }) + @ManyToOne + private Parent parent2; + + @Id + private int birthOrder; + + private String name; + + public Person() { + } + + public Person(String name, Parent parent1, Parent parent2) { + this(); + setName( name ); + this.parent1 = parent1; + this.parent2 = parent2; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public Parent getParent1() { + return parent1; + } + public void setParent1(Parent parent1) { + this.parent1 = parent1; + } + + public Parent getParent2() { + return parent2; + } + public void setParent2(Parent parent2) { + this.parent2 = parent2; + } + + public int getBirthOrder() { + return birthOrder; + } + public void setBirthOrder(int birthOrder) { + this.birthOrder = birthOrder; + } + } + + @Entity(name = "Parent") + public static class Parent implements Serializable { + @Id + private String name; + + @Id + private int index; + + public Parent() { + } + + public Parent(String name, int index) { + this.name = name; + this.index = index; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInJoinedSubclassTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInJoinedSubclassTest.java new file mode 100644 index 000000000000..87b9b39b9c1b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInJoinedSubclassTest.java @@ -0,0 +1,117 @@ +/* + * 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.idprops; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Gail Badner + */ +public class IdPropertyInJoinedSubclassTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Human.class, Genius.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Genius() ); + session.persist( new Genius( 1L ) ); + session.persist( new Genius( 1L ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13114") + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( + 2, session.createQuery( "from Genius g where g.id = :id", Genius.class ) + .setParameter( "id", 1L ) + .list() + .size() + ); + + assertEquals( + 1, session.createQuery( "from Genius g where g.id is null", Genius.class ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( g ) from Genius g" ).uniqueResult() ); + + assertEquals( + 2, session.createQuery( "from Human h where h.id = :id", Human.class ) + .setParameter( "id", 1L ) + .list() + .size() + ); + + assertEquals( + 1, session.createQuery( "from Human h where h.id is null", Human.class ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( h ) from Human h" ).uniqueResult() ); + } ); + } + + @Entity(name = "Human") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Human { + + private Long realId; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "realId") + public Long getRealId() { + return realId; + } + + public void setRealId(Long realId) { + this.realId = realId; + } + } + + @Entity(name = "Genius") + public static class Genius extends Human { + private Long id; + + public Genius() { + } + + public Genius(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInSingleTableSubclassTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInSingleTableSubclassTest.java new file mode 100644 index 000000000000..fd7e56ef6ea1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInSingleTableSubclassTest.java @@ -0,0 +1,116 @@ +/* + * 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.idprops; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Gail Badner + */ +public class IdPropertyInSingleTableSubclassTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Human.class, Genius.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Genius() ); + session.persist( new Genius( 1L ) ); + session.persist( new Genius( 1L ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13114") + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( + 2, session.createQuery( "from Genius g where g.id = :id", Genius.class ) + .setParameter( "id", 1L ) + .list() + .size() + ); + + assertEquals( + 1, session.createQuery( "from Genius g where g.id is null", Genius.class ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( g ) from Genius g" ).uniqueResult() ); + + assertEquals( + 2, session.createQuery( "from Human h where h.id = :id", Human.class ) + .setParameter( "id", 1L ) + .list() + .size() + ); + + assertEquals( + 1, session.createQuery( "from Human h where h.id is null", Human.class ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( h ) from Human h" ).uniqueResult() ); + } ); + } + + @Entity(name = "Human") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class Human { + private Long realId; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "realId") + public Long getRealId() { + return realId; + } + + public void setRealId(Long realId) { + this.realId = realId; + } + } + + @Entity(name = "Genius") + public static class Genius extends Human { + private Long id; + + public Genius() { + } + + public Genius(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInSubclassIdInMappedSuperclassTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInSubclassIdInMappedSuperclassTest.java new file mode 100644 index 000000000000..8cbd837d38c0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInSubclassIdInMappedSuperclassTest.java @@ -0,0 +1,123 @@ +/* + * 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.idprops; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Gail Badner + */ +public class IdPropertyInSubclassIdInMappedSuperclassTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Human.class, Genius.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Genius() ); + session.persist( new Genius( 1L ) ); + session.persist( new Genius( 1L ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13114") + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( + 2, session.createQuery( "from Genius g where g.id = :id", Genius.class ) + .setParameter( "id", 1L ) + .list() + .size() + ); + + assertEquals( + 1, session.createQuery( "from Genius g where g.id is null", Genius.class ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( g ) from Genius g" ).uniqueResult() ); + + assertEquals( + 2, session.createQuery( "from Human h where h.id = :id", Human.class ) + .setParameter( "id", 1L ) + .list() + .size() + ); + + assertEquals( + 1, session.createQuery( "from Human h where h.id is null", Human.class ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( h ) from Human h" ).uniqueResult() ); + + } ); + } + + @MappedSuperclass + public static class Animal { + + private Long realId; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "realId") + public Long getRealId() { + return realId; + } + + public void setRealId(Long realId) { + this.realId = realId; + } + } + + @Entity(name = "Human") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static class Human extends Animal { + } + + @Entity(name = "Genius") + public static class Genius extends Human { + private Long id; + + public Genius() { + } + + public Genius(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInTablePerClassSubclassTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInTablePerClassSubclassTest.java new file mode 100644 index 000000000000..5a0ce3f2071b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/IdPropertyInTablePerClassSubclassTest.java @@ -0,0 +1,116 @@ +/* + * 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.idprops; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Gail Badner + */ +public class IdPropertyInTablePerClassSubclassTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Human.class, Genius.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Genius() ); + session.persist( new Genius( 1L ) ); + session.persist( new Genius( 1L ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13114") + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( + 2, session.createQuery( "from Genius g where g.id = :id", Genius.class ) + .setParameter( "id", 1L ) + .list() + .size() + ); + + assertEquals( + 1, session.createQuery( "from Genius g where g.id is null", Genius.class ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( g ) from Genius g" ).uniqueResult() ); + + assertEquals( + 2, session.createQuery( "from Human h where h.id = :id", Human.class ) + .setParameter( "id", 1L ) + .list() + .size() + ); + + assertEquals( + 1, session.createQuery( "from Human h where h.id is null", Human.class ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( h ) from Human h" ).uniqueResult() ); + + } ); + } + + @Entity(name = "Human") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static class Human { + private Long realId; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "realId") + public Long getRealId() { + return realId; + } + + public void setRealId(Long realId) { + this.realId = realId; + } + } + + @Entity(name = "Genius") + public static class Genius extends Human { + private Long id; + + public Genius() { + } + + public Genius(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInEmbeddedIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInEmbeddedIdTest.java new file mode 100644 index 000000000000..2452e492144a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInEmbeddedIdTest.java @@ -0,0 +1,136 @@ +/* + * 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.idprops; + +import java.io.Serializable; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Gail Badner + */ +public class PropertyNamedIdInEmbeddedIdTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Person( "John Doe", 0 ) ); + session.persist( new Person( "John Doe", 1 ) ); + session.persist( new Person( "Jane Doe", 0 ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13084") + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + + assertEquals( + 1, session.createQuery( "from Person p where p.id = :id", Person.class ) + .setParameter( "id", new PersonId( "John Doe", 0 ) ) + .list() + .size() + ); + + assertEquals( + 2, session.createQuery( "from Person p where p.id.id = :id", Person.class ) + .setParameter( "id", 0 ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( p ) from Person p" ).uniqueResult() ); + + } ); + } + + @Entity(name = "Person") + public static class Person implements Serializable { + @EmbeddedId + private PersonId personId; + + public Person(String name, int id) { + this(); + personId = new PersonId( name, id ); + } + + protected Person() { + // this form used by Hibernate + } + + public PersonId getPersonId() { + return personId; + } + } + + @Embeddable + public static class PersonId implements Serializable { + private String name; + private Integer id; + + public PersonId() { + } + + public PersonId(String name, int id) { + setName( name ); + setId( id ); + } + + public String getName() { + return name; + } + + public Integer getId() { + return id; + } + + public void setName(String name) { + this.name = name; + } + + public void setId(Integer id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PersonId personId = (PersonId) o; + + if ( id != personId.id ) { + return false; + } + return name.equals( personId.name ); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + id; + return result; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInIdClassTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInIdClassTest.java new file mode 100644 index 000000000000..79469e812183 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInIdClassTest.java @@ -0,0 +1,139 @@ +/* + * 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.idprops; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Gail Badner + */ +public class PropertyNamedIdInIdClassTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Person( "John Doe", 0 ) ); + session.persist( new Person( "John Doe", 1 ) ); + session.persist( new Person( "Jane Doe", 0 ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13084") + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( 2, session.createQuery( "from Person p where p.id = 0", Person.class ).list().size() ); + + assertEquals( 3L, session.createQuery( "select count( p ) from Person p" ).uniqueResult() ); + + } ); + } + + @Entity(name = "Person") + @IdClass(PersonId.class) + public static class Person implements Serializable { + @Id + private String name; + + @Id + private Integer id; + + public Person(String name, Integer id) { + this(); + setName( name ); + setId( id ); + } + + public String getName() { + return name; + } + + public Integer getId() { + return id; + } + + protected Person() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setId(Integer id) { + this.id = id; + } + } + + public static class PersonId implements Serializable { + private String name; + private Integer id; + + public PersonId() { + } + + public PersonId(String name, int id) { + setName( name ); + setId( id ); + } + + public String getName() { + return name; + } + + public Integer getId() { + return id; + } + + public void setName(String name) { + this.name = name; + } + + public void setId(Integer id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PersonId personId = (PersonId) o; + + if ( id != personId.id ) { + return false; + } + return name.equals( personId.name ); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + id; + return result; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInNonJpaCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInNonJpaCompositeIdTest.java new file mode 100644 index 000000000000..ea557fbee0cf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdInNonJpaCompositeIdTest.java @@ -0,0 +1,84 @@ +/* + * 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.idprops; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Gail Badner + */ +public class PropertyNamedIdInNonJpaCompositeIdTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Person( "John Doe", 0 ) ); + session.persist( new Person( "John Doe", 1 ) ); + session.persist( new Person( "Jane Doe", 0 ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13084") + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( 2, session.createQuery( "from Person p where p.id = 0", Person.class ).list().size() ); + + assertEquals( 3L, session.createQuery( "select count( p ) from Person p" ).uniqueResult() ); + + } ); + } + + @Entity(name = "Person") + public static class Person implements Serializable { + @Id + private String name; + + @Id + private Integer id; + + public Person(String name, Integer id) { + this(); + setName( name ); + setId( id ); + } + + public String getName() { + return name; + } + + public Integer getId() { + return id; + } + + protected Person() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setId(Integer id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfEmbeddedIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfEmbeddedIdTest.java new file mode 100644 index 000000000000..985f0705256b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfEmbeddedIdTest.java @@ -0,0 +1,137 @@ +/* + * 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.idprops; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Gail Badner + */ +public class PropertyNamedIdOutOfEmbeddedIdTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Person( "John Doe", 0, 6 ) ); + session.persist( new Person( "John Doe", 1, 6 ) ); + session.persist( new Person( "Jane Doe", 0 ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13084") + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( + 2, session.createQuery( "from Person p where p.id = :id", Person.class ) + .setParameter( "id", 6 ) + .list() + .size() + ); + + assertEquals( 3L, session.createQuery( "select count( p ) from Person p" ).uniqueResult() ); + + } ); + } + + @Entity(name = "Person") + public static class Person implements Serializable { + @EmbeddedId + private PersonId personId; + + private Integer id; + + public Person(String name, int index) { + this(); + personId = new PersonId( name, index ); + } + + public Person(String name, int index, Integer id) { + this( name, index ); + this.id = id; + } + + protected Person() { + // this form used by Hibernate + } + + public PersonId getPersonId() { + return personId; + } + } + + @Embeddable + public static class PersonId implements Serializable { + private String name; + @Column(name = "ind") + private int index; + + public PersonId() { + } + + public PersonId(String name, int index) { + setName( name ); + setIndex( index ); + } + + public String getName() { + return name; + } + + public int getIndex() { + return index; + } + + public void setName(String name) { + this.name = name; + } + + public void setIndex(int index) { + this.index = index; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PersonId personId = (PersonId) o; + + if ( index != personId.index ) { + return false; + } + return name.equals( personId.name ); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + index; + return result; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfIdClassTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfIdClassTest.java new file mode 100644 index 000000000000..233c6efe9447 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfIdClassTest.java @@ -0,0 +1,157 @@ +/* + * 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.idprops; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Gail Badner + */ +public class PropertyNamedIdOutOfIdClassTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Person( "John Doe", 0 ) ); + session.persist( new Person( "John Doe", 1, 1 ) ); + session.persist( new Person( "John Doe", 2, 2 ) ); + } ); + } + + + @Test + @TestForIssue(jiraKey = "HHH-13084") + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( 1, session.createQuery( "from Person p where p.id is null", Person.class ).list().size() ); + assertEquals( 2, session.createQuery( "from Person p where p.id is not null", Person.class ).list().size() ); + assertEquals( 3L, session.createQuery( "select count( p ) from Person p" ).uniqueResult() ); + } ); + } + + @Entity(name = "Person") + @IdClass(PersonId.class) + public static class Person implements Serializable { + @Id + private String name; + + @Id + @Column(name = "ind") + private int index; + + private Integer id; + + public Person(String name, int index) { + this(); + setName( name ); + setIndex( index ); + } + + + public Person(String name, int index, int id) { + this( name, index ); + this.id = id; + } + + public String getName() { + return name; + } + + public int getIndex() { + return index; + } + + public Integer getId() { + return id; + } + + protected Person() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setIndex(int index) { + this.index = index; + } + + protected void setId(Integer id) { + this.id = id; + } + } + + public static class PersonId implements Serializable { + private String name; + private int index; + + public PersonId() { + } + + public PersonId(String name, int index) { + setName( name ); + setIndex( index ); + } + + public String getName() { + return name; + } + + public int getIndex() { + return index; + } + + public void setName(String name) { + this.name = name; + } + + public void setIndex(int index) { + this.index = index; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PersonId personId = (PersonId) o; + + if ( index != personId.index ) { + return false; + } + return name.equals( personId.name ); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + index; + return result; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfNonJpaCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfNonJpaCompositeIdTest.java new file mode 100644 index 000000000000..8b8d0d0350dc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/idprops/PropertyNamedIdOutOfNonJpaCompositeIdTest.java @@ -0,0 +1,153 @@ +/* + * 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.idprops; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * @author Gail Badner + */ +public class PropertyNamedIdOutOfNonJpaCompositeIdTest extends BaseCoreFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + session.persist( new Person( "John Doe", 0 ) ); + session.persist( new Person( "John Doe", 1, 1 ) ); + session.persist( new Person( "John Doe", 2, 2 ) ); + } ); + } + + @Test + public void testHql() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( 1, session.createQuery( "from Person p where p.id = 1", Person.class ).list().size() ); + + assertEquals( 3L, session.createQuery( "select count( p ) from Person p" ).uniqueResult() ); + } ); + } + + @Entity(name = "Person") + public static class Person implements Serializable { + @Id + private String name; + + @Id + @Column(name = "ind") + private int index; + + private Integer id; + + public Person(String name, int index) { + this(); + setName( name ); + setIndex( index ); + } + + + public Person(String name, int index, int id) { + this( name, index ); + this.id = id; + } + + public String getName() { + return name; + } + + public int getIndex() { + return index; + } + + public Integer getId() { + return id; + } + + protected Person() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setIndex(int index) { + this.index = index; + } + + protected void setId(Integer id) { + this.id = id; + } + } + + public static class PersonId implements Serializable { + private String name; + private int index; + + public PersonId() { + } + + public PersonId(String name, int index) { + setName( name ); + setIndex( index ); + } + + public String getName() { + return name; + } + + public int getIndex() { + return index; + } + + public void setName(String name) { + this.name = name; + } + + public void setIndex(int index) { + this.index = index; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PersonId personId = (PersonId) o; + + if ( index != personId.index ) { + return false; + } + return name.equals( personId.name ); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + index; + return result; + } + } +} From 4137181bb69d0e65308d3cae420e62f1ff1ff3d7 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 18 Dec 2018 15:15:00 -0800 Subject: [PATCH 192/772] HHH-13084 HHH-13114 : Correct test case using reserved word (cherry picked from commit fa5b632f4e791f8b9b3c3065ea5e24a442a59f9c) --- .../CompositeIdAssociationsWithEmbeddedCompositeIdTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/ecid/CompositeIdAssociationsWithEmbeddedCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/ecid/CompositeIdAssociationsWithEmbeddedCompositeIdTest.java index 0fa8d59cbdfb..a9151193e340 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/ecid/CompositeIdAssociationsWithEmbeddedCompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/ecid/CompositeIdAssociationsWithEmbeddedCompositeIdTest.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.Iterator; +import javax.persistence.Column; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.Id; @@ -147,6 +148,7 @@ public static class Parent implements Serializable { private String name; @Id + @Column(name="ind") private int index; public Parent() { From 704711906139eaf4a4bf7ee918d46f64a7198e0f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 9 Jan 2019 12:54:30 +0100 Subject: [PATCH 193/772] HHH-13059 Add test (cherry picked from commit 502fd78c454e8118c9a317f7d6e596315b8fd5bb) --- .../BatchFetchReferencedColumnNameTest.java | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java new file mode 100644 index 000000000000..c161191188fe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java @@ -0,0 +1,146 @@ +/* + * 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.batchfetch; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import java.time.ZonedDateTime; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Test; + +public class BatchFetchReferencedColumnNameTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Child.class, Parent.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + + configuration.setProperty( AvailableSettings.SHOW_SQL, Boolean.TRUE.toString() ); + configuration.setProperty( AvailableSettings.FORMAT_SQL, Boolean.TRUE.toString() ); + + configuration.setProperty( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "64" ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13059") + public void test() throws Exception { + doInHibernate( this::sessionFactory, session -> { + Parent p = new Parent(); + p.setId( 1L ); + session.save( p ); + + Child c1 = new Child(); + c1.setCreatedOn( ZonedDateTime.now() ); + c1.setParentId( 1L ); + c1.setId( 10L ); + session.save( c1 ); + + Child c2 = new Child(); + c2.setCreatedOn( ZonedDateTime.now() ); + c2.setParentId( 1L ); + c2.setId( 11L ); + session.save( c2 ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Parent p = session.get( Parent.class, 1L ); + Assert.assertNotNull( p ); + + Assert.assertEquals( 2, p.getChildren().size() ); + } ); + } + + @Entity + @Table(name = "CHILD") + public static class Child { + + @Id + @Column(name = "CHILD_ID") + private Long id; + + @Column(name = "PARENT_ID") + private Long parentId; + + @Column(name = "CREATED_ON") + private ZonedDateTime createdOn; + + public ZonedDateTime getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(ZonedDateTime createdOn) { + this.createdOn = createdOn; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + } + + @Entity + @Table(name = "PARENT") + public static class Parent { + + @Id + @Column(name = "PARENT_ID") + private Long id; + + @OneToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL }) + @JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID") + @OrderBy("createdOn desc") + private List children; + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + } +} From 4a564ab45ab817f9e089ed85c082c8afeda39519 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 9 Jan 2019 12:51:43 +0100 Subject: [PATCH 194/772] HHH-13059 Partially revert HHH-12594 The first commit was on the safe side, we decided to go the extra mile and that was a mistake as we missed all the consequences. The new issue is about having a shared ReaderCollector: we add the info there for each batch which leads to collecting the elements several times. This reverts commit a19fc84320d1ba2172ecb37f5c8c9cbcde616712. HHH-13059 : Correct Javadoc (cherry picked from commit e6286e04f4f866bb555dd06dedb40a76109442f9) --- .../internal/AbstractLoadQueryDetails.java | 28 +------ .../exec/internal/EntityLoadQueryDetails.java | 21 ++--- .../LoadQueryJoinAndFetchProcessor.java | 84 +++++++------------ 3 files changed, 37 insertions(+), 96 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java index 106c2c9e315e..ee0e6482082e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java @@ -40,7 +40,6 @@ public abstract class AbstractLoadQueryDetails implements LoadQueryDetails { private final String[] keyColumnNames; private final Return rootReturn; private final LoadQueryJoinAndFetchProcessor queryProcessor; - private final QueryBuildingParameters buildingParameters; private String sqlStatement; private ResultSetProcessor resultSetProcessor; @@ -59,30 +58,7 @@ protected AbstractLoadQueryDetails( this.keyColumnNames = keyColumnNames; this.rootReturn = rootReturn; this.loadPlan = loadPlan; - this.queryProcessor = new LoadQueryJoinAndFetchProcessor( - aliasResolutionContext, buildingParameters.getQueryInfluencers(), factory - ); - this.buildingParameters = buildingParameters; - } - - /** - * Constructs an AbstractLoadQueryDetails object from an initial object and new building parameters, - * with the guarantee that only batch size changed between the initial parameters and the new ones. - * - * @param initialLoadQueryDetails The initial object to be copied - * @param buildingParameters The new building parameters, with only the batch size being different - * from the parameters used in the initial object. - */ - protected AbstractLoadQueryDetails( - AbstractLoadQueryDetails initialLoadQueryDetails, - QueryBuildingParameters buildingParameters) { - this.keyColumnNames = initialLoadQueryDetails.keyColumnNames; - this.rootReturn = initialLoadQueryDetails.rootReturn; - this.loadPlan = initialLoadQueryDetails.loadPlan; - this.queryProcessor = new LoadQueryJoinAndFetchProcessor( - initialLoadQueryDetails.queryProcessor, buildingParameters.getQueryInfluencers() - ); - this.buildingParameters = buildingParameters; + this.queryProcessor = new LoadQueryJoinAndFetchProcessor( aliasResolutionContext, buildingParameters, factory ); } protected QuerySpace getQuerySpace(String querySpaceUid) { @@ -108,7 +84,7 @@ protected final AliasResolutionContext getAliasResolutionContext() { } protected final QueryBuildingParameters getQueryBuildingParameters() { - return buildingParameters; + return queryProcessor.getQueryBuildingParameters(); } protected final SessionFactoryImplementor getSessionFactory() { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java index 2495fb7f2e72..7c78004c5112 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java @@ -85,24 +85,17 @@ protected EntityLoadQueryDetails( generate(); } - /** - * Constructs an EntityLoadQueryDetails object from an initial object and new building parameters, - * with the guarantee that only batch size changed between the initial parameters and the new ones. - * - * @param initialEntityLoadQueryDetails The initial object to be copied - * @param buildingParameters The new building parameters, with only the batch size being different - * from the parameters used in the initial object. - */ protected EntityLoadQueryDetails( EntityLoadQueryDetails initialEntityLoadQueryDetails, QueryBuildingParameters buildingParameters) { - super( - initialEntityLoadQueryDetails, - buildingParameters + this( + initialEntityLoadQueryDetails.getLoadPlan(), + initialEntityLoadQueryDetails.getKeyColumnNames(), + new AliasResolutionContextImpl( initialEntityLoadQueryDetails.getSessionFactory() ), + (EntityReturn) initialEntityLoadQueryDetails.getRootReturn(), + buildingParameters, + initialEntityLoadQueryDetails.getSessionFactory() ); - this.entityReferenceAliases = initialEntityLoadQueryDetails.entityReferenceAliases; - this.readerCollector = initialEntityLoadQueryDetails.readerCollector; - generate(); } public boolean hasCollectionInitializers() { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java index 070b8c5bb133..9d9aae2a019e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java @@ -13,7 +13,6 @@ import org.hibernate.AssertionFailure; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.util.StringHelper; @@ -21,6 +20,7 @@ import org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl; import org.hibernate.loader.plan.exec.process.spi.ReaderCollector; import org.hibernate.loader.plan.exec.query.internal.SelectStatementBuilder; +import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.exec.spi.CollectionReferenceAliases; import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases; @@ -66,51 +66,34 @@ public class LoadQueryJoinAndFetchProcessor { private static final Logger LOG = CoreLogging.logger( LoadQueryJoinAndFetchProcessor.class ); - private final AliasResolutionContext aliasResolutionContext; - private final AliasResolutionContextImpl mutableAliasResolutionContext; - private final LoadQueryInfluencers queryInfluencers; + private final AliasResolutionContextImpl aliasResolutionContext; + private final QueryBuildingParameters buildingParameters; private final SessionFactoryImplementor factory; /** * Instantiates a LoadQueryJoinAndFetchProcessor with the given information * - * @param mutableAliasResolutionContext - * @param queryInfluencers + * @param aliasResolutionContext + * @param buildingParameters * @param factory */ public LoadQueryJoinAndFetchProcessor( - AliasResolutionContextImpl mutableAliasResolutionContext, - LoadQueryInfluencers queryInfluencers, + AliasResolutionContextImpl aliasResolutionContext, + QueryBuildingParameters buildingParameters, SessionFactoryImplementor factory) { - this.aliasResolutionContext = mutableAliasResolutionContext; - this.mutableAliasResolutionContext = mutableAliasResolutionContext; - this.queryInfluencers = queryInfluencers; + this.aliasResolutionContext = aliasResolutionContext; + this.buildingParameters = buildingParameters; this.factory = factory; } - /** - * Instantiates a LoadQueryJoinAndFetchProcessor from an initial object and new query influencers. - * - * Aliases are considered already contributed to the initial objects alias resolution context - * and won't be added again. - * - * @param initialLoadQueryJoinAndFetchProcessor The initial object to be copied - * @param queryInfluencers The new query influencers - */ - public LoadQueryJoinAndFetchProcessor( - LoadQueryJoinAndFetchProcessor initialLoadQueryJoinAndFetchProcessor, - LoadQueryInfluencers queryInfluencers) { - this.aliasResolutionContext = initialLoadQueryJoinAndFetchProcessor.aliasResolutionContext; - // Do not change the alias resolution context, it should be pre-populated - this.mutableAliasResolutionContext = null; - this.queryInfluencers = queryInfluencers; - this.factory = initialLoadQueryJoinAndFetchProcessor.factory; - } - public AliasResolutionContext getAliasResolutionContext() { return aliasResolutionContext; } + public QueryBuildingParameters getQueryBuildingParameters() { + return buildingParameters; + } + public SessionFactoryImplementor getSessionFactory() { return factory; } @@ -182,12 +165,7 @@ private void handleCompositeJoin(Join join, JoinFragment joinFragment) { ); } - if ( mutableAliasResolutionContext != null ) { - mutableAliasResolutionContext.registerCompositeQuerySpaceUidResolution( - rightHandSideUid, - leftHandSideTableAlias - ); - } + aliasResolutionContext.registerCompositeQuerySpaceUidResolution( rightHandSideUid, leftHandSideTableAlias ); } private void renderEntityJoin(Join join, JoinFragment joinFragment) { @@ -195,8 +173,8 @@ private void renderEntityJoin(Join join, JoinFragment joinFragment) { // see if there is already aliases registered for this entity query space (collection joins) EntityReferenceAliases aliases = aliasResolutionContext.resolveEntityReferenceAliases( rightHandSide.getUid() ); - if ( aliases == null && mutableAliasResolutionContext != null ) { - mutableAliasResolutionContext.generateEntityReferenceAliases( + if ( aliases == null ) { + aliasResolutionContext.generateEntityReferenceAliases( rightHandSide.getUid(), rightHandSide.getEntityPersister() ); @@ -227,10 +205,10 @@ private String resolveAdditionalJoinCondition(String rhsTableAlias, String withC // calls to the Joinable.filterFragment() method where the Joinable is either the entity or // collection persister final String filter = associationType!=null? - associationType.getOnCondition( rhsTableAlias, factory, queryInfluencers.getEnabledFilters() ): + associationType.getOnCondition( rhsTableAlias, factory, buildingParameters.getQueryInfluencers().getEnabledFilters() ): joinable.filterFragment( rhsTableAlias, - queryInfluencers.getEnabledFilters() + buildingParameters.getQueryInfluencers().getEnabledFilters() ); if ( StringHelper.isEmpty( withClause ) && StringHelper.isEmpty( filter ) ) { @@ -317,19 +295,7 @@ else if ( !StringHelper.isEmpty( joinConditions ) ) { private void renderCollectionJoin(Join join, JoinFragment joinFragment) { final CollectionQuerySpace rightHandSide = (CollectionQuerySpace) join.getRightHandSide(); - if ( mutableAliasResolutionContext != null ) { - registerCollectionJoinAliases( mutableAliasResolutionContext, rightHandSide ); - } - addJoins( - join, - joinFragment, - (Joinable) rightHandSide.getCollectionPersister(), - null - ); - } - private void registerCollectionJoinAliases(AliasResolutionContextImpl mutableAliasResolutionContext, - CollectionQuerySpace rightHandSide) { // The SQL join to the "collection table" needs to be rendered. // // In the case of a basic collection, that's the only join needed. @@ -385,14 +351,14 @@ private void registerCollectionJoinAliases(AliasResolutionContextImpl mutableAli ) ); } - mutableAliasResolutionContext.generateCollectionReferenceAliases( + aliasResolutionContext.generateCollectionReferenceAliases( rightHandSide.getUid(), rightHandSide.getCollectionPersister(), collectionElementJoin.getRightHandSide().getUid() ); } else { - mutableAliasResolutionContext.generateCollectionReferenceAliases( + aliasResolutionContext.generateCollectionReferenceAliases( rightHandSide.getUid(), rightHandSide.getCollectionPersister(), null @@ -412,11 +378,17 @@ private void registerCollectionJoinAliases(AliasResolutionContextImpl mutableAli ) ); } - mutableAliasResolutionContext.generateEntityReferenceAliases( + aliasResolutionContext.generateEntityReferenceAliases( collectionIndexJoin.getRightHandSide().getUid(), rightHandSide.getCollectionPersister().getIndexDefinition().toEntityDefinition().getEntityPersister() ); } + addJoins( + join, + joinFragment, + (Joinable) rightHandSide.getCollectionPersister(), + null + ); } private void renderManyToManyJoin( @@ -447,7 +419,7 @@ private void renderManyToManyJoin( final CollectionPersister persister = leftHandSide.getCollectionPersister(); manyToManyFilter = persister.getManyToManyFilterFragment( entityTableAlias, - queryInfluencers.getEnabledFilters() + buildingParameters.getQueryInfluencers().getEnabledFilters() ); } else { From aea7b31996e32af475fea1ca73a280e61ce36733 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 26 Jul 2018 16:00:22 -0700 Subject: [PATCH 195/772] HHH-11209 : Test cases HHH-11209 : NullPointerException in EntityType.replace() with a PersistentBag HHH-11209 : Add test for merging a detached collection with queued operations HHH-11209 : Throw UnsupportedOperationException if a detached collection with queued operations is merged HHH-11209 : Ignore queued operations when merging a detached collection with queued operations; add warnings HHH-11209 : Fix typo in comment (cherry picked from commit 6f5b1e554386662e02d4ef3b91c23863258ff481) --- .../internal/CollectionUpdateAction.java | 7 +- .../QueuedOperationCollectionAction.java | 17 + .../AbstractPersistentCollection.java | 40 +- .../hibernate/internal/CoreMessageLogger.java | 12 + .../org/hibernate/type/CollectionType.java | 21 +- .../BagDelayedOperationNoCascadeTest.java | 288 +++++++++++++ .../BagDelayedOperationTest.java | 49 ++- .../DetachedBagDelayedOperationTest.java | 387 ++++++++++++++++++ 8 files changed, 797 insertions(+), 24 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationNoCascadeTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/DetachedBagDelayedOperationTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java index b8cb3c18ccf3..39264ee6b858 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java @@ -57,8 +57,11 @@ public void execute() throws HibernateException { preUpdate(); if ( !collection.wasInitialized() ) { - if ( !collection.hasQueuedOperations() ) { - throw new AssertionFailure( "no queued adds" ); + // If there were queued operations, they would have been processed + // and cleared by now. + // The collection should still be dirty. + if ( !collection.isDirty() ) { + throw new AssertionFailure( "collection is not dirty" ); } //do nothing - we only need to notify the cache... } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java index 4abf3efb24d3..532b12e377c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java @@ -9,7 +9,9 @@ import java.io.Serializable; import org.hibernate.HibernateException; +import org.hibernate.collection.internal.AbstractPersistentCollection; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.collection.CollectionPersister; @@ -40,6 +42,21 @@ public QueuedOperationCollectionAction( @Override public void execute() throws HibernateException { + // this QueuedOperationCollectionAction has to be executed before any other + // CollectionAction involving the same collection. + getPersister().processQueuedOps( getCollection(), getKey(), getSession() ); + + // TODO: It would be nice if this could be done safely by CollectionPersister#processQueuedOps; + // Can't change the SPI to do this though. + ((AbstractPersistentCollection) getCollection() ).clearOperationQueue(); + + // The other CollectionAction types call CollectionEntry#afterAction, which + // clears the dirty flag. We don't want to call CollectionEntry#afterAction unless + // there is no other CollectionAction that will be executed on the same collection. + final CollectionEntry ce = getSession().getPersistenceContext().getCollectionEntry( getCollection() ); + if ( !ce.isDoremove() && !ce.isDoupdate() && !ce.isDorecreate() ) { + ce.afterAction( getCollection() ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index 44d0a506c2e6..54f714770fce 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -512,6 +512,7 @@ protected final void performQueuedOperations() { for ( DelayedOperation operation : operationQueue ) { operation.operate(); } + clearOperationQueue(); } @Override @@ -523,11 +524,15 @@ public void setSnapshot(Serializable key, String role, Serializable snapshot) { @Override public void postAction() { - operationQueue = null; + clearOperationQueue(); cachedSize = -1; clearDirty(); } + public final void clearOperationQueue() { + operationQueue = null; + } + @Override public Object getValue() { return this; @@ -549,9 +554,8 @@ public boolean endRead() { public boolean afterInitialize() { setInitialized(); //do this bit after setting initialized to true or it will recurse - if ( operationQueue != null ) { + if ( hasQueuedOperations() ) { performQueuedOperations(); - operationQueue = null; cachedSize = -1; return false; } @@ -620,6 +624,9 @@ public final boolean unsetSession(SharedSessionContractImplementor currentSessio prepareForPossibleLoadingOutsideTransaction(); if ( currentSession == this.session ) { if ( !isTempSession ) { + if ( hasQueuedOperations() ) { + LOG.queuedOperationWhenDetachFromSession( MessageHelper.collectionInfoString( getRole(), getKey() ) ); + } this.session = null; } return true; @@ -647,25 +654,22 @@ public final boolean setCurrentSession(SharedSessionContractImplementor session) if ( session == this.session ) { return false; } - else { - if ( this.session != null ) { - final String msg = generateUnexpectedSessionStateMessage( session ); - if ( isConnectedToSession() ) { - throw new HibernateException( - "Illegal attempt to associate a collection with two open sessions. " + msg - ); - } - else { - LOG.logUnexpectedSessionInCollectionNotConnected( msg ); - this.session = session; - return true; - } + else if ( this.session != null ) { + final String msg = generateUnexpectedSessionStateMessage( session ); + if ( isConnectedToSession() ) { + throw new HibernateException( + "Illegal attempt to associate a collection with two open sessions. " + msg + ); } else { - this.session = session; - return true; + LOG.logUnexpectedSessionInCollectionNotConnected( msg ); } } + if ( hasQueuedOperations() ) { + LOG.queuedOperationWhenAttachToSession( MessageHelper.collectionInfoString( getRole(), getKey() ) ); + } + this.session = session; + return true; } private String generateUnexpectedSessionStateMessage(SharedSessionContractImplementor session) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 6b7d7e9e042b..745f8d87c222 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1814,4 +1814,16 @@ void attemptToAssociateProxyWithTwoOpenSessions( @LogMessage(level = WARN) @Message(value = "Setting " + AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE + "=true is not valid with JPA bootstrapping; setting will be ignored.", id = 489 ) void nativeExceptionHandling51ComplianceJpaBootstrapping(); + + @LogMessage(level = WARN) + @Message(value = "Attempt to merge an uninitialized collection with queued operations; queued operations will be ignored: %s", id = 494) + void ignoreQueuedOperationsOnMerge(String collectionInfoString); + + @LogMessage(level = WARN) + @Message(value = "Attaching an uninitialized collection with queued operations to a session: %s", id = 495) + void queuedOperationWhenAttachToSession(String collectionInfoString); + + @LogMessage(level = WARN) + @Message(value = "Detaching an uninitialized collection with queued operations from a session: %s", id = 496) + void queuedOperationWhenDetachFromSession(String collectionInfoString); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java index 428c7bbb0894..5d4febce59cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java @@ -454,7 +454,7 @@ public Object hydrate(ResultSet rs, String[] name, SharedSessionContractImplemen public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { - return resolve(value, session, owner, null); + return resolve( value, session, owner, null ); } @Override @@ -681,8 +681,23 @@ public Object replace( } if ( !Hibernate.isInitialized( original ) ) { if ( ( (PersistentCollection) original ).hasQueuedOperations() ) { - final AbstractPersistentCollection pc = (AbstractPersistentCollection) original; - pc.replaceQueuedOperationValues( getPersister( session ), copyCache ); + if ( original == target ) { + // A managed entity with an uninitialized collection is being merged, + // We need to replace any detached entities in the queued operations + // with managed copies. + final AbstractPersistentCollection pc = (AbstractPersistentCollection) original; + pc.replaceQueuedOperationValues( getPersister( session ), copyCache ); + } + else { + // original is a detached copy of the collection; + // it contains queued operations, which will be ignored + LOG.ignoreQueuedOperationsOnMerge( + MessageHelper.collectionInfoString( + getRole(), + ( (PersistentCollection) original ).getKey() + ) + ); + } } return target; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationNoCascadeTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationNoCascadeTest.java new file mode 100644 index 000000000000..bd63bbeac7d5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationNoCascadeTest.java @@ -0,0 +1,288 @@ +/* + * 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.collection.delayedOperation; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.collection.internal.AbstractPersistentCollection; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests delayed operations that are queued for a PersistentBag. The Bag does not have + * to be extra-lazy to queue the operations. + * @author Gail Badner + */ +public class BagDelayedOperationNoCascadeTest extends BaseCoreFunctionalTestCase { + private Long parentId; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Before + public void setup() { + // start by cleaning up in case a test fails + if ( parentId != null ) { + cleanup(); + } + + Parent parent = new Parent(); + Child child1 = new Child( "Sherman" ); + Child child2 = new Child( "Yogi" ); + parent.addChild( child1 ); + parent.addChild( child2 ); + + Session s = openSession(); + s.getTransaction().begin(); + s.persist( child1 ); + s.persist( child2 ); + s.persist( parent ); + s.getTransaction().commit(); + s.close(); + + parentId = parent.getId(); + } + + @After + public void cleanup() { + Session s = openSession(); + s.getTransaction().begin(); + s.createQuery( "delete from Child" ).executeUpdate(); + s.createQuery( "delete from Parent" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + + parentId = null; + } + + @Test + @TestForIssue( jiraKey = "HHH-5855") + public void testSimpleAddManaged() { + // Add 2 Child entities + Session s = openSession(); + s.getTransaction().begin(); + Child c1 = new Child( "Darwin" ); + s.persist( c1 ); + Child c2 = new Child( "Comet" ); + s.persist( c2 ); + s.getTransaction().commit(); + s.close(); + + // Add a managed Child and commit + s = openSession(); + s.getTransaction().begin(); + Parent p = s.get( Parent.class, parentId ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + // get the first Child so it is managed; add to collection + p.addChild( s.get( Child.class, c1.getId() ) ); + // collection should still be uninitialized + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.getTransaction().begin(); + p = s.get( Parent.class, parentId ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + assertEquals( 3, p.getChildren().size() ); + s.getTransaction().commit(); + s.close(); + + // Add the other managed Child, merge and commit. + s = openSession(); + s.getTransaction().begin(); + p = s.get( Parent.class, parentId ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + // get the second Child so it is managed; add to collection + p.addChild( s.get( Child.class, c2.getId() ) ); + // collection should still be uninitialized + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + s.merge( p ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.getTransaction().begin(); + p = s.get( Parent.class, parentId ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + assertEquals( 4, p.getChildren().size() ); + s.getTransaction().commit(); + s.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-11209") + public void testMergeInitializedBagAndRemerge() { + Session s = openSession(); + s.getTransaction().begin(); + Parent p = s.get( Parent.class, parentId ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + // initialize + Hibernate.initialize( p.getChildren() ); + assertTrue( Hibernate.isInitialized( p.getChildren() ) ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.getTransaction().begin(); + p = (Parent) s.merge( p ); + Child c = new Child( "Zeke" ); + c.setParent( p ); + s.persist( c ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + p.getChildren().size(); + p.getChildren().add( c ); + s.getTransaction().commit(); + s.close(); + + // Merge detached Parent with initialized children + s = openSession(); + s.getTransaction().begin(); + p = (Parent) s.merge( p ); + // after merging, p#children will be uninitialized + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + assertTrue( ( (AbstractPersistentCollection) p.getChildren() ).hasQueuedOperations() ); + s.getTransaction().commit(); + assertFalse( ( (AbstractPersistentCollection) p.getChildren() ).hasQueuedOperations() ); + s.close(); + + // Merge detached Parent, now with uninitialized children no queued operations + s = openSession(); + s.getTransaction().begin(); + p = (Parent) s.merge( p ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + assertFalse( ( (AbstractPersistentCollection) p.getChildren() ).hasQueuedOperations() ); + s.getTransaction().commit(); + s.close(); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + // Don't need extra-lazy to delay add operations to a bag. + @OneToMany(mappedBy = "parent") + private List children = new ArrayList(); + + public Parent() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getChildren() { + return children; + } + + public void addChild(Child child) { + children.add(child); + child.setParent(this); + } + } + + @Entity(name = "Child") + public static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false) + private String name; + + @ManyToOne + private Parent parent; + + public Child() { + } + + public Child(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + @Override + public String toString() { + return "Child{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + Child child = (Child) o; + + return name.equals( child.name ); + + } + + @Override + public int hashCode() { + return name.hashCode(); + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationTest.java index 0a18eac43576..b45a252d7350 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/BagDelayedOperationTest.java @@ -24,11 +24,13 @@ import org.hibernate.Hibernate; import org.hibernate.Session; +import org.hibernate.collection.internal.AbstractPersistentCollection; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Tests delayed operations that are queued for a PersistentBag. The Bag does not have @@ -123,7 +125,7 @@ public void testSimpleAddDetached() { assertFalse( Hibernate.isInitialized( p.getChildren() ) ); p.addChild( c2 ); assertFalse( Hibernate.isInitialized( p.getChildren() ) ); - s.merge( p ); + p = (Parent) s.merge( p ); s.getTransaction().commit(); s.close(); @@ -236,6 +238,51 @@ public void testSimpleAddManaged() { s.close(); } + @Test + @TestForIssue( jiraKey = "HHH-11209") + public void testMergeInitializedBagAndRemerge() { + Session s = openSession(); + s.getTransaction().begin(); + Parent p = s.get( Parent.class, parentId ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + // initialize + Hibernate.initialize( p.getChildren() ); + assertTrue( Hibernate.isInitialized( p.getChildren() ) ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.getTransaction().begin(); + p = (Parent) s.merge( p ); + assertTrue( Hibernate.isInitialized( p.getChildren() ) ); + Child c = new Child( "Zeke" ); + c.setParent( p ); + s.persist( c ); + p.getChildren().size(); + p.getChildren().add( c ); + s.getTransaction().commit(); + s.close(); + + // Merge detached Parent with initialized children + s = openSession(); + s.getTransaction().begin(); + p = (Parent) s.merge( p ); + // after merging, p#children will be initialized + assertTrue( Hibernate.isInitialized( p.getChildren() ) ); + assertFalse( ( (AbstractPersistentCollection) p.getChildren() ).hasQueuedOperations() ); + s.getTransaction().commit(); + s.close(); + + // Merge detached Parent + s = openSession(); + s.getTransaction().begin(); + p = (Parent) s.merge( p ); + assertTrue( Hibernate.isInitialized( p.getChildren() ) ); + assertFalse( ( (AbstractPersistentCollection) p.getChildren() ).hasQueuedOperations() ); + s.getTransaction().commit(); + s.close(); + } + @Entity(name = "Parent") public static class Parent { diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/DetachedBagDelayedOperationTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/DetachedBagDelayedOperationTest.java new file mode 100644 index 000000000000..2e5cea84d501 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/DetachedBagDelayedOperationTest.java @@ -0,0 +1,387 @@ +/* + * 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.collection.delayedOperation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.collection.internal.AbstractPersistentCollection; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.type.CollectionType; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests merge of detached PersistentBag + * + * @author Gail Badner + */ +public class DetachedBagDelayedOperationTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Rule + public LoggerInspectionRule logInspectionCollectionType = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, CollectionType.class.getName() ) + ); + + @Rule + public LoggerInspectionRule logInspectionAbstractPersistentCollection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AbstractPersistentCollection.class.getName() ) + ); + + private Triggerable triggerableIgnoreQueuedOperationsOnMerge; + private Triggerable triggerableQueuedOperationWhenAttachToSession; + private Triggerable triggerableQueuedOperationWhenDetachFromSession; + + @Before + public void setup() { + Parent parent = new Parent(); + parent.id = 1L; + Child child1 = new Child( "Sherman" ); + Child child2 = new Child( "Yogi" ); + parent.addChild( child1 ); + parent.addChild( child2 ); + + doInHibernate( + this::sessionFactory, session -> { + + session.persist( child1 ); + session.persist( child2 ); + session.persist( parent ); + } + ); + + triggerableIgnoreQueuedOperationsOnMerge = logInspectionCollectionType.watchForLogMessages( "HHH000494" ); + triggerableQueuedOperationWhenAttachToSession = logInspectionAbstractPersistentCollection.watchForLogMessages( "HHH000495" ); + triggerableQueuedOperationWhenDetachFromSession = logInspectionAbstractPersistentCollection.watchForLogMessages( "HHH000496" ); + + resetTriggerables(); + } + + @After + public void cleanup() { + doInHibernate( + this::sessionFactory, session -> { + session.createQuery( "delete from Child" ).executeUpdate(); + session.createQuery( "delete from Parent" ).executeUpdate(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-11209" ) + public void testMergeDetachedCollectionWithQueuedOperations() { + final Parent pOriginal = doInHibernate( + this::sessionFactory, session -> { + Parent p = session.get( Parent.class, 1L ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + // initialize + Hibernate.initialize( p.getChildren() ); + assertTrue( Hibernate.isInitialized( p.getChildren() ) ); + return p; + } + ); + final Parent pWithQueuedOperations = doInHibernate( + this::sessionFactory, session -> { + Parent p = (Parent) session.merge( pOriginal ); + Child c = new Child( "Zeke" ); + c.setParent( p ); + session.persist( c ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + p.getChildren().add( c ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + assertTrue( ( (AbstractPersistentCollection) p.getChildren() ).hasQueuedOperations() ); + + checkTriggerablesNotTriggered(); + session.detach( p ); + assertTrue( triggerableQueuedOperationWhenDetachFromSession.wasTriggered() ); + assertEquals( + "HHH000496: Detaching an uninitialized collection with queued operations from a session: [org.hibernate.test.collection.delayedOperation.DetachedBagDelayedOperationTest$Parent.children#1]", + triggerableQueuedOperationWhenDetachFromSession.triggerMessage() + ); + triggerableQueuedOperationWhenDetachFromSession.reset(); + + // Make sure nothing else got triggered + checkTriggerablesNotTriggered(); + + return p; + } + ); + + checkTriggerablesNotTriggered(); + + assertTrue( ( (AbstractPersistentCollection) pWithQueuedOperations.getChildren() ).hasQueuedOperations() ); + + // Merge detached Parent with uninitialized collection with queued operations + doInHibernate( + this::sessionFactory, session -> { + + checkTriggerablesNotTriggered(); + + assertFalse( triggerableIgnoreQueuedOperationsOnMerge.wasTriggered() ); + Parent p = (Parent) session.merge( pWithQueuedOperations ); + assertTrue( triggerableIgnoreQueuedOperationsOnMerge.wasTriggered() ); + assertEquals( + "HHH000494: Attempt to merge an uninitialized collection with queued operations; queued operations will be ignored: [org.hibernate.test.collection.delayedOperation.DetachedBagDelayedOperationTest$Parent.children#1]", + triggerableIgnoreQueuedOperationsOnMerge.triggerMessage() + ); + triggerableIgnoreQueuedOperationsOnMerge.reset(); + + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + assertFalse( ( (AbstractPersistentCollection) p.getChildren() ).hasQueuedOperations() ); + + // When initialized, p.children will not include the new Child ("Zeke"), + // because that Child was flushed without a parent before being detached + // along with its parent. + Hibernate.initialize( p.getChildren() ); + final Set childNames = new HashSet( + Arrays.asList( new String[] { "Yogi", "Sherman" } ) + ); + assertEquals( childNames.size(), p.getChildren().size() ); + for ( Child child : p.getChildren() ) { + childNames.remove( child.getName() ); + } + assertEquals( 0, childNames.size() ); + } + ); + + checkTriggerablesNotTriggered(); + } + + @Test + @TestForIssue( jiraKey = "HHH-11209" ) + public void testSaveOrUpdateDetachedCollectionWithQueuedOperations() { + final Parent pOriginal = doInHibernate( + this::sessionFactory, session -> { + Parent p = session.get( Parent.class, 1L ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + // initialize + Hibernate.initialize( p.getChildren() ); + assertTrue( Hibernate.isInitialized( p.getChildren() ) ); + return p; + } + ); + final Parent pAfterDetachWithQueuedOperations = doInHibernate( + this::sessionFactory, session -> { + Parent p = (Parent) session.merge( pOriginal ); + Child c = new Child( "Zeke" ); + c.setParent( p ); + session.persist( c ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + p.getChildren().add( c ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + assertTrue( ( (AbstractPersistentCollection) p.getChildren() ).hasQueuedOperations() ); + + checkTriggerablesNotTriggered(); + session.detach( p ); + assertTrue( triggerableQueuedOperationWhenDetachFromSession.wasTriggered() ); + assertEquals( + "HHH000496: Detaching an uninitialized collection with queued operations from a session: [org.hibernate.test.collection.delayedOperation.DetachedBagDelayedOperationTest$Parent.children#1]", + triggerableQueuedOperationWhenDetachFromSession.triggerMessage() + ); + triggerableQueuedOperationWhenDetachFromSession.reset(); + + // Make sure nothing else got triggered + checkTriggerablesNotTriggered(); + + return p; + } + ); + + checkTriggerablesNotTriggered(); + + assertTrue( ( (AbstractPersistentCollection) pAfterDetachWithQueuedOperations.getChildren() ).hasQueuedOperations() ); + + // Save detached Parent with uninitialized collection with queued operations + doInHibernate( + this::sessionFactory, session -> { + + checkTriggerablesNotTriggered(); + + assertFalse( triggerableQueuedOperationWhenAttachToSession.wasTriggered() ); + session.saveOrUpdate( pAfterDetachWithQueuedOperations ); + assertTrue( triggerableQueuedOperationWhenAttachToSession.wasTriggered() ); + assertEquals( + "HHH000495: Attaching an uninitialized collection with queued operations to a session: [org.hibernate.test.collection.delayedOperation.DetachedBagDelayedOperationTest$Parent.children#1]", + triggerableQueuedOperationWhenAttachToSession.triggerMessage() + ); + triggerableQueuedOperationWhenAttachToSession.reset(); + + // Make sure nothing else got triggered + checkTriggerablesNotTriggered(); + + assertFalse( Hibernate.isInitialized( pAfterDetachWithQueuedOperations.getChildren() ) ); + assertTrue( ( (AbstractPersistentCollection) pAfterDetachWithQueuedOperations.getChildren() ).hasQueuedOperations() ); + + // Queued operations will be executed when the collection is initialized, + // After initialization, the collection will contain the Child that was added as a + // queued operation before being detached above. + Hibernate.initialize( pAfterDetachWithQueuedOperations.getChildren() ); + final Set childNames = new HashSet( + Arrays.asList( new String[] { "Yogi", "Sherman", "Zeke" } ) + ); + assertEquals( childNames.size(), pAfterDetachWithQueuedOperations.getChildren().size() ); + for ( Child child : pAfterDetachWithQueuedOperations.getChildren() ) { + childNames.remove( child.getName() ); + } + assertEquals( 0, childNames.size() ); + } + ); + + checkTriggerablesNotTriggered(); + } + + private void resetTriggerables() { + triggerableIgnoreQueuedOperationsOnMerge.reset(); + triggerableQueuedOperationWhenAttachToSession.reset(); + triggerableQueuedOperationWhenDetachFromSession.reset(); + } + + private void checkTriggerablesNotTriggered() { + assertFalse( triggerableIgnoreQueuedOperationsOnMerge.wasTriggered() ); + assertFalse( triggerableQueuedOperationWhenAttachToSession.wasTriggered() ); + assertFalse( triggerableQueuedOperationWhenDetachFromSession.wasTriggered() ); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + private Long id; + + // Don't need extra-lazy to delay add operations to a bag. + @OneToMany(mappedBy = "parent", cascade = CascadeType.DETACH) + private List children ; + + public Parent() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getChildren() { + return children; + } + + public void addChild(Child child) { + if ( children == null ) { + children = new ArrayList<>(); + } + children.add(child); + child.setParent(this); + } + } + + @Entity(name = "Child") + public static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false) + private String name; + + @ManyToOne + private Parent parent; + + public Child() { + } + + public Child(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + @Override + public String toString() { + return "Child{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + Child child = (Child) o; + + return name.equals( child.name ); + + } + + @Override + public int hashCode() { + return name.hashCode(); + } + } + +} From ed55fff92bb9c08d679421d4e560f6aa63125149 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 6 Nov 2018 18:22:46 -0800 Subject: [PATCH 196/772] HHH-11209 : Added test case for testing debug logging when collection is detached during rollback (cherry picked from commit 22ad668b88b8ff04769396dbe4e153be024d64dc) --- .../DetachedBagDelayedOperationTest.java | 49 +++++++++++++++++++ .../src/test/resources/log4j.properties | 1 + 2 files changed, 50 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/DetachedBagDelayedOperationTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/DetachedBagDelayedOperationTest.java index 2e5cea84d501..e6b2fd95e2ad 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/DetachedBagDelayedOperationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/delayedOperation/DetachedBagDelayedOperationTest.java @@ -15,6 +15,7 @@ import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EntityExistsException; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -41,6 +42,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Tests merge of detached PersistentBag @@ -70,6 +72,7 @@ protected Class[] getAnnotatedClasses() { private Triggerable triggerableIgnoreQueuedOperationsOnMerge; private Triggerable triggerableQueuedOperationWhenAttachToSession; private Triggerable triggerableQueuedOperationWhenDetachFromSession; + private Triggerable triggerableQueuedOperationOnRollback; @Before public void setup() { @@ -92,6 +95,7 @@ public void setup() { triggerableIgnoreQueuedOperationsOnMerge = logInspectionCollectionType.watchForLogMessages( "HHH000494" ); triggerableQueuedOperationWhenAttachToSession = logInspectionAbstractPersistentCollection.watchForLogMessages( "HHH000495" ); triggerableQueuedOperationWhenDetachFromSession = logInspectionAbstractPersistentCollection.watchForLogMessages( "HHH000496" ); + triggerableQueuedOperationOnRollback = logInspectionAbstractPersistentCollection.watchForLogMessages( "HHH000498" ); resetTriggerables(); } @@ -269,6 +273,51 @@ public void testSaveOrUpdateDetachedCollectionWithQueuedOperations() { checkTriggerablesNotTriggered(); } + @Test + @TestForIssue( jiraKey = "HHH-11209" ) + public void testCollectionWithQueuedOperationsOnRollback() { + final Parent pOriginal = doInHibernate( + this::sessionFactory, session -> { + Parent p = session.get( Parent.class, 1L ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + // initialize + Hibernate.initialize( p.getChildren() ); + assertTrue( Hibernate.isInitialized( p.getChildren() ) ); + return p; + } + ); + try { + doInHibernate( + this::sessionFactory, session -> { + Parent p = (Parent) session.merge( pOriginal ); + Child c = new Child( "Zeke" ); + c.setParent( p ); + session.persist( c ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + p.getChildren().add( c ); + assertFalse( Hibernate.isInitialized( p.getChildren() ) ); + assertTrue( ( (AbstractPersistentCollection) p.getChildren() ).hasQueuedOperations() ); + + checkTriggerablesNotTriggered(); + + // save a new Parent with the same ID to throw an exception. + + Parent pDup = new Parent(); + pDup.id = 1L; + session.persist( pDup ); + } + ); + fail( "should have thrown EntityExistsException" ); + } + catch (EntityExistsException expected) { + } + + assertTrue( triggerableQueuedOperationOnRollback.wasTriggered() ); + triggerableQueuedOperationOnRollback.reset(); + + checkTriggerablesNotTriggered(); + } + private void resetTriggerables() { triggerableIgnoreQueuedOperationsOnMerge.reset(); triggerableQueuedOperationWhenAttachToSession.reset(); diff --git a/hibernate-core/src/test/resources/log4j.properties b/hibernate-core/src/test/resources/log4j.properties index d288a3b43d79..ce6c1821422c 100644 --- a/hibernate-core/src/test/resources/log4j.properties +++ b/hibernate-core/src/test/resources/log4j.properties @@ -60,6 +60,7 @@ log4j.logger.org.hibernate.engine.internal.Cascade=trace log4j.logger.org.hibernate.testing.junit4.TestClassMetadata=info, unclosedSessionFactoryFile log4j.logger.org.hibernate.boot.model.process.internal.ScanningCoordinator=debug +log4j.logger.org.hibernate.collection.internal.AbstractPersistentCollection=debug log4j.logger.org.hibernate.cache trace log4j.logger.org.hibernate.stat trace \ No newline at end of file From cf189164445cb2ac82265e7f13d2071eb2d8bc25 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 6 Nov 2018 19:03:13 -0800 Subject: [PATCH 197/772] HHH-11209 : Log a DEBUG message if collection with queued operations is detached due to rollback (cherry picked from commit 7af7182cc1b2d3ec390d3f64a913a952767b8191) --- .../internal/AbstractPersistentCollection.java | 16 +++++++++++++++- .../hibernate/internal/CoreMessageLogger.java | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index 54f714770fce..633b7c2c4f94 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -38,6 +38,7 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; +import org.hibernate.resource.transaction.spi.TransactionStatus; import org.hibernate.type.CompositeType; import org.hibernate.type.IntegerType; import org.hibernate.type.LongType; @@ -625,7 +626,20 @@ public final boolean unsetSession(SharedSessionContractImplementor currentSessio if ( currentSession == this.session ) { if ( !isTempSession ) { if ( hasQueuedOperations() ) { - LOG.queuedOperationWhenDetachFromSession( MessageHelper.collectionInfoString( getRole(), getKey() ) ); + final String collectionInfoString = MessageHelper.collectionInfoString( getRole(), getKey() ); + final TransactionStatus transactionStatus = + session.getTransactionCoordinator().getTransactionDriverControl().getStatus(); + if ( transactionStatus.isOneOf( + TransactionStatus.ROLLED_BACK, + TransactionStatus.MARKED_ROLLBACK, + TransactionStatus.FAILED_COMMIT, + TransactionStatus.FAILED_ROLLBACK, + TransactionStatus.ROLLING_BACK + ) ) + LOG.queuedOperationWhenDetachFromSessionOnRollback( collectionInfoString ); + else { + LOG.queuedOperationWhenDetachFromSession( collectionInfoString ); + } } this.session = null; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 745f8d87c222..df3d26807f48 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1826,4 +1826,8 @@ void attemptToAssociateProxyWithTwoOpenSessions( @LogMessage(level = WARN) @Message(value = "Detaching an uninitialized collection with queued operations from a session: %s", id = 496) void queuedOperationWhenDetachFromSession(String collectionInfoString); + + @LogMessage(level = DEBUG) + @Message(value = "Detaching an uninitialized collection with queued operations from a session due to rollback: %s", id = 498) + void queuedOperationWhenDetachFromSessionOnRollback(String collectionInfoString); } From 8c5b1b7740d9430f572afc9c976361b596624a46 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 8 Nov 2018 21:28:04 -0800 Subject: [PATCH 198/772] HHH-11209 : Fix checkstyle error (cherry picked from commit 741c84a10cee915642a8c1f00cb8b9a92446930f) --- .../collection/internal/AbstractPersistentCollection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index 633b7c2c4f94..fc21c3d62c08 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -635,8 +635,9 @@ public final boolean unsetSession(SharedSessionContractImplementor currentSessio TransactionStatus.FAILED_COMMIT, TransactionStatus.FAILED_ROLLBACK, TransactionStatus.ROLLING_BACK - ) ) + ) ) { LOG.queuedOperationWhenDetachFromSessionOnRollback( collectionInfoString ); + } else { LOG.queuedOperationWhenDetachFromSession( collectionInfoString ); } From 36877bfd6932a6ccf7923872a420bc745c5a0c94 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 14 Nov 2018 00:25:24 -0800 Subject: [PATCH 199/772] HHH-11209 : Log a DEBUG message if collection with queued operations is detached due to rollback; otherwise, log INFO message (cherry picked from commit e12a68852413b2c0f6291894c9c719496dce8664) --- .../AbstractPersistentCollection.java | 32 ++++++++++++------- .../hibernate/internal/CoreMessageLogger.java | 2 +- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index fc21c3d62c08..dd80a81621dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -627,18 +627,28 @@ public final boolean unsetSession(SharedSessionContractImplementor currentSessio if ( !isTempSession ) { if ( hasQueuedOperations() ) { final String collectionInfoString = MessageHelper.collectionInfoString( getRole(), getKey() ); - final TransactionStatus transactionStatus = - session.getTransactionCoordinator().getTransactionDriverControl().getStatus(); - if ( transactionStatus.isOneOf( - TransactionStatus.ROLLED_BACK, - TransactionStatus.MARKED_ROLLBACK, - TransactionStatus.FAILED_COMMIT, - TransactionStatus.FAILED_ROLLBACK, - TransactionStatus.ROLLING_BACK - ) ) { - LOG.queuedOperationWhenDetachFromSessionOnRollback( collectionInfoString ); + try { + final TransactionStatus transactionStatus = + session.getTransactionCoordinator().getTransactionDriverControl().getStatus(); + if ( transactionStatus.isOneOf( + TransactionStatus.ROLLED_BACK, + TransactionStatus.MARKED_ROLLBACK, + TransactionStatus.FAILED_COMMIT, + TransactionStatus.FAILED_ROLLBACK, + TransactionStatus.ROLLING_BACK + ) ) { + // It was due to a rollback. + LOG.queuedOperationWhenDetachFromSessionOnRollback( collectionInfoString ); + } + else { + // We don't know why the collection is being detached. + // Just log the info. + LOG.queuedOperationWhenDetachFromSession( collectionInfoString ); + } } - else { + catch (Exception e) { + // We don't know why the collection is being detached. + // Just log the info. LOG.queuedOperationWhenDetachFromSession( collectionInfoString ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index df3d26807f48..9dc78ca98dd8 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1823,7 +1823,7 @@ void attemptToAssociateProxyWithTwoOpenSessions( @Message(value = "Attaching an uninitialized collection with queued operations to a session: %s", id = 495) void queuedOperationWhenAttachToSession(String collectionInfoString); - @LogMessage(level = WARN) + @LogMessage(level = INFO) @Message(value = "Detaching an uninitialized collection with queued operations from a session: %s", id = 496) void queuedOperationWhenDetachFromSession(String collectionInfoString); From 6a91c6b0c5976f4f120be1574fdcfd4c72bbe778 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 6 Nov 2018 14:02:53 -0500 Subject: [PATCH 200/772] HHH-13050 - Add test for issue (cherry picked from commit be0ee006ab1f7d530d219861eae19efc85040ad2) --- .../JtaWithStatementsBatchTest.java | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java new file mode 100644 index 000000000000..224b3d793256 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java @@ -0,0 +1,248 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; +import javax.persistence.FlushModeType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13050") +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +public class JtaWithStatementsBatchTest extends BaseEntityManagerFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AbstractBatchImpl.class.getName() ) + ); + + private Triggerable triggerable; + + @Before + public void setUp() { + triggerable = logInspection.watchForLogMessages( + "HHH000352: Unable to release batch statement..." ); + triggerable.reset(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Comment.class, EventLog.class }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + TestingJtaBootstrap.prepare( options ); + options.put( BatchBuilderInitiator.BUILDER, TestBatchBuilder.class.getName() ); + + options.put( AvailableSettings.JPA_TRANSACTION_TYPE, "JTA" ); + options.put( AvailableSettings.STATEMENT_BATCH_SIZE, "50" ); + } + + @Test + public void testPersist() { + EntityManager em = createEntityManager(); + EntityTransaction transaction = null; + try { + transaction = em.getTransaction(); + transaction.begin(); + + em.setFlushMode( FlushModeType.AUTO ); + + // Persist entity with non-generated id + EventLog eventLog1 = new EventLog(); + eventLog1.setMessage( "Foo1" ); + em.persist( eventLog1 ); + + // Persist entity with non-generated id + EventLog eventLog2 = new EventLog(); + eventLog2.setMessage( "Foo2" ); + em.persist( eventLog2 ); + + Comment comment = new Comment(); + comment.setMessage( "Bar" ); + em.persist( comment ); + + transaction.commit(); + } + finally { + assertThat( statements.size(), not( 0 ) ); + assertThat( numberOfStatementsAfterReleasing, is( 0 ) ); + statements.forEach( statement -> { + try { + assertThat( statement.isClosed(), is( true ) ); + } + catch (SQLException e) { + fail( e.getMessage() ); + } + } ); + if ( transaction != null && transaction.isActive() ) { + transaction.rollback(); + } + + em.close(); + } + + assertFalse( triggerable.wasTriggered() ); + + em = createEntityManager(); + + try { + transaction = em.getTransaction(); + transaction.begin(); + Integer savedComments + = em.createQuery( "from Comment" ).getResultList().size(); + assertThat( savedComments, is( 1 ) ); + + Integer savedEventLogs + = em.createQuery( "from EventLog" ).getResultList().size(); + assertThat( savedEventLogs, is( 2 ) ); + } + finally { + if ( transaction != null && transaction.isActive() ) { + transaction.rollback(); + } + em.close(); + } + } + + @Entity(name = "Comment") + public static class Comment { + private Long id; + private String message; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + + @Entity(name = "EventLog") + public static class EventLog { + private Long id; + private String message; + + @Id + @GeneratedValue(generator = "eventLogIdGenerator") + @GenericGenerator(name = "eventLogIdGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = { + @Parameter(name = "table_name", value = "primaryKeyPools"), + @Parameter(name = "segment_value", value = "eventLog"), + @Parameter(name = "optimizer", value = "pooled"), + @Parameter(name = "increment_size", value = "500"), + @Parameter(name = "initial_value", value = "1") + }) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + + private static int numberOfStatementsAfterReleasing; + private static List statements = new ArrayList<>(); + + public static class TestBatch extends BatchingBatch { + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + protected void releaseStatements() { + statements.addAll( getStatements().values() ); + super.releaseStatements(); + numberOfStatementsAfterReleasing += getStatements().size(); + } + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + private int jdbcBatchSize; + + @Override + public void setJdbcBatchSize(int jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; + } + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + } + } + +} From 1642853c89ec854be09310051d79b457e266527a Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 13 Nov 2018 00:07:07 -0800 Subject: [PATCH 201/772] HHH-13050 : Add test with a batch that fails when addToBatch() is called (cherry picked from commit a7fccaa377b306b60102c868dc69b750c4d37ba8) --- .../JtaWithStatementsBatchTest.java | 248 ------------------ .../batch/AbstractJtaBatchTest.java | 140 ++++++++++ .../batch/JtaWithFailingBatchTest.java | 170 ++++++++++++ .../batch/JtaWithStatementsBatchTest.java | 147 +++++++++++ 4 files changed, 457 insertions(+), 248 deletions(-) delete mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java deleted file mode 100644 index 224b3d793256..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * 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 . - */ - -/* - * 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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.jpa.test.transaction; - -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import javax.persistence.Entity; -import javax.persistence.EntityManager; -import javax.persistence.EntityTransaction; -import javax.persistence.FlushModeType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; - -import org.hibernate.annotations.GenericGenerator; -import org.hibernate.annotations.Parameter; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl; -import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; -import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; -import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; -import org.hibernate.engine.jdbc.batch.spi.Batch; -import org.hibernate.engine.jdbc.batch.spi.BatchKey; -import org.hibernate.engine.jdbc.spi.JdbcCoordinator; -import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.jta.TestingJtaBootstrap; -import org.hibernate.testing.logger.LoggerInspectionRule; -import org.hibernate.testing.logger.Triggerable; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import org.jboss.logging.Logger; - -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNot.not; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -/** - * @author Andrea Boriero - */ -@TestForIssue(jiraKey = "HHH-13050") -@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) -public class JtaWithStatementsBatchTest extends BaseEntityManagerFunctionalTestCase { - - @Rule - public LoggerInspectionRule logInspection = new LoggerInspectionRule( - Logger.getMessageLogger( CoreMessageLogger.class, AbstractBatchImpl.class.getName() ) - ); - - private Triggerable triggerable; - - @Before - public void setUp() { - triggerable = logInspection.watchForLogMessages( - "HHH000352: Unable to release batch statement..." ); - triggerable.reset(); - } - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Comment.class, EventLog.class }; - } - - @Override - protected void addConfigOptions(Map options) { - super.addConfigOptions( options ); - TestingJtaBootstrap.prepare( options ); - options.put( BatchBuilderInitiator.BUILDER, TestBatchBuilder.class.getName() ); - - options.put( AvailableSettings.JPA_TRANSACTION_TYPE, "JTA" ); - options.put( AvailableSettings.STATEMENT_BATCH_SIZE, "50" ); - } - - @Test - public void testPersist() { - EntityManager em = createEntityManager(); - EntityTransaction transaction = null; - try { - transaction = em.getTransaction(); - transaction.begin(); - - em.setFlushMode( FlushModeType.AUTO ); - - // Persist entity with non-generated id - EventLog eventLog1 = new EventLog(); - eventLog1.setMessage( "Foo1" ); - em.persist( eventLog1 ); - - // Persist entity with non-generated id - EventLog eventLog2 = new EventLog(); - eventLog2.setMessage( "Foo2" ); - em.persist( eventLog2 ); - - Comment comment = new Comment(); - comment.setMessage( "Bar" ); - em.persist( comment ); - - transaction.commit(); - } - finally { - assertThat( statements.size(), not( 0 ) ); - assertThat( numberOfStatementsAfterReleasing, is( 0 ) ); - statements.forEach( statement -> { - try { - assertThat( statement.isClosed(), is( true ) ); - } - catch (SQLException e) { - fail( e.getMessage() ); - } - } ); - if ( transaction != null && transaction.isActive() ) { - transaction.rollback(); - } - - em.close(); - } - - assertFalse( triggerable.wasTriggered() ); - - em = createEntityManager(); - - try { - transaction = em.getTransaction(); - transaction.begin(); - Integer savedComments - = em.createQuery( "from Comment" ).getResultList().size(); - assertThat( savedComments, is( 1 ) ); - - Integer savedEventLogs - = em.createQuery( "from EventLog" ).getResultList().size(); - assertThat( savedEventLogs, is( 2 ) ); - } - finally { - if ( transaction != null && transaction.isActive() ) { - transaction.rollback(); - } - em.close(); - } - } - - @Entity(name = "Comment") - public static class Comment { - private Long id; - private String message; - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - } - - @Entity(name = "EventLog") - public static class EventLog { - private Long id; - private String message; - - @Id - @GeneratedValue(generator = "eventLogIdGenerator") - @GenericGenerator(name = "eventLogIdGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = { - @Parameter(name = "table_name", value = "primaryKeyPools"), - @Parameter(name = "segment_value", value = "eventLog"), - @Parameter(name = "optimizer", value = "pooled"), - @Parameter(name = "increment_size", value = "500"), - @Parameter(name = "initial_value", value = "1") - }) - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - } - - private static int numberOfStatementsAfterReleasing; - private static List statements = new ArrayList<>(); - - public static class TestBatch extends BatchingBatch { - - public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { - super( key, jdbcCoordinator, batchSize ); - } - - protected void releaseStatements() { - statements.addAll( getStatements().values() ); - super.releaseStatements(); - numberOfStatementsAfterReleasing += getStatements().size(); - } - } - - public static class TestBatchBuilder extends BatchBuilderImpl { - private int jdbcBatchSize; - - @Override - public void setJdbcBatchSize(int jdbcBatchSize) { - this.jdbcBatchSize = jdbcBatchSize; - } - - @Override - public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { - return new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); - } - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java new file mode 100644 index 000000000000..062d5f3a4d1d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java @@ -0,0 +1,140 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Before; +import org.junit.Rule; + +import org.jboss.logging.Logger; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Andrea Boriero + */ +public abstract class AbstractJtaBatchTest extends BaseEntityManagerFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AbstractBatchImpl.class.getName() ) + ); + + protected Triggerable triggerable; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Comment.class, EventLog.class }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + TestingJtaBootstrap.prepare( options ); + options.put( BatchBuilderInitiator.BUILDER, getBatchBuilderClassName() ); + options.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + options.put( AvailableSettings.JPA_TRANSACTION_TYPE, "JTA" ); + options.put( AvailableSettings.STATEMENT_BATCH_SIZE, "50" ); + } + + @Before + public void setUp() { + triggerable = logInspection.watchForLogMessages( + "HHH000352: Unable to release batch statement..." ); + triggerable.reset(); + } + + protected void assertAllStatementsAreClosed(List statements) { + statements.forEach( statement -> { + try { + assertThat( "A PreparedStatement has not been closed", statement.isClosed(), is( true ) ); + } + catch (SQLException e) { + fail( e.getMessage() ); + } + } ); + } + + protected abstract String getBatchBuilderClassName(); + + @Entity(name = "Comment") + public static class Comment { + private Long id; + private String message; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + + @Entity(name = "EventLog") + public static class EventLog { + private Long id; + private String message; + + @Id + @GeneratedValue(generator = "eventLogIdGenerator") + @GenericGenerator(name = "eventLogIdGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = { + @Parameter(name = "table_name", value = "primaryKeyPools"), + @Parameter(name = "segment_value", value = "eventLog"), + @Parameter(name = "optimizer", value = "pooled"), + @Parameter(name = "increment_size", value = "500"), + @Parameter(name = "initial_value", value = "1") + }) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java new file mode 100644 index 000000000000..ee38b55626ad --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java @@ -0,0 +1,170 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Gail Badner + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13050") +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +public class JtaWithFailingBatchTest extends AbstractJtaBatchTest { + + private static TestBatch testBatch; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Comment.class, EventLog.class }; + } + + @Test + public void testAllStatementsAreClosedInCaseOfBatchExecutionFailure() throws Exception { + TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager(); + EntityManager em = createEntityManager(); + try { + transactionManager.begin(); + + em.setFlushMode( FlushModeType.AUTO ); + + // Persist entity with non-generated id + EventLog eventLog1 = new EventLog(); + eventLog1.setMessage( "Foo1" ); + em.persist( eventLog1 ); + + // Persist entity with non-generated id + EventLog eventLog2 = new EventLog(); + eventLog2.setMessage( "Foo2" ); + em.persist( eventLog2 ); + + Comment comment = new Comment(); + comment.setMessage( "Bar" ); + + try { + em.persist( comment ); + transactionManager.commit(); + } + catch (Exception expected) { + //expected + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + } + + assertThat( + "AbstractBatchImpl#releaseStatements() has not been callled", + testBatch.calledReleaseStatements, + is( true ) + ); + assertAllStatementsAreClosed( testBatch.createdStatements ); + assertStatementsListIsCleared(); + } + finally { + + em.close(); + } + + assertFalse( "HHH000352: Unable to release batch statement... has been thrown", triggerable.wasTriggered() ); + } + + private void assertStatementsListIsCleared() { + assertThat( testBatch.createdStatements.size(), not( 0 ) ); + assertThat( + "Not all PreparedStatements have been released", + testBatch.numberOfStatementsAfterReleasing, + is( 0 ) + ); + } + + public static class TestBatch extends BatchingBatch { + private int numberOfStatementsAfterReleasing; + private List createdStatements = new ArrayList<>(); + private boolean calledReleaseStatements; + + private String currentStatementSql; + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + @Override + public PreparedStatement getBatchStatement(String sql, boolean callable) { + currentStatementSql = sql; + PreparedStatement batchStatement = super.getBatchStatement( sql, callable ); + createdStatements.add( batchStatement ); + return batchStatement; + } + + @Override + public void addToBatch() { + // Implementations really should call abortBatch() before throwing an exception. + // Purposely skipping the call to abortBatch() to ensure that Hibernate works properly when + // a legacy implementation does not call abortBatch(). + throw sqlExceptionHelper().convert( + new SQLException( "fake SQLException" ), + "could not perform addBatch", + currentStatementSql + ); + } + + @Override + protected void releaseStatements() { + super.releaseStatements(); + calledReleaseStatements = true; + numberOfStatementsAfterReleasing += getStatements().size(); + } + } + + @Override + protected String getBatchBuilderClassName() { + return TestBatchBuilder.class.getName(); + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + private int jdbcBatchSize; + + @Override + public void setJdbcBatchSize(int jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; + } + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, jdbcBatchSize ); + } + + protected BatchingBatch buildBatchTest(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) { + testBatch = new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + return testBatch; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java new file mode 100644 index 000000000000..da0459e800a6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java @@ -0,0 +1,147 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13050") +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +public class JtaWithStatementsBatchTest extends AbstractJtaBatchTest { + + private static TestBatch testBatch; + + @Test + public void testUnableToReleaseStatementMessageIsNotLogged() + throws Exception { + TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager(); + EntityManager em = createEntityManager(); + try { + transactionManager.begin(); + + em.setFlushMode( FlushModeType.AUTO ); + + // Persist entity with non-generated id + EventLog eventLog1 = new EventLog(); + eventLog1.setMessage( "Foo1" ); + em.persist( eventLog1 ); + + // Persist entity with non-generated id + EventLog eventLog2 = new EventLog(); + eventLog2.setMessage( "Foo2" ); + em.persist( eventLog2 ); + + Comment comment = new Comment(); + comment.setMessage( "Bar" ); + em.persist( comment ); + + transactionManager.commit(); + assertStatementsListIsCleared(); + assertAllStatementsAreClosed( testBatch.createtdStatements ); + } + finally { + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + em.close(); + } + + assertFalse( "HHH000352: Unable to release batch statement... has been thrown", triggerable.wasTriggered() ); + + em = createEntityManager(); + + try { + transactionManager.begin(); + Integer savedComments + = em.createQuery( "from Comment" ).getResultList().size(); + assertThat( savedComments, is( 1 ) ); + + Integer savedEventLogs + = em.createQuery( "from EventLog" ).getResultList().size(); + assertThat( savedEventLogs, is( 2 ) ); + } + finally { + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + em.close(); + } + } + + private void assertStatementsListIsCleared() { + assertThat( testBatch.createtdStatements.size(), not( 0 ) ); + assertThat( + "Not all PreparedStatements have been released", + testBatch.numberOfStatementsAfterReleasing, + is( 0 ) + ); + } + + public static class TestBatch extends BatchingBatch { + private int numberOfStatementsAfterReleasing; + private List createtdStatements = new ArrayList<>(); + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + protected void releaseStatements() { + createtdStatements.addAll( getStatements().values() ); + super.releaseStatements(); + numberOfStatementsAfterReleasing += getStatements().size(); + } + } + + @Override + protected String getBatchBuilderClassName() { + return TestBatchBuilder.class.getName(); + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + private int jdbcBatchSize; + + @Override + public void setJdbcBatchSize(int jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; + } + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, jdbcBatchSize ); + } + + protected BatchingBatch buildBatchTest(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) { + testBatch = new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + return testBatch; + } + } +} From fdab459b82fdb552237821604c8ed252cdbc9857 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 6 Nov 2018 14:28:30 -0500 Subject: [PATCH 202/772] HHH-13050 - On release of batch it still contained JDBC statements logged (cherry picked from commit 11e71f6977fcf4366eb6875d9fc1ce46f029853c) --- .../jdbc/batch/internal/AbstractBatchImpl.java | 14 +++++++++++--- .../engine/jdbc/batch/internal/BatchingBatch.java | 1 + .../persister/entity/AbstractEntityPersister.java | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java index b27f6be90a66..03dfb5d98811 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java @@ -40,8 +40,8 @@ public abstract class AbstractBatchImpl implements Batch { private final SqlStatementLogger sqlStatementLogger; private final SqlExceptionHelper sqlExceptionHelper; - private LinkedHashMap statements = new LinkedHashMap(); - private LinkedHashSet observers = new LinkedHashSet(); + private LinkedHashMap statements = new LinkedHashMap<>(); + private LinkedHashSet observers = new LinkedHashSet<>(); protected AbstractBatchImpl(BatchKey key, JdbcCoordinator jdbcCoordinator) { if ( key == null ) { @@ -162,7 +162,15 @@ protected void releaseStatements() { protected void clearBatch(PreparedStatement statement) { try { - statement.clearBatch(); + // This code can be called after the connection is released + // and the statement is closed. If the statement is closed, + // then SQLException will be thrown when PreparedStatement#clearBatch + // is called. + // Ensure the statement is not closed before + // calling PreparedStatement#clearBatch. + if ( !statement.isClosed() ) { + statement.clearBatch(); + } } catch ( SQLException e ) { LOG.unableToReleaseBatchStatement(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java index 99310128787e..bf5f7e4b0767 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java @@ -77,6 +77,7 @@ public void addToBatch() { currentStatement.addBatch(); } catch ( SQLException e ) { + abortBatch(); LOG.debugf( "SQLException escaped proxy", e ); throw sqlExceptionHelper().convert( e, "could not perform addBatch", currentStatementSql ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 34dc8572c93c..d22ffe8eb078 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -29,6 +29,7 @@ import org.hibernate.EntityMode; import org.hibernate.FetchMode; import org.hibernate.HibernateException; +import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; @@ -3171,9 +3172,8 @@ protected void insert( .executeUpdate( insert ), insert, -1 ); } - } - catch (SQLException e) { + catch (SQLException | JDBCException e) { if ( useBatch ) { session.getJdbcCoordinator().abortBatch(); } From e20d66ad49690daa898982642f1166b461642c20 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 31 Oct 2018 18:24:13 +0100 Subject: [PATCH 203/772] HHH-10891 Add a test case (cherry picked from commit ac03494e7059803e058c8ea81043ce92e9388248) --- .../test/annotations/any/EmbeddedAnyTest.java | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java new file mode 100644 index 000000000000..b64902b8729f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java @@ -0,0 +1,213 @@ +/* + * 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.annotations.any; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Table; + +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyMetaDef; +import org.hibernate.annotations.MetaValue; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; + +public class EmbeddedAnyTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Foo.class, Bar1.class, Bar2.class }; + } + + @Test + public void testEmbeddedAny() { + doInJPA( this::entityManagerFactory, em -> { + Foo foo1 = new Foo(); + foo1.setId( 1 ); + + Bar1 bar1 = new Bar1(); + bar1.setId( 1 ); + bar1.setBar1( "bar 1" ); + bar1.setBarType( "1" ); + + FooEmbeddable foo1Embedded = new FooEmbeddable(); + foo1Embedded.setBar( bar1 ); + + foo1.setFooEmbedded( foo1Embedded ); + + em.persist( bar1 ); + em.persist( foo1 ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Foo foo2 = new Foo(); + foo2.setId( 2 ); + + Bar2 bar2 = new Bar2(); + bar2.setId( 2 ); + bar2.setBar2( "bar 2" ); + bar2.setBarType( "2" ); + + FooEmbeddable foo2Embedded = new FooEmbeddable(); + foo2Embedded.setBar( bar2 ); + + foo2.setFooEmbedded( foo2Embedded ); + + em.persist( bar2 ); + em.persist( foo2 ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Foo foo1 = em.find( Foo.class, 1 ); + + assertTrue( foo1.getFooEmbedded().getBar() instanceof Bar1 ); + assertEquals( "bar 1", ( (Bar1) foo1.getFooEmbedded().getBar() ).getBar1() ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Foo foo2 = em.find( Foo.class, 2 ); + + assertTrue( foo2.getFooEmbedded().getBar() instanceof Bar2 ); + assertEquals( "bar 2", ( (Bar2) foo2.getFooEmbedded().getBar() ).getBar2() ); + } ); + } + + @Entity(name = "Foo") + public static class Foo { + + @Id + private Integer id; + + @Embedded + private FooEmbeddable fooEmbedded; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public FooEmbeddable getFooEmbedded() { + return fooEmbedded; + } + + public void setFooEmbedded(FooEmbeddable fooEmbedded) { + this.fooEmbedded = fooEmbedded; + } + } + + @Embeddable + public static class FooEmbeddable { + + @AnyMetaDef(idType = "integer", metaType = "string", metaValues = { + @MetaValue(value = "1", targetEntity = Bar1.class), + @MetaValue(value = "2", targetEntity = Bar2.class) + }) + @Any(metaColumn = @Column(name = "bar_type")) + @JoinColumn(name = "bar_id") + private BarInt bar; + + public BarInt getBar() { + return bar; + } + + public void setBar(BarInt bar) { + this.bar = bar; + } + } + + public interface BarInt { + + String getBarType(); + } + + @Entity(name = "Bar1") + @Table(name = "bar") + public static class Bar1 implements BarInt { + + @Id + private Integer id; + + private String bar1; + + @Column(name = "bar_type") + private String barType; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getBar1() { + return bar1; + } + + public void setBar1(String bar1) { + this.bar1 = bar1; + } + + @Override + public String getBarType() { + return barType; + } + + public void setBarType(String barType) { + this.barType = barType; + } + } + + @Entity(name = "Bar2") + @Table(name = "bar") + public static class Bar2 implements BarInt { + + @Id + private Integer id; + + private String bar2; + + @Column(name = "bar_type") + private String barType; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getBar2() { + return bar2; + } + + public void setBar2(String bar2) { + this.bar2 = bar2; + } + + @Override + public String getBarType() { + return barType; + } + + public void setBarType(String barType) { + this.barType = barType; + } + } +} From 56f67fb0cd8764429c8fb8fceb0478a06b590c60 Mon Sep 17 00:00:00 2001 From: Keshavan Santhanam Date: Wed, 31 Oct 2018 16:53:26 +0530 Subject: [PATCH 204/772] HHH-10891 Support @Any in @Embedded (cherry picked from commit 64179673a332febe238db879e5cc911bed9654f3) --- .../tuple/component/AbstractCompositionAttribute.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java index 953cc19f5d83..f712fa4d6fc0 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java @@ -92,7 +92,6 @@ public AttributeDefinition next() { // we build the association-key here because of the "goofiness" with 'currentColumnPosition' final AssociationKey associationKey; final AssociationType aType = (AssociationType) type; - final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); if ( aType.isAnyType() ) { associationKey = new AssociationKey( @@ -111,6 +110,8 @@ public AttributeDefinition next() { ); } else if ( aType.getForeignKeyDirection() == ForeignKeyDirection.FROM_PARENT ) { + final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); + final String lhsTableName; final String[] lhsColumnNames; @@ -133,6 +134,8 @@ else if ( aType.getForeignKeyDirection() == ForeignKeyDirection.FROM_PARENT ) { associationKey = new AssociationKey( lhsTableName, lhsColumnNames ); } else { + final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); + associationKey = new AssociationKey( joinable.getTableName(), getRHSColumnNames( aType, sessionFactory() ) From fb3b710d9905e9f03361a4fc5d8c6df2658f2d10 Mon Sep 17 00:00:00 2001 From: Bolek Ziobrowski Date: Thu, 29 Nov 2018 18:15:18 +0100 Subject: [PATCH 205/772] HHH-13129 - Add test HHH-13129 : Move and reformat test HHH-13129 : Add more tests (cherry picked from commit c62f0a75cde09a773e24aaa0d26c2a1c41358060) --- .../cascade/CascadeDeleteCollectionTest.java | 256 ++++++++++++++++ .../cascade/CascadeDeleteManyToOneTest.java | 288 ++++++++++++++++++ .../cascade/CascadeDeleteTest.java | 156 ---------- .../cascade/CascadeOnUninitializedTest.java | 286 +++++++++++++++++ .../merge/MergeEnhancedEntityTest.java | 7 +- 5 files changed, 834 insertions(+), 159 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java new file mode 100644 index 000000000000..1f8860e41ca8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java @@ -0,0 +1,256 @@ +/* + * 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.bytecode.enhancement.cascade; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.hibernate.Hibernate; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Luis Barreiro + */ +@TestForIssue( jiraKey = "HHH-10252" ) +@RunWith( BytecodeEnhancerRunner.class ) +public class CascadeDeleteCollectionTest extends BaseCoreFunctionalTestCase { + private Parent originalParent; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{Parent.class, Child.class}; + } + + @Before + public void prepare() { + // Create a Parent with one Child + originalParent = doInHibernate( this::sessionFactory, s -> { + Parent p = new Parent(); + p.setName( "PARENT" ); + p.setLazy( "LAZY" ); + p.makeChild(); + s.persist( p ); + return p; + } + ); + } + + @Test + public void testManagedWithUninitializedAssociation() { + // Delete the Parent + doInHibernate( this::sessionFactory, s -> { + Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) + .setParameter( "name", "PARENT" ) + .uniqueResult(); + checkInterceptor( loadedParent, false ); + assertFalse( Hibernate.isPropertyInitialized( loadedParent, "children" ) ); + s.delete( loadedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testManagedWithInitializedAssociation() { + // Delete the Parent + doInHibernate( this::sessionFactory, s -> { + Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) + .setParameter( "name", "PARENT" ) + .uniqueResult(); + checkInterceptor( loadedParent, false ); + loadedParent.getChildren(); + assertTrue( Hibernate.isPropertyInitialized( loadedParent, "children" ) ); + s.delete( loadedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testDetachedWithUninitializedAssociation() { + final Parent detachedParent = doInHibernate( this::sessionFactory, s -> { + return s.get( Parent.class, originalParent.getId() ); + } ); + + assertFalse( Hibernate.isPropertyInitialized( detachedParent, "children" ) ); + + checkInterceptor( detachedParent, false ); + + // Delete the detached Parent with uninitialized children + doInHibernate( this::sessionFactory, s -> { + s.delete( detachedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testDetachedWithInitializedAssociation() { + final Parent detachedParent = doInHibernate( this::sessionFactory, s -> { + Parent parent = s.get( Parent.class, originalParent.getId() ); + assertFalse( Hibernate.isPropertyInitialized( parent, "children" ) ); + + // initialize collection before detaching + parent.getChildren(); + return parent; + } ); + + assertTrue( Hibernate.isPropertyInitialized( detachedParent, "children" ) ); + + checkInterceptor( detachedParent, false ); + + // Delete the detached Parent with initialized children + doInHibernate( this::sessionFactory, s -> { + s.delete( detachedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testDetachedOriginal() { + + // originalParent#children should be initialized + assertTrue( Hibernate.isPropertyInitialized( originalParent, "children" ) ); + + checkInterceptor( originalParent, true ); + + // Delete the Parent + doInHibernate( this::sessionFactory, s -> { + s.delete( originalParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + private void checkInterceptor(Parent parent, boolean isNullExpected) { + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = + sessionFactory() + .getMetamodel() + .entityPersister( Parent.class ) + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + if ( isNullExpected ) { + // if a null Interceptor is expected, then there shouldn't be any uninitialized attributes + assertFalse( bytecodeEnhancementMetadata.hasUnFetchedAttributes( parent ) ); + assertNull( bytecodeEnhancementMetadata.extractInterceptor( parent ) ); + } + else { + assertNotNull( bytecodeEnhancementMetadata.extractInterceptor( parent ) ); + } + } + + // --- // + + @Entity( name = "Parent" ) + @Table( name = "PARENT" ) + public static class Parent { + + Long id; + + String name; + + List children = new ArrayList<>(); + + String lazy; + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + @OneToMany( mappedBy = "parent", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY ) + List getChildren() { + return Collections.unmodifiableList( children ); + } + + void setChildren(List children) { + this.children = children; + } + + String getName() { + return name; + } + + void setName(String name) { + this.name = name; + } + + @Basic( fetch = FetchType.LAZY ) + String getLazy() { + return lazy; + } + + void setLazy(String lazy) { + this.lazy = lazy; + } + + void makeChild() { + Child c = new Child(); + c.setParent( this ); + children.add( c ); + } + } + + @Entity + @Table( name = "CHILD" ) + private static class Child { + + @Id + @GeneratedValue( strategy = GenerationType.AUTO ) + Long id; + + @ManyToOne( optional = false ) + @JoinColumn( name = "parent_id" ) + Parent parent; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + Parent getParent() { + return parent; + } + + void setParent(Parent parent) { + this.parent = parent; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java new file mode 100644 index 000000000000..5ba8d3a6016f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java @@ -0,0 +1,288 @@ +/* + * 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.bytecode.enhancement.cascade; + +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Luis Barreiro + */ +@TestForIssue(jiraKey = "HHH-10252") +@RunWith(BytecodeEnhancerRunner.class) +public class CascadeDeleteManyToOneTest extends BaseCoreFunctionalTestCase { + private Child originalChild; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class }; + } + + @Before + public void prepare() { + // Create a Parent with one Child + originalChild = doInHibernate( + this::sessionFactory, s -> { + Child c = new Child(); + c.setName( "CHILD" ); + c.setLazy( "LAZY" ); + c.makeParent(); + s.persist( c ); + return c; + } + ); + } + + @Test + public void testManagedWithUninitializedAssociation() { + // Delete the Child + doInHibernate( + this::sessionFactory, s -> { + Child loadedChild = (Child) s.createQuery( "SELECT c FROM Child c WHERE name=:name" ) + .setParameter( "name", "CHILD" ) + .uniqueResult(); + checkInterceptor( loadedChild, false ); + assertFalse( Hibernate.isPropertyInitialized( loadedChild, "parent" ) ); + s.delete( loadedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testManagedWithInitializedAssociation() { + // Delete the Child + doInHibernate( + this::sessionFactory, s -> { + Child loadedChild = (Child) s.createQuery( "SELECT c FROM Child c WHERE name=:name" ) + .setParameter( "name", "CHILD" ) + .uniqueResult(); + checkInterceptor( loadedChild, false ); + loadedChild.getParent(); + assertTrue( Hibernate.isPropertyInitialized( loadedChild, "parent" ) ); + s.delete( loadedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testDetachedWithUninitializedAssociation() { + final Child detachedChild = doInHibernate( + this::sessionFactory, s -> { + return s.get( Child.class, originalChild.getId() ); + } + ); + + assertFalse( Hibernate.isPropertyInitialized( detachedChild, "parent" ) ); + + checkInterceptor( detachedChild, false ); + + // Delete the detached Child with uninitialized parent + doInHibernate( + this::sessionFactory, s -> { + s.delete( detachedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testDetachedWithInitializedAssociation() { + final Child detachedChild = doInHibernate( + this::sessionFactory, s -> { + Child child = s.get( Child.class, originalChild.getId() ); + assertFalse( Hibernate.isPropertyInitialized( child, "parent" ) ); + + // initialize parent before detaching + child.getParent(); + return child; + } + ); + + assertTrue( Hibernate.isPropertyInitialized( detachedChild, "parent" ) ); + + checkInterceptor( detachedChild, false ); + + // Delete the detached Child with initialized parent + doInHibernate( + this::sessionFactory, s -> { + s.delete( detachedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testDetachedOriginal() { + + // originalChild#parent should be initialized + assertTrue( Hibernate.isPropertyInitialized( originalChild, "parent" ) ); + + checkInterceptor( originalChild, true ); + + // Delete the Child + doInHibernate( + this::sessionFactory, s -> { + s.delete( originalChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + private void checkInterceptor(Child child, boolean isNullExpected) { + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = + sessionFactory() + .getMetamodel() + .entityPersister( Child.class ) + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + if ( isNullExpected ) { + // if a null Interceptor is expected, then there shouldn't be any uninitialized attributes + assertFalse( bytecodeEnhancementMetadata.hasUnFetchedAttributes( child ) ); + assertNull( bytecodeEnhancementMetadata.extractInterceptor( child ) ); + } + else { + assertNotNull( bytecodeEnhancementMetadata.extractInterceptor( child ) ); + } + } + + // --- // + + @Entity(name = "Parent") + @Table(name = "PARENT") + public static class Parent { + + Long id; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + } + + @Entity(name = "Child") + @Table(name = "CHILD") + private static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + String name; + + @ManyToOne(optional = false, cascade = { + CascadeType.PERSIST, + CascadeType.MERGE, + CascadeType.REMOVE + }, fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + @LazyToOne(LazyToOneOption.NO_PROXY) + Parent parent; + + @Basic(fetch = FetchType.LAZY) + String lazy; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + String getName() { + return name; + } + + void setName(String name) { + this.name = name; + } + + Parent getParent() { + return parent; + } + + void setParent(Parent parent) { + this.parent = parent; + } + + String getLazy() { + return lazy; + } + + void setLazy(String lazy) { + this.lazy = lazy; + } + + void makeParent() { + parent = new Parent(); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTest.java deleted file mode 100644 index 7578d18ec08d..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTest.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.bytecode.enhancement.cascade; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import javax.persistence.Basic; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; -import javax.persistence.Table; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; - -/** - * @author Luis Barreiro - */ -@TestForIssue( jiraKey = "HHH-10252" ) -@RunWith( BytecodeEnhancerRunner.class ) -public class CascadeDeleteTest extends BaseCoreFunctionalTestCase { - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[]{Parent.class, Child.class}; - } - - @Before - public void prepare() { - // Create a Parent with one Child - doInHibernate( this::sessionFactory, s -> { - Parent p = new Parent(); - p.setName( "PARENT" ); - p.setLazy( "LAZY" ); - p.makeChild(); - s.persist( p ); - } - ); - } - - @Test - public void test() { - // Delete the Parent - doInHibernate( this::sessionFactory, s -> { - Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) - .setParameter( "name", "PARENT" ) - .uniqueResult(); - - s.delete( loadedParent ); - } ); - // If the lazy relation is not fetch on cascade there is a constraint violation on commit - } - - // --- // - - @Entity( name = "Parent" ) - @Table( name = "PARENT" ) - public static class Parent { - - Long id; - - String name; - - List children = new ArrayList<>(); - - String lazy; - - @Id - @GeneratedValue( strategy = GenerationType.AUTO ) - Long getId() { - return id; - } - - void setId(Long id) { - this.id = id; - } - - @OneToMany( mappedBy = "parent", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY ) - List getChildren() { - return Collections.unmodifiableList( children ); - } - - void setChildren(List children) { - this.children = children; - } - - String getName() { - return name; - } - - void setName(String name) { - this.name = name; - } - - @Basic( fetch = FetchType.LAZY ) - String getLazy() { - return lazy; - } - - void setLazy(String lazy) { - this.lazy = lazy; - } - - void makeChild() { - Child c = new Child(); - c.setParent( this ); - children.add( c ); - } - } - - @Entity - @Table( name = "CHILD" ) - private static class Child { - - @Id - @GeneratedValue( strategy = GenerationType.AUTO ) - Long id; - - @ManyToOne( optional = false ) - @JoinColumn( name = "parent_id" ) - Parent parent; - - Long getId() { - return id; - } - - void setId(Long id) { - this.id = id; - } - - Parent getParent() { - return parent; - } - - void setParent(Parent parent) { - this.parent = parent; - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java new file mode 100644 index 000000000000..39a2637f5096 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java @@ -0,0 +1,286 @@ +package org.hibernate.test.bytecode.enhancement.cascade; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Bolek Ziobrowski + * @author Gail Badner + */ +@RunWith(BytecodeEnhancerRunner.class) +@TestForIssue(jiraKey = "HHH-13129") +public class CascadeOnUninitializedTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Address.class, + }; + } + + @Override + protected void addSettings(Map settings) { + super.addSettings( settings ); + settings.put( AvailableSettings.SHOW_SQL, "true" ); + settings.put( AvailableSettings.FORMAT_SQL, "true" ); + } + + @Test + public void testMergeDetachedEnhancedEntityWithUninitializedManyToOne() { + + Person person = persistPersonWithManyToOne(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "primaryAddress" ) ); + detachedPerson.setName( "newName" ); + + Person mergedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return (Person) session.merge( detachedPerson ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( mergedPerson, "primaryAddress" ) ); + assertEquals( "newName", mergedPerson.getName() ); + } + + @Test + public void testDeleteEnhancedEntityWithUninitializedManyToOne() { + Person person = persistPersonWithManyToOne(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "primaryAddress" ) ); + + // deleting detachedPerson should result in detachedPerson.address being initialized, + // so that the DELETE operation can be cascaded to it. + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.delete( detachedPerson ); + } + ); + + // both the Person and its Address should be deleted + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + assertNull( session.get( Person.class, person.getId() ) ); + assertNull( session.get( Person.class, person.getPrimaryAddress().getId() ) ); + } + ); + } + + @Test + public void testMergeDetachedEnhancedEntityWithUninitializedOneToMany() { + + Person person = persistPersonWithOneToMany(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) ); + detachedPerson.setName( "newName" ); + + Person mergedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return (Person) session.merge( detachedPerson ); + } + ); + + // address should be initialized + assertTrue( Hibernate.isPropertyInitialized( mergedPerson, "addresses" ) ); + assertEquals( "newName", mergedPerson.getName() ); + } + + @Test + public void testDeleteEnhancedEntityWithUninitializedOneToMany() { + Person person = persistPersonWithOneToMany(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) ); + + // deleting detachedPerson should result in detachedPerson.address being initialized, + // so that the DELETE operation can be cascaded to it. + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.delete( detachedPerson ); + } + ); + + // both the Person and its Address should be deleted + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + assertNull( session.get( Person.class, person.getId() ) ); + assertNull( session.get( Person.class, person.getAddresses().iterator().next().getId() ) ); + } + ); + } + + public Person persistPersonWithManyToOne() { + Address address = new Address(); + address.setDescription( "ABC" ); + + final Person person = new Person(); + person.setName( "John Doe" ); + person.setPrimaryAddress( address ); + + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.persist( person ); + } + ); + + return person; + } + + public Person persistPersonWithOneToMany() { + Address address = new Address(); + address.setDescription( "ABC" ); + + final Person person = new Person(); + person.setName( "John Doe" ); + person.getAddresses().add( address ); + + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.persist( person ); + } + ); + + return person; + } + + @Entity + @Table(name = "TEST_PERSON") + public static class Person { + @Id + @GeneratedValue + private Long id; + + @Column(name = "NAME", length = 300, nullable = true) + private String name; + + @ManyToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY) + @JoinColumn(name = "ADDRESS_ID") + @LazyToOne(LazyToOneOption.NO_PROXY) + private Address primaryAddress; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn + private Set

    addresses = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getPrimaryAddress() { + return primaryAddress; + } + + public void setPrimaryAddress(Address primaryAddress) { + this.primaryAddress = primaryAddress; + } + + public Set
    getAddresses() { + return addresses; + } + + public void setAddresses(Set
    addresses) { + this.addresses = addresses; + } + } + + @Entity + @Table(name = "TEST_ADDRESS") + public static class Address { + @Id + @GeneratedValue + private Long id; + + @Column(name = "DESCRIPTION", length = 300, nullable = true) + private String description; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } +} + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java index acfff2416eec..672734428254 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/merge/MergeEnhancedEntityTest.java @@ -34,7 +34,7 @@ @TestForIssue( jiraKey = "HHH-11459" ) @RunWith( BytecodeEnhancerRunner.class ) public class MergeEnhancedEntityTest extends BaseCoreFunctionalTestCase { - + private Person person; @Override public Class[] getAnnotatedClasses() { return new Class[]{Person.class, PersonAddress.class}; @@ -42,8 +42,9 @@ public Class[] getAnnotatedClasses() { @Before public void prepare() { + person = new Person( 1L, "Sam" ); doInHibernate( this::sessionFactory, s -> { - s.persist( new Person( 1L, "Sam" ) ); + s.persist( person ); } ); } @@ -76,7 +77,7 @@ public void testRefresh() { @After public void cleanup() { doInHibernate( this::sessionFactory, s -> { - s.delete( new Person( 1L, "Sam" ) ); + s.delete( person ); } ); } From 438733d32ce4d5deadb970c571354b985ff0bf09 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 8 Jan 2019 19:26:15 -0800 Subject: [PATCH 206/772] HHH-13129 : Cascaded merge fails for detached bytecode-enhanced entity with uninitialized ToOne (cherry picked from commit a66ca0463ea5d965238aeaa7f63c215bbb49675e) --- .../hibernate/engine/internal/Cascade.java | 51 +++++++++++++------ .../internal/DefaultDeleteEventListener.java | 1 + 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index 08a42c420d3f..6afa9ce858d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -12,12 +12,10 @@ import java.util.Iterator; import org.hibernate.HibernateException; -import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadingAction; -import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.Status; @@ -102,25 +100,46 @@ public static void cascade( final Object child; if ( isUninitializedProperty ) { // parent is a bytecode enhanced entity. - // cascading to an uninitialized, lazy value. + // Cascade to an uninitialized, lazy value only if + // parent is managed in the PersistenceContext. + // If parent is a detached entity being merged, + // then parent will not be in the PersistencContext + // (so lazy attributes must not be initialized). + if ( eventSource.getPersistenceContext().getEntry( parent ) == null ) { + // parent was not in the PersistenceContext + continue; + } if ( types[ i ].isCollectionType() ) { - // The collection does not need to be loaded from the DB. - // CollectionType#resolve will return an uninitialized PersistentCollection. - // The action will initialize the collection later, if necessary. - child = types[ i ].resolve( LazyPropertyInitializer.UNFETCHED_PROPERTY, eventSource, parent ); - // TODO: it would be nice to be able to set the attribute in parent using - // persister.setPropertyValue( parent, i, child ). - // Unfortunately, that would cause the uninitialized collection to be - // loaded from the DB. + // CollectionType#getCollection gets the PersistentCollection + // that corresponds to the uninitialized collection from the + // PersistenceContext. If not present, an uninitialized + // PersistentCollection will be added to the PersistenceContext. + // The action may initialize it later, if necessary. + // This needs to be done even when action.performOnLazyProperty() returns false. + final CollectionType collectionType = (CollectionType) types[i]; + child = collectionType.getCollection( + collectionType.getKeyOfOwner( parent, eventSource ), + eventSource, + parent, + null + ); } - else if ( action.performOnLazyProperty() ) { - // The (non-collection) attribute needs to be initialized so that - // the action can be performed on the initialized attribute. - LazyAttributeLoadingInterceptor interceptor = persister.getInstrumentationMetadata().extractInterceptor( parent ); + else if ( types[ i ].isComponentType() ) { + // Hibernate does not support lazy embeddables, so this shouldn't happen. + throw new UnsupportedOperationException( + "Lazy components are not supported." + ); + } + else if ( action.performOnLazyProperty() && types[ i ].isEntityType() ) { + // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() + // returns true. + LazyAttributeLoadingInterceptor interceptor = persister.getInstrumentationMetadata() + .extractInterceptor( parent ); child = interceptor.fetchAttribute( parent, propertyName ); + } else { - // Nothing to do, so just skip cascading to this lazy (non-collection) attribute. + // Nothing to do, so just skip cascading to this lazy attribute. continue; } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java index d6c17287f00b..bfb60701661c 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java @@ -131,6 +131,7 @@ public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateE persister, false ); + persister.afterReassociate( entity, source ); } else { LOG.trace( "Deleting a persistent instance" ); From e88e45cf38032d221d8c0964489eddc1503de85f Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 5 Nov 2018 10:33:14 +0000 Subject: [PATCH 207/772] HHH-13076 - Add test for issue (cherry picked from commit 0fa4b50188f435aeecf2c2d7150dd27a25ab2931) --- .../jdbc/AlreadyStartedTransactionTest.java | 37 ++++++++ ...mplianceAlreadyStartedTransactionTest.java | 66 ++++++++++++++ ...mplianceAlreadyStartedTransactionTest.java | 90 +++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/AlreadyStartedTransactionTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/JpaComplianceAlreadyStartedTransactionTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/NonJpaComplianceAlreadyStartedTransactionTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/AlreadyStartedTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/AlreadyStartedTransactionTest.java new file mode 100644 index 000000000000..9ec157d08b7e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/AlreadyStartedTransactionTest.java @@ -0,0 +1,37 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.resource.transaction.jdbc; + +import org.hibernate.Session; +import org.hibernate.Transaction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13076") +public class AlreadyStartedTransactionTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test(expected = IllegalStateException.class) + public void anIllegalStateExceptionShouldBeThrownWhenBeginTxIsCalledWithAnAlreadyActiveTX() { + Transaction transaction = null; + try (Session session = openSession()) { + transaction = session.getTransaction(); + transaction.begin(); + // A call to begin() with an active Tx should cause an IllegalStateException + transaction.begin(); + } + finally { + if ( transaction != null && transaction.isActive() ) { + transaction.rollback(); + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/JpaComplianceAlreadyStartedTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/JpaComplianceAlreadyStartedTransactionTest.java new file mode 100644 index 000000000000..ac12f10b344b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/JpaComplianceAlreadyStartedTransactionTest.java @@ -0,0 +1,66 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.resource.transaction.jta; + +import java.util.Map; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13076") +public class JpaComplianceAlreadyStartedTransactionTest extends BaseNonConfigCoreFunctionalTestCase { + private TransactionManager tm; + + @Override + protected void addSettings(Map settings) { + super.addSettings( settings ); + TestingJtaBootstrap.prepare( settings ); + settings.put( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" ); + settings.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + } + + @Before + public void setUp() { + tm = JtaPlatformStandardTestingImpl.INSTANCE.transactionManager(); + } + + @Test(expected = IllegalStateException.class) + public void anIllegalStateExceptionShouldBeThrownWhenBeginTxIsCalledWithAnAlreadyActiveTX() throws Exception { + try (Session s = openSession()) { + tm.begin(); + Transaction tx = null; + try { + // A call to begin() with an active Tx should cause an IllegalStateException + tx = s.beginTransaction(); + } + catch (Exception e) { + if ( tx != null && tx.isActive() ) { + tx.rollback(); + } + throw e; + } + } + catch (Exception e) { + if ( tm.getStatus() == Status.STATUS_ACTIVE ) { + tm.rollback(); + } + throw e; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/NonJpaComplianceAlreadyStartedTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/NonJpaComplianceAlreadyStartedTransactionTest.java new file mode 100644 index 000000000000..78e501c6f299 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jta/NonJpaComplianceAlreadyStartedTransactionTest.java @@ -0,0 +1,90 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.resource.transaction.jta; + +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13076") +public class NonJpaComplianceAlreadyStartedTransactionTest extends BaseNonConfigCoreFunctionalTestCase { + private TransactionManager tm; + + @Override + protected void addSettings(Map settings) { + super.addSettings( settings ); + TestingJtaBootstrap.prepare( settings ); + settings.put( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Before + public void setUp() { + tm = JtaPlatformStandardTestingImpl.INSTANCE.transactionManager(); + } + + @Test + public void noIllegalStateExceptionShouldBeThrownWhenBeginTxIsCalledWithAnAlreadyActiveTx() throws Exception { + tm.begin(); + try (Session s = openSession()) { + Transaction tx = s.beginTransaction(); + try { + s.saveOrUpdate( new TestEntity( "ABC" ) ); + tx.commit(); + } + catch (Exception e) { + if ( tx.isActive() ) { + tx.rollback(); + } + throw e; + } + } + try { + tm.commit(); + } + catch (Exception e) { + if ( tm.getStatus() == Status.STATUS_ACTIVE ) { + tm.rollback(); + } + throw e; + } + } + + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + @GeneratedValue + private Long id; + + private String stringAttribute; + + public TestEntity(String stringAttribute) { + this.stringAttribute = stringAttribute; + } + } +} From 14f5473fef06680d04efa92a6c444bb51123f81d Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 9 Nov 2018 09:15:07 +0000 Subject: [PATCH 208/772] HHH-13076 - Hibernate 'Transaction already active' behaviour with JTA transaction manager (cherry picked from commit a15dfe0e056c55664846b621c0d880ef2e367fff) --- .../engine/transaction/internal/TransactionImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java index 33d03f6f639d..42879d569e52 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java @@ -72,7 +72,13 @@ public void begin() { // per-JPA if ( isActive() ) { - throw new IllegalStateException( "Transaction already active" ); + if ( jpaCompliance.isJpaTransactionComplianceEnabled() + || !transactionCoordinator.getTransactionCoordinatorBuilder().isJta() ) { + throw new IllegalStateException( "Transaction already active" ); + } + else { + return; + } } LOG.debug( "begin" ); From b1ffde0e9444ace5ab2cda3857cdf1d82285fd8f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 25 Dec 2018 14:06:43 +0100 Subject: [PATCH 209/772] HHH-13172 Log a warning instead of throwing an exception when @AttributeOverride is used in conjunction with entity inheritance (cherry picked from commit 2dd008adb7e39a8513a027582ba8a55ee7b4eedb) --- .../org/hibernate/cfg/AnnotationBinder.java | 7 +--- .../hibernate/internal/CoreMessageLogger.java | 2 + ...ntityInheritanceAttributeOverrideTest.java | 37 ++++++++++--------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 5f421537178e..176947751a96 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -561,10 +561,7 @@ public static void bindClass( if(superEntity != null && ( clazzToProcess.getAnnotation( AttributeOverride.class ) != null || clazzToProcess.getAnnotation( AttributeOverrides.class ) != null ) ) { - throw new AnnotationException( - "An entity annotated with @Inheritance cannot use @AttributeOverride or @AttributeOverrides: " + - clazzToProcess.getName() - ); + LOG.unsupportedAttributeOverrideWithEntityInheritance( clazzToProcess.getName() ); } PersistentClass persistentClass = makePersistentClass( inheritanceState, superEntity, context ); @@ -3004,7 +3001,7 @@ private static void bindManyToOne( column.setUpdatable( false ); } } - + final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class ); final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 9dc78ca98dd8..d01ad6367b67 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1830,4 +1830,6 @@ void attemptToAssociateProxyWithTwoOpenSessions( @LogMessage(level = DEBUG) @Message(value = "Detaching an uninitialized collection with queued operations from a session due to rollback: %s", id = 498) void queuedOperationWhenDetachFromSessionOnRollback(String collectionInfoString); + @Message(value = "Using @AttributeOverride or @AttributeOverrides in conjunction with entity inheritance is not supported: %s. The overriding definitions are ignored.", id = 499) + void unsupportedAttributeOverrideWithEntityInheritance(String entityName); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java index e39188d0b73c..7ed667757d8f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java @@ -6,36 +6,39 @@ */ package org.hibernate.test.annotations.override.inheritance; +import static org.junit.Assert.assertTrue; + import javax.persistence.AttributeOverride; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; -import javax.persistence.MappedSuperclass; import javax.persistence.Table; import javax.persistence.UniqueConstraint; -import org.hibernate.AnnotationException; +import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; - -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.jboss.logging.Logger; +import org.junit.Rule; import org.junit.Test; -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - /** * @author Vlad Mihalcea */ -@TestForIssue( jiraKey = "HHH-12609, HHH-12654" ) +@TestForIssue( jiraKey = "HHH-12609, HHH-12654, HHH-13172" ) public class EntityInheritanceAttributeOverrideTest extends BaseEntityManagerFunctionalTestCase { + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AnnotationBinder.class.getName() ) ); + @Override - public Class[] getAnnotatedClasses() { + public Class[] getAnnotatedClasses() { return new Class[]{ CategoryEntity.class, TaxonEntity.class, @@ -45,13 +48,11 @@ public Class[] getAnnotatedClasses() { @Override public void buildEntityManagerFactory() { - try { - super.buildEntityManagerFactory(); - fail("Should throw AnnotationException"); - } - catch (AnnotationException e) { - assertTrue( e.getMessage().startsWith( "An entity annotated with @Inheritance cannot use @AttributeOverride or @AttributeOverrides" ) ); - } + Triggerable warningLogged = logInspection.watchForLogMessages( "HHH000499:" ); + + super.buildEntityManagerFactory(); + + assertTrue("A warning should have been logged for this unsupported configuration", warningLogged.wasTriggered()); } @Test From d9e7428cbe49187df2e862457871ebb1583ffcec Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 7 Feb 2019 14:31:29 -0800 Subject: [PATCH 210/772] HHH-13172 : Add @LogMessage(level = WARN) to CoreMessageLogger#unsupportedAttributeOverrideWithEntityInheritance (was lost due to bad conflict resolution) --- .../src/main/java/org/hibernate/internal/CoreMessageLogger.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index d01ad6367b67..07bc8d4c58de 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1830,6 +1830,8 @@ void attemptToAssociateProxyWithTwoOpenSessions( @LogMessage(level = DEBUG) @Message(value = "Detaching an uninitialized collection with queued operations from a session due to rollback: %s", id = 498) void queuedOperationWhenDetachFromSessionOnRollback(String collectionInfoString); + + @LogMessage(level = WARN) @Message(value = "Using @AttributeOverride or @AttributeOverrides in conjunction with entity inheritance is not supported: %s. The overriding definitions are ignored.", id = 499) void unsupportedAttributeOverrideWithEntityInheritance(String entityName); } From 516eac225e90360f0a1d70f7de76b2aad047bc49 Mon Sep 17 00:00:00 2001 From: Moritz Becker Date: Wed, 19 Dec 2018 15:02:44 +0100 Subject: [PATCH 211/772] HHH-13169 - Use exact table name for multitable update queries instead of table alias (cherry picked from commit 8f748db9b023ebb9e69ab9cfee361aed52d8cc35) --- .../internal/ast/tree/FromElementType.java | 11 ++- .../UpdateJoinedSubclassCorrelationTest.java | 91 +++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/UpdateJoinedSubclassCorrelationTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java index 872e6dcb1e8b..53e1ca2dac46 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java @@ -456,11 +456,16 @@ String[] toColumns(String tableAlias, String path, boolean inSelect, boolean for // executors being used (as this subquery will // actually be used in the "id select" phase // of that multi-table executor) + // for update queries, the real table name of the updated table must be used if not in the top level where + // clause, typically in a SET clause // B) otherwise, we need to use the persister's // table name as the column qualification // 2) otherwise (not correlated), use the given alias if ( isCorrelation() ) { - if ( isMultiTable() || isInsertQuery() ) { + if ( isMultiTable() && ( !isUpdateQuery() || inWhereClause() ) ) { + return propertyMapping.toColumns( tableAlias, path ); + } + else if ( isInsertQuery() ) { return propertyMapping.toColumns( tableAlias, path ); } return propertyMapping.toColumns( extractTableName(), path ); @@ -505,6 +510,10 @@ private boolean isInsertQuery() { return fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.INSERT; } + private boolean isUpdateQuery() { + return fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE; + } + private boolean isManipulationQuery() { return fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE || fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.DELETE; diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/UpdateJoinedSubclassCorrelationTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/UpdateJoinedSubclassCorrelationTest.java new file mode 100644 index 000000000000..1939a48f5931 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/UpdateJoinedSubclassCorrelationTest.java @@ -0,0 +1,91 @@ +package org.hibernate.test.hql; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +import javax.persistence.*; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; + +import java.util.List; + +import static javax.persistence.InheritanceType.JOINED; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +@TestForIssue(jiraKey = "HHH-13169") +public class UpdateJoinedSubclassCorrelationTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Master.class, SubMaster.class, Detail.class }; + } + + @Test + public void testJoinedSubclassUpdateWithCorrelation() { + // prepare + doInJPA( this::entityManagerFactory, entityManager -> { + Master m1 = new SubMaster( 1, null ); + entityManager.persist( m1 ); + Detail d11 = new Detail( 10, m1 ); + entityManager.persist( d11 ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + // DO NOT CHANGE this query: it used to trigger a very specific bug caused + // by the root table alias being added to the generated subquery instead of the table name + String u = "update SubMaster m set name = (select 'test' from Detail d where d.master = m)"; + Query updateQuery = entityManager.createQuery( u ); + updateQuery.executeUpdate(); + + // so check if the name of the SubMaster has been correctly updated + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Master.class ); + query.select( query.from( Master.class ) ); + List masters = entityManager.createQuery( query ).getResultList(); + Assert.assertEquals( 1, masters.size() ); + Assert.assertEquals( "test", ((SubMaster) masters.get(0)).name ); + } ); + } + + @Inheritance(strategy = JOINED) + @Entity(name = "Master") + public static abstract class Master { + @Id + private Integer id; + + public Master() { } + + public Master( Integer id ) { + this.id = id; + } + } + + @Entity(name = "SubMaster") + public static class SubMaster extends Master { + private String name; + + public SubMaster() { } + + public SubMaster( Integer id, String name ) { + super(id); + this.name = name; + } + } + + @Entity(name = "Detail") + public static class Detail { + @Id + private Integer id; + + @ManyToOne(optional = false) + private Master master; + + public Detail( Integer id, Master master ) { + this.id = id; + this.master = master; + } + } +} + From 6002a7bd7aa4509e90803957a642f214c793324d Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Mon, 22 Oct 2018 18:15:58 -0400 Subject: [PATCH 212/772] HHH-13164 - Revert change made for HHH-12464. (cherry picked from commit dc873c3d3672f24bf6f6a1bd15e53d31cfdaf523) --- .../action/internal/EntityAction.java | 15 +---------- .../org/hibernate/id/CreateDeleteTest.java | 26 ------------------- 2 files changed, 1 insertion(+), 40 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java index 59bf7c049e76..35d2ce984cfe 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java @@ -12,7 +12,6 @@ import org.hibernate.action.spi.AfterTransactionCompletionProcess; import org.hibernate.action.spi.BeforeTransactionCompletionProcess; import org.hibernate.action.spi.Executable; -import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistry; @@ -101,19 +100,7 @@ public String getEntityName() { */ public final Serializable getId() { if ( id instanceof DelayedPostInsertIdentifier ) { - final EntityEntry entry = session.getPersistenceContext().getEntry( instance ); - if ( entry == null ) { - if ( LOG.isDebugEnabled() ) { - LOG.debugf( - "Skipping action - the persistence context does not contain any entry for the entity [%s]. This may occur if an entity is created and then deleted in the same transaction/flush.", - instance - ); - } - // If an Entity is created and then deleted in the same Transaction, when Action#postDelete() calls this method the persistence context - // does not contain anymore an entry. - return null; - } - final Serializable eeId = entry.getId(); + final Serializable eeId = session.getPersistenceContext().getEntry( instance ).getId(); return eeId instanceof DelayedPostInsertIdentifier ? null : eeId; } return id; diff --git a/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java b/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java index f893fccc1b64..468b511c8b3e 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java @@ -7,44 +7,20 @@ package org.hibernate.id; import org.hibernate.FlushMode; -import org.hibernate.action.internal.EntityAction; import org.hibernate.dialect.AbstractHANADialect; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.logger.LoggerInspectionRule; -import org.hibernate.testing.logger.Triggerable; -import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.jboss.logging.Logger; - -import static junit.framework.TestCase.assertTrue; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) @TestForIssue(jiraKey = "HHH-12464") public class CreateDeleteTest extends BaseCoreFunctionalTestCase { - - @Rule - public LoggerInspectionRule logInspection = new LoggerInspectionRule( - Logger.getMessageLogger( CoreMessageLogger.class, EntityAction.class.getName() ) - ); - - private Triggerable triggerable; - - @Before - public void setUp() { - triggerable = logInspection.watchForLogMessages( - "Skipping action - the persistence context does not contain any entry for the entity" ); - triggerable.reset(); - } - @Test @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") public void createAndDeleteAnEntityInTheSameTransactionTest() { @@ -54,8 +30,6 @@ public void createAndDeleteAnEntityInTheSameTransactionTest() { session.persist( entity ); session.delete( entity ); } ); - - assertTrue( triggerable.wasTriggered() ); } @Override From 7ea75516b695ee0ffe2a6ce8b8225f2b545c7fab Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Thu, 10 Jan 2019 14:32:34 -0500 Subject: [PATCH 213/772] HHH-13164 - Revert changes made for HHH-11019. --- .../internal/AbstractSaveEventListener.java | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java index 120f6c4e0d92..39806b7d69f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java @@ -9,7 +9,6 @@ import java.io.Serializable; import java.util.Map; -import org.hibernate.FlushMode; import org.hibernate.LockMode; import org.hibernate.NonUniqueObjectException; import org.hibernate.action.internal.AbstractEntityInsertAction; @@ -39,9 +38,6 @@ import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; -import static org.hibernate.FlushMode.COMMIT; -import static org.hibernate.FlushMode.MANUAL; - /** * A convenience base class for listeners responding to save events. * @@ -248,7 +244,8 @@ protected Serializable performSaveOrReplicate( Serializable id = key == null ? null : key.getIdentifier(); - boolean shouldDelayIdentityInserts = shouldDelayIdentityInserts( requiresImmediateIdAccess, source ); + boolean inTrx = source.isTransactionInProgress(); + boolean shouldDelayIdentityInserts = !inTrx && !requiresImmediateIdAccess; // Put a placeholder in entries, so we don't recurse back and try to save() the // same object again. QUESTION: should this be done before onSave() is called? @@ -320,30 +317,6 @@ protected Serializable performSaveOrReplicate( return id; } - private static boolean shouldDelayIdentityInserts(boolean requiresImmediateIdAccess, EventSource source) { - return shouldDelayIdentityInserts( requiresImmediateIdAccess, isPartOfTransaction( source ), source.getHibernateFlushMode() ); - } - - private static boolean shouldDelayIdentityInserts( - boolean requiresImmediateIdAccess, - boolean partOfTransaction, - FlushMode flushMode) { - if ( requiresImmediateIdAccess ) { - // todo : make this configurable? as a way to support this behavior with Session#save etc - return false; - } - - // otherwise we should delay the IDENTITY insertions if either: - // 1) we are not part of a transaction - // 2) we are in FlushMode MANUAL or COMMIT (not AUTO nor ALWAYS) - return !partOfTransaction || flushMode == MANUAL || flushMode == COMMIT; - - } - - private static boolean isPartOfTransaction(EventSource source) { - return source.isTransactionInProgress() && source.getTransactionCoordinator().isJoined(); - } - private AbstractEntityInsertAction addInsertAction( Object[] values, Serializable id, From 855dafc02921edad3ec03d06a0adb9c4dd25f488 Mon Sep 17 00:00:00 2001 From: Stoty Date: Fri, 1 Feb 2019 15:05:03 +0100 Subject: [PATCH 214/772] HHH-13244 - setting hibernate.jpa.compliance.proxy=true and org.hibernate debug level to DEBUG breaks hibernate test case simplify test case (cherry picked from commit 80ff6b4fe64bb69ae730a5db02084ea1b504f063) --- .../jpa/test/JpaProxyComplianceWithDebug.java | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java new file mode 100644 index 000000000000..6423649fed84 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java @@ -0,0 +1,228 @@ +package org.hibernate.jpa.test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-13244") +public class JpaProxyComplianceWithDebug extends BaseEntityManagerFunctionalTestCase { + + @Override + protected void addConfigOptions(Map options) { + options.put( + AvailableSettings.JPA_PROXY_COMPLIANCE, + Boolean.TRUE); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + MvnoBillingAgreement.class, + MvnoOpcio.class, + }; + } + + @Before + public void setUp() { + List opciok = Arrays.asList(2008, 2010, 2012, 2014, 2015, 2026, 2027, 2103, 2110, 2145, 992068, 992070); + + doInJPA(this::entityManagerFactory, entityManager -> { + + MvnoBillingAgreement ba = new MvnoBillingAgreement(); + ba.setId(1); + ba.setName("1"); + entityManager.persist(ba); + + for (int opcioId : opciok) { + MvnoOpcio o = new MvnoOpcio(); + o.setId(opcioId); + o.setMegnevezes(Integer.toString(opcioId)); + o.getMvnoBillingAgreementekDefaultOpcioja().add(ba); + ba.getMvnoDefaultUniverzalisOpcioi().add(o); + entityManager.persist(o); + } + + ba.setBehajtasEgyiranyusitasOpcio(entityManager.find(MvnoOpcio.class, 2026)); + ba.setBehajtasFelfuggesztesOpcio(entityManager.find(MvnoOpcio.class, 992070)); + ba.setHotlimitEmeltDijasBarOpcio(entityManager.find(MvnoOpcio.class, 2145)); + + }); + } + + + @Test + @TestForIssue(jiraKey = "HHH-13244") + public void testJpaComplianceProxyWithDebug() { + + //This could be replaced with setting the root logger level, or the "org.hibernate" logger to debug. + //These are simply the narrowest log settings that trigger the bug + Logger entityLogger = LogManager.getLogger("org.hibernate.internal.util.EntityPrinter"); + Logger listenerLogger = LogManager.getLogger("org.hibernate.event.internal.AbstractFlushingEventListener"); + + Level oldEntityLogLevel = entityLogger.getLevel(); + Level oldListenerLogLevel = listenerLogger.getLevel(); + + entityLogger.setLevel((Level) Level.DEBUG); + listenerLogger.setLevel((Level) Level.DEBUG); + try { + doInJPA(this::entityManagerFactory, entityManager -> { + entityManager.find(MvnoBillingAgreement.class, 1); + }); + } finally { + entityLogger.setLevel(oldEntityLogLevel); + listenerLogger.setLevel(oldListenerLogLevel); + } + + } + + @Entity + @Table(name = "mvno_billing_agreement") + public static class MvnoBillingAgreement implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + private int id; + + private String name; + + @ManyToMany + @JoinTable( + name = "mvno_billing_agreement_default_univerzalis_opcio", joinColumns = { + @JoinColumn(name = "billing_agreement_id") + }, + inverseJoinColumns = { + @JoinColumn(name = "univerzalis_opcio_id") + }) + private Set mvnoDefaultUniverzalisOpcioi = new HashSet<>(); + + @JoinColumn(name = "behajtas_egyiranyusitas_opcio_id") + @ManyToOne(fetch = FetchType.LAZY) + private MvnoOpcio behajtasEgyiranyusitasOpcio; + + @JoinColumn(name = "behajtas_felfuggesztes_opcio_id") + @ManyToOne(fetch = FetchType.LAZY) + private MvnoOpcio behajtasFelfuggesztesOpcio; + + @JoinColumn(name = "hotlimit_emeltdijas_bar_opcio_id") + @ManyToOne(fetch = FetchType.LAZY) + private MvnoOpcio hotlimitEmeltDijasBarOpcio; + + public MvnoBillingAgreement() {} + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getMvnoDefaultUniverzalisOpcioi() { + return this.mvnoDefaultUniverzalisOpcioi; + } + + public void setMvnoDefaultUniverzalisOpcioi(Set mvnoDefaultUniverzalisOpcioi) { + this.mvnoDefaultUniverzalisOpcioi = mvnoDefaultUniverzalisOpcioi; + } + + public MvnoOpcio getBehajtasEgyiranyusitasOpcio() { + return this.behajtasEgyiranyusitasOpcio; + } + + public void setBehajtasEgyiranyusitasOpcio(MvnoOpcio behajtasEgyiranyusitasOpcio) { + this.behajtasEgyiranyusitasOpcio = behajtasEgyiranyusitasOpcio; + } + + public MvnoOpcio getBehajtasFelfuggesztesOpcio() { + return this.behajtasFelfuggesztesOpcio; + } + + public void setBehajtasFelfuggesztesOpcio(MvnoOpcio behajtasFelfuggesztesOpcio) { + this.behajtasFelfuggesztesOpcio = behajtasFelfuggesztesOpcio; + } + + public MvnoOpcio getHotlimitEmeltDijasBarOpcio() { + return this.hotlimitEmeltDijasBarOpcio; + } + + public void setHotlimitEmeltDijasBarOpcio(MvnoOpcio hotlimitEmeltDijasBarOpcio) { + this.hotlimitEmeltDijasBarOpcio = hotlimitEmeltDijasBarOpcio; + } + + } + + @Entity + @Table(name = "mvno_opcio") + public static class MvnoOpcio implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + private int id; + + @Column(name = "megnevezes") + private String megnevezes; + + @ManyToMany(mappedBy = "mvnoDefaultUniverzalisOpcioi") + private Set mvnoBillingAgreementekDefaultOpcioja = new HashSet<>(); + + public MvnoOpcio() {} + + public int getId() { + return this.id; + } + + public void setId(int id) { + this.id = id; + } + + public String getMegnevezes() { + return this.megnevezes; + } + + public void setMegnevezes(String megnevezes) { + this.megnevezes = megnevezes; + } + + public Set getMvnoBillingAgreementekDefaultOpcioja() { + return this.mvnoBillingAgreementekDefaultOpcioja; + } + + public void setMvnoBillingAgreementekDefaultOpcioja(Set mvnoBillingAgreementekDefaultOpcioja) { + this.mvnoBillingAgreementekDefaultOpcioja = mvnoBillingAgreementekDefaultOpcioja; + } + + } + + +} From 86879b3dd58a6b24d0f3de767beded8955e0cee4 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 4 Feb 2019 13:51:28 -0800 Subject: [PATCH 215/772] HHH-13244 : Fix EntityPrinter to log "" for uninitalized proxies (cherry picked from commit 36fc1ad35e094df3aa42abe3a29c1798c1543414) --- .../org/hibernate/internal/util/EntityPrinter.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java b/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java index f4ac35f059d3..09d2649b439a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.Map; +import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.EntityKey; @@ -61,9 +62,16 @@ public String toString(String entityName, Object entity) throws HibernateExcepti Object[] values = entityPersister.getPropertyValues( entity ); for ( int i = 0; i < types.length; i++ ) { if ( !names[i].startsWith( "_" ) ) { - String strValue = values[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ? - values[i].toString() : - types[i].toLoggableString( values[i], factory ); + final String strValue; + if ( values[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + strValue = values[i].toString(); + } + else if ( !Hibernate.isInitialized( values[i] ) ) { + strValue = ""; + } + else { + strValue = types[i].toLoggableString( values[i], factory ); + } result.put( names[i], strValue ); } } From 98fb897712b120d1590671435c327f56f4cc4ce1 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 6 Feb 2019 16:55:09 -0800 Subject: [PATCH 216/772] HHH-13244 : add header to test; shorten table/column names to avoid oracle failure (cherry picked from commit 0720b2b376ccfe6960db7aa81cddace51cef8f19) --- .../jpa/test/JpaProxyComplianceWithDebug.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java index 6423649fed84..a619e6a9e15d 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java @@ -1,3 +1,9 @@ +/* + * 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; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -110,7 +116,7 @@ public static class MvnoBillingAgreement implements Serializable { @ManyToMany @JoinTable( - name = "mvno_billing_agreement_default_univerzalis_opcio", joinColumns = { + name = "mvnobillagr_def_univerzalis", joinColumns = { @JoinColumn(name = "billing_agreement_id") }, inverseJoinColumns = { @@ -118,15 +124,15 @@ public static class MvnoBillingAgreement implements Serializable { }) private Set mvnoDefaultUniverzalisOpcioi = new HashSet<>(); - @JoinColumn(name = "behajtas_egyiranyusitas_opcio_id") + @JoinColumn(name = "egyiranyusitas_opcio_id") @ManyToOne(fetch = FetchType.LAZY) private MvnoOpcio behajtasEgyiranyusitasOpcio; - @JoinColumn(name = "behajtas_felfuggesztes_opcio_id") + @JoinColumn(name = "felfuggesztes_opcio_id") @ManyToOne(fetch = FetchType.LAZY) private MvnoOpcio behajtasFelfuggesztesOpcio; - @JoinColumn(name = "hotlimit_emeltdijas_bar_opcio_id") + @JoinColumn(name = "emeltdijas_bar_opcio_id") @ManyToOne(fetch = FetchType.LAZY) private MvnoOpcio hotlimitEmeltDijasBarOpcio; From 8b4fd69dfa3d42a7ba9b3d50dbb6196c07c2bcb9 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 5 Feb 2019 14:30:46 -0800 Subject: [PATCH 217/772] HHH-13194 : Some methods returning org.hibernate.query.Query are not defined for StatelessSession HHH-13194 : Remove @Incubating from org.hibernate.query.Query --- .../src/main/java/org/hibernate/Session.java | 5 -- .../org/hibernate/SharedSessionContract.java | 6 +++ .../java/org/hibernate/StatelessSession.java | 5 ++ .../main/java/org/hibernate/query/Query.java | 3 -- .../hibernate/test/stateless/Contact.hbm.xml | 2 + .../stateless/StatelessSessionQueryTest.java | 49 +++++++++++++++++++ 6 files changed, 62 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index 36b334b46275..b758a3e2be5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -1143,9 +1143,6 @@ interface LockRequest { */ void addEventListeners(SessionEventListener... listeners); - @Override - org.hibernate.query.Query createQuery(String queryString); - @Override org.hibernate.query.Query createQuery(String queryString, Class resultType); @@ -1158,8 +1155,6 @@ interface LockRequest { @Override org.hibernate.query.Query createQuery(CriteriaDelete deleteQuery); - @Override - org.hibernate.query.Query getNamedQuery(String queryName); org.hibernate.query.Query createNamedQuery(String name, Class resultType); diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index e92d7bdb8725..e5e49f95ea53 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -67,6 +67,12 @@ public interface SharedSessionContract extends QueryProducer, Serializable { */ Transaction getTransaction(); + @Override + org.hibernate.query.Query createQuery(String queryString); + + @Override + org.hibernate.query.Query getNamedQuery(String queryName); + /** * Gets a ProcedureCall based on a named template * diff --git a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java index 2263501c49fc..8a15cf1f7577 100755 --- a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java +++ b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java @@ -10,6 +10,8 @@ import java.io.Serializable; import java.sql.Connection; +import org.hibernate.query.NativeQuery; + /** * A command-oriented API for performing bulk operations against a database. *

    @@ -170,4 +172,7 @@ public interface StatelessSession extends SharedSessionContract, AutoCloseable, */ @Deprecated Connection connection(); + + @Override + NativeQuery createSQLQuery(String queryString); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/Query.java b/hibernate-core/src/main/java/org/hibernate/query/Query.java index 6b43ba8b4737..e522a5b4750e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Query.java @@ -29,12 +29,10 @@ import org.hibernate.CacheMode; import org.hibernate.FlushMode; -import org.hibernate.Incubating; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; -import org.hibernate.engine.spi.RowSelection; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.BigDecimalType; import org.hibernate.type.BigIntegerType; @@ -67,7 +65,6 @@ * @author Steve Ebersole * @author Gavin King */ -@Incubating @SuppressWarnings("UnusedDeclaration") public interface Query extends TypedQuery, org.hibernate.Query, CommonQueryContract { /** diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/Contact.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/stateless/Contact.hbm.xml index e3f69ec966cb..ce2918275720 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/stateless/Contact.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/Contact.hbm.xml @@ -16,6 +16,8 @@ + from Contact + diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/StatelessSessionQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/StatelessSessionQueryTest.java index 40bfe390c92e..9e99980f3366 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/stateless/StatelessSessionQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/StatelessSessionQueryTest.java @@ -16,7 +16,10 @@ import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.query.NativeQuery; + import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -72,6 +75,52 @@ public void testHQL() { testData.cleanData(); } + @Test + @TestForIssue( jiraKey = "HHH-13194") + @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") + public void testDeprecatedQueryApis() { + TestData testData=new TestData(); + testData.createData(); + + final String queryString = "from Contact c join fetch c.org join fetch c.org.country"; + StatelessSession s = sessionFactory().openStatelessSession(); + + org.hibernate.Query query = s.createQuery( queryString ); + assertEquals( 1, query.getResultList().size() ); + + query = s.getNamedQuery( Contact.class.getName() + ".contacts" ); + assertEquals( 1, query.getResultList().size() ); + + org.hibernate.SQLQuery sqlQuery = s.createSQLQuery( "select id from Contact" ); + assertEquals( 1, sqlQuery.getResultList().size() ); + + s.close(); + testData.cleanData(); + } + + @Test + @TestForIssue( jiraKey = "HHH-13194") + @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") + public void testNewQueryApis() { + TestData testData=new TestData(); + testData.createData(); + + final String queryString = "from Contact c join fetch c.org join fetch c.org.country"; + StatelessSession s = sessionFactory().openStatelessSession(); + + org.hibernate.query.Query query = s.createQuery( queryString ); + assertEquals( 1, query.getResultList().size() ); + + query = s.getNamedQuery( Contact.class.getName() + ".contacts" ); + assertEquals( 1, query.getResultList().size() ); + + org.hibernate.query.NativeQuery sqlQuery = s.createSQLQuery( "select id from Contact" ); + assertEquals( 1, sqlQuery.getResultList().size() ); + + s.close(); + testData.cleanData(); + } + private class TestData{ List list = new ArrayList(); public void createData(){ From e520fd9b996faf2a0c39d7db12d822bb722f917a Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 17 Aug 2018 12:26:29 +0100 Subject: [PATCH 218/772] HHH-12917 Interning of strings for Filter definitions (cherry picked from commit 4fa04913411c839ffc3ef0337187c5b1dbe6800c) --- .../org/hibernate/internal/FilterHelper.java | 28 +++++++++++-------- .../hibernate/internal/util/StringHelper.java | 21 ++++++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java index 8ae733f23354..f027f8a9759d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java @@ -14,6 +14,8 @@ import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.sql.Template; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * Implementation of FilterHelper. * @@ -44,23 +46,27 @@ public FilterHelper(List filters, SessionFactoryImplementor filterCount = 0; for ( final FilterConfiguration filter : filters ) { filterAutoAliasFlags[filterCount] = false; - filterNames[filterCount] = filter.getName(); - filterConditions[filterCount] = filter.getCondition(); + filterNames[filterCount] = safeInterning( filter.getName() ); + filterConditions[filterCount] = safeInterning( filter.getCondition() ); filterAliasTableMaps[filterCount] = filter.getAliasTableMap( factory ); if ( ( filterAliasTableMaps[filterCount].isEmpty() || isTableFromPersistentClass( filterAliasTableMaps[filterCount] ) ) && filter .useAutoAliasInjection() ) { - filterConditions[filterCount] = Template.renderWhereStringTemplate( - filter.getCondition(), - FilterImpl.MARKER, - factory.getDialect(), - factory.getSqlFunctionRegistry() + filterConditions[filterCount] = safeInterning( + Template.renderWhereStringTemplate( + filter.getCondition(), + FilterImpl.MARKER, + factory.getDialect(), + factory.getSqlFunctionRegistry() + ) ); filterAutoAliasFlags[filterCount] = true; } - filterConditions[filterCount] = StringHelper.replace( - filterConditions[filterCount], - ":", - ":" + filterNames[filterCount] + "." + filterConditions[filterCount] = safeInterning( + StringHelper.replace( + filterConditions[filterCount], + ":", + ":" + filterNames[filterCount] + "." + ) ); filterCount++; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java index acd425617da5..4533d5b6966b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java @@ -886,4 +886,25 @@ else if ( isSecondExpressionNonEmpty ) { return null; } } + + /** + * Return the interned form of a String, or null if the parameter is null. + *

    + * Use with caution: excessive interning is known to cause issues. + * Best to use only with strings which are known to be long lived constants, + * and for which the chances of being actual duplicates is proven. + * (Even better: avoid needing interning by design changes such as reusing + * the known reference) + * @param string The string to intern. + * @return The interned string. + */ + public static String safeInterning(final String string) { + if ( string == null ) { + return null; + } + else { + return string.intern(); + } + } + } From 5f7b41d54b6234a8b4ec68cc1cf6a5a7862d1a17 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 17 Aug 2018 12:41:08 +0100 Subject: [PATCH 219/772] HHH-12918 Interning of strings for Formula and Column exctraction templates (cherry picked from commit 096b436f3dd1bb2b3daf16bf3e82812bebde232c) --- .../src/main/java/org/hibernate/mapping/Column.java | 8 ++++++-- .../src/main/java/org/hibernate/mapping/Formula.java | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java index 5e5fa4e96207..6cd81c90c0c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -17,6 +17,8 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.sql.Template; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * A column of a relational database table * @@ -268,10 +270,12 @@ public boolean hasCheckConstraint() { @Override public String getTemplate(Dialect dialect, SQLFunctionRegistry functionRegistry) { - return hasCustomRead() + return safeInterning( + hasCustomRead() // see note in renderTransformerReadFragment wrt access to SessionFactory ? Template.renderTransformerReadFragment( customRead, getQuotedName( dialect ) ) - : Template.TEMPLATE + '.' + getQuotedName( dialect ); + : Template.TEMPLATE + '.' + getQuotedName( dialect ) + ); } public boolean hasCustomRead() { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java b/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java index 4d16249a97d5..f044ea54f967 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java @@ -13,6 +13,8 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.sql.Template; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * A formula is a derived column value * @author Gavin King @@ -35,7 +37,7 @@ public Formula(String formula) { @Override public String getTemplate(Dialect dialect, SQLFunctionRegistry functionRegistry) { String template = Template.renderWhereStringTemplate(formula, dialect, functionRegistry); - return StringHelper.replace( template, "{alias}", Template.TEMPLATE ); + return safeInterning( StringHelper.replace( template, "{alias}", Template.TEMPLATE ) ); } @Override From bf1f56b6bb7c4bfadccf89ef066ca7e9b9ac63db Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 17 Aug 2018 12:59:34 +0100 Subject: [PATCH 220/772] HHH-12919 Interning of strings for EntityReferenceAliases (cherry picked from commit fb54090329e7cdfcadd0576ee24dbcdca651367d) --- .../internal/AliasResolutionContextImpl.java | 4 +++- .../internal/EntityReferenceAliasesImpl.java | 4 +++- .../java/org/hibernate/mapping/Column.java | 18 +++++++++++------- .../entity/AbstractEntityPersister.java | 2 ++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java index fdd434ec8413..e656ea22a843 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java @@ -34,6 +34,8 @@ import org.jboss.logging.Logger; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * Provides aliases that are used by load queries and ResultSet processors. * @@ -237,7 +239,7 @@ private void registerSqlTableAliasMapping(String querySpaceUid, String sqlTableA if ( querySpaceUidToSqlTableAliasMap == null ) { querySpaceUidToSqlTableAliasMap = new HashMap(); } - String old = querySpaceUidToSqlTableAliasMap.put( querySpaceUid, sqlTableAlias ); + String old = querySpaceUidToSqlTableAliasMap.put( safeInterning( querySpaceUid ), safeInterning( sqlTableAlias ) ); if ( old != null ) { if ( old.equals( sqlTableAlias ) ) { // silently ignore... diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java index 16df015cec23..2d4d87c74e27 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java @@ -9,6 +9,8 @@ import org.hibernate.loader.EntityAliases; import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * @author Gail Badner * @author Steve Ebersole @@ -18,7 +20,7 @@ public class EntityReferenceAliasesImpl implements EntityReferenceAliases { private final EntityAliases columnAliases; public EntityReferenceAliasesImpl(String tableAlias, EntityAliases columnAliases) { - this.tableAlias = tableAlias; + this.tableAlias = safeInterning( tableAlias ); this.columnAliases = columnAliases; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java index 6cd81c90c0c9..4f75b98a491c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -92,15 +92,19 @@ public void setName(String name) { * returns quoted name as it would be in the mapping file. */ public String getQuotedName() { - return quoted ? + return safeInterning( + quoted ? "`" + name + "`" : - name; + name + ); } public String getQuotedName(Dialect d) { - return quoted ? + return safeInterning( + quoted ? d.openQuote() + name + d.closeQuote() : - name; + name + ); } @Override @@ -139,7 +143,7 @@ else if ( name.length() > lastLetter + 1 ) { */ @Override public String getAlias(Dialect dialect, Table table) { - return getAlias( dialect ) + table.getUniqueInteger() + '_'; + return safeInterning( getAlias( dialect ) + table.getUniqueInteger() + '_' ); } public boolean isNullable() { @@ -342,7 +346,7 @@ public String getCustomWrite() { } public void setCustomWrite(String customWrite) { - this.customWrite = customWrite; + this.customWrite = safeInterning( customWrite ); } public String getCustomRead() { @@ -350,7 +354,7 @@ public String getCustomRead() { } public void setCustomRead(String customRead) { - this.customRead = StringHelper.nullIfEmpty( customRead ); + this.customRead = safeInterning( StringHelper.nullIfEmpty( customRead ) ); } public String getCanonicalName() { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index d22ffe8eb078..21ef9cf37c4a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -139,6 +139,8 @@ import org.hibernate.type.TypeHelper; import org.hibernate.type.VersionType; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * Basic functionality for persisting an entity via JDBC * through either generated or custom SQL From f388420ecaba2553eaee53cedc335fea6262eea1 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 2 Oct 2018 14:34:57 +0200 Subject: [PATCH 221/772] HHH-13005 Upgrade to ByteBuddy 1.9.0 --- gradle/libraries.gradle | 2 +- .../internal/bytebuddy/PersistentAttributeTransformer.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index d1d983edece5..f1b26f883e75 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -23,7 +23,7 @@ ext { weldVersion = '3.0.0.Final' javassistVersion = '3.23.1-GA' - byteBuddyVersion = '1.8.17' // Now with JDK10 compatibility and preliminary support for JDK11 + byteBuddyVersion = '1.9.0' geolatteVersion = '1.3.0' diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 69f5da9caac4..fd12ab426be6 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -168,7 +168,7 @@ private boolean isEnhanced(String owner, String name, String desc) { DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) { boolean compositeOwner = false; - builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().method( not( nameStartsWith( "$$_hibernate_" ) ), this ) ); + builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( not( nameStartsWith( "$$_hibernate_" ) ), this ) ); for ( FieldDescription enhancedField : enhancedFields ) { builder = builder .defineMethod( @@ -250,7 +250,7 @@ private Implementation fieldWriter(FieldDescription enhancedField) { DynamicType.Builder applyExtended(DynamicType.Builder builder) { AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper enhancer = new FieldAccessEnhancer( managedCtClass, enhancementContext, classPool ); - return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().method( not( nameStartsWith( "$$_hibernate_" ) ), enhancer ) ); + return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( not( nameStartsWith( "$$_hibernate_" ) ), enhancer ) ); } private static class FieldMethodReader implements ByteCodeAppender { From 0c62351515c51d93bf8cac23f752ba9afeeb91f5 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 22 Oct 2018 19:31:51 +0100 Subject: [PATCH 222/772] HHH-13057 Prevent Byte Buddy's Advice helper to reload to many resources from the ClassLoader --- .../internal/bytebuddy/EnhancerImpl.java | 65 +++++++++++++------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 8e06c2e8494d..74438094f5bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -71,6 +71,27 @@ public class EnhancerImpl implements Enhancer { private final TypePool typePool; + /** + * Extract the following constants so that enhancement on large projects + * can be done efficiently: otherwise each instance use will trigger a + * resource load on the ClassLoader tree, triggering allocation of + * several streams to unzip each JAR file each time. + */ + private final ClassFileLocator adviceLocator = ClassFileLocator.ForClassLoader.of(CodeTemplates.class.getClassLoader()); + private final Implementation implementationTrackChange = Advice.to( CodeTemplates.TrackChange.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationGetDirtyAttributesWithoutCollections = Advice.to( CodeTemplates.GetDirtyAttributesWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationAreFieldsDirtyWithoutCollections = Advice.to( CodeTemplates.AreFieldsDirtyWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationClearDirtyAttributesWithoutCollections = Advice.to( CodeTemplates.ClearDirtyAttributesWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationSuspendDirtyTracking = Advice.to( CodeTemplates.SuspendDirtyTracking.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationGetDirtyAttributes = Advice.to( CodeTemplates.GetDirtyAttributes.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationAreFieldsDirty = Advice.to( CodeTemplates.AreFieldsDirty.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationGetCollectionTrackerWithoutCollections = Advice.to( CodeTemplates.GetCollectionTrackerWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationClearDirtyAttributes = Advice.to( CodeTemplates.ClearDirtyAttributes.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + //In this case we just extract the Advice: + private final Advice adviceInitializeLazyAttributeLoadingInterceptor = Advice.to( CodeTemplates.InitializeLazyAttributeLoadingInterceptor.class, adviceLocator ); + private final Implementation implementationSetOwner = Advice.to( CodeTemplates.SetOwner.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationClearOwner = Advice.to( CodeTemplates.ClearOwner.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + /** * Constructs the Enhancer, using the given context. * @@ -167,18 +188,18 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationTrackChange ) .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationGetDirtyAttributesWithoutCollections ) .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.AreFieldsDirtyWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationAreFieldsDirtyWithoutCollections ) .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.ClearDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationClearDirtyAttributesWithoutCollections ) .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) .withParameters( boolean.class ) - .intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationSuspendDirtyTracking ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetCollectionTrackerWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ); + .intercept( implementationGetCollectionTrackerWithoutCollections ); } else { builder = builder.implement( ExtendedSelfDirtinessTracker.class ) @@ -188,16 +209,16 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationTrackChange ) .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationGetDirtyAttributes ) .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.AreFieldsDirty.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationAreFieldsDirty ) .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.ClearDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationClearDirtyAttributes ) .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) .withParameters( boolean.class ) - .intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationSuspendDirtyTracking ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); @@ -207,40 +228,40 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.MapAreCollectionFieldsDirty.class ) + .to( CodeTemplates.MapAreCollectionFieldsDirty.class, adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.MapGetCollectionFieldDirtyNames.class ) + .to( CodeTemplates.MapGetCollectionFieldDirtyNames.class, adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.MapGetCollectionClearDirtyNames.class ) + .to( CodeTemplates.MapGetCollectionClearDirtyNames.class, adviceLocator ) .wrap( clearDirtyNames ); } else { isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.CollectionAreCollectionFieldsDirty.class ) + .to( CodeTemplates.CollectionAreCollectionFieldsDirty.class, adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.CollectionGetCollectionFieldDirtyNames.class ) + .to( CodeTemplates.CollectionGetCollectionFieldDirtyNames.class, adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.CollectionGetCollectionClearDirtyNames.class ) + .to( CodeTemplates.CollectionGetCollectionClearDirtyNames.class, adviceLocator ) .wrap( clearDirtyNames ); } } if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) { - clearDirtyNames = Advice.to( CodeTemplates.InitializeLazyAttributeLoadingInterceptor.class ).wrap( clearDirtyNames ); + clearDirtyNames = adviceInitializeLazyAttributeLoadingInterceptor.wrap( clearDirtyNames ); } builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) @@ -249,7 +270,9 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes .withParameters( DirtyTracker.class ) .intercept( getDirtyNames ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.withCustomMapping().to( CodeTemplates.ClearDirtyCollectionNames.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( Advice.withCustomMapping() + .to( CodeTemplates.ClearDirtyCollectionNames.class, adviceLocator ) + .wrap( StubMethod.INSTANCE ) ) .defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, void.class, Visibility.PUBLIC ) .withParameters( LazyAttributeLoadingInterceptor.class ) .intercept( clearDirtyNames ); @@ -279,14 +302,14 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { Visibility.PUBLIC ) .withParameters( String.class, CompositeOwner.class ) - .intercept( Advice.to( CodeTemplates.SetOwner.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationSetOwner ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER, void.class, Visibility.PUBLIC ) .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.ClearOwner.class ).wrap( StubMethod.INSTANCE ) ); + .intercept( implementationClearOwner ); } return transformer.applyTo( builder, false ); From 9110fc1ce862526bc052e37328e14dd45f228738 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 13 Nov 2018 22:24:12 +0000 Subject: [PATCH 223/772] HHH-13099 Update to Byte Buddy 1.9.4 --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index f1b26f883e75..a5f6b96c8177 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -23,7 +23,7 @@ ext { weldVersion = '3.0.0.Final' javassistVersion = '3.23.1-GA' - byteBuddyVersion = '1.9.0' + byteBuddyVersion = '1.9.4' geolatteVersion = '1.3.0' From ddcb0bb0e67ca2a927a1a725e66efacda5adb1a7 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 13 Nov 2018 22:52:58 +0000 Subject: [PATCH 224/772] HHH-13100 All custom implementation of Byte Buddy "Implementation" s should have a proper equals and hashcode --- .../BiDirectionalAssociationHandler.java | 24 ++++++++++++++++- .../bytebuddy/FieldAccessEnhancer.java | 24 ++++++++++++++++- .../bytebuddy/FieldReaderAppender.java | 22 ++++++++++++++++ .../bytebuddy/InlineDirtyCheckingHandler.java | 21 ++++++++++++++- .../PersistentAttributeTransformer.java | 26 ++++++++++++++++++- 5 files changed, 113 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java index 8792cac4a181..63af04277ef9 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java @@ -8,6 +8,8 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; + import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.ManyToMany; @@ -34,7 +36,7 @@ import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; -class BiDirectionalAssociationHandler implements Implementation { +final class BiDirectionalAssociationHandler implements Implementation { private static final CoreMessageLogger log = CoreLogging.messageLogger( BiDirectionalAssociationHandler.class ); @@ -327,4 +329,24 @@ else if ( name.equals( "setterNull" ) ) { }, implementationContext, instrumentedMethod ); } } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if ( o == null || BiDirectionalAssociationHandler.class != o.getClass() ) { + return false; + } + final BiDirectionalAssociationHandler that = (BiDirectionalAssociationHandler) o; + return Objects.equals( delegate, that.delegate ) && + Objects.equals( targetEntity, that.targetEntity ) && + Objects.equals( targetType, that.targetType ) && + Objects.equals( mappedBy, that.mappedBy ); + } + + @Override + public int hashCode() { + return Objects.hash( delegate, targetEntity, targetType, mappedBy ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java index f543f6f87082..796d324102dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java @@ -28,7 +28,9 @@ import static net.bytebuddy.matcher.ElementMatchers.hasDescriptor; import static net.bytebuddy.matcher.ElementMatchers.named; -class FieldAccessEnhancer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { +import java.util.Objects; + +final class FieldAccessEnhancer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private static final CoreMessageLogger log = CoreLogging.messageLogger( FieldAccessEnhancer.class ); @@ -130,4 +132,24 @@ private FieldDescription findField(String owner, String name, String desc) { } return fields.getOnly(); } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || FieldAccessEnhancer.class != o.getClass() ) { + return false; + } + final FieldAccessEnhancer that = (FieldAccessEnhancer) o; + return Objects.equals( managedCtClass, that.managedCtClass ) && + Objects.equals( enhancementContext, that.enhancementContext ) && + Objects.equals( classPool, that.classPool ); + } + + @Override + public int hashCode() { + return Objects.hash( managedCtClass, enhancementContext, classPool ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java index 5098c04290f6..9163f91c1d46 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java @@ -6,6 +6,8 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import java.util.Objects; + import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -170,4 +172,24 @@ protected void fieldWrite(MethodVisitor methodVisitor) { ); } } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + final FieldReaderAppender that = (FieldReaderAppender) o; + return Objects.equals( managedCtClass, that.managedCtClass ) && + Objects.equals( persistentField, that.persistentField ) && + Objects.equals( persistentFieldAsDefined, that.persistentFieldAsDefined ); + } + + @Override + public int hashCode() { + return Objects.hash( managedCtClass, persistentField, persistentFieldAsDefined ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java index 28b3a83480f0..e08cb5231856 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java @@ -27,7 +27,7 @@ import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; -class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppender { +final class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppender { private final Implementation delegate; @@ -151,4 +151,23 @@ else if ( persistentField.getType().represents( double.class ) ) { } return new Size( 1 + 2 * persistentField.getType().asErasure().getStackSize().getSize(), instrumentedMethod.getStackSize() ); } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || InlineDirtyCheckingHandler.class != o.getClass() ) { + return false; + } + final InlineDirtyCheckingHandler that = (InlineDirtyCheckingHandler) o; + return Objects.equals( delegate, that.delegate ) && + Objects.equals( managedCtClass, that.managedCtClass ) && + Objects.equals( persistentField, that.persistentField ); + } + + @Override + public int hashCode() { + return Objects.hash( delegate, managedCtClass, persistentField ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index fd12ab426be6..543820d4bd84 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -11,6 +11,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; + import javax.persistence.Embedded; import net.bytebuddy.description.field.FieldList; @@ -41,7 +43,7 @@ import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; import static net.bytebuddy.matcher.ElementMatchers.not; -class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { +final class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributeTransformer.class ); @@ -313,4 +315,26 @@ public Size apply( return new Size( 1 + persistentField.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() ); } } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || PersistentAttributeTransformer.class != o.getClass() ) { + return false; + } + final PersistentAttributeTransformer that = (PersistentAttributeTransformer) o; + return Objects.equals( managedCtClass, that.managedCtClass ) && + Objects.equals( enhancementContext, that.enhancementContext ) && + Objects.equals( classPool, that.classPool ) && + Arrays.equals( enhancedFields, that.enhancedFields ); + } + + @Override + public int hashCode() { + int result = Objects.hash( managedCtClass, enhancementContext, classPool ); + result = 31 * result + Arrays.hashCode( enhancedFields ); + return result; + } } From 5163c824050ab4ec53b2510e75f3c5f1b80ddcf1 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 13 Nov 2018 10:43:34 +0100 Subject: [PATCH 225/772] HHH-13097 Add a missing @Override --- .../java/org/hibernate/orm/tooling/maven/MavenEnhancePlugin.java | 1 + 1 file changed, 1 insertion(+) diff --git a/tooling/hibernate-enhance-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/MavenEnhancePlugin.java b/tooling/hibernate-enhance-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/MavenEnhancePlugin.java index 8b940b70130c..7f3c2be3854e 100644 --- a/tooling/hibernate-enhance-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/MavenEnhancePlugin.java +++ b/tooling/hibernate-enhance-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/MavenEnhancePlugin.java @@ -83,6 +83,7 @@ private boolean shouldApply() { return enableLazyInitialization || enableDirtyTracking || enableAssociationManagement || enableExtendedEnhancement; } + @Override public void execute() throws MojoExecutionException, MojoFailureException { if ( !shouldApply() ) { getLog().warn( "Skipping Hibernate bytecode enhancement plugin execution since no feature is enabled" ); From 3b0f092bcee5d0e7b80818d26c0a0d7ac6c4782a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 13 Nov 2018 11:07:47 +0100 Subject: [PATCH 226/772] HHH-13097 Only rewrite class if enhanced in the ByteBuddy enhancer This makes the behavior of the ByteBuddy enhancer consistent with the behavior of the Javassist enhancer. Currently, the Maven plugin rewrites every class provided. --- .../enhance/internal/bytebuddy/EnhancerImpl.java | 2 +- .../bytecode/internal/bytebuddy/ByteBuddyState.java | 10 ++++++---- .../internal/bytebuddy/EnhancerWildFlyNamesTest.java | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 74438094f5bb..b34537f102c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -123,7 +123,7 @@ public synchronized byte[] enhance(String className, byte[] originalBytes) throw try { final TypeDescription typeDescription = typePool.describe( safeClassName ).resolve(); - return byteBuddyState.rewrite( typePool, safeClassName, originalBytes, byteBuddy -> doEnhance( + return byteBuddyState.rewrite( typePool, safeClassName, byteBuddy -> doEnhance( byteBuddy.ignore( isDefaultFinalizer() ).redefine( typeDescription, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ), typeDescription ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index 1cdeecbc8887..9cc5ef9e1adf 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -135,18 +135,20 @@ public Class load(Class referenceClass, Function + * WARNING: Returns null if rewriteClassFunction returns a null builder. Do not use if you expect the original + * content. * * @param typePool the ByteBuddy TypePool * @param className The original class name. - * @param originalBytes The original content of the class. * @param rewriteClassFunction The function used to rewrite the class. - * @return The rewritten content of the class. + * @return The rewritten content of the class or null if rewriteClassFunction returns a null builder. */ - public byte[] rewrite(TypePool typePool, String className, byte[] originalBytes, + public byte[] rewrite(TypePool typePool, String className, Function> rewriteClassFunction) { DynamicType.Builder builder = rewriteClassFunction.apply( byteBuddy ); if ( builder == null ) { - return originalBytes; + return null; } return make( typePool, builder ).getBytes(); diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java index cb8ddaf6d6ff..42de28c88cf3 100644 --- a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java @@ -31,7 +31,7 @@ public class EnhancerWildFlyNamesTest { @TestForIssue( jiraKey = "HHH-12545" ) public void test() { Enhancer enhancer = createByteBuddyEnhancer(); - String internalName = Bean.class.getName().replace( '.', '/' ); + String internalName = SimpleEntity.class.getName().replace( '.', '/' ); String resourceName = internalName + ".class"; byte[] buffer = new byte[0]; try { From 43ee75b9666d3fc3a01c791527ab57957b624350 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 13 Nov 2018 16:08:50 +0100 Subject: [PATCH 227/772] HHH-13097 Cache the annotations resolution --- .../BiDirectionalAssociationHandler.java | 50 +++--- .../ByteBuddyEnhancementContext.java | 36 ++-- .../internal/bytebuddy/EnhancerImpl.java | 157 +++++++++++++----- .../bytebuddy/FieldAccessEnhancer.java | 17 +- .../bytebuddy/FieldReaderAppender.java | 11 +- .../bytebuddy/FieldWriterAppender.java | 3 +- .../bytebuddy/InlineDirtyCheckingHandler.java | 15 +- .../PersistentAttributeTransformer.java | 52 +++--- .../bytebuddy/UnloadedFieldDescription.java | 27 --- .../bytebuddy/UnloadedTypeDescription.java | 1 - 10 files changed, 203 insertions(+), 166 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedFieldDescription.java diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java index 63af04277ef9..d5e35473c636 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java @@ -17,6 +17,7 @@ import javax.persistence.OneToMany; import javax.persistence.OneToOne; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.internal.CoreLogging; @@ -43,7 +44,7 @@ final class BiDirectionalAssociationHandler implements Implementation { static Implementation wrap( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, - FieldDescription persistentField, + AnnotatedFieldDescription persistentField, Implementation implementation) { if ( !enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) { return implementation; @@ -69,17 +70,17 @@ static Implementation wrap( .getType() .asErasure(); - if ( EnhancerImpl.isAnnotationPresent( persistentField, OneToOne.class ) ) { + if ( persistentField.hasAnnotation( OneToOne.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.MappedBy.class, mappedBy ) .to( CodeTemplates.OneToOneHandler.class ) .wrap( implementation ); } - if ( EnhancerImpl.isAnnotationPresent( persistentField, OneToMany.class ) ) { + if ( persistentField.hasAnnotation( OneToMany.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.MappedBy.class, mappedBy ) .to( persistentField.getType().asErasure().isAssignableTo( Map.class ) ? CodeTemplates.OneToManyOnMapHandler.class @@ -87,15 +88,15 @@ static Implementation wrap( .wrap( implementation ); } - if ( EnhancerImpl.isAnnotationPresent( persistentField, ManyToOne.class ) ) { + if ( persistentField.hasAnnotation( ManyToOne.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.MappedBy.class, mappedBy ) .to( CodeTemplates.ManyToOneHandler.class ) .wrap( implementation ); } - if ( EnhancerImpl.isAnnotationPresent( persistentField, ManyToMany.class ) ) { + if ( persistentField.hasAnnotation( ManyToMany.class ) ) { if ( persistentField.getType().asErasure().isAssignableTo( Map.class ) || targetType.isAssignableTo( Map.class ) ) { log.infof( @@ -107,7 +108,7 @@ static Implementation wrap( } implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.MappedBy.class, mappedBy ) .to( CodeTemplates.ManyToManyHandler.class ) .wrap( implementation ); @@ -116,12 +117,12 @@ static Implementation wrap( return new BiDirectionalAssociationHandler( implementation, targetEntity, targetType, mappedBy ); } - public static TypeDescription getTargetEntityClass(TypeDescription managedCtClass, FieldDescription persistentField) { + public static TypeDescription getTargetEntityClass(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { try { - AnnotationDescription.Loadable oto = EnhancerImpl.getAnnotation( persistentField, OneToOne.class ); - AnnotationDescription.Loadable otm = EnhancerImpl.getAnnotation( persistentField, OneToMany.class ); - AnnotationDescription.Loadable mto = EnhancerImpl.getAnnotation( persistentField, ManyToOne.class ); - AnnotationDescription.Loadable mtm = EnhancerImpl.getAnnotation( persistentField, ManyToMany.class ); + AnnotationDescription.Loadable oto = persistentField.getAnnotation( OneToOne.class ); + AnnotationDescription.Loadable otm = persistentField.getAnnotation( OneToMany.class ); + AnnotationDescription.Loadable mto = persistentField.getAnnotation( ManyToOne.class ); + AnnotationDescription.Loadable mtm = persistentField.getAnnotation( ManyToMany.class ); if ( oto == null && otm == null && mto == null && mtm == null ) { return null; @@ -159,7 +160,7 @@ else if ( !targetClass.resolve( TypeDescription.class ).represents( void.class ) return entityType( target( persistentField ) ); } - private static TypeDescription.Generic target(FieldDescription persistentField) { + private static TypeDescription.Generic target(AnnotatedFieldDescription persistentField) { AnnotationDescription.Loadable access = persistentField.getDeclaringType().asErasure().getDeclaredAnnotations().ofType( Access.class ); if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { return persistentField.getType(); @@ -175,7 +176,7 @@ private static TypeDescription.Generic target(FieldDescription persistentField) } } - private static String getMappedBy(FieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { + private static String getMappedBy(AnnotatedFieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { String mappedBy = getMappedByNotManyToMany( target ); if ( mappedBy == null || mappedBy.isEmpty() ) { return getMappedByManyToMany( target, targetEntity, context ); @@ -185,19 +186,19 @@ private static String getMappedBy(FieldDescription target, TypeDescription targe } } - private static String getMappedByNotManyToMany(FieldDescription target) { + private static String getMappedByNotManyToMany(AnnotatedFieldDescription target) { try { - AnnotationDescription.Loadable oto = EnhancerImpl.getAnnotation( target, OneToOne.class ); + AnnotationDescription.Loadable oto = target.getAnnotation( OneToOne.class ); if ( oto != null ) { return oto.getValue( new MethodDescription.ForLoadedMethod( OneToOne.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class ); } - AnnotationDescription.Loadable otm = EnhancerImpl.getAnnotation( target, OneToMany.class ); + AnnotationDescription.Loadable otm = target.getAnnotation( OneToMany.class ); if ( otm != null ) { return otm.getValue( new MethodDescription.ForLoadedMethod( OneToMany.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class ); } - AnnotationDescription.Loadable mtm = EnhancerImpl.getAnnotation( target, ManyToMany.class ); + AnnotationDescription.Loadable mtm = target.getAnnotation( ManyToMany.class ); if ( mtm != null ) { return mtm.getValue( new MethodDescription.ForLoadedMethod( ManyToMany.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class ); } @@ -208,11 +209,12 @@ private static String getMappedByNotManyToMany(FieldDescription target) { return null; } - private static String getMappedByManyToMany(FieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { + private static String getMappedByManyToMany(AnnotatedFieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { for ( FieldDescription f : targetEntity.getDeclaredFields() ) { - if ( context.isPersistentField( f ) - && target.getName().equals( getMappedByNotManyToMany( f ) ) - && target.getDeclaringType().asErasure().isAssignableTo( entityType( f.getType() ) ) ) { + AnnotatedFieldDescription annotatedF = new AnnotatedFieldDescription( f ); + if ( context.isPersistentField( annotatedF ) + && target.getName().equals( getMappedByNotManyToMany( annotatedF ) ) + && target.getDeclaringType().asErasure().isAssignableTo( entityType( annotatedF.getType() ) ) ) { log.debugf( "mappedBy association for field [%s#%s] is [%s#%s]", target.getDeclaringType().asErasure().getName(), diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java index 09a6e30620d6..40df641c4f4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java @@ -6,10 +6,9 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementContext; -import org.hibernate.bytecode.enhance.spi.UnloadedField; -import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.type.TypeDescription; class ByteBuddyEnhancementContext { @@ -36,10 +35,6 @@ public boolean isMappedSuperclassClass(TypeDescription classDescriptor) { return enhancementContext.isMappedSuperclassClass( new UnloadedTypeDescription( classDescriptor ) ); } - public boolean doBiDirectionalAssociationManagement(FieldDescription field) { - return enhancementContext.doBiDirectionalAssociationManagement( new UnloadedFieldDescription( field ) ); - } - public boolean doDirtyCheckingInline(TypeDescription classDescriptor) { return enhancementContext.doDirtyCheckingInline( new UnloadedTypeDescription( classDescriptor ) ); } @@ -52,28 +47,23 @@ public boolean hasLazyLoadableAttributes(TypeDescription classDescriptor) { return enhancementContext.hasLazyLoadableAttributes( new UnloadedTypeDescription( classDescriptor ) ); } - public boolean isPersistentField(FieldDescription ctField) { - return enhancementContext.isPersistentField( new UnloadedFieldDescription( ctField ) ); + public boolean isPersistentField(AnnotatedFieldDescription field) { + return enhancementContext.isPersistentField( field ); + } + + public AnnotatedFieldDescription[] order(AnnotatedFieldDescription[] persistentFields) { + return (AnnotatedFieldDescription[]) enhancementContext.order( persistentFields ); } - public FieldDescription[] order(FieldDescription[] persistentFields) { - UnloadedField[] unloadedFields = new UnloadedField[persistentFields.length]; - for ( int i = 0; i < unloadedFields.length; i++ ) { - unloadedFields[i] = new UnloadedFieldDescription( persistentFields[i] ); - } - UnloadedField[] ordered = enhancementContext.order( unloadedFields ); - FieldDescription[] orderedFields = new FieldDescription[persistentFields.length]; - for ( int i = 0; i < orderedFields.length; i++ ) { - orderedFields[i] = ( (UnloadedFieldDescription) ordered[i] ).fieldDescription; - } - return orderedFields; + public boolean isLazyLoadable(AnnotatedFieldDescription field) { + return enhancementContext.isLazyLoadable( field ); } - public boolean isLazyLoadable(FieldDescription field) { - return enhancementContext.isLazyLoadable( new UnloadedFieldDescription( field ) ); + public boolean isMappedCollection(AnnotatedFieldDescription field) { + return enhancementContext.isMappedCollection( field ); } - public boolean isMappedCollection(FieldDescription field) { - return enhancementContext.isMappedCollection( new UnloadedFieldDescription( field ) ); + public boolean doBiDirectionalAssociationManagement(AnnotatedFieldDescription field) { + return enhancementContext.doBiDirectionalAssociationManagement( field ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index b34537f102c1..44a79e5a1d8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -28,6 +28,7 @@ import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.Enhancer; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; +import org.hibernate.bytecode.enhance.spi.UnloadedField; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.engine.spi.CompositeOwner; @@ -46,13 +47,16 @@ import net.bytebuddy.asm.Advice; import net.bytebuddy.description.annotation.AnnotationDescription; +import net.bytebuddy.description.annotation.AnnotationList; import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.field.FieldDescription.InDefinedShape; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.MethodList; import net.bytebuddy.description.modifier.FieldPersistence; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeDescription.Generic; import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.scaffold.MethodGraph; @@ -60,6 +64,7 @@ import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; +import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.pool.TypePool; public class EnhancerImpl implements Enhancer { @@ -182,7 +187,9 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes builder = addInterceptorHandling( builder, managedCtClass ); if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { - if ( collectCollectionFields( managedCtClass ).isEmpty() ) { + List collectionFields = collectCollectionFields( managedCtClass ); + + if ( collectionFields.isEmpty() ) { builder = builder.implement( SelfDirtinessTracker.class ) .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) @@ -223,38 +230,38 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); Implementation isDirty = StubMethod.INSTANCE, getDirtyNames = StubMethod.INSTANCE, clearDirtyNames = StubMethod.INSTANCE; - for ( FieldDescription collectionField : collectCollectionFields( managedCtClass ) ) { + for ( AnnotatedFieldDescription collectionField : collectionFields ) { if ( collectionField.getType().asErasure().isAssignableTo( Map.class ) ) { isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) .to( CodeTemplates.MapAreCollectionFieldsDirty.class, adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) .to( CodeTemplates.MapGetCollectionFieldDirtyNames.class, adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) .to( CodeTemplates.MapGetCollectionClearDirtyNames.class, adviceLocator ) .wrap( clearDirtyNames ); } else { isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) .to( CodeTemplates.CollectionAreCollectionFieldsDirty.class, adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) .to( CodeTemplates.CollectionGetCollectionFieldDirtyNames.class, adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) .to( CodeTemplates.CollectionGetCollectionClearDirtyNames.class, adviceLocator ) .wrap( clearDirtyNames ); } @@ -375,17 +382,18 @@ private static DynamicType.Builder addFieldWithGetterAndSetter( .intercept( FieldAccessor.ofField( fieldName ) ); } - private List collectCollectionFields(TypeDescription managedCtClass) { - List collectionList = new ArrayList<>(); + private List collectCollectionFields(TypeDescription managedCtClass) { + List collectionList = new ArrayList<>(); for ( FieldDescription ctField : managedCtClass.getDeclaredFields() ) { // skip static fields and skip fields added by enhancement if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) { continue; } - if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) { + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( ctField ); + if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) { if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) { - collectionList.add( ctField ); + collectionList.add( annotatedField ); } } } @@ -399,7 +407,7 @@ private List collectCollectionFields(TypeDescription managedCt return collectionList; } - private Collection collectInheritCollectionFields(TypeDefinition managedCtClass) { + private Collection collectInheritCollectionFields(TypeDefinition managedCtClass) { TypeDefinition managedCtSuperclass = managedCtClass.getSuperClass(); if ( managedCtSuperclass == null || managedCtSuperclass.represents( Object.class ) ) { return Collections.emptyList(); @@ -408,13 +416,14 @@ private Collection collectInheritCollectionFields(TypeDefiniti if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { return collectInheritCollectionFields( managedCtSuperclass.asErasure() ); } - List collectionList = new ArrayList(); + List collectionList = new ArrayList<>(); for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) { if ( !Modifier.isStatic( ctField.getModifiers() ) ) { - if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) { + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( ctField ); + if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) { if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) { - collectionList.add( ctField ); + collectionList.add( annotatedField ); } } } @@ -427,41 +436,15 @@ static String capitalize(String value) { return Character.toUpperCase( value.charAt( 0 ) ) + value.substring( 1 ); } - static boolean isAnnotationPresent(FieldDescription fieldDescription, Class type) { - return getAnnotation( fieldDescription, type ) != null; - } - - static AnnotationDescription.Loadable getAnnotation(FieldDescription fieldDescription, Class type) { - AnnotationDescription.Loadable access = fieldDescription.getDeclaringType().asErasure().getDeclaredAnnotations().ofType( Access.class ); - if ( access != null && access.loadSilent().value() == AccessType.PROPERTY ) { - MethodDescription getter = getterOf( fieldDescription ); - if ( getter == null ) { - return fieldDescription.getDeclaredAnnotations().ofType( type ); - } - else { - return getter.getDeclaredAnnotations().ofType( type ); - } - } - else if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { - return fieldDescription.getDeclaredAnnotations().ofType( type ); - } - else { - MethodDescription getter = getterOf( fieldDescription ); - if ( getter != null ) { - AnnotationDescription.Loadable annotationDescription = getter.getDeclaredAnnotations().ofType( type ); - if ( annotationDescription != null ) { - return annotationDescription; - } - } - return fieldDescription.getDeclaredAnnotations().ofType( type ); - } + static MethodDescription getterOf(AnnotatedFieldDescription persistentField) { + return getterOf( persistentField.fieldDescription ); } static MethodDescription getterOf(FieldDescription persistentField) { MethodList methodList = MethodGraph.Compiler.DEFAULT.compile( persistentField.getDeclaringType().asErasure() ) .listNodes() .asMethodList() - .filter( isGetter(persistentField.getName() ) ); + .filter( isGetter( persistentField.getName() ) ); if ( methodList.size() == 1 ) { return methodList.getOnly(); } @@ -469,4 +452,88 @@ static MethodDescription getterOf(FieldDescription persistentField) { return null; } } + + static class AnnotatedFieldDescription implements UnloadedField { + + private final FieldDescription fieldDescription; + + private AnnotationList annotations; + + AnnotatedFieldDescription(FieldDescription fieldDescription) { + this.fieldDescription = fieldDescription; + } + + @Override + public boolean hasAnnotation(Class annotationType) { + return getAnnotations().isAnnotationPresent( annotationType ); + } + + AnnotationDescription.Loadable getAnnotation(Class annotationType) { + return getAnnotations().ofType( annotationType ); + } + + String getName() { + return fieldDescription.getName(); + } + + TypeDefinition getDeclaringType() { + return fieldDescription.getDeclaringType(); + } + + Generic getType() { + return fieldDescription.getType(); + } + + InDefinedShape asDefined() { + return fieldDescription.asDefined(); + } + + String getDescriptor() { + return fieldDescription.getDescriptor(); + } + + boolean isVisibleTo(TypeDescription typeDescription) { + return fieldDescription.isVisibleTo( typeDescription ); + } + + FieldDescription getFieldDescription() { + return fieldDescription; + } + + private AnnotationList getAnnotations() { + if ( annotations == null ) { + annotations = doGetAnnotations( fieldDescription ); + } + return annotations; + } + + private static AnnotationList doGetAnnotations(FieldDescription fieldDescription) { + AnnotationDescription.Loadable access = fieldDescription.getDeclaringType().asErasure() + .getDeclaredAnnotations().ofType( Access.class ); + if ( access != null && access.loadSilent().value() == AccessType.PROPERTY ) { + MethodDescription getter = getterOf( fieldDescription ); + if ( getter == null ) { + return fieldDescription.getDeclaredAnnotations(); + } + else { + return getter.getDeclaredAnnotations(); + } + } + else if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { + return fieldDescription.getDeclaredAnnotations(); + } + else { + MethodDescription getter = getterOf( fieldDescription ); + + // Note that the order here is important + List annotationDescriptions = new ArrayList<>(); + if ( getter != null ) { + annotationDescriptions.addAll( getter.getDeclaredAnnotations() ); + } + annotationDescriptions.addAll( fieldDescription.getDeclaredAnnotations() ); + + return fieldDescription.getDeclaredAnnotations(); + } + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java index 796d324102dd..d5f2020ca703 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java @@ -6,16 +6,18 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.hasDescriptor; +import static net.bytebuddy.matcher.ElementMatchers.named; + import javax.persistence.Id; -import net.bytebuddy.description.method.MethodList; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import net.bytebuddy.asm.AsmVisitorWrapper; -import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.field.FieldList; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -25,9 +27,6 @@ import net.bytebuddy.jar.asm.Type; import net.bytebuddy.pool.TypePool; -import static net.bytebuddy.matcher.ElementMatchers.hasDescriptor; -import static net.bytebuddy.matcher.ElementMatchers.named; - import java.util.Objects; final class FieldAccessEnhancer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { @@ -63,13 +62,13 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { return; } - FieldDescription field = findField( owner, name, desc ); + AnnotatedFieldDescription field = findField( owner, name, desc ); if ( ( enhancementContext.isEntityClass( field.getDeclaringType().asErasure() ) || enhancementContext.isCompositeClass( field.getDeclaringType().asErasure() ) ) && !field.getType().asErasure().equals( managedCtClass ) && enhancementContext.isPersistentField( field ) - && !EnhancerImpl.isAnnotationPresent( field, Id.class ) + && !field.hasAnnotation( Id.class ) && !field.getName().equals( "this$0" ) ) { log.debugf( @@ -110,7 +109,7 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { }; } - private FieldDescription findField(String owner, String name, String desc) { + private AnnotatedFieldDescription findField(String owner, String name, String desc) { //Classpool#describe does not accept '/' in the description name as it expects a class name final String cleanedOwner = owner.replace( '/', '.' ); final TypePool.Resolution resolution = classPool.describe( cleanedOwner ); @@ -130,7 +129,7 @@ private FieldDescription findField(String owner, String name, String desc) { ); throw new EnhancementException( msg ); } - return fields.getOnly(); + return new AnnotatedFieldDescription( fields.getOnly() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java index 9163f91c1d46..56b12d8eac52 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java @@ -8,6 +8,7 @@ import java.util.Objects; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -26,17 +27,17 @@ abstract class FieldReaderAppender implements ByteCodeAppender { protected final TypeDescription managedCtClass; - protected final FieldDescription persistentField; + protected final AnnotatedFieldDescription persistentField; protected final FieldDescription.InDefinedShape persistentFieldAsDefined; - private FieldReaderAppender(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldReaderAppender(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; this.persistentFieldAsDefined = persistentField.asDefined(); } - static ByteCodeAppender of(TypeDescription managedCtClass, FieldDescription persistentField) { + static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { if ( !persistentField.isVisibleTo( managedCtClass ) ) { return new MethodDispatching( managedCtClass, persistentField ); } @@ -119,7 +120,7 @@ public Size apply( private static class FieldWriting extends FieldReaderAppender { - private FieldWriting(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldWriting(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { super( managedCtClass, persistentField ); } @@ -146,7 +147,7 @@ protected void fieldWrite(MethodVisitor methodVisitor) { private static class MethodDispatching extends FieldReaderAppender { - private MethodDispatching(TypeDescription managedCtClass, FieldDescription persistentField) { + private MethodDispatching(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { super( managedCtClass, persistentField ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java index 9ab725d62fdd..d666ae43b10d 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java @@ -6,6 +6,7 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -31,7 +32,7 @@ private FieldWriterAppender(TypeDescription managedCtClass, FieldDescription.InD this.persistentFieldAsDefined = persistentFieldAsDefined; } - static ByteCodeAppender of(TypeDescription managedCtClass, FieldDescription persistentField) { + static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { if ( !persistentField.isVisibleTo( managedCtClass ) ) { return new MethodDispatching( managedCtClass, persistentField.asDefined() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java index e08cb5231856..34e2bb30cf50 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java @@ -8,10 +8,12 @@ import java.util.Collection; import java.util.Objects; + import javax.persistence.Embedded; import javax.persistence.EmbeddedId; import javax.persistence.Id; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import net.bytebuddy.ClassFileVersion; @@ -44,25 +46,26 @@ private InlineDirtyCheckingHandler(Implementation delegate, TypeDescription mana static Implementation wrap( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, - FieldDescription persistentField, + AnnotatedFieldDescription persistentField, Implementation implementation) { if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { if ( enhancementContext.isCompositeClass( managedCtClass ) ) { implementation = Advice.to( CodeTemplates.CompositeDirtyCheckingHandler.class ).wrap( implementation ); } - else if ( !EnhancerImpl.isAnnotationPresent( persistentField, Id.class ) - && !EnhancerImpl.isAnnotationPresent( persistentField, EmbeddedId.class ) + else if ( !persistentField.hasAnnotation( Id.class ) + && !persistentField.hasAnnotation( EmbeddedId.class ) && !( persistentField.getType().asErasure().isAssignableTo( Collection.class ) && enhancementContext.isMappedCollection( persistentField ) ) ) { - implementation = new InlineDirtyCheckingHandler( implementation, managedCtClass, persistentField.asDefined() ); + implementation = new InlineDirtyCheckingHandler( implementation, managedCtClass, + persistentField.asDefined() ); } if ( enhancementContext.isCompositeClass( persistentField.getType().asErasure() ) - && EnhancerImpl.isAnnotationPresent( persistentField, Embedded.class ) ) { + && persistentField.hasAnnotation( Embedded.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.FieldName.class, persistentField.getName() ) .to( CodeTemplates.CompositeFieldDirtyCheckingHandler.class ) .wrap( implementation ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 543820d4bd84..715ceef90a8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -6,6 +6,9 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.not; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -15,14 +18,12 @@ import javax.persistence.Embedded; -import net.bytebuddy.description.field.FieldList; -import net.bytebuddy.description.method.MethodList; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import net.bytebuddy.ClassFileVersion; import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.AsmVisitorWrapper; import net.bytebuddy.description.field.FieldDescription; @@ -40,9 +41,6 @@ import net.bytebuddy.jar.asm.Type; import net.bytebuddy.pool.TypePool; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -import static net.bytebuddy.matcher.ElementMatchers.not; - final class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributeTransformer.class ); @@ -53,13 +51,13 @@ final class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDecla private final TypePool classPool; - private final FieldDescription[] enhancedFields; + private final AnnotatedFieldDescription[] enhancedFields; private PersistentAttributeTransformer( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, TypePool classPool, - FieldDescription[] enhancedFields) { + AnnotatedFieldDescription[] enhancedFields) { this.managedCtClass = managedCtClass; this.enhancementContext = enhancementContext; this.classPool = classPool; @@ -70,14 +68,15 @@ public static PersistentAttributeTransformer collectPersistentFields( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, TypePool classPool) { - List persistentFieldList = new ArrayList(); + List persistentFieldList = new ArrayList<>(); for ( FieldDescription ctField : managedCtClass.getDeclaredFields() ) { // skip static fields and skip fields added by enhancement and outer reference in inner classes if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) { continue; } - if ( !ctField.isStatic() && enhancementContext.isPersistentField( ctField ) ) { - persistentFieldList.add( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( ctField ); + if ( !ctField.isStatic() && enhancementContext.isPersistentField( annotatedField ) ) { + persistentFieldList.add( annotatedField ); } } // HHH-10646 Add fields inherited from @MappedSuperclass @@ -86,12 +85,12 @@ public static PersistentAttributeTransformer collectPersistentFields( persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass, enhancementContext ) ); } - FieldDescription[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new FieldDescription[0] ) ); + AnnotatedFieldDescription[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new AnnotatedFieldDescription[0] ) ); log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ) ); return new PersistentAttributeTransformer( managedCtClass, enhancementContext, classPool, orderedFields ); } - private static Collection collectInheritPersistentFields( + private static Collection collectInheritPersistentFields( TypeDefinition managedCtClass, ByteBuddyEnhancementContext enhancementContext) { if ( managedCtClass == null || managedCtClass.represents( Object.class ) ) { @@ -102,15 +101,18 @@ private static Collection collectInheritPersistentFields( if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { return collectInheritPersistentFields( managedCtSuperclass, enhancementContext ); } + log.debugf( "Found @MappedSuperclass %s to collectPersistenceFields", managedCtSuperclass ); - List persistentFieldList = new ArrayList(); + + List persistentFieldList = new ArrayList<>(); for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) { if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) { continue; } - if ( !ctField.isStatic() && enhancementContext.isPersistentField( ctField ) ) { - persistentFieldList.add( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( ctField ); + if ( !ctField.isStatic() && enhancementContext.isPersistentField( annotatedField ) ) { + persistentFieldList.add( annotatedField ); } } persistentFieldList.addAll( collectInheritPersistentFields( managedCtSuperclass, enhancementContext ) ); @@ -157,7 +159,7 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { } private boolean isEnhanced(String owner, String name, String desc) { - for ( FieldDescription enhancedField : enhancedFields ) { + for ( AnnotatedFieldDescription enhancedField : enhancedFields ) { if ( enhancedField.getName().equals( name ) && enhancedField.getDescriptor().equals( desc ) && enhancedField.getDeclaringType().asErasure().getInternalName().equals( owner ) ) { @@ -171,7 +173,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) boolean compositeOwner = false; builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( not( nameStartsWith( "$$_hibernate_" ) ), this ) ); - for ( FieldDescription enhancedField : enhancedFields ) { + for ( AnnotatedFieldDescription enhancedField : enhancedFields ) { builder = builder .defineMethod( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + enhancedField.getName(), @@ -195,7 +197,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) if ( !compositeOwner && !accessor - && EnhancerImpl.isAnnotationPresent( enhancedField, Embedded.class ) + && enhancedField.hasAnnotation( Embedded.class ) && enhancementContext.isCompositeClass( enhancedField.getType().asErasure() ) && enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { compositeOwner = true; @@ -219,7 +221,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) return builder; } - private Implementation fieldReader(FieldDescription enhancedField) { + private Implementation fieldReader(AnnotatedFieldDescription enhancedField) { if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( enhancedField ) ) { if ( enhancedField.getDeclaringType().asErasure().equals( managedCtClass ) ) { return FieldAccessor.ofField( enhancedField.getName() ).in( enhancedField.getDeclaringType().asErasure() ); @@ -233,7 +235,7 @@ private Implementation fieldReader(FieldDescription enhancedField) { } } - private Implementation fieldWriter(FieldDescription enhancedField) { + private Implementation fieldWriter(AnnotatedFieldDescription enhancedField) { Implementation implementation; if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( enhancedField ) ) { if ( enhancedField.getDeclaringType().asErasure().equals( managedCtClass ) ) { @@ -259,9 +261,9 @@ private static class FieldMethodReader implements ByteCodeAppender { private final TypeDescription managedCtClass; - private final FieldDescription persistentField; + private final AnnotatedFieldDescription persistentField; - private FieldMethodReader(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldMethodReader(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; } @@ -289,9 +291,9 @@ private static class FieldMethodWriter implements ByteCodeAppender { private final TypeDescription managedCtClass; - private final FieldDescription persistentField; + private final AnnotatedFieldDescription persistentField; - private FieldMethodWriter(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldMethodWriter(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedFieldDescription.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedFieldDescription.java deleted file mode 100644 index c95915017d31..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedFieldDescription.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.bytecode.enhance.internal.bytebuddy; - -import java.lang.annotation.Annotation; - -import org.hibernate.bytecode.enhance.spi.UnloadedField; - -import net.bytebuddy.description.field.FieldDescription; - -class UnloadedFieldDescription implements UnloadedField { - - final FieldDescription fieldDescription; - - UnloadedFieldDescription(FieldDescription fieldDescription) { - this.fieldDescription = fieldDescription; - } - - @Override - public boolean hasAnnotation(Class annotationType) { - return fieldDescription.getDeclaredAnnotations().isAnnotationPresent( annotationType ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java index ef462326912d..e2ca1bde11e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java @@ -9,7 +9,6 @@ import java.lang.annotation.Annotation; import org.hibernate.bytecode.enhance.spi.UnloadedClass; -import org.hibernate.bytecode.enhance.spi.UnloadedField; import net.bytebuddy.description.type.TypeDescription; From 38e48ef0bc94ad6f8957f87c891a156230fcc566 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 13 Nov 2018 17:09:15 +0100 Subject: [PATCH 228/772] HHH-13097 Cache the resolution of the getters --- .../BiDirectionalAssociationHandler.java | 11 ++-- .../ByteBuddyEnhancementContext.java | 54 ++++++++++++++++ .../internal/bytebuddy/EnhancerImpl.java | 61 ++++++++++--------- .../bytebuddy/FieldAccessEnhancer.java | 2 +- .../PersistentAttributeTransformer.java | 4 +- 5 files changed, 94 insertions(+), 38 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java index d5e35473c636..9e120435a85b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java @@ -9,6 +9,7 @@ import java.util.Collection; import java.util.Map; import java.util.Objects; +import java.util.Optional; import javax.persistence.Access; import javax.persistence.AccessType; @@ -166,12 +167,12 @@ private static TypeDescription.Generic target(AnnotatedFieldDescription persiste return persistentField.getType(); } else { - MethodDescription getter = EnhancerImpl.getterOf( persistentField ); - if ( getter == null ) { - return persistentField.getType(); + Optional getter = persistentField.getGetter(); + if ( getter.isPresent() ) { + return getter.get().getReturnType(); } else { - return getter.getReturnType(); + return persistentField.getType(); } } } @@ -211,7 +212,7 @@ private static String getMappedByNotManyToMany(AnnotatedFieldDescription target) private static String getMappedByManyToMany(AnnotatedFieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { for ( FieldDescription f : targetEntity.getDeclaredFields() ) { - AnnotatedFieldDescription annotatedF = new AnnotatedFieldDescription( f ); + AnnotatedFieldDescription annotatedF = new AnnotatedFieldDescription( context, f ); if ( context.isPersistentField( annotatedF ) && target.getName().equals( getMappedByNotManyToMany( annotatedF ) ) && target.getDeclaringType().asErasure().isAssignableTo( entityType( annotatedF.getType() ) ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java index 40df641c4f4e..1cb20ff0e6e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java @@ -6,15 +6,33 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.isGetter; + +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.hibernate.MappingException; import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.scaffold.MethodGraph; +import net.bytebuddy.matcher.ElementMatcher; class ByteBuddyEnhancementContext { + private static final ElementMatcher.Junction IS_GETTER = isGetter(); + private final EnhancementContext enhancementContext; + private final ConcurrentHashMap> getterByTypeMap = new ConcurrentHashMap<>(); + ByteBuddyEnhancementContext(EnhancementContext enhancementContext) { this.enhancementContext = enhancementContext; } @@ -66,4 +84,40 @@ public boolean isMappedCollection(AnnotatedFieldDescription field) { public boolean doBiDirectionalAssociationManagement(AnnotatedFieldDescription field) { return enhancementContext.doBiDirectionalAssociationManagement( field ); } + + Optional resolveGetter(FieldDescription fieldDescription) { + Map getters = getterByTypeMap + .computeIfAbsent( fieldDescription.getDeclaringType().asErasure(), declaringType -> { + return MethodGraph.Compiler.DEFAULT.compile( declaringType ) + .listNodes() + .asMethodList() + .filter( IS_GETTER ) + .stream() + .collect( Collectors.toMap( MethodDescription::getActualName, Function.identity() ) ); + } ); + + String capitalizedFieldName = Character.toUpperCase( fieldDescription.getName().charAt( 0 ) ) + + fieldDescription.getName().substring( 1 ); + + MethodDescription getCandidate = getters.get( "get" + capitalizedFieldName ); + MethodDescription isCandidate = getters.get( "is" + capitalizedFieldName ); + + if ( getCandidate != null ) { + if ( isCandidate != null ) { + throw new MappingException( + String.format( + Locale.ROOT, + "In trying to locate getter for property [%s], Class [%s] defined " + + "both a `get` [%s] and `is` [%s] variant", + fieldDescription.getName(), + fieldDescription.getDeclaringType().getActualName(), + getCandidate.getActualName(), + isCandidate.getActualName() ) ); + } + + return Optional.of( getCandidate ); + } + + return Optional.ofNullable( isCandidate ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 44a79e5a1d8f..ba710b0bb98a 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -15,12 +15,18 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Transient; +import org.hibernate.MappingException; import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker; import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker; import org.hibernate.bytecode.enhance.spi.CollectionTracker; @@ -51,7 +57,6 @@ import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.field.FieldDescription.InDefinedShape; import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.method.MethodList; import net.bytebuddy.description.modifier.FieldPersistence; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.type.TypeDefinition; @@ -390,7 +395,7 @@ private List collectCollectionFields(TypeDescription if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) { continue; } - AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) { if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) { collectionList.add( annotatedField ); @@ -420,7 +425,7 @@ private Collection collectInheritCollectionFields(Typ for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) { if ( !Modifier.isStatic( ctField.getModifiers() ) ) { - AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) { if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) { collectionList.add( annotatedField ); @@ -436,30 +441,18 @@ static String capitalize(String value) { return Character.toUpperCase( value.charAt( 0 ) ) + value.substring( 1 ); } - static MethodDescription getterOf(AnnotatedFieldDescription persistentField) { - return getterOf( persistentField.fieldDescription ); - } - - static MethodDescription getterOf(FieldDescription persistentField) { - MethodList methodList = MethodGraph.Compiler.DEFAULT.compile( persistentField.getDeclaringType().asErasure() ) - .listNodes() - .asMethodList() - .filter( isGetter( persistentField.getName() ) ); - if ( methodList.size() == 1 ) { - return methodList.getOnly(); - } - else { - return null; - } - } - static class AnnotatedFieldDescription implements UnloadedField { + private final ByteBuddyEnhancementContext context; + private final FieldDescription fieldDescription; private AnnotationList annotations; - AnnotatedFieldDescription(FieldDescription fieldDescription) { + private Optional getter; + + AnnotatedFieldDescription(ByteBuddyEnhancementContext context, FieldDescription fieldDescription) { + this.context = context; this.fieldDescription = fieldDescription; } @@ -500,35 +493,43 @@ FieldDescription getFieldDescription() { return fieldDescription; } + Optional getGetter() { + if ( getter == null ) { + getter = context.resolveGetter( fieldDescription ); + } + + return getter; + } + private AnnotationList getAnnotations() { if ( annotations == null ) { - annotations = doGetAnnotations( fieldDescription ); + annotations = doGetAnnotations(); } return annotations; } - private static AnnotationList doGetAnnotations(FieldDescription fieldDescription) { + private AnnotationList doGetAnnotations() { AnnotationDescription.Loadable access = fieldDescription.getDeclaringType().asErasure() .getDeclaredAnnotations().ofType( Access.class ); if ( access != null && access.loadSilent().value() == AccessType.PROPERTY ) { - MethodDescription getter = getterOf( fieldDescription ); - if ( getter == null ) { - return fieldDescription.getDeclaredAnnotations(); + Optional getter = getGetter(); + if ( getter.isPresent() ) { + return getter.get().getDeclaredAnnotations(); } else { - return getter.getDeclaredAnnotations(); + return fieldDescription.getDeclaredAnnotations(); } } else if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { return fieldDescription.getDeclaredAnnotations(); } else { - MethodDescription getter = getterOf( fieldDescription ); + Optional getter = getGetter(); // Note that the order here is important List annotationDescriptions = new ArrayList<>(); - if ( getter != null ) { - annotationDescriptions.addAll( getter.getDeclaredAnnotations() ); + if ( getter.isPresent() ) { + annotationDescriptions.addAll( getter.get().getDeclaredAnnotations() ); } annotationDescriptions.addAll( fieldDescription.getDeclaredAnnotations() ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java index d5f2020ca703..2bde18a1a144 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java @@ -129,7 +129,7 @@ private AnnotatedFieldDescription findField(String owner, String name, String de ); throw new EnhancementException( msg ); } - return new AnnotatedFieldDescription( fields.getOnly() ); + return new AnnotatedFieldDescription( enhancementContext, fields.getOnly() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 715ceef90a8c..b285a9200e75 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -74,7 +74,7 @@ public static PersistentAttributeTransformer collectPersistentFields( if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) { continue; } - AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); if ( !ctField.isStatic() && enhancementContext.isPersistentField( annotatedField ) ) { persistentFieldList.add( annotatedField ); } @@ -110,7 +110,7 @@ private static Collection collectInheritPersistentFie if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) { continue; } - AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); if ( !ctField.isStatic() && enhancementContext.isPersistentField( annotatedField ) ) { persistentFieldList.add( annotatedField ); } From af519e3c12e3cca99c7de842d3ee97ed0c3f9b6c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 13 Nov 2018 17:11:28 +0100 Subject: [PATCH 229/772] HHH-13097 Cache a static ByteBuddy matcher --- .../bytebuddy/PersistentAttributeTransformer.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index b285a9200e75..4026144be3eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -26,6 +26,7 @@ import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.AsmVisitorWrapper; +import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.modifier.Visibility; @@ -39,12 +40,15 @@ import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; +import net.bytebuddy.matcher.ElementMatcher.Junction; import net.bytebuddy.pool.TypePool; final class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributeTransformer.class ); + private static final Junction NOT_HIBERNATE_GENERATED = not( nameStartsWith( "$$_hibernate_" ) ); + private final TypeDescription managedCtClass; private final ByteBuddyEnhancementContext enhancementContext; @@ -172,7 +176,7 @@ private boolean isEnhanced(String owner, String name, String desc) { DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) { boolean compositeOwner = false; - builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( not( nameStartsWith( "$$_hibernate_" ) ), this ) ); + builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, this ) ); for ( AnnotatedFieldDescription enhancedField : enhancedFields ) { builder = builder .defineMethod( @@ -254,7 +258,7 @@ private Implementation fieldWriter(AnnotatedFieldDescription enhancedField) { DynamicType.Builder applyExtended(DynamicType.Builder builder) { AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper enhancer = new FieldAccessEnhancer( managedCtClass, enhancementContext, classPool ); - return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( not( nameStartsWith( "$$_hibernate_" ) ), enhancer ) ); + return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, enhancer ) ); } private static class FieldMethodReader implements ByteCodeAppender { From f511e871fbd59060037ca670c653dd5fa0e75a05 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 13 Nov 2018 20:26:33 +0100 Subject: [PATCH 230/772] HHH-13097 Don't throw an exception if there are two getter candidates --- .../bytebuddy/ByteBuddyEnhancementContext.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java index 1cb20ff0e6e2..082e73adc4a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java @@ -104,15 +104,10 @@ Optional resolveGetter(FieldDescription fieldDescription) { if ( getCandidate != null ) { if ( isCandidate != null ) { - throw new MappingException( - String.format( - Locale.ROOT, - "In trying to locate getter for property [%s], Class [%s] defined " + - "both a `get` [%s] and `is` [%s] variant", - fieldDescription.getName(), - fieldDescription.getDeclaringType().getActualName(), - getCandidate.getActualName(), - isCandidate.getActualName() ) ); + // if there are two candidates, the existing code considered there was no getter. + // not sure it's such a good idea but throwing an exception apparently throws exception + // in cases where Hibernate does not usually throw a mapping error. + return Optional.empty(); } return Optional.of( getCandidate ); From 7ecf22793ff4d087dbe090855dd8b1372a806930 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 14 Nov 2018 14:09:49 +0100 Subject: [PATCH 231/772] HHH-13100 Make a couple of equals()/hashCode() implementations lighter --- .../enhance/internal/bytebuddy/FieldAccessEnhancer.java | 6 ++---- .../bytebuddy/PersistentAttributeTransformer.java | 9 ++------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java index 2bde18a1a144..3fff802b1257 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java @@ -141,14 +141,12 @@ public boolean equals(final Object o) { return false; } final FieldAccessEnhancer that = (FieldAccessEnhancer) o; - return Objects.equals( managedCtClass, that.managedCtClass ) && - Objects.equals( enhancementContext, that.enhancementContext ) && - Objects.equals( classPool, that.classPool ); + return Objects.equals( managedCtClass, that.managedCtClass ); } @Override public int hashCode() { - return Objects.hash( managedCtClass, enhancementContext, classPool ); + return managedCtClass.hashCode(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 4026144be3eb..6e332a18639f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -331,16 +331,11 @@ public boolean equals(final Object o) { return false; } final PersistentAttributeTransformer that = (PersistentAttributeTransformer) o; - return Objects.equals( managedCtClass, that.managedCtClass ) && - Objects.equals( enhancementContext, that.enhancementContext ) && - Objects.equals( classPool, that.classPool ) && - Arrays.equals( enhancedFields, that.enhancedFields ); + return Objects.equals( managedCtClass, that.managedCtClass ); } @Override public int hashCode() { - int result = Objects.hash( managedCtClass, enhancementContext, classPool ); - result = 31 * result + Arrays.hashCode( enhancedFields ); - return result; + return managedCtClass.hashCode(); } } From 747c784dc1f35e75e95f9d879171b0139623fba7 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 14 Nov 2018 15:32:29 +0000 Subject: [PATCH 232/772] HHH-13100 Remove unused import --- .../internal/bytebuddy/PersistentAttributeTransformer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 6e332a18639f..18050ac72f35 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -26,7 +26,6 @@ import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.AsmVisitorWrapper; -import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.modifier.Visibility; From 72b9fb13ad81356e6d9d3eca20fc5f1cc879f056 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 22 Jan 2019 16:24:16 +0100 Subject: [PATCH 233/772] HHH-13220 Only create the PersistentAttributeTransformer if the class is enhanced --- .../enhance/internal/bytebuddy/EnhancerImpl.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index ba710b0bb98a..b543750cf8c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -159,8 +159,6 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes return null; } - PersistentAttributeTransformer transformer = PersistentAttributeTransformer.collectPersistentFields( managedCtClass, enhancementContext, typePool ); - if ( enhancementContext.isEntityClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as Entity", managedCtClass.getName() ); builder = builder.implement( ManagedEntity.class ) @@ -291,7 +289,7 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes } } - return transformer.applyTo( builder, false ); + return createTransformer( managedCtClass ).applyTo( builder, false ); } else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as Composite", managedCtClass.getName() ); @@ -324,17 +322,17 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { .intercept( implementationClearOwner ); } - return transformer.applyTo( builder, false ); + return createTransformer( managedCtClass ).applyTo( builder, false ); } else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() ); builder = builder.implement( ManagedMappedSuperclass.class ); - return transformer.applyTo( builder, true ); + return createTransformer( managedCtClass ).applyTo( builder, true ); } else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { log.infof( "Extended enhancement of [%s]", managedCtClass.getName() ); - return transformer.applyExtended( builder ); + return createTransformer( managedCtClass ).applyExtended( builder ); } else { log.debugf( "Skipping enhancement of [%s]: not entity or composite", managedCtClass.getName() ); @@ -342,6 +340,10 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { } } + private PersistentAttributeTransformer createTransformer(TypeDescription typeDescription) { + return PersistentAttributeTransformer.collectPersistentFields( typeDescription, enhancementContext, typePool ); + } + // See HHH-10977 HHH-11284 HHH-11404 --- check for declaration of Managed interface on the class, not inherited private boolean alreadyEnhanced(TypeDescription managedCtClass) { for ( TypeDescription.Generic declaredInterface : managedCtClass.getInterfaces() ) { From 4d4deacf63d309a71b6744003b6d45999fde3e8e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 19 Feb 2019 11:01:46 +0100 Subject: [PATCH 234/772] 5.3.8.Final --- changelog.txt | 37 ++++++++++++++++++++++++++++++++++ gradle/base-information.gradle | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index fe1a5dbd0442..90b05c1a448a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,43 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.3.8.final (February 19th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31715/tab/release-report-done + +** Bug + * [HHH-10891] - Exception at bootstrap when @Any is inside an @Embeddable object + * [HHH-11209] - NullPointerException in EntityType.replace() with a PersistentBag + * [HHH-12555] - Merging a blob on an entity results in a class cast exception + * [HHH-13050] - On release of batch it still contained JDBC statements logged; unable to release batch statement + * [HHH-13059] - OneToMany with referencedColumnName returns too many entities + * [HHH-13064] - Documentation of Lock and LockModeType is on two columns instead of 3 + * [HHH-13076] - Hibernate “Transaction already active” behaviour with custom transaction manager + * [HHH-13084] - Querying entity with non-ID property named 'id' fails if entity has an IdClass composite key + * [HHH-13097] - Hibernate enhancer is superslow after upgrade to latest 5.3 or 5.4-SNAPSHOT + * [HHH-13114] - Query "select count(h) from Human h" fails if a subclass has a non-Id property named "id" + * [HHH-13129] - Cascaded merge fails for detached bytecode-enhanced entity with uninitialized ToOne + * [HHH-13164] - Detecting transient state of mandatory toOne relations is broken + * [HHH-13169] - Table alias used instead of exact table name in multitable update query + * [HHH-13172] - Log a warning instead of throwing an Exception when @AttributeOverride is used in conjunction with inheritance + * [HHH-13194] - Some methods returning org.hibernate.query.Query are not defined for StatelessSession + * [HHH-13244] - setting hibernate.jpa.compliance.proxy=true and org.hibernate debug level to DEBUG breaks hibernate + +** Task + * [HHH-13099] - Update to Byte Buddy 1.9.4 + * [HHH-13100] - All custom implementation of Byte Buddy "Implementation" s should have a proper equals and hashcode + +** Improvement + * [HHH-12917] - Interning of strings for Filter definitions + * [HHH-12918] - Interning of strings for Formula and Column exctraction templates + * [HHH-12919] - Interning of strings for EntityReferenceAliases + * [HHH-13005] - Upgrade ByteBuddy to 1.9.0 + * [HHH-13057] - Prevent Byte Buddy's Advice helper to need reloading many resources from the ClassLoader + * [HHH-13220] - In the ByteBuddy enhancer, avoid creating a PersistentAttributeTransformer if the class is not enhanced + + + Changes in 5.3.7.final (October 16th, 2018) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 16e8367d1891..7367dd8a9b3d 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.8-SNAPSHOT', project ) + ormVersion = new HibernateVersion( '5.3.8.Final', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From 8e2ebf817f5e69d6a0ccc2c4cf36c4997df53a99 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 19 Feb 2019 12:19:07 +0100 Subject: [PATCH 235/772] Prepare for next development iteration --- gradle/base-information.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 7367dd8a9b3d..9fe951e4dca6 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.8.Final', project ) + ormVersion = new HibernateVersion( '5.3.9-SNAPSHOT', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From e86a7a43d9356c628171155d4a99066fd908e974 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 15 Nov 2018 20:35:44 +0000 Subject: [PATCH 236/772] HHH-13107 - JtaWithStatementsBatchTest fails on Oracle (cherry picked from commit 5515347ec3d765760543bdc3289fa646bf24d5fd) --- .../jpa/test/transaction/batch/AbstractJtaBatchTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java index 062d5f3a4d1d..4342954524f2 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java @@ -14,6 +14,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.Table; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; @@ -83,6 +84,7 @@ protected void assertAllStatementsAreClosed(List statements) protected abstract String getBatchBuilderClassName(); @Entity(name = "Comment") + @Table(name = "COMMENTS") public static class Comment { private Long id; private String message; From 2c60c8d2a014e23babbbc5b029315e8cdfb51e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 21 Nov 2018 09:11:13 +0100 Subject: [PATCH 237/772] HHH-13112 Add a test with an entity in the default package --- .../java/AnnotationMappedNoPackageEntity.java | 35 ++++++++++++++ .../src/test/java/NoPackageTest.java | 48 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java create mode 100644 hibernate-core/src/test/java/NoPackageTest.java diff --git a/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java b/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java new file mode 100644 index 000000000000..78bb726afa12 --- /dev/null +++ b/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java @@ -0,0 +1,35 @@ +/* + * 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 . + */ + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class AnnotationMappedNoPackageEntity { + @Id + private Integer id; + + @Basic + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/NoPackageTest.java b/hibernate-core/src/test/java/NoPackageTest.java new file mode 100644 index 000000000000..5c8e20aef34c --- /dev/null +++ b/hibernate-core/src/test/java/NoPackageTest.java @@ -0,0 +1,48 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ + +import org.hibernate.query.Query; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test using an entity which is in no package. + * We had problems with ByteBuddy in the past. + */ +@TestForIssue(jiraKey = "HHH-13112") +public class NoPackageTest extends BaseCoreFunctionalTestCase { + + @Test + public void testNoException() { + inTransaction( session -> { + AnnotationMappedNoPackageEntity box = new AnnotationMappedNoPackageEntity(); + box.setId( 42 ); + box.setName( "This feels dirty" ); + session.persist( box ); + } ); + + inTransaction( session -> { + Query query = session.createQuery( + "select e from " + AnnotationMappedNoPackageEntity.class.getSimpleName() + " e", + AnnotationMappedNoPackageEntity.class + ); + AnnotationMappedNoPackageEntity box = query.getSingleResult(); + assertEquals( (Integer) 42, box.getId() ); + } ); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + AnnotationMappedNoPackageEntity.class + }; + } +} From 9d716bd333b4a652d03641d4542e344ba0e7f29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 23 Nov 2018 08:06:13 +0100 Subject: [PATCH 238/772] HHH-13112 Upgrade to ByteBuddy 1.9.5 ... which fixes https://github.com/raphw/byte-buddy/issues/568 --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index a5f6b96c8177..35bb364d1883 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -23,7 +23,7 @@ ext { weldVersion = '3.0.0.Final' javassistVersion = '3.23.1-GA' - byteBuddyVersion = '1.9.4' + byteBuddyVersion = '1.9.5' geolatteVersion = '1.3.0' From fd93c89d9540e63058ea7c2ffc8f35865c93e712 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 12 Feb 2019 10:48:27 +0000 Subject: [PATCH 239/772] HHH-13262 - Add test for issue --- .../flush/NonTransactionalDataAccessTest.java | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/flush/NonTransactionalDataAccessTest.java b/hibernate-core/src/test/java/org/hibernate/test/flush/NonTransactionalDataAccessTest.java index dc2592c981d4..9ecb834156e2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/flush/NonTransactionalDataAccessTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/flush/NonTransactionalDataAccessTest.java @@ -8,20 +8,18 @@ import javax.persistence.Entity; import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.Table; import javax.persistence.TransactionRequiredException; -import java.io.Serializable; import org.hibernate.Session; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; -import org.hibernate.resource.transaction.spi.TransactionStatus; - -import org.junit.Test; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Test; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNull.nullValue; @@ -50,21 +48,21 @@ protected void configure(Configuration configuration) { @Override protected void prepareTest() throws Exception { - try (Session s = openSession()) { - final MyEntity entity = new MyEntity( "entity" ); - s.getTransaction().begin(); - try { - s.save( entity ); - - s.getTransaction().commit(); - } - catch (Exception e) { - if ( s.getTransaction().getStatus() == TransactionStatus.ACTIVE ) { - s.getTransaction().rollback(); + final MyEntity entity = new MyEntity( "entity" ); + inTransaction( + session -> { + session.save( entity ); } - throw e; - } - } + ); + } + + @After + public void tearDown() { + inTransaction( + session -> { + session.createQuery( "delete from MyEntity" ).executeUpdate(); + } + ); } @Test @@ -82,6 +80,26 @@ public void testFlushAllowingOutOfTransactionUpdateOperations() throws Exception } } + @Test + public void testNativeQueryAllowingOutOfTransactionUpdateOperations() throws Exception { + allowUpdateOperationOutsideTransaction = "true"; + rebuildSessionFactory(); + prepareTest(); + try (Session s = openSession()) { + s.createSQLQuery( "delete from MY_ENTITY" ).executeUpdate(); + } + } + + @Test(expected = TransactionRequiredException.class) + public void testNativeQueryDisallowingOutOfTransactionUpdateOperations() throws Exception { + allowUpdateOperationOutsideTransaction = "false"; + rebuildSessionFactory(); + prepareTest(); + try (Session s = openSession()) { + s.createSQLQuery( "delete from MY_ENTITY" ).executeUpdate(); + } + } + @Test(expected = TransactionRequiredException.class) public void testFlushDisallowingOutOfTransactionUpdateOperations() throws Exception { allowUpdateOperationOutsideTransaction = "false"; @@ -113,6 +131,7 @@ public void testFlushOutOfTransaction() throws Exception { } @Entity(name = "MyEntity") + @Table(name = "MY_ENTITY") public static class MyEntity { @Id @GeneratedValue @@ -135,20 +154,4 @@ public void setName(String name) { this.name = name; } } - - public final Serializable doInsideTransaction(Session s, java.util.function.Supplier sp) { - s.getTransaction().begin(); - try { - final Serializable result = sp.get(); - - s.getTransaction().commit(); - return result; - } - catch (Exception e) { - if ( s.getTransaction().getStatus() == TransactionStatus.ACTIVE ) { - s.getTransaction().rollback(); - } - throw e; - } - } } From 9d04140fc69042553e9fe2b01f33b81b09372803 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 12 Feb 2019 11:22:34 +0000 Subject: [PATCH 240/772] HHH-13262 - javax.persistence.TransactionRequiredException: Executing an update/delete query --- .../engine/spi/SessionDelegatorBaseImpl.java | 5 +++++ .../spi/SharedSessionContractImplementor.java | 13 +++++++++++++ .../AbstractSharedSessionContract.java | 12 ++++++++++++ .../org/hibernate/internal/SessionImpl.java | 18 +++++++----------- .../procedure/internal/ProcedureCallImpl.java | 6 ++---- .../query/internal/AbstractProducedQuery.java | 9 ++------- 6 files changed, 41 insertions(+), 22 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java index 4ec317d20c59..38762d1debd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java @@ -156,6 +156,11 @@ public boolean isTransactionInProgress() { return delegate.isTransactionInProgress(); } + @Override + public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { + delegate.checkTransactionNeededForUpdateOperation( exceptionMessage ); + } + @Override public LockOptions getLockRequest(LockModeType lockModeType, Map properties) { return delegate.getLockRequest( lockModeType, properties ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java index 0c3fe2ea2ceb..cb227d0c20e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.UUID; import javax.persistence.FlushModeType; +import javax.persistence.TransactionRequiredException; import org.hibernate.CacheMode; import org.hibernate.Criteria; @@ -180,6 +181,18 @@ default long getTimestamp() { */ boolean isTransactionInProgress(); + /** + * Check if an active Transaction is necessary for the update operation to be executed. + * If an active Transaction is necessary but it is not then a TransactionRequiredException is raised. + * + * @param exceptionMessage, the message to use for the TransactionRequiredException + */ + default void checkTransactionNeededForUpdateOperation(String exceptionMessage) { + if ( !isTransactionInProgress() ) { + throw getExceptionConverter().convert( new TransactionRequiredException( exceptionMessage ) ); + } + } + /** * Provides access to the underlying transaction or creates a new transaction if * one does not already exist or is active. This is primarily for internal or diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index ac3d5da8acf5..a4b6a5c1f114 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -15,6 +15,7 @@ import java.util.TimeZone; import java.util.UUID; import javax.persistence.FlushModeType; +import javax.persistence.TransactionRequiredException; import javax.persistence.Tuple; import org.hibernate.AssertionFailure; @@ -128,6 +129,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont protected boolean closed; protected boolean waitingForAutoClose; + private transient boolean disallowOutOfTransactionUpdateOperations; // transient & non-final for Serialization purposes - ugh private transient SessionEventListenerManagerImpl sessionEventsManager = new SessionEventListenerManagerImpl(); @@ -141,6 +143,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreationOptions options) { this.factory = factory; this.cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); + this.disallowOutOfTransactionUpdateOperations = !factory.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); this.flushMode = options.getInitialSessionFlushMode(); @@ -389,6 +392,13 @@ public boolean isTransactionInProgress() { return !isClosed() && transactionCoordinator.isTransactionActive(); } + @Override + public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { + if ( disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { + throw getExceptionConverter().convert( new TransactionRequiredException( exceptionMessage ) ); + } + } + @Override public Transaction getTransaction() throws HibernateException { if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { @@ -1133,5 +1143,7 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor ); exceptionConverter = new ExceptionConverterImpl( this ); + this.disallowOutOfTransactionUpdateOperations = !getFactory().getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index de441c2b044b..9a5b59b601ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -238,7 +238,6 @@ public final class SessionImpl private transient int dontFlushFromFind; - private transient boolean disallowOutOfTransactionUpdateOperations; private transient ExceptionMapper exceptionMapper; private transient ManagedFlushChecker managedFlushChecker; @@ -262,7 +261,7 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { this.autoClear = options.shouldAutoClear(); this.autoClose = options.shouldAutoClose(); this.queryParametersValidationEnabled = options.isQueryParametersValidationEnabled(); - this.disallowOutOfTransactionUpdateOperations = !factory.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + this.discardOnClose = getFactory().getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); if ( options instanceof SharedSessionCreationOptions && ( (SharedSessionCreationOptions) options ).isTransactionCoordinatorShared() ) { @@ -1441,7 +1440,7 @@ public void flush() throws HibernateException { } private void doFlush() { - checkTransactionNeeded(); + checkTransactionNeededForUpdateOperation(); checkTransactionSynchStatus(); try { @@ -3474,7 +3473,7 @@ public T find(Class entityClass, Object primaryKey, LockModeType lockMode if ( lockModeType != null ) { if ( !LockModeType.NONE.equals( lockModeType) ) { - checkTransactionNeeded(); + checkTransactionNeededForUpdateOperation(); } lockOptions = buildLockOptions( lockModeType, properties ); loadAccess.with( lockOptions ); @@ -3547,10 +3546,8 @@ private CacheStoreMode determineCacheStoreMode(Map settings) { return ( CacheStoreMode ) settings.get( JPA_SHARED_CACHE_STORE_MODE ); } - private void checkTransactionNeeded() { - if ( disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { - throw new TransactionRequiredException( "no transaction is in progress" ); - } + private void checkTransactionNeededForUpdateOperation() { + checkTransactionNeededForUpdateOperation( "no transaction is in progress" ); } @Override @@ -3576,7 +3573,7 @@ public void lock(Object entity, LockModeType lockModeType) { @Override public void lock(Object entity, LockModeType lockModeType, Map properties) { checkOpen(); - checkTransactionNeeded(); + checkTransactionNeededForUpdateOperation(); if ( !contains( entity ) ) { throw new IllegalArgumentException( "entity not in the persistence context" ); @@ -3618,7 +3615,7 @@ public void refresh(Object entity, LockModeType lockModeType, Map R uniqueElement(List list) throws NonUniqueResultException @Override public int executeUpdate() throws HibernateException { - if ( ! getProducer().isTransactionInProgress() ) { - throw getProducer().getExceptionConverter().convert( - new TransactionRequiredException( - "Executing an update/delete query" - ) - ); - } + getProducer().checkTransactionNeededForUpdateOperation( "Executing an update/delete query" ); + beforeQuery(); try { return doExecuteUpdate(); From a3bd66c0a43d5ea3eaa1551579ea482adf201e17 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Sun, 24 Feb 2019 17:01:05 -0800 Subject: [PATCH 241/772] HHH-13262 - javax.persistence.TransactionRequiredException: Executing an update/delete query --- .../hibernate/engine/spi/SharedSessionContractImplementor.java | 2 +- .../org/hibernate/internal/AbstractSharedSessionContract.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java index cb227d0c20e3..d0db9689f6e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java @@ -189,7 +189,7 @@ default long getTimestamp() { */ default void checkTransactionNeededForUpdateOperation(String exceptionMessage) { if ( !isTransactionInProgress() ) { - throw getExceptionConverter().convert( new TransactionRequiredException( exceptionMessage ) ); + throw new TransactionRequiredException( exceptionMessage ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index a4b6a5c1f114..ee523b802dea 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -395,7 +395,7 @@ public boolean isTransactionInProgress() { @Override public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { if ( disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { - throw getExceptionConverter().convert( new TransactionRequiredException( exceptionMessage ) ); + throw new TransactionRequiredException( exceptionMessage ); } } From 55d18d0259055a78afdd89588632fa20138555d3 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Fri, 15 Feb 2019 10:15:34 +0200 Subject: [PATCH 242/772] HHH-13269 - Embeddable collection regression due to HHH-11544 (cherry picked from commit 819f92c42588abe3c61a92abd89e56db464cba66) --- .../hql/internal/ast/tree/DotNode.java | 9 +- .../LoadQueryJoinAndFetchProcessor.java | 6 +- .../test/mapping/NestedEmbeddableTest.java | 318 ++++++++++++++++++ 3 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index 14086dd770e3..34cbed457ae2 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -6,11 +6,6 @@ */ package org.hibernate.hql.internal.ast.tree; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - import org.hibernate.QueryException; import org.hibernate.engine.internal.JoinSequence; import org.hibernate.hql.internal.CollectionProperties; @@ -21,7 +16,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.loader.plan.spi.EntityQuerySpace; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; @@ -504,7 +499,7 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b JoinSequence joinSequence; - if ( joinColumns.length == 0 ) { + if ( joinColumns.length == 0 && lhsFromElement instanceof EntityQuerySpace ) { // When no columns are available, this is a special join that involves multiple subtypes String lhsTableAlias = getLhs().getFromElement().getTableAlias(); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java index 9d9aae2a019e..333f3384d01e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java @@ -259,9 +259,11 @@ else if ( !StringHelper.isEmpty( joinConditions ) ) { ); String[] joinColumns = join.resolveAliasedLeftHandSideJoinConditionColumns( lhsTableAlias ); - if ( joinColumns.length == 0 ) { + QuerySpace lhsQuerySpace = join.getLeftHandSide(); + if ( joinColumns.length == 0 && lhsQuerySpace instanceof EntityQuerySpace ) { // When no columns are available, this is a special join that involves multiple subtypes - AbstractEntityPersister persister = (AbstractEntityPersister) ( (EntityQuerySpace) join.getLeftHandSide() ).getEntityPersister(); + EntityQuerySpace entityQuerySpace = (EntityQuerySpace) lhsQuerySpace; + AbstractEntityPersister persister = (AbstractEntityPersister) entityQuerySpace.getEntityPersister(); String[][] polyJoinColumns = persister.getPolymorphicJoinColumns( lhsTableAlias, diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java new file mode 100644 index 000000000000..dfba5e9e5c9f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java @@ -0,0 +1,318 @@ +/* + * 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.mapping; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import javax.persistence.AssociationOverride; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Type; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.jpa.test.criteria.components.Alias; +import org.hibernate.jpa.test.criteria.components.Client; +import org.hibernate.jpa.test.criteria.components.Client_; +import org.hibernate.jpa.test.criteria.components.Name_; + +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class NestedEmbeddableTest extends BaseEntityManagerFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Categorization.class, + Category.class, + CcmObject.class, + Domain.class + }; + } + + @Test + public void test() { + + } + + @Entity + @Table(name = "CATEGORIZATIONS") + public static class Categorization implements Serializable { + + @Id + @Column(name = "CATEGORIZATION_ID") + @GeneratedValue(strategy = GenerationType.AUTO) + private long categorizationId; + + @ManyToOne + @JoinColumn(name = "CATEGORY_ID") + private Category category; + + @ManyToOne + @JoinColumn(name = "OBJECT_ID") + private CcmObject categorizedObject; + + public long getCategorizationId() { + return categorizationId; + } + + public void setCategorizationId(long categorizationId) { + this.categorizationId = categorizationId; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public CcmObject getCategorizedObject() { + return categorizedObject; + } + + public void setCategorizedObject(CcmObject categorizedObject) { + this.categorizedObject = categorizedObject; + } + } + + @Entity + @Table(name = "CATEGORIES") + public static class Category extends CcmObject implements Serializable { + + private static final long serialVersionUID = 1L; + + @Column(name = "NAME", nullable = false) + private String name; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "CATEGORY_TITLES", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")} + )) + private LocalizedString title; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "CATEGORY_DESCRIPTIONS", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")} + )) + private LocalizedString description; + + @OneToMany(mappedBy = "category", fetch = FetchType.LAZY) + @OrderBy("objectOrder ASC") + private List objects; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalizedString getTitle() { + return title; + } + + public void setTitle(LocalizedString title) { + this.title = title; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(LocalizedString description) { + this.description = description; + } + } + + @Entity + @Table(name = "CCM_OBJECTS") + @Inheritance(strategy = InheritanceType.JOINED) + public static class CcmObject implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "OBJECT_ID") + @GeneratedValue(strategy = GenerationType.AUTO) + private long objectId; + + @Column(name = "DISPLAY_NAME") + private String displayName; + + @OneToMany(mappedBy = "categorizedObject", fetch = FetchType.LAZY) + @OrderBy("categoryOrder ASC") + private List categories; + + public long getObjectId() { + return objectId; + } + + public void setObjectId(long objectId) { + this.objectId = objectId; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + } + + @Entity + @Table(name = "CATEGORY_DOMAINS") + public static class Domain extends CcmObject { + + private static final long serialVersionUID = 1L; + + @Column(name = "DOMAIN_KEY", nullable = false, unique = true, length = 255) + private String domainKey; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "DOMAIN_TITLES", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")})) + private LocalizedString title; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "DOMAIN_DESCRIPTIONS", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")})) + private LocalizedString description; + + public String getDomainKey() { + return domainKey; + } + + public void setDomainKey(String domainKey) { + this.domainKey = domainKey; + } + + public LocalizedString getTitle() { + return title; + } + + public void setTitle(LocalizedString title) { + this.title = title; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(LocalizedString description) { + this.description = description; + } + } + + @Embeddable + public static class LocalizedString implements Serializable { + + private static final long serialVersionUID = 1L; + + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "LOCALE") + @Column(name = "LOCALIZED_VALUE") + @Lob + @Type(type = "org.hibernate.type.TextType") + private Map values; + + public LocalizedString() { + values = new HashMap<>(); + } + + public Map getValues() { + if (values == null) { + return null; + } else { + return Collections.unmodifiableMap( values); + } + } + + protected void setValues(final Map values) { + if (values == null) { + this.values = new HashMap<>(); + } else { + this.values = new HashMap<>(values); + } + } + + public String getValue() { + return getValue(Locale.getDefault()); + } + + public String getValue(final Locale locale) { + return values.get(locale); + } + + public void addValue(final Locale locale, final String value) { + values.put(locale, value); + } + + public void removeValue(final Locale locale) { + values.remove(locale); + } + + public boolean hasValue(final Locale locale) { + return values.containsKey(locale); + } + + public Set getAvailableLocales() { + return values.keySet(); + } + + } +} From 3ec7e1795905b657d2e1920502389fe5f3544a32 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Wed, 20 Feb 2019 17:13:04 -0500 Subject: [PATCH 243/772] HHH-13281 - Avoid ClassCastException when unwrapping EntityManager to deprecated org.hibernate.ejb.HibernateEntityManager. (cherry picked from commit 6813d1f488f6b2bba82158740a40f5d0363b415a) --- .../HibernateEntityManagerImplementor.java | 2 +- .../test/jpa/EntityManagerUnwrapTest.java | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/jpa/EntityManagerUnwrapTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java index 0891f98bd324..2bd4e68d5ff4 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java @@ -15,8 +15,8 @@ import org.hibernate.HibernateException; import org.hibernate.LockOptions; import org.hibernate.StaleStateException; +import org.hibernate.ejb.HibernateEntityManager; import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.jpa.HibernateEntityManager; import org.hibernate.query.Query; import org.hibernate.query.criteria.internal.ValueHandlerFactory; import org.hibernate.type.Type; diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/EntityManagerUnwrapTest.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/EntityManagerUnwrapTest.java new file mode 100644 index 000000000000..9f54b7859039 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/EntityManagerUnwrapTest.java @@ -0,0 +1,50 @@ +/* + * 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.jpa; + +import org.hibernate.Session; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +public class EntityManagerUnwrapTest extends BaseEntityManagerFunctionalTestCase { + @Test + @TestForIssue(jiraKey = "HHH-13281") + public void testUnwrapEjbHibernateEntityManagerInterface() { + org.hibernate.ejb.HibernateEntityManager em = getOrCreateEntityManager().unwrap( org.hibernate.ejb.HibernateEntityManager.class ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13281") + public void testUnwrapJpaHibernateEntityManagerInterface() { + org.hibernate.jpa.HibernateEntityManager em = getOrCreateEntityManager().unwrap( org.hibernate.jpa.HibernateEntityManager.class ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13281") + public void testUnwrapSessionImplementor() { + SessionImplementor session = getOrCreateEntityManager().unwrap( SessionImplementor.class ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13281") + public void testUnwrapSession() { + Session session = getOrCreateEntityManager().unwrap( Session.class ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13281") + public void testUnwrapSharedSessionContractImplementor() { + SharedSessionContractImplementor session = getOrCreateEntityManager().unwrap( SharedSessionContractImplementor.class ); + } +} From 4580039fe226722e6ee141068ec9f89f5becf7c8 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Sun, 24 Feb 2019 14:25:32 -0800 Subject: [PATCH 244/772] HHH-13285 : ClassCastException: org.dom4j.DocumentFactory cannot be cast to org.dom4j.DocumentFactory after dom4j update --- .../internal/util/xml/XMLHelper.java | 44 ++++++++++++------- .../JPAOverriddenAnnotationReaderTest.java | 2 +- .../reflection/XMLContextTest.java | 3 +- .../boot/internal/EnversServiceImpl.java | 2 +- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java index 4eb276dbfca2..4239bd0dedff 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java @@ -6,7 +6,8 @@ */ package org.hibernate.internal.util.xml; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import java.security.AccessController; +import java.security.PrivilegedAction; import org.dom4j.DocumentFactory; import org.dom4j.io.SAXReader; @@ -22,23 +23,34 @@ public final class XMLHelper { private final DocumentFactory documentFactory; - public XMLHelper(ClassLoaderService classLoaderService) { - this.documentFactory = classLoaderService.workWithClassLoader( - new ClassLoaderService.Work() { - @Override - public DocumentFactory doWork(ClassLoader classLoader) { - final ClassLoader originalTccl = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader( classLoader ); - return DocumentFactory.getInstance(); - } - finally { - Thread.currentThread().setContextClassLoader( originalTccl ); - } - } + public XMLHelper() { + PrivilegedAction action = new PrivilegedAction() { + public DocumentFactory run() { + final ClassLoader originalTccl = Thread.currentThread().getContextClassLoader(); + try { + // We need to make sure we get DocumentFactory + // loaded from the same ClassLoader that loads + // Hibernate classes, to make sure we get the + // proper version of DocumentFactory, This class + // is "internal", and should only be used for XML + // files generated by Envers. + + // Using the (Hibernate) ClassLoader that loads + // this Class will avoid collisions in the case + // that DocumentFactory can be loaded from, + // for example, the application ClassLoader. + Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() ); + return DocumentFactory.getInstance(); + } + finally { + Thread.currentThread().setContextClassLoader( originalTccl ); } - ); + } + }; + this.documentFactory = System.getSecurityManager() != null + ? AccessController.doPrivileged( action ) + : action.run(); } public DocumentFactory getDocumentFactory() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java index d7030ac9a95f..c79a60e708d2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java @@ -393,7 +393,7 @@ public void testEntityListeners() throws Exception { } private XMLContext buildContext(String ormfile) throws SAXException, DocumentException, IOException { - XMLHelper xmlHelper = new XMLHelper( ClassLoaderServiceTestingImpl.INSTANCE ); + XMLHelper xmlHelper = new XMLHelper(); InputStream is = ClassLoaderServiceTestingImpl.INSTANCE.locateResourceStream( ormfile ); assertNotNull( "ORM.xml not found: " + ormfile, is ); XMLContext context = new XMLContext( BootstrapContextImpl.INSTANCE ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java index c79061002884..235fe820e4a3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java @@ -22,7 +22,6 @@ import org.hibernate.internal.util.xml.XMLHelper; import org.hibernate.testing.boot.BootstrapContextImpl; -import org.hibernate.testing.boot.ClassLoaderAccessTestingImpl; import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; /** @@ -31,7 +30,7 @@ public class XMLContextTest { @Test public void testAll() throws Exception { - final XMLHelper xmlHelper = new XMLHelper( ClassLoaderServiceTestingImpl.INSTANCE ); + final XMLHelper xmlHelper = new XMLHelper(); final XMLContext context = new XMLContext( BootstrapContextImpl.INSTANCE ); InputStream is = ClassLoaderServiceTestingImpl.INSTANCE.locateResourceStream( diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java index e724dc4aa7d1..2b03d31647e3 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java @@ -113,7 +113,7 @@ public void initialize(final MetadataImplementor metadata, final MappingCollecto this.serviceRegistry = metadata.getMetadataBuildingOptions().getServiceRegistry(); this.classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); - this.xmlHelper = new XMLHelper( classLoaderService ); + this.xmlHelper = new XMLHelper(); doInitialize( metadata, mappingCollector, serviceRegistry ); } From c8545dc705b24b7272ac5b5750da2e28abe186ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 23 Nov 2018 14:43:39 +0100 Subject: [PATCH 245/772] HHH-13112 Fix a table name in NoPackageTest (cherry picked from commit a89a9beeb0f9a4f8c279aa6c5458d5d0b01e6316) --- .../src/test/java/AnnotationMappedNoPackageEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java b/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java index 78bb726afa12..819356a0a1b4 100644 --- a/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java +++ b/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java @@ -9,7 +9,7 @@ import javax.persistence.Entity; import javax.persistence.Id; -@Entity +@Entity(name = "annotationnopackage") public class AnnotationMappedNoPackageEntity { @Id private Integer id; From 8e1ed4f3c04123aef9a36b86ed1bc14e49e84ebf Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 25 Feb 2019 22:43:44 +0100 Subject: [PATCH 246/772] 5.3.9.Final --- changelog.txt | 15 +++++++++++++++ gradle/base-information.gradle | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 90b05c1a448a..3609af669334 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,21 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.3.9.final (February 25th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31757/tab/release-report-done + +** Bug + * [HHH-13107] - JtaWithStatementsBatchTest fails on Oracle + * [HHH-13112] - Proxies on entity types in the default package lead to MappingException with JDK9+ + * [HHH-13262] - javax.persistence.TransactionRequiredException: Executing an update/delete query + * [HHH-13269] - Embeddable collection regression due to HHH-11544 + * [HHH-13281] - java.lang.ClassCastException: org.hibernate.internal.SessionImpl cannot be cast to org.hibernate.ejb.HibernateEntityManager + * [HHH-13285] - ClassCastException: org.dom4j.DocumentFactory cannot be cast to org.dom4j.DocumentFactory after dom4j update + + + Changes in 5.3.8.final (February 19th, 2019) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 9fe951e4dca6..d531150435e9 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.9-SNAPSHOT', project ) + ormVersion = new HibernateVersion( '5.3.9.Final', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From eb8db53e21c50345504b2269e4fd2f15eefb5f85 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 25 Feb 2019 23:51:18 +0100 Subject: [PATCH 247/772] Prepare for next development iteration --- gradle/base-information.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index d531150435e9..d9af8ac32d8d 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.9.Final', project ) + ormVersion = new HibernateVersion( '5.3.10-SNAPSHOT', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From d4c47d46f26e063ce7da08b79899ac632bde3201 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 26 Feb 2019 22:24:54 -0800 Subject: [PATCH 248/772] HHH-13241 : Constraint violation when deleting entites in bi-directional, lazy OneToMany association with bytecode enhancement (cherry picked from commit 980f24916ca27cd4e5ae658de353358d42c94cd2) --- .../internal/AbstractEntityInsertAction.java | 4 +- .../engine/internal/ForeignKeys.java | 95 ++++- .../internal/DefaultDeleteEventListener.java | 3 +- .../entity/AbstractEntityPersister.java | 16 +- .../lazy/BidirectionalLazyTest.java | 354 ++++++++++++++++++ ...directionalLazyGroupsInEmbeddableTest.java | 225 +++++++++++ .../group/BidirectionalLazyGroupsTest.java | 218 +++++++++++ 7 files changed, 889 insertions(+), 26 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java index ad44c4b447a1..6bc20950878c 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java @@ -110,8 +110,8 @@ public NonNullableTransientDependencies findNonNullableTransientEntities() { */ protected final void nullifyTransientReferencesIfNotAlready() { if ( ! areTransientReferencesNullified ) { - new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession() ) - .nullifyTransientReferences( getState(), getPersister().getPropertyTypes() ); + new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession(), getPersister() ) + .nullifyTransientReferences( getState() ); new Nullability( getSession() ).checkNullability( getState(), getPersister(), false ); areTransientReferencesNullified = true; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java index e98f9e7c7f0c..94e05a7b910f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java @@ -13,7 +13,9 @@ import org.hibernate.TransientObjectException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; @@ -36,6 +38,7 @@ public static class Nullifier { private final boolean isEarlyInsert; private final SharedSessionContractImplementor session; private final Object self; + private final EntityPersister persister; /** * Constructs a Nullifier @@ -44,11 +47,18 @@ public static class Nullifier { * @param isDelete Are we in the middle of a delete action? * @param isEarlyInsert Is this an early insert (INSERT generated id strategy)? * @param session The session + * @param persister The EntityPersister for {@code self} */ - public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SharedSessionContractImplementor session) { + public Nullifier( + final Object self, + final boolean isDelete, + final boolean isEarlyInsert, + final SharedSessionContractImplementor session, + final EntityPersister persister) { this.isDelete = isDelete; this.isEarlyInsert = isEarlyInsert; this.session = session; + this.persister = persister; this.self = self; } @@ -57,11 +67,12 @@ public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SharedSes * points toward that entity. * * @param values The entity attribute values - * @param types The entity attribute types */ - public void nullifyTransientReferences(final Object[] values, final Type[] types) { + public void nullifyTransientReferences(final Object[] values) { + final String[] propertyNames = persister.getPropertyNames(); + final Type[] types = persister.getPropertyTypes(); for ( int i = 0; i < types.length; i++ ) { - values[i] = nullifyTransientReferences( values[i], types[i] ); + values[i] = nullifyTransientReferences( values[i], propertyNames[i], types[i] ); } } @@ -70,34 +81,47 @@ public void nullifyTransientReferences(final Object[] values, final Type[] types * input argument otherwise. This is how Hibernate avoids foreign key constraint violations. * * @param value An entity attribute value + * @param propertyName An entity attribute name * @param type An entity attribute type * * @return {@code null} if the argument is an unsaved entity; otherwise return the argument. */ - private Object nullifyTransientReferences(final Object value, final Type type) { + private Object nullifyTransientReferences(final Object value, final String propertyName, final Type type) { + final Object returnedValue; if ( value == null ) { - return null; + returnedValue = null; } else if ( type.isEntityType() ) { final EntityType entityType = (EntityType) type; if ( entityType.isOneToOne() ) { - return value; + returnedValue = value; } else { - final String entityName = entityType.getAssociatedEntityName(); - return isNullifiable( entityName, value ) ? null : value; + // If value is lazy, it may need to be initialized to + // determine if the value is nullifiable. + final Object possiblyInitializedValue = initializeIfNecessary( value, propertyName, entityType ); + // If the value is not nullifiable, make sure that the + // possibly initialized value is returned. + returnedValue = isNullifiable( entityType.getAssociatedEntityName(), possiblyInitializedValue ) + ? null + : possiblyInitializedValue; } } else if ( type.isAnyType() ) { - return isNullifiable( null, value ) ? null : value; + returnedValue = isNullifiable( null, value ) ? null : value; } else if ( type.isComponentType() ) { final CompositeType actype = (CompositeType) type; final Object[] subvalues = actype.getPropertyValues( value, session ); final Type[] subtypes = actype.getSubtypes(); + final String[] subPropertyNames = actype.getPropertyNames(); boolean substitute = false; for ( int i = 0; i < subvalues.length; i++ ) { - final Object replacement = nullifyTransientReferences( subvalues[i], subtypes[i] ); + final Object replacement = nullifyTransientReferences( + subvalues[i], + StringHelper.qualify( propertyName, subPropertyNames[i] ), + subtypes[i] + ); if ( replacement != subvalues[i] ) { substitute = true; subvalues[i] = replacement; @@ -107,7 +131,50 @@ else if ( type.isComponentType() ) { // todo : need to account for entity mode on the CompositeType interface :( actype.setPropertyValues( value, subvalues, EntityMode.POJO ); } - return value; + returnedValue = value; + } + else { + returnedValue = value; + } + // value != returnedValue if either: + // 1) returnedValue was nullified (set to null); + // or 2) returnedValue was initialized, but not nullified. + // When bytecode-enhancement is used for dirty-checking, the change should + // only be tracked when returnedValue was nullified (1)). + if ( value != returnedValue && returnedValue == null && SelfDirtinessTracker.class.isInstance( self ) ) { + ( (SelfDirtinessTracker) self ).$$_hibernate_trackChange( propertyName ); + } + return returnedValue; + } + + private Object initializeIfNecessary( + final Object value, + final String propertyName, + final Type type) { + if ( isDelete && + value == LazyPropertyInitializer.UNFETCHED_PROPERTY && + type.isEntityType() && + !session.getPersistenceContext().getNullifiableEntityKeys().isEmpty() ) { + // IMPLEMENTATION NOTE: If cascade-remove was mapped for the attribute, + // then value should have been initialized previously, when the remove operation was + // cascaded to the property (because CascadingAction.DELETE.performOnLazyProperty() + // returns true). This particular situation can only arise when cascade-remove is not + // mapped for the association. + + // There is at least one nullifiable entity. We don't know if the lazy + // associated entity is one of the nullifiable entities. If it is, and + // the property is not nullified, then a constraint violation will result. + // The only way to find out if the associated entity is nullifiable is + // to initialize it. + // TODO: there may be ways to fine-tune when initialization is necessary + // (e.g., only initialize when the associated entity type is a + // superclass or the same as the entity type of a nullifiable entity). + // It is unclear if a more complicated check would impact performance + // more than just initializing the associated entity. + return persister + .getInstrumentationMetadata() + .extractInterceptor( self ) + .fetchAttribute( self, propertyName ); } else { return value; @@ -162,9 +229,7 @@ private boolean isNullifiable(final String entityName, Object object) else { return entityEntry.isNullifiable( isEarlyInsert, session ); } - } - } /** @@ -307,8 +372,8 @@ public static NonNullableTransientDependencies findNonNullableTransientEntities( Object[] values, boolean isEarlyInsert, SharedSessionContractImplementor session) { - final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session ); final EntityPersister persister = session.getEntityPersister( entityName, entity ); + final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session, persister ); final String[] propertyNames = persister.getPropertyNames(); final Type[] types = persister.getPropertyTypes(); final boolean[] nullability = persister.getPropertyNullability(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java index bfb60701661c..f04b7504b010 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java @@ -285,8 +285,7 @@ protected final void deleteEntity( cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities ); - new ForeignKeys.Nullifier( entity, true, false, session ) - .nullifyTransientReferences( entityEntry.getDeletedState(), propTypes ); + new ForeignKeys.Nullifier( entity, true, false, session, persister ).nullifyTransientReferences( entityEntry.getDeletedState() ); new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, Nullability.NullabilityCheckType.DELETE ); persistenceContext.getNullifiableEntityKeys().add( key ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 21ef9cf37c4a..d0b794d91843 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -1179,7 +1179,6 @@ private Object initializeLazyPropertiesFromDatastore( rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps ); rs.next(); } - final Object[] snapshot = entry.getLoadedState(); for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) { final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() ); @@ -1210,7 +1209,7 @@ private Object initializeLazyPropertiesFromDatastore( fieldName, entity, session, - snapshot, + entry, fetchGroupAttributeDescriptor.getLazyIndex(), selectedValue ); @@ -1259,7 +1258,6 @@ private Object initializeLazyPropertiesFromCache( Object result = null; Serializable[] disassembledValues = cacheEntry.getDisassembledState(); - final Object[] snapshot = entry.getLoadedState(); for ( int j = 0; j < lazyPropertyNames.length; j++ ) { final Serializable cachedValue = disassembledValues[lazyPropertyNumbers[j]]; final Type lazyPropertyType = lazyPropertyTypes[j]; @@ -1276,7 +1274,7 @@ private Object initializeLazyPropertiesFromCache( session, entity ); - if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) { + if ( initializeLazyProperty( fieldName, entity, session, entry, j, propValue ) ) { result = propValue; } } @@ -1291,13 +1289,17 @@ private boolean initializeLazyProperty( final String fieldName, final Object entity, final SharedSessionContractImplementor session, - final Object[] snapshot, + final EntityEntry entry, final int j, final Object propValue) { setPropertyValue( entity, lazyPropertyNumbers[j], propValue ); - if ( snapshot != null ) { + if ( entry.getLoadedState() != null ) { // object have been loaded with setReadOnly(true); HHH-2236 - snapshot[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory ); + entry.getLoadedState()[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory ); + } + // If the entity has deleted state, then update that as well + if ( entry.getDeletedState() != null ) { + entry.getDeletedState()[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory ); } return fieldName.equals( lazyPropertyNames[j] ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java new file mode 100644 index 000000000000..867d965be50d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java @@ -0,0 +1,354 @@ +/* + * 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.bytecode.enhancement.lazy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.bytecode.enhance.spi.UnloadedField; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.SessionImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * Tests removing non-owning side of the bidirectional association, + * with and without dirty-checking using enhancement. + * + * @author Gail Badner + */ +@TestForIssue(jiraKey = "HHH-13241") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ + EnhancerTestContext.class, // supports laziness and dirty-checking + BidirectionalLazyTest.NoDirtyCheckEnhancementContext.class // supports laziness; does not support dirty-checking +}) +public class BidirectionalLazyTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { Employer.class, Employee.class, Unrelated.class }; + } + + @Test + public void testRemoveWithDeletedAssociatedEntity() { + + doInHibernate( + this::sessionFactory, session -> { + + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + employer.addEmployee( new Employee( "Jack" ) ); + employer.addEmployee( new Employee( "Jill" ) ); + employer.addEmployee( new Employee( "John" ) ); + for ( Employee employee : employer.getEmployees() ) { + session.persist( employee ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = session.get( Employer.class, "RedHat" ); + // Delete the associated entity first + session.remove( employer ); + for ( Employee employee : employer.getEmployees() ) { + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + // Should be initialized because at least one entity was deleted beforehand + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + assertSame( employer, employee.getEmployer() ); + // employee.getEmployer was initialized, and should be nullified in EntityEntry#deletedState + checkEntityEntryState( session, employee, employer, true ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Employer.class, "RedHat" ) ); + assertTrue( session.createQuery( "from Employee e", Employee.class ).getResultList().isEmpty() ); + } + ); + } + + @Test + public void testRemoveWithNonAssociatedRemovedEntity() { + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employer.addEmployee( employee ); + session.persist( employee ); + session.persist( new Unrelated( 1 ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + // Delete an entity that is not associated with Employee + session.remove( session.get( Unrelated.class, 1 ) ); + final Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + // Should be initialized because at least one entity was deleted beforehand + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + // employee.getEmployer was initialized, and should not be nullified in EntityEntry#deletedState + checkEntityEntryState( session, employee, employee.getEmployer(), false ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Unrelated.class, 1 ) ); + assertNull( session.find( Employee.class, "Jack" ) ); + session.remove( session.find( Employer.class, "RedHat" ) ); + } + ); + } + + @Test + public void testRemoveWithNoRemovedEntities() { + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employer.addEmployee( employee ); + session.persist( employee ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + // Don't delete any entities before deleting the Employee + final Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + // There were no other deleted entities before employee was deleted, + // so there was no need to initialize employee.employer. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + // employee.getEmployer was not initialized, and should not be nullified in EntityEntry#deletedState + checkEntityEntryState( session, employee, LazyPropertyInitializer.UNFETCHED_PROPERTY, false ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Employee.class, "Jack" ) ); + session.remove( session.find( Employer.class, "RedHat" ) ); + } + ); + } + + private void checkEntityEntryState( + final Session session, + final Employee employee, + final Object employer, + final boolean isEmployerNullified) { + final SessionImplementor sessionImplementor = (SessionImplementor) session; + final EntityEntry entityEntry = sessionImplementor.getPersistenceContext().getEntry( employee ); + final int propertyNumber = entityEntry.getPersister().getEntityMetamodel().getPropertyIndex( "employer" ); + assertEquals( + employer, + entityEntry.getLoadedState()[propertyNumber] + ); + if ( isEmployerNullified ) { + assertEquals( null, entityEntry.getDeletedState()[propertyNumber] ); + } + else { + assertEquals( + employer, + entityEntry.getDeletedState()[propertyNumber] + ); + } + } + + @Entity(name = "Employer") + public static class Employer { + private String name; + + private Set employees; + + public Employer(String name) { + this(); + setName( name ); + } + + @Id + public String getName() { + return name; + } + + @OneToMany(mappedBy = "employer", fetch = FetchType.LAZY) + public Set getEmployees() { + return employees; + } + + public void addEmployee(Employee employee) { + if ( getEmployees() == null ) { + setEmployees( new HashSet<>() ); + } + employees.add( employee ); + employee.setEmployer( this ); + } + + protected Employer() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployees(Set employees) { + this.employees = employees; + } + } + + @Entity(name = "Employee") + public static class Employee { + private long id; + + private String name; + + private Employer employer; + + public Employee(String name) { + this(); + setName( name ); + } + + public long getId() { + return id; + } + + @Id + public String getName() { + return name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "employer_name") + public Employer getEmployer() { + return employer; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setId(long id) { + this.id = id; + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployer(Employer employer) { + this.employer = employer; + } + + public int hashCode() { + if ( name != null ) { + return name.hashCode(); + } + else { + return 0; + } + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + else if ( o instanceof Employee ) { + Employee other = Employee.class.cast( o ); + if ( name != null ) { + return getName().equals( other.getName() ); + } + else { + return other.getName() == null; + } + } + else { + return false; + } + } + } + + @Entity(name = "Manager") + public static class Manager extends Employee { + } + + @Entity(name = "Unrelated") + public static class Unrelated { + private int id; + + public Unrelated() { + } + + public Unrelated(int id) { + setId( id ); + } + + @Id + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + } + + public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + return true; + } + + @Override + public boolean isLazyLoadable(UnloadedField field) { + return true; + } + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java new file mode 100644 index 000000000000..d29dca5371a0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java @@ -0,0 +1,225 @@ +/* + * 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.bytecode.enhancement.lazy.group; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * Tests removing non-owning side of the bidirectional association, + * where owning side is in an embeddable. + * + * Tests with and without dirty-checking using enhancement. + * + * @author Gail Badner + */ +@TestForIssue(jiraKey = "HHH-13241") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ + EnhancerTestContext.class, + BidirectionalLazyGroupsInEmbeddableTest.NoDirtyCheckEnhancementContext.class +}) +public class BidirectionalLazyGroupsInEmbeddableTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { Employer.class, Employee.class }; + } + + @Test + public void test() { + + doInHibernate( + this::sessionFactory, session -> { + + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + employer.addEmployee( new Employee( "Jack" ) ); + employer.addEmployee( new Employee( "Jill" ) ); + employer.addEmployee( new Employee( "John" ) ); + for ( Employee employee : employer.getEmployees() ) { + session.persist( employee ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = session.createQuery( "from Employer e", Employer.class ).getSingleResult(); + session.remove( employer ); + for ( Employee employee : employer.getEmployees() ) { + session.remove( employee ); + } + } + ); + } + + @Entity(name = "Employer") + public static class Employer { + private String name; + + private Set employees; + + public Employer(String name) { + this(); + setName( name ); + } + + @Id + public String getName() { + return name; + } + + @OneToMany(mappedBy = "employerContainer.employer", fetch = FetchType.LAZY) + @LazyGroup("Employees") + public Set getEmployees() { + return employees; + } + + public void addEmployee(Employee employee) { + if ( getEmployees() == null ) { + setEmployees( new HashSet<>() ); + } + employees.add( employee ); + if ( employee.getEmployerContainer() == null ) { + employee.setEmployerContainer( new EmployerContainer() ); + } + employee.getEmployerContainer().setEmployer( this ); + } + + protected Employer() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployees(Set employees) { + this.employees = employees; + } + } + + @Entity(name = "Employee") + public static class Employee { + private long id; + + private String name; + + public Employee(String name) { + this(); + setName( name ); + } + + @Id + @GeneratedValue + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public EmployerContainer employerContainer; + + protected Employee() { + // this form used by Hibernate + } + + public EmployerContainer getEmployerContainer() { + return employerContainer; + } + + protected void setId(long id) { + this.id = id; + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployerContainer(EmployerContainer employerContainer) { + this.employerContainer = employerContainer; + } + + public int hashCode() { + if ( name != null ) { + return name.hashCode(); + } + else { + return 0; + } + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + else if ( o instanceof Employee ) { + Employee other = Employee.class.cast( o ); + if ( name != null ) { + return getName().equals( other.getName() ); + } + else { + return other.getName() == null; + } + } + else { + return false; + } + } + } + + @Embeddable + public static class EmployerContainer { + private Employer employer; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("EmployerForEmployee") + @JoinColumn(name = "employer_name") + public Employer getEmployer() { + return employer; + } + + protected void setEmployer(Employer employer) { + this.employer = employer; + } + } + + public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java new file mode 100644 index 000000000000..8898e38d3008 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java @@ -0,0 +1,218 @@ +/* + * 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.bytecode.enhancement.lazy.group; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests removing non-owning side of the bidirectional association, + * with and without dirty-checking using enhancement. + * + * @author Gail Badner + */ +@TestForIssue(jiraKey = "HHH-13241") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ + EnhancerTestContext.class, + BidirectionalLazyGroupsTest.NoDirtyCheckEnhancementContext.class +}) +public class BidirectionalLazyGroupsTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { Employer.class, Employee.class }; + } + + @Test + public void testRemoveCollectionOwnerNoCascade() { + + doInHibernate( + this::sessionFactory, session -> { + + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + employer.addEmployee( new Employee( "Jack" ) ); + employer.addEmployee( new Employee( "Jill" ) ); + employer.addEmployee( new Employee( "John" ) ); + for ( Employee employee : employer.getEmployees() ) { + session.persist( employee ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = session.createQuery( "from Employer e", Employer.class ).getSingleResult(); + session.remove( employer ); + for ( Employee employee : employer.getEmployees() ) { + assertFalse( Hibernate.isPropertyInitialized( employee, "employer") ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Employer.class, "RedHat" ) ); + assertNull( session.createQuery( "from Employee e", Employee.class ).uniqueResult() ); + } + ); + } + + @Entity(name = "Employer") + public static class Employer { + private String name; + + private Set employees; + + public Employer(String name) { + this(); + setName( name ); + } + + @Id + public String getName() { + return name; + } + + @OneToMany(mappedBy = "employer", fetch = FetchType.LAZY) + @LazyGroup("Employees") + public Set getEmployees() { + return employees; + } + + public void addEmployee(Employee employee) { + if ( getEmployees() == null ) { + setEmployees( new HashSet<>() ); + } + employees.add( employee ); + employee.setEmployer( this ); + } + + protected Employer() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployees(Set employees) { + this.employees = employees; + } + } + + @Entity(name = "Employee") + public static class Employee { + private long id; + + private String name; + + private Employer employer; + + public Employee(String name) { + this(); + setName( name ); + } + + @Id + @GeneratedValue + public long getId() { + return id; + } + + public String getName() { + return name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("EmployerForEmployee") + @JoinColumn(name = "employer_name") + public Employer getEmployer() { + return employer; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setId(long id) { + this.id = id; + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployer(Employer employer) { + this.employer = employer; + } + + public int hashCode() { + if ( name != null ) { + return name.hashCode(); + } + else { + return 0; + } + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + else if ( o instanceof Employee ) { + Employee other = Employee.class.cast( o ); + if ( name != null ) { + return getName().equals( other.getName() ); + } + else { + return other.getName() == null; + } + } + else { + return false; + } + } + } + + public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} From bc8bf9a60d2d675445d3e6a5d5dcefe2f8c803a4 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 3 Dec 2018 15:27:53 +0100 Subject: [PATCH 249/772] HHH-13138 By default, pass the class loader of the test to the EMF Not doing it causes issues when using the BytecodeEnhancerRunner which introduces an enhancing class loader. We could do it on a per test basis but it's easier to do it once and for all. And it can still be overridden anyway. (cherry picked from commit bae98ffaccf55c78bb1eb39b1e249f9b8ae92726) --- .../jpa/test/BaseEntityManagerFunctionalTestCase.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java index 7a0ecd4ca91b..4f0648baac3e 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; + import javax.persistence.EntityManager; import javax.persistence.SharedCacheMode; import javax.persistence.ValidationMode; @@ -21,20 +22,17 @@ import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.boot.spi.Bootstrap; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; - import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; -import org.jboss.logging.Logger; - /** * A base class for all ejb tests. * @@ -209,6 +207,8 @@ protected Map getConfig() { Map config = Environment.getProperties(); ArrayList classes = new ArrayList(); + config.put( AvailableSettings.CLASSLOADERS, getClass().getClassLoader() ); + classes.addAll( Arrays.asList( getAnnotatedClasses() ) ); config.put( AvailableSettings.LOADED_CLASSES, classes ); for ( Map.Entry entry : getCachedClasses().entrySet() ) { From ee6e3844d1c441b3243a7840d46d22bfe19f4af0 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 3 Dec 2018 19:06:44 +0100 Subject: [PATCH 250/772] HHH-13138 Set the TCCL in BytecodeEnhancerRunner We are not consistently using the ClassLoaderService and we sometimes use the TCCL so better set it correctly. (cherry picked from commit 2a8582be7f114565fa1fcf8b16a85b721d88d47c) --- .../enhancement/BytecodeEnhancerRunner.java | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java index 6de2bad33354..4801752bc50c 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java @@ -6,23 +6,25 @@ */ package org.hibernate.testing.bytecode.enhancement; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.Enhancer; import org.hibernate.cfg.Environment; import org.hibernate.testing.junit4.CustomRunner; import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.ParentRunner; import org.junit.runners.Suite; import org.junit.runners.model.InitializationError; import org.junit.runners.model.RunnerBuilder; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; - /** * @author Luis Barreiro */ @@ -111,4 +113,22 @@ public Class loadClass(String name) throws ClassNotFoundException { }; } + @Override + protected void runChild(Runner runner, RunNotifier notifier) { + // This is ugly but, for now, ORM class loading is inconsistent. + // It sometimes use ClassLoaderService which takes into account AvailableSettings.CLASSLOADERS, and sometimes + // ReflectHelper#classForName() which uses the TCCL. + // See https://hibernate.atlassian.net/browse/HHH-13136 for more information. + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader( + ( (ParentRunner) runner ).getTestClass().getJavaClass().getClassLoader() ); + + super.runChild( runner, notifier ); + } + finally { + Thread.currentThread().setContextClassLoader( originalClassLoader ); + } + } + } From b98d78b2ad3bf1e04c069ecf02e9a12c4e884471 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 20 Mar 2019 17:26:32 -0700 Subject: [PATCH 251/772] HHH-13241 : Added test case with a lazy null many-to-one association (cherry picked from commit 65eebbb96b2a5557867994c27b382e9da6f22b12) --- .../lazy/BidirectionalLazyTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java index 867d965be50d..ed6e755a17fe 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java @@ -173,6 +173,40 @@ public void testRemoveWithNoRemovedEntities() { ); } + @Test + public void testRemoveEntityWithNullLazyManyToOne() { + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + session.persist( employee ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employee employee = session.get( Employee.class, "Jack" ); + + // Get and delete an Employer that is not associated with employee + Employer employer = session.get( Employer.class, "RedHat" ); + session.remove( employer ); + + // employee.employer is uninitialized. Since the column for employee.employer + // is a foreign key, and there is an Employer that has already been removed, + // employee.employer will need to be iniitialized to determine if + // employee.employee is nullifiable. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + ); + + + + } + private void checkEntityEntryState( final Session session, final Employee employee, From 2e698d47444dde9e0d467d508975c9438b0edd6d Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 20 Mar 2019 17:27:32 -0700 Subject: [PATCH 252/772] HHH-13241 : Fix regression with an uninitialized null many-to-one association (cherry picked from commit b28dc488a11f580ebb8128d620cc01646d832343) --- .../hibernate/engine/internal/ForeignKeys.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java index 94e05a7b910f..78f6b97cc3b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java @@ -100,11 +100,17 @@ else if ( type.isEntityType() ) { // If value is lazy, it may need to be initialized to // determine if the value is nullifiable. final Object possiblyInitializedValue = initializeIfNecessary( value, propertyName, entityType ); - // If the value is not nullifiable, make sure that the - // possibly initialized value is returned. - returnedValue = isNullifiable( entityType.getAssociatedEntityName(), possiblyInitializedValue ) - ? null - : possiblyInitializedValue; + if ( possiblyInitializedValue == null ) { + // The uninitialized value was initialized to null + returnedValue = null; + } + else { + // If the value is not nullifiable, make sure that the + // possibly initialized value is returned. + returnedValue = isNullifiable( entityType.getAssociatedEntityName(), possiblyInitializedValue ) + ? null + : possiblyInitializedValue; + } } } else if ( type.isAnyType() ) { From e5873b9fe405342cf101d0494b9394422cc508d4 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 19 Mar 2019 13:12:15 +0100 Subject: [PATCH 253/772] HHH-13241 Comment the test for now We have issues with this test in both Javassist and ByteBuddy enhancers. (cherry picked from commit bf78b73aa71856402d356d08c481cb594352472a) --- .../group/BidirectionalLazyGroupsInEmbeddableTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java index d29dca5371a0..2ff22a49adbe 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java @@ -6,8 +6,11 @@ */ package org.hibernate.test.bytecode.enhancement.lazy.group; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + import java.util.HashSet; import java.util.Set; + import javax.persistence.CascadeType; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -23,17 +26,15 @@ import org.hibernate.annotations.LazyToOneOption; import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; import org.hibernate.bytecode.enhance.spi.UnloadedClass; - import org.hibernate.testing.TestForIssue; import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; - /** * Tests removing non-owning side of the bidirectional association, * where owning side is in an embeddable. @@ -55,6 +56,7 @@ public Class[] getAnnotatedClasses() { } @Test + @Ignore("Test is failing with Javassist and also fails with ByteBuddy if the mappings are moved to the fields.") public void test() { doInHibernate( From c0f0a731d4deba018e3d26e1bee5af51f5ff544c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 10 Sep 2018 11:05:56 +0200 Subject: [PATCH 254/772] HHH-12939 - Database name not quoted at schema update (cherry picked from commit 6e9c1893a12a5e29fcaed9263a93728bca166b31) --- gradle/libraries.gradle | 2 +- .../java/org/hibernate/mapping/Table.java | 32 +-- .../internal/AbstractSchemaMigrator.java | 16 +- .../AbstractAlterTableQuoteSchemaTest.java | 45 ++++ .../AlterTableQuoteDefaultSchemaTest.java | 194 ++++++++++++++++++ .../AlterTableQuoteSpecifiedSchemaTest.java | 166 +++++++++++++++ .../SqlServerQuoteSchemaTest.java | 2 +- 7 files changed, 420 insertions(+), 37 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AbstractAlterTableQuoteSchemaTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 35bb364d1883..589489af2e9f 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -111,7 +111,7 @@ ext { mariadb: 'org.mariadb.jdbc:mariadb-java-client:2.2.3', oracle: 'com.oracle.jdbc:ojdbc8:12.2.0.1', - mssql: 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8', + mssql: 'com.microsoft.sqlserver:mssql-jdbc:7.0.0.jre8', db2: 'com.ibm.db2:db2jcc:10.5', hana: 'com.sap.cloud.db.jdbc:ngdbc:2.2.16', // for HANA 1 the minimum required client version is 1.120.20 diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index f7ade4cb7c65..f9a9d868faac 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -408,7 +408,7 @@ public boolean equals(Table table) { && Identifier.areEqual( schema, table.schema ) && Identifier.areEqual( catalog, table.catalog ); } - + public void validateColumns(Dialect dialect, Mapping mapping, TableMetadata tableInfo) { Iterator iter = getColumnIterator(); while ( iter.hasNext() ) { @@ -441,28 +441,16 @@ public Iterator sqlAlterStrings( Dialect dialect, Metadata metadata, TableInformation tableInfo, - String defaultCatalog, - String defaultSchema) throws HibernateException { - - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - - Identifier quotedCatalog = catalog != null && catalog.isQuoted() ? - new Identifier( tableInfo.getName().getCatalogName().getText(), true ) : - tableInfo.getName().getCatalogName(); - - Identifier quotedSchema = schema != null && schema.isQuoted() ? - new Identifier( tableInfo.getName().getSchemaName().getText(), true ) : - tableInfo.getName().getSchemaName(); + Identifier defaultCatalog, + Identifier defaultSchema) throws HibernateException { - Identifier quotedTable = name != null && name.isQuoted() ? - new Identifier( tableInfo.getName().getObjectName().getText(), true ) : - tableInfo.getName().getObjectName(); + final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( new QualifiedTableName( - quotedCatalog, - quotedSchema, - quotedTable + catalog != null ? catalog : defaultCatalog, + schema != null ? schema : defaultSchema, + name ), dialect ); @@ -473,7 +461,7 @@ public Iterator sqlAlterStrings( Iterator iter = getColumnIterator(); List results = new ArrayList(); - + while ( iter.hasNext() ) { final Column column = (Column) iter.next(); final ColumnInformation columnInfo = tableInfo.getColumn( Identifier.toIdentifier( column.getName(), column.isQuoted() ) ); @@ -581,7 +569,7 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, } } - + if ( col.isUnique() ) { String keyName = Constraint.generateName( "UK_", this, col ); UniqueKey uk = getOrCreateUniqueKey( keyName ); @@ -589,7 +577,7 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, buf.append( dialect.getUniqueDelegate() .getColumnDefinitionUniquenessFragment( col ) ); } - + if ( col.hasCheckConstraint() && dialect.supportsColumnCheck() ) { buf.append( " check (" ) .append( col.getCheckConstraint() ) diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java index a05270293098..84769b2ab480 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java @@ -300,8 +300,8 @@ protected void migrateTable( dialect, metadata, tableInformation, - getDefaultCatalogName( database, dialect ), - getDefaultSchemaName( database, dialect ) + database.getDefaultNamespace().getPhysicalName().getCatalog(), + database.getDefaultNamespace().getPhysicalName().getSchema() ), formatter, options, @@ -446,7 +446,7 @@ protected void applyForeignKeys( /** * Check if the ForeignKey already exists. First check based on definition and if that is not matched check if a key * with the exact same name exists. Keys with the same name are presumed to be functional equal. - * + * * @param foreignKey - ForeignKey, new key to be created * @param tableInformation - TableInformation, information of existing keys * @return boolean, true if key already exists @@ -581,14 +581,4 @@ private static void applySqlStrings( } } } - - private String getDefaultCatalogName(Database database, Dialect dialect) { - final Identifier identifier = database.getDefaultNamespace().getPhysicalName().getCatalog(); - return identifier == null ? null : identifier.render( dialect ); - } - - private String getDefaultSchemaName(Database database, Dialect dialect) { - final Identifier identifier = database.getDefaultNamespace().getPhysicalName().getSchema(); - return identifier == null ? null : identifier.render( dialect ); - } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AbstractAlterTableQuoteSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AbstractAlterTableQuoteSchemaTest.java new file mode 100644 index 000000000000..7d5ab1c526d2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AbstractAlterTableQuoteSchemaTest.java @@ -0,0 +1,45 @@ +/* + * 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.schemaupdate; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.dialect.SQLServerDialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-12939") +public abstract class AbstractAlterTableQuoteSchemaTest extends BaseCoreFunctionalTestCase { + + private Dialect dialect = Dialect.getDialect(); + + protected String quote(String element) { + return dialect.quote( "`" + element + "`" ); + } + + protected String quote(String schema, String table) { + return quote( schema ) + "." + quote( table ); + } + + protected String regexpQuote(String element) { + return dialect.quote( "`" + element + "`" ) + .replace( "-", "\\-" ) + .replace( "[", "\\[" ) + .replace( "]", "\\]" ); + } + + protected String regexpQuote(String schema, String table) { + return regexpQuote( schema ) + "\\." + regexpQuote( table ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java new file mode 100644 index 000000000000..d40da45b2c67 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java @@ -0,0 +1,194 @@ +/* + * 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.schemaupdate; + +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.EnumSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.tool.hbm2ddl.SchemaUpdate; +import org.hibernate.tool.schema.TargetType; +import org.junit.Test; + +/** + * @author Guillaume Smet + */ +@TestForIssue(jiraKey = "HHH-12939") +@RequiresDialectFeature(DialectChecks.SupportSchemaCreation.class) +public class AlterTableQuoteDefaultSchemaTest extends AbstractAlterTableQuoteSchemaTest { + + @Override + protected void afterSessionFactoryBuilt() { + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP TABLE " + quote( "default-schema", "my_entity" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP SCHEMA " + quote( "default-schema" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "CREATE SCHEMA " + quote( "default-schema" ) ) + .executeUpdate(); + } ); + } + + @Override + protected void cleanupTest() { + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP SCHEMA " + quote( "default-schema" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + } + + @Test + public void testDefaultSchema() throws IOException { + File output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE.toString() ) + .build(); + + try { + final MetadataSources metadataSources = new MetadataSources( ssr ) { + @Override + public MetadataBuilder getMetadataBuilder() { + MetadataBuilder metadataBuilder = super.getMetadataBuilder(); + metadataBuilder.applyImplicitSchemaName( "default-schema" ); + return metadataBuilder; + } + }; + metadataSources.addAnnotatedClass( MyEntity.class ); + + final MetadataImplementor metadata = (MetadataImplementor) metadataSources.buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( ";" ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.DATABASE, TargetType.SCRIPT ), metadata ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + + try { + + + String fileContent = new String( Files.readAllBytes( output.toPath() ) ); + + Pattern fileContentPattern = Pattern + .compile( "create table " + regexpQuote( "default-schema", "my_entity" ) ); + Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); + assertThat( fileContentMatcher.find(), is( true ) ); + } + catch (IOException e) { + fail( e.getMessage() ); + } + + ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE.toString() ) + .build(); + try { + final MetadataSources metadataSources = new MetadataSources( ssr ) { + @Override + public MetadataBuilder getMetadataBuilder() { + MetadataBuilder metadataBuilder = super.getMetadataBuilder(); + metadataBuilder.applyImplicitSchemaName( "default-schema" ); + return metadataBuilder; + } + }; + metadataSources.addAnnotatedClass( MyEntityUpdated.class ); + + final MetadataImplementor metadata = (MetadataImplementor) metadataSources.buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( ";" ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.DATABASE, TargetType.SCRIPT ), metadata ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + + try { + + String fileContent = new String( Files.readAllBytes( output.toPath() ) ); + Pattern fileContentPattern = Pattern + .compile( "alter table.* " + regexpQuote( "default-schema", "my_entity" ) ); + Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); + assertThat( fileContentMatcher.find(), is( true ) ); + } + catch (IOException e) { + fail( e.getMessage() ); + } + } + + @Entity(name = "MyEntity") + @Table(name = "my_entity") + public static class MyEntity { + + @Id + public Integer id; + } + + @Entity(name = "MyEntity") + @Table(name = "my_entity") + public static class MyEntityUpdated { + + @Id + public Integer id; + + private String title; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java new file mode 100644 index 000000000000..ec957f5884b3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java @@ -0,0 +1,166 @@ +/* + * 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.schemaupdate; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.EnumSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.tool.hbm2ddl.SchemaUpdate; +import org.hibernate.tool.schema.TargetType; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Guillaume Smet + */ +@TestForIssue(jiraKey = "HHH-12939") +@RequiresDialectFeature(DialectChecks.SupportSchemaCreation.class) +public class AlterTableQuoteSpecifiedSchemaTest extends AbstractAlterTableQuoteSchemaTest { + + @Override + protected void afterSessionFactoryBuilt() { + + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP TABLE " + quote( "my-schema", "my_entity" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP SCHEMA " + quote( "my-schema" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "CREATE SCHEMA " + quote( "my-schema" ) ) + .executeUpdate(); + } ); + } + + @Override + protected void cleanupTest() { + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP SCHEMA " + quote( "my-schema" ) ) + .executeUpdate(); + } ); + + } + catch (Exception ignore) { + } + } + + @Test + public void testSpecifiedSchema() throws IOException { + File output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE.toString() ) + .build(); + + try { + final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) + .addAnnotatedClass( MyEntity.class ) + .buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( ";" ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.DATABASE, TargetType.SCRIPT ), metadata ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + + try { + String fileContent = new String( Files.readAllBytes( output.toPath() ) ); + Pattern fileContentPattern = Pattern.compile( "create table " + regexpQuote( "my-schema", "my_entity" ) ); + Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); + assertThat( fileContentMatcher.find(), is( true ) ); + } + catch (IOException e) { + fail( e.getMessage() ); + } + + ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE.toString() ) + .build(); + try { + final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) + .addAnnotatedClass( MyEntityUpdated.class ) + .buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( ";" ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.DATABASE, TargetType.SCRIPT ), metadata ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + + try { + String fileContent = new String( Files.readAllBytes( output.toPath() ) ); + Pattern fileContentPattern = Pattern.compile( "alter table.* " + regexpQuote( "my-schema", "my_entity" ) ); + Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); + assertThat( fileContentMatcher.find(), is( true ) ); + } + catch (IOException e) { + fail( e.getMessage() ); + } + } + + @Entity(name = "MyEntity") + @Table(name = "my_entity", schema = "my-schema") + public static class MyEntity { + + @Id + public Integer id; + } + + @Entity(name = "MyEntity") + @Table(name = "my_entity", schema = "my-schema") + public static class MyEntityUpdated { + + @Id + public Integer id; + + private String title; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SqlServerQuoteSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SqlServerQuoteSchemaTest.java index f6cade36da8f..af70650e9555 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SqlServerQuoteSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SqlServerQuoteSchemaTest.java @@ -151,7 +151,7 @@ public void test() { try { String fileContent = new String( Files.readAllBytes( output.toPath() ) ); - Pattern fileContentPattern = Pattern.compile( "alter table .*?\\.\\[my\\-schema\\]\\.\\[my_entity\\]" ); + Pattern fileContentPattern = Pattern.compile( "alter table \\[my\\-schema\\]\\.\\[my_entity\\]" ); Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); assertThat( fileContentMatcher.find(), is( true ) ); } From 5827ada543b239644027ae3cfd5559dd48e505ce Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Tue, 9 Oct 2018 09:32:34 +0300 Subject: [PATCH 255/772] HHH-12939 - Database name not quoted at schema update Restrict tests to H2, PostgreSQL and SQL Server only (cherry picked from commit c3febcaaedfd3aab228234307fd41d8a5a2d955a) --- .../AlterTableQuoteDefaultSchemaTest.java | 30 +++++++++---------- .../AlterTableQuoteSpecifiedSchemaTest.java | 12 ++++++-- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java index d40da45b2c67..41e4f398de56 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java @@ -6,18 +6,12 @@ */ package org.hibernate.test.schemaupdate; -import static org.hamcrest.core.Is.is; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.EnumSet; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @@ -28,25 +22,31 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialect; -import org.hibernate.testing.RequiresDialectFeature; -import org.hibernate.testing.TestForIssue; - import org.hibernate.dialect.H2Dialect; -import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL82Dialect; -import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SQLServer2012Dialect; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.schema.TargetType; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; import org.junit.Test; +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + /** * @author Guillaume Smet */ @TestForIssue(jiraKey = "HHH-12939") -@RequiresDialectFeature(DialectChecks.SupportSchemaCreation.class) +@RequiresDialect(value = { + H2Dialect.class, + PostgreSQL82Dialect.class, + SQLServer2012Dialect.class, +}) public class AlterTableQuoteDefaultSchemaTest extends AbstractAlterTableQuoteSchemaTest { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java index ec957f5884b3..85d07cd8b82a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java @@ -21,11 +21,13 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.dialect.SQLServer2012Dialect; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.schema.TargetType; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -38,7 +40,11 @@ * @author Guillaume Smet */ @TestForIssue(jiraKey = "HHH-12939") -@RequiresDialectFeature(DialectChecks.SupportSchemaCreation.class) +@RequiresDialect(value = { + H2Dialect.class, + PostgreSQL82Dialect.class, + SQLServer2012Dialect.class, +}) public class AlterTableQuoteSpecifiedSchemaTest extends AbstractAlterTableQuoteSchemaTest { @Override From c46b6d0d0bbdf6183a4369c1dc7e044cd818cddd Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 17 Apr 2019 22:47:07 -0700 Subject: [PATCH 256/772] HHH-12939 : Change mssql-jdbc version back to 6.4.0.jre8 --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 589489af2e9f..35bb364d1883 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -111,7 +111,7 @@ ext { mariadb: 'org.mariadb.jdbc:mariadb-java-client:2.2.3', oracle: 'com.oracle.jdbc:ojdbc8:12.2.0.1', - mssql: 'com.microsoft.sqlserver:mssql-jdbc:7.0.0.jre8', + mssql: 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8', db2: 'com.ibm.db2:db2jcc:10.5', hana: 'com.sap.cloud.db.jdbc:ngdbc:2.2.16', // for HANA 1 the minimum required client version is 1.120.20 From 374ba3580956a934b0b43ea29a2d15a1cdb0315a Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 28 Mar 2019 14:23:37 -0700 Subject: [PATCH 257/772] HHH-13343 : test case (cherry picked from commit 6f110275ad3034a0ffd178ce00f58670c5659602) --- ...eByteCodeNotInProvidedClassLoaderTest.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java new file mode 100644 index 000000000000..8189554a17d1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java @@ -0,0 +1,68 @@ +/* + * 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.bytecode.internal.bytebuddy; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; + +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests that bytecode can be enhanced when the original class cannot be loaded from + * the ClassLoader provided to ByteBuddy. + */ +public class EnhanceByteCodeNotInProvidedClassLoaderTest { + + @Test + @TestForIssue( jiraKey = "HHH-13343" ) + public void test() { + Enhancer enhancer = createByteBuddyEnhancer(); + byte[] buffer = readResource( SimpleEntity.class ); + // Now use a fake class name so it won't be found in the ClassLoader + // provided by DefaultEnhancementContext + byte[] enhanced = enhancer.enhance( SimpleEntity.class.getName() + "Fake", buffer ); + Assert.assertNotNull( "This is null when there have been swallowed exceptions during enhancement. Check Logs!", enhanced ); + // Make sure enhanced bytecode is different from original bytecode. + Assert.assertFalse( Arrays.equals( buffer, enhanced ) ); + } + + private byte[] readResource(Class clazz) { + String internalName = clazz.getName().replace( '.', '/' ); + String resourceName = internalName + ".class"; + + final int BUF_SIZE = 256; + byte[] buffer = new byte[BUF_SIZE]; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int readSize = 0; + try ( InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream( resourceName ) ) { + while ( ( readSize = inputStream.read( buffer ) ) != -1 ) { + os.write( buffer, 0, readSize ); + } + os.flush(); + os.close(); + } + catch (IOException ex) { + Assert.fail( "Should not have an IOException here" ); + } + return os.toByteArray(); + } + + private Enhancer createByteBuddyEnhancer() { + ByteBuddyState bytebuddy = new ByteBuddyState(); + DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext(); + EnhancerImpl impl = new EnhancerImpl( enhancementContext, bytebuddy ); + return impl; + } +} From de09e67872d6a2112bb32afcd2000ecce056e2d2 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 27 Mar 2019 22:56:07 -0700 Subject: [PATCH 258/772] HHH-13343 : Bytecode enhancement using ByteBuddy fails when the class is not available from the provided ClassLoader (cherry picked from commit af3f48519353fcbc4e550faf5b3ebbb852dcf37f) --- .../internal/bytebuddy/EnhancerImpl.java | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index b543750cf8c5..a4f655c29a5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -7,26 +7,22 @@ package org.hibernate.bytecode.enhance.internal.bytebuddy; import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; -import static net.bytebuddy.matcher.ElementMatchers.isGetter; +import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.stream.Collectors; import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Transient; -import org.hibernate.MappingException; import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker; import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker; import org.hibernate.bytecode.enhance.spi.CollectionTracker; @@ -64,12 +60,10 @@ import net.bytebuddy.description.type.TypeDescription.Generic; import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.DynamicType; -import net.bytebuddy.dynamic.scaffold.MethodGraph; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; -import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.pool.TypePool; public class EnhancerImpl implements Enhancer { @@ -79,6 +73,7 @@ public class EnhancerImpl implements Enhancer { protected final ByteBuddyEnhancementContext enhancementContext; private final ByteBuddyState byteBuddyState; + private final EnhancerClassFileLocator classFileLocator; private final TypePool typePool; /** @@ -112,7 +107,8 @@ public class EnhancerImpl implements Enhancer { public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState) { this.enhancementContext = new ByteBuddyEnhancementContext( enhancementContext ); this.byteBuddyState = byteBuddyState; - this.typePool = buildTypePool( this.enhancementContext ); + this.classFileLocator = new EnhancerClassFileLocator( enhancementContext.getLoadingClassLoader() ); + this.typePool = buildTypePool( classFileLocator ); } /** @@ -130,6 +126,7 @@ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddy public synchronized byte[] enhance(String className, byte[] originalBytes) throws EnhancementException { //Classpool#describe does not accept '/' in the description name as it expects a class name. See HHH-12545 final String safeClassName = className.replace( '/', '.' ); + classFileLocator.addNameAndBytes( safeClassName, originalBytes ); try { final TypeDescription typeDescription = typePool.describe( safeClassName ).resolve(); @@ -143,8 +140,8 @@ public synchronized byte[] enhance(String className, byte[] originalBytes) throw } } - private TypePool buildTypePool(final ByteBuddyEnhancementContext enhancementContext) { - return TypePool.Default.WithLazyResolution.of( enhancementContext.getLoadingClassLoader() ); + private TypePool buildTypePool(final ClassFileLocator classFileLocator) { + return TypePool.Default.WithLazyResolution.of( classFileLocator ); } private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDescription managedCtClass) { @@ -539,4 +536,35 @@ else if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { } } } + + private class EnhancerClassFileLocator extends ClassFileLocator.ForClassLoader { + + private Map nameToResolutionMap = new HashMap<>(); + + /** + * Creates a new class file locator for the given class loader. + * + * @param classLoader The class loader to query which must not be the bootstrap class loader, i.e. {@code null}. + */ + protected EnhancerClassFileLocator(ClassLoader classLoader) { + super( classLoader ); + } + + @Override + public Resolution locate(String name) throws IOException { + Resolution resolution = nameToResolutionMap.get( name ); + if ( resolution != null ) { + return resolution; + } + else { + return super.locate( name ); + } + } + + void addNameAndBytes(String name, byte[] bytes ) { + assert name != null; + assert bytes != null; + nameToResolutionMap.put( name, new Resolution.Explicit( bytes) ); + } + } } From 75b25a4e4ce68a25f99a29a5c30bd07d98d0e8eb Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 4 Apr 2019 22:44:02 -0700 Subject: [PATCH 259/772] HHH-13343 : Bytecode enhancement using ByteBuddy fails when the class is not available from the provided ClassLoader --- .../internal/bytebuddy/EnhancerImpl.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index a4f655c29a5e..cec2f0f0d118 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -126,7 +125,7 @@ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddy public synchronized byte[] enhance(String className, byte[] originalBytes) throws EnhancementException { //Classpool#describe does not accept '/' in the description name as it expects a class name. See HHH-12545 final String safeClassName = className.replace( '/', '.' ); - classFileLocator.addNameAndBytes( safeClassName, originalBytes ); + classFileLocator.setClassNameAndBytes( safeClassName, originalBytes ); try { final TypeDescription typeDescription = typePool.describe( safeClassName ).resolve(); @@ -539,7 +538,10 @@ else if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { private class EnhancerClassFileLocator extends ClassFileLocator.ForClassLoader { - private Map nameToResolutionMap = new HashMap<>(); + // The name of the class to (possibly be) transformed. + private String className; + // The explicitly resolved Resolution for the class to (possibly be) transformed. + private Resolution resolution; /** * Creates a new class file locator for the given class loader. @@ -551,20 +553,21 @@ protected EnhancerClassFileLocator(ClassLoader classLoader) { } @Override - public Resolution locate(String name) throws IOException { - Resolution resolution = nameToResolutionMap.get( name ); - if ( resolution != null ) { + public Resolution locate(String className) throws IOException { + assert className != null; + if ( className.equals( this.className ) ) { return resolution; } else { - return super.locate( name ); + return super.locate( className ); } } - void addNameAndBytes(String name, byte[] bytes ) { - assert name != null; + void setClassNameAndBytes(String className, byte[] bytes) { + assert className != null; assert bytes != null; - nameToResolutionMap.put( name, new Resolution.Explicit( bytes) ); + this.className = className; + this.resolution = new Resolution.Explicit( bytes); } } } From 17c1ddb22138548f084a01c3fbc8220967206a5f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 22 Feb 2019 00:29:27 +0100 Subject: [PATCH 260/772] HHH-13277 Make HibernateMethodLookupDispatcher less fragile And less dependent of the JVM. (cherry picked from commit 0b3babe4fb118b9396d90d4325257917a5289f50) --- .../HibernateMethodLookupDispatcher.java | 66 ++++++++++++------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java index bb3b287f6a16..50132a24c17a 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java @@ -10,7 +10,6 @@ import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -23,9 +22,12 @@ public class HibernateMethodLookupDispatcher { private static final SecurityActions SECURITY_ACTIONS = new SecurityActions(); private static final Function> STACK_FRAME_GET_DECLARING_CLASS_FUNCTION; + @SuppressWarnings("rawtypes") + private static final Function STACK_FRAME_EXTRACT_FUNCTION; private static Object stackWalker; private static Method stackWalkerWalkMethod; private static Method stackFrameGetDeclaringClass; + private static final PrivilegedAction> GET_CALLER_CLASS_ACTION; // Currently, the bytecode provider is created statically and shared between all the session factories. Thus we // can't clear this set when we close a session factory as we might remove elements coming from another one. @@ -133,49 +135,67 @@ public Class apply(Object t) { } } }; - } - private static Class getCallerClass() { - PrivilegedAction> getCallerClassAction = new PrivilegedAction>() { + STACK_FRAME_EXTRACT_FUNCTION = new Function() { @Override @SuppressWarnings({ "unchecked", "rawtypes" }) + public Object apply(Stream stream) { + return stream.map( STACK_FRAME_GET_DECLARING_CLASS_FUNCTION ) + .limit( 16 ) + .toArray( Class[]::new ); + } + }; + + GET_CALLER_CLASS_ACTION = new PrivilegedAction>() { + + @Override public Class run() { try { + Class[] stackTrace; if ( stackWalker != null ) { - Optional> clazzOptional = (Optional>) stackWalkerWalkMethod.invoke( stackWalker, new Function() { - @Override - public Object apply(Stream stream) { - return stream.map( STACK_FRAME_GET_DECLARING_CLASS_FUNCTION ) - .skip( System.getSecurityManager() != null ? 6 : 5 ) - .findFirst(); - } - }); - - if ( !clazzOptional.isPresent() ) { - throw new HibernateException( "Unable to determine the caller class" ); - } - - return clazzOptional.get(); + stackTrace = (Class[]) stackWalkerWalkMethod.invoke( stackWalker, STACK_FRAME_EXTRACT_FUNCTION ); } else { - return SECURITY_ACTIONS.getCallerClass(); + stackTrace = SECURITY_ACTIONS.getCallerClass(); } + + // this shouldn't happen but let's be safe + if ( stackTrace.length < 4 ) { + throw new SecurityException( "Unable to determine the caller class" ); + } + + boolean hibernateMethodLookupDispatcherDetected = false; + // start at the 4th frame and limit that to the 16 first frames + int maxFrames = Math.min( 16, stackTrace.length ); + for ( int i = 3; i < maxFrames; i++ ) { + if ( stackTrace[i].getName().equals( HibernateMethodLookupDispatcher.class.getName() ) ) { + hibernateMethodLookupDispatcherDetected = true; + continue; + } + if ( hibernateMethodLookupDispatcherDetected ) { + return stackTrace[i]; + } + } + + throw new SecurityException( "Unable to determine the caller class" ); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new SecurityException( "Unable to determine the caller class", e ); } } }; + } - return System.getSecurityManager() != null ? AccessController.doPrivileged( getCallerClassAction ) : - getCallerClassAction.run(); + private static Class getCallerClass() { + return System.getSecurityManager() != null ? AccessController.doPrivileged( GET_CALLER_CLASS_ACTION ) : + GET_CALLER_CLASS_ACTION.run(); } private static class SecurityActions extends SecurityManager { - private Class getCallerClass() { - return getClassContext()[7]; + private Class[] getCallerClass() { + return getClassContext(); } } } From 635ccbddd432f744a1a03f9d0837b828fbc05e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 22 Feb 2019 12:48:16 +0100 Subject: [PATCH 261/772] HHH-13277 Simplify HibernateMethodLookupDispatcher (cherry picked from commit 38a0cd2690990e5b114564dbcb5c9924b8f0f3f7) --- .../HibernateMethodLookupDispatcher.java | 172 ++++++++++-------- 1 file changed, 94 insertions(+), 78 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java index 50132a24c17a..e757e9549227 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java @@ -19,15 +19,17 @@ public class HibernateMethodLookupDispatcher { - private static final SecurityActions SECURITY_ACTIONS = new SecurityActions(); - - private static final Function> STACK_FRAME_GET_DECLARING_CLASS_FUNCTION; - @SuppressWarnings("rawtypes") - private static final Function STACK_FRAME_EXTRACT_FUNCTION; - private static Object stackWalker; - private static Method stackWalkerWalkMethod; - private static Method stackFrameGetDeclaringClass; - private static final PrivilegedAction> GET_CALLER_CLASS_ACTION; + /** + * The minimum number of stack frames to drop before we can hope to find the caller frame. + */ + private static final int MIN_STACK_FRAMES = 3; + /** + * The maximum number of stack frames to explore to find the caller frame. + *

    + * Beyond that, we give up and throw an exception. + */ + private static final int MAX_STACK_FRAMES = 16; + private static final PrivilegedAction[]> GET_CALLER_STACK_ACTION; // Currently, the bytecode provider is created statically and shared between all the session factories. Thus we // can't clear this set when we close a session factory as we might remove elements coming from another one. @@ -85,10 +87,10 @@ static void registerAuthorizedClass(String className) { } static { - PrivilegedAction initializeGetCallerClassRequirementsAction = new PrivilegedAction() { - + // The action below will return the action used at runtime to retrieve the caller stack + PrivilegedAction[]>> initializeGetCallerStackAction = new PrivilegedAction[]>>() { @Override - public Void run() { + public PrivilegedAction[]> run() { Class stackWalkerClass = null; try { stackWalkerClass = Class.forName( "java.lang.StackWalker" ); @@ -98,104 +100,118 @@ public Void run() { } if ( stackWalkerClass != null ) { + // We can use a stack walker try { Class optionClass = Class.forName( "java.lang.StackWalker$Option" ); - stackWalker = stackWalkerClass.getMethod( "getInstance", optionClass ) + Object stackWalker = stackWalkerClass.getMethod( "getInstance", optionClass ) // The first one is RETAIN_CLASS_REFERENCE .invoke( null, optionClass.getEnumConstants()[0] ); - stackWalkerWalkMethod = stackWalkerClass.getMethod( "walk", Function.class ); - stackFrameGetDeclaringClass = Class.forName( "java.lang.StackWalker$StackFrame" ) + Method stackWalkerWalkMethod = stackWalkerClass.getMethod( "walk", Function.class ); + Method stackFrameGetDeclaringClass = Class.forName( "java.lang.StackWalker$StackFrame" ) .getMethod( "getDeclaringClass" ); + return new StackWalkerGetCallerStackAction( + stackWalker, stackWalkerWalkMethod,stackFrameGetDeclaringClass + ); } catch (Throwable e) { throw new HibernateException( "Unable to initialize the stack walker", e ); } } - - return null; + else { + // We cannot use a stack walker, default to fetching the security manager class context + return new SecurityManagerClassContextGetCallerStackAction(); + } } }; - if ( System.getSecurityManager() != null ) { - AccessController.doPrivileged( initializeGetCallerClassRequirementsAction ); - } - else { - initializeGetCallerClassRequirementsAction.run(); + GET_CALLER_STACK_ACTION = System.getSecurityManager() != null + ? AccessController.doPrivileged( initializeGetCallerStackAction ) + : initializeGetCallerStackAction.run(); + } + + private static Class getCallerClass() { + Class[] stackTrace = System.getSecurityManager() != null + ? AccessController.doPrivileged( GET_CALLER_STACK_ACTION ) + : GET_CALLER_STACK_ACTION.run(); + + // this shouldn't happen but let's be safe + if ( stackTrace.length <= MIN_STACK_FRAMES ) { + throw new SecurityException( "Unable to determine the caller class" ); } - STACK_FRAME_GET_DECLARING_CLASS_FUNCTION = new Function>() { - @Override - public Class apply(Object t) { - try { - return (Class) stackFrameGetDeclaringClass.invoke( t ); - } - catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new HibernateException( "Unable to get stack frame declaring class", e ); - } + boolean hibernateMethodLookupDispatcherDetected = false; + // start at the 4th frame and limit that to the MAX_STACK_FRAMES first frames + int maxFrames = Math.min( MAX_STACK_FRAMES, stackTrace.length ); + for ( int i = MIN_STACK_FRAMES; i < maxFrames; i++ ) { + if ( stackTrace[i].getName().equals( HibernateMethodLookupDispatcher.class.getName() ) ) { + hibernateMethodLookupDispatcherDetected = true; + continue; } - }; + if ( hibernateMethodLookupDispatcherDetected ) { + return stackTrace[i]; + } + } + + throw new SecurityException( "Unable to determine the caller class" ); + } + + /** + * A privileged action that retrieves the caller stack from the security manager class context. + */ + private static class SecurityManagerClassContextGetCallerStackAction extends SecurityManager + implements PrivilegedAction[]> { + @Override + public Class[] run() { + return getClassContext(); + } + } - STACK_FRAME_EXTRACT_FUNCTION = new Function() { + /** + * A privileged action that retrieves the caller stack using a stack walker. + */ + private static class StackWalkerGetCallerStackAction implements PrivilegedAction[]> { + private final Object stackWalker; + private final Method stackWalkerWalkMethod; + private final Method stackFrameGetDeclaringClass; + + StackWalkerGetCallerStackAction(Object stackWalker, Method stackWalkerWalkMethod, + Method stackFrameGetDeclaringClass) { + this.stackWalker = stackWalker; + this.stackWalkerWalkMethod = stackWalkerWalkMethod; + this.stackFrameGetDeclaringClass = stackFrameGetDeclaringClass; + } + @Override + public Class[] run() { + try { + return (Class[]) stackWalkerWalkMethod.invoke( stackWalker, stackFrameExtractFunction ); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new SecurityException( "Unable to determine the caller class", e ); + } + } + + private final Function stackFrameExtractFunction = new Function() { @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public Object apply(Stream stream) { - return stream.map( STACK_FRAME_GET_DECLARING_CLASS_FUNCTION ) - .limit( 16 ) + return stream.map( stackFrameGetDeclaringClassFunction ) + .limit( MAX_STACK_FRAMES ) .toArray( Class[]::new ); } }; - GET_CALLER_CLASS_ACTION = new PrivilegedAction>() { - + private final Function> stackFrameGetDeclaringClassFunction = new Function>() { @Override - public Class run() { + public Class apply(Object t) { try { - Class[] stackTrace; - if ( stackWalker != null ) { - stackTrace = (Class[]) stackWalkerWalkMethod.invoke( stackWalker, STACK_FRAME_EXTRACT_FUNCTION ); - } - else { - stackTrace = SECURITY_ACTIONS.getCallerClass(); - } - - // this shouldn't happen but let's be safe - if ( stackTrace.length < 4 ) { - throw new SecurityException( "Unable to determine the caller class" ); - } - - boolean hibernateMethodLookupDispatcherDetected = false; - // start at the 4th frame and limit that to the 16 first frames - int maxFrames = Math.min( 16, stackTrace.length ); - for ( int i = 3; i < maxFrames; i++ ) { - if ( stackTrace[i].getName().equals( HibernateMethodLookupDispatcher.class.getName() ) ) { - hibernateMethodLookupDispatcherDetected = true; - continue; - } - if ( hibernateMethodLookupDispatcherDetected ) { - return stackTrace[i]; - } - } - - throw new SecurityException( "Unable to determine the caller class" ); + return (Class) stackFrameGetDeclaringClass.invoke( t ); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new SecurityException( "Unable to determine the caller class", e ); + throw new HibernateException( "Unable to get stack frame declaring class", e ); } } }; } - - private static Class getCallerClass() { - return System.getSecurityManager() != null ? AccessController.doPrivileged( GET_CALLER_CLASS_ACTION ) : - GET_CALLER_CLASS_ACTION.run(); - } - - private static class SecurityActions extends SecurityManager { - - private Class[] getCallerClass() { - return getClassContext(); - } - } } From d25d7dc34bfa468548c5b7cd71b99ea39a637443 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 22 Feb 2019 13:07:22 +0100 Subject: [PATCH 262/772] HHH-13277 Add a couple of comments (cherry picked from commit 89f523c87f5ebdd4f794d69a8d6bde664abd38ee) --- .../bytebuddy/HibernateMethodLookupDispatcher.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java index e757e9549227..61cc1c962d2b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java @@ -17,6 +17,13 @@ import org.hibernate.HibernateException; +/** + * This dispatcher analyzes the stack frames to detect if a particular call should be authorized. + *

    + * Authorized classes are registered when creating the ByteBuddy proxies. + *

    + * It should only be used when the Security Manager is enabled. + */ public class HibernateMethodLookupDispatcher { /** @@ -93,6 +100,7 @@ static void registerAuthorizedClass(String className) { public PrivilegedAction[]> run() { Class stackWalkerClass = null; try { + // JDK 9 introduced the StackWalker stackWalkerClass = Class.forName( "java.lang.StackWalker" ); } catch (ClassNotFoundException e) { @@ -192,9 +200,9 @@ public Class[] run() { } } + @SuppressWarnings({ "unchecked", "rawtypes" }) private final Function stackFrameExtractFunction = new Function() { @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) public Object apply(Stream stream) { return stream.map( stackFrameGetDeclaringClassFunction ) .limit( MAX_STACK_FRAMES ) From 8aa976ea2d2a126e0fb51b1c1b5e8a2be2d36634 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 19 Mar 2019 16:58:28 -0700 Subject: [PATCH 263/772] HHH-13326 : test cases (cherry picked from commit 705ecec94f6624797b91a4fd4f48db68c8bffdb4) --- ...paOrNativeBootstrapFunctionalTestCase.java | 6 + .../InterceptorNonNullTransactionTest.java | 256 +++++++++++++ .../test/tm/InterceptorTransactionTest.java | 358 ++++++++++++++++++ 3 files changed, 620 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/interceptor/InterceptorNonNullTransactionTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/tm/InterceptorTransactionTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java index 732cd4c0d699..dad7771844b6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java @@ -19,6 +19,7 @@ import javax.persistence.spi.PersistenceUnitTransactionType; import org.hibernate.HibernateException; +import org.hibernate.Interceptor; import org.hibernate.Session; import org.hibernate.boot.registry.BootstrapServiceRegistry; import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; @@ -84,6 +85,11 @@ protected Session openSession() throws HibernateException { return session; } + protected Session openSession(Interceptor interceptor) throws HibernateException { + session = sessionFactory().withOptions().interceptor( interceptor ).openSession(); + return session; + } + protected EntityManager openEntityManager() throws HibernateException { return openSession().unwrap( EntityManager.class ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/interceptor/InterceptorNonNullTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/test/interceptor/InterceptorNonNullTransactionTest.java new file mode 100644 index 000000000000..415531600597 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/interceptor/InterceptorNonNullTransactionTest.java @@ -0,0 +1,256 @@ +/* + * 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.interceptor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Map; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.EmptyInterceptor; +import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.resource.transaction.spi.TransactionStatus; +import org.hibernate.test.exceptionhandling.BaseJpaOrNativeBootstrapFunctionalTestCase; +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(CustomParameterized.class) +public class InterceptorNonNullTransactionTest extends BaseJpaOrNativeBootstrapFunctionalTestCase { + + public enum JpaComplianceTransactionSetting { DEFAULT, TRUE, FALSE } + public enum JtaAllowTransactionAccessSetting { DEFAULT, TRUE, FALSE; }; + + @Parameterized.Parameters(name = "Bootstrap={0}, JpaComplianceTransactionSetting={1}, JtaAllowTransactionAccessSetting={2}") + public static Iterable parameters() { + return Arrays.asList( new Object[][] { + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.DEFAULT, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.TRUE }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.FALSE }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.TRUE }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.FALSE }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.DEFAULT, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.TRUE }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.FALSE }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.TRUE }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.FALSE }, + } ); + } + + private final JpaComplianceTransactionSetting jpaComplianceTransactionSetting; + private final JtaAllowTransactionAccessSetting jtaAllowTransactionAccessSetting; + + public InterceptorNonNullTransactionTest( + BootstrapMethod bootstrapMethod, + JpaComplianceTransactionSetting jpaComplianceTransactionSetting, + JtaAllowTransactionAccessSetting jtaAllowTransactionAccessSetting) { + super( bootstrapMethod ); + this.jpaComplianceTransactionSetting = jpaComplianceTransactionSetting; + this.jtaAllowTransactionAccessSetting = jtaAllowTransactionAccessSetting; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleEntity.class }; + } + + @Override + protected void configure(Map properties) { + super.configure( properties ); + + switch ( jpaComplianceTransactionSetting ) { + case DEFAULT: + // Keep the default (false) + break; + case TRUE: + properties.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + break; + case FALSE: + properties.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "false" ); + break; + + } + switch ( jtaAllowTransactionAccessSetting ) { + case DEFAULT: + // Keep the default (true native bootstrap; false if jpa bootstrap) + break; + case TRUE: + properties.put( AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS, "true" ); + break; + case FALSE: + properties.put( AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS, "false" ); + break; + } + } + + @Test + public void testHibernateTransactionApi() throws Exception { + + final TransactionInterceptor interceptor = new TransactionInterceptor(); + + Session session = sessionFactory().withOptions().interceptor( interceptor ).openSession(); + + session.getTransaction().begin(); + + assertTrue( interceptor.afterTransactionBeginMethodCalled ); + assertTrue( interceptor.afterTransactionBeginAssertionPassed ); + assertFalse( interceptor.beforeTransactionCompletionMethodCalled ); + assertNull( interceptor.beforeTransactionCompletionAssertionPassed ); + assertFalse( interceptor.afterTransactionCompletionMethodCalled ); + assertNull( interceptor.afterTransactionCompletionAssertionPassed ); + + SimpleEntity entity = new SimpleEntity( "Hello World" ); + session.save( entity ); + + interceptor.reset(); + + session.getTransaction().commit(); + + + assertFalse( interceptor.afterTransactionBeginMethodCalled ); + assertNull( interceptor.afterTransactionBeginAssertionPassed ); + assertTrue( interceptor.beforeTransactionCompletionMethodCalled ); + assertTrue( interceptor.afterTransactionCompletionMethodCalled ); + assertEquals( true, interceptor.beforeTransactionCompletionAssertionPassed ); + assertEquals( true, interceptor.afterTransactionCompletionAssertionPassed ); + + session.close(); + } + + @Test + public void testJtaApiWithSharedTransactionCoordinator() throws Exception { + + final TransactionInterceptor interceptor = new TransactionInterceptor(); + + Session originalSession = openSession( interceptor ); + + Session session = originalSession.sessionWithOptions().interceptor( interceptor ).connection().openSession(); + + interceptor.reset(); + + session.getTransaction().begin(); + + assertTrue( interceptor.afterTransactionBeginMethodCalled ); + assertTrue( interceptor.afterTransactionBeginAssertionPassed ); + assertFalse( interceptor.beforeTransactionCompletionMethodCalled ); + assertNull( interceptor.beforeTransactionCompletionAssertionPassed ); + assertFalse( interceptor.afterTransactionCompletionMethodCalled ); + assertNull( interceptor.afterTransactionCompletionAssertionPassed ); + + SimpleEntity entity = new SimpleEntity( "Hello World" ); + session.save( entity ); + + interceptor.reset(); + + session.getTransaction().commit(); + + assertFalse( interceptor.afterTransactionBeginMethodCalled ); + assertNull( interceptor.afterTransactionBeginAssertionPassed ); + assertTrue( interceptor.beforeTransactionCompletionMethodCalled ); + assertTrue( interceptor.afterTransactionCompletionMethodCalled ); + assertEquals( true, interceptor.beforeTransactionCompletionAssertionPassed ); + assertEquals( true, interceptor.afterTransactionCompletionAssertionPassed ); + + session.close(); + + originalSession.close(); + } + + @Entity(name = "SimpleEntity") + public static class SimpleEntity { + @Id + @GeneratedValue + private Integer id; + private String name; + + SimpleEntity() { + + } + + SimpleEntity(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + private class TransactionInterceptor extends EmptyInterceptor { + private boolean afterTransactionBeginMethodCalled; + private Boolean afterTransactionBeginAssertionPassed; + + + private boolean beforeTransactionCompletionMethodCalled; + private Boolean beforeTransactionCompletionAssertionPassed; + + private boolean afterTransactionCompletionMethodCalled; + private Boolean afterTransactionCompletionAssertionPassed; + + public void reset() { + afterTransactionBeginMethodCalled = false; + afterTransactionBeginAssertionPassed = null; + beforeTransactionCompletionMethodCalled = false; + beforeTransactionCompletionAssertionPassed = null; + afterTransactionCompletionMethodCalled = false; + afterTransactionCompletionAssertionPassed = null; + } + + @Override + public void afterTransactionBegin(org.hibernate.Transaction tx) { + afterTransactionBeginMethodCalled = true; + if ( tx != null ) { + afterTransactionBeginAssertionPassed = false; + assertEquals( TransactionStatus.ACTIVE, tx.getStatus() ); + afterTransactionBeginAssertionPassed = true; + } + } + @Override + public void beforeTransactionCompletion(org.hibernate.Transaction tx) { + beforeTransactionCompletionMethodCalled = true; + if ( tx != null ) { + beforeTransactionCompletionAssertionPassed = false; + assertEquals( TransactionStatus.ACTIVE, tx.getStatus() ); + beforeTransactionCompletionAssertionPassed = true; + } + } + @Override + public void afterTransactionCompletion(org.hibernate.Transaction tx) { + afterTransactionCompletionMethodCalled = true; + if ( tx != null ) { + afterTransactionCompletionAssertionPassed = false; + assertEquals( TransactionStatus.COMMITTED, tx.getStatus() ); + afterTransactionCompletionAssertionPassed = true; + } + } + }; +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/tm/InterceptorTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/test/tm/InterceptorTransactionTest.java new file mode 100644 index 000000000000..d20d110c513f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/tm/InterceptorTransactionTest.java @@ -0,0 +1,358 @@ +/* + * 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.tm; + +import java.util.Arrays; +import java.util.Map; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import javax.transaction.Status; + +import org.hibernate.EmptyInterceptor; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.resource.transaction.spi.TransactionStatus; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.hibernate.testing.junit4.CustomParameterized; +import org.hibernate.test.exceptionhandling.BaseJpaOrNativeBootstrapFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@TestForIssue( jiraKey = "HHH-13326") +@RunWith(CustomParameterized.class) +public class InterceptorTransactionTest extends BaseJpaOrNativeBootstrapFunctionalTestCase { + + public enum JpaComplianceTransactionSetting { DEFAULT, TRUE, FALSE } + public enum JtaAllowTransactionAccessSetting { + DEFAULT { + @Override + public boolean allowTransactionAccess( + SessionFactory sessionFactory, + JpaComplianceTransactionSetting jpaComplianceTransactionSetting) { + return !sessionFactory.getSessionFactoryOptions().isJpaBootstrap() || + jpaComplianceTransactionSetting != JpaComplianceTransactionSetting.TRUE; + } + }, + TRUE { + @Override + public boolean allowTransactionAccess( + SessionFactory sessionFactory, + JpaComplianceTransactionSetting jpaComplianceTransactionSetting) { + return true; + } + }, + FALSE { + @Override + public boolean allowTransactionAccess( + SessionFactory sessionFactory, + JpaComplianceTransactionSetting jpaComplianceTransactionSetting) { + // setting is ignored if jpaComplianceTransactionSetting != JpaComplianceTransactionSetting.TRUE + return jpaComplianceTransactionSetting != JpaComplianceTransactionSetting.TRUE; + } + }; + + public abstract boolean allowTransactionAccess( + SessionFactory sessionFactory, + JpaComplianceTransactionSetting jpaComplianceTransactionSetting + ); + }; + + @Parameterized.Parameters(name = "Bootstrap={0}, JpaComplianceTransactionSetting={1}, JtaAllowTransactionAccessSetting={2}") + public static Iterable parameters() { + return Arrays.asList( new Object[][] { + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.DEFAULT, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.TRUE }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.FALSE }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.TRUE }, + { BootstrapMethod.JPA, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.FALSE }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.DEFAULT, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.TRUE }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.TRUE, JtaAllowTransactionAccessSetting.FALSE }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.DEFAULT }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.TRUE }, + { BootstrapMethod.NATIVE, JpaComplianceTransactionSetting.FALSE, JtaAllowTransactionAccessSetting.FALSE }, + } ); + } + + private final JpaComplianceTransactionSetting jpaComplianceTransactionSetting; + private final JtaAllowTransactionAccessSetting jtaAllowTransactionAccessSetting; + + public InterceptorTransactionTest( + BootstrapMethod bootstrapMethod, + JpaComplianceTransactionSetting jpaComplianceTransactionSetting, + JtaAllowTransactionAccessSetting jtaAllowTransactionAccessSetting) { + super( bootstrapMethod ); + this.jpaComplianceTransactionSetting = jpaComplianceTransactionSetting; + this.jtaAllowTransactionAccessSetting = jtaAllowTransactionAccessSetting; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleEntity.class }; + } + + protected void configure(Map properties) { + super.configure( properties ); + TestingJtaBootstrap.prepare( properties ); + properties.put( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" ); + + switch ( jpaComplianceTransactionSetting ) { + case DEFAULT: + // Keep the default (false) + break; + case TRUE: + properties.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + break; + case FALSE: + properties.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "false" ); + break; + + } + switch ( jtaAllowTransactionAccessSetting ) { + case DEFAULT: + // Keep the default (true native bootstrap; false if jpa bootstrap) + break; + case TRUE: + properties.put( AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS, "true" ); + break; + case FALSE: + properties.put( AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS, "false" ); + break; + } + } + + @Test + public void testHibernateTransactionApi() throws Exception { + + final TransactionInterceptor interceptor = new TransactionInterceptor(); + + Session session = sessionFactory().withOptions().interceptor( interceptor ).openSession(); + + try { + session.getTransaction().begin(); + if ( !jtaAllowTransactionAccessSetting.allowTransactionAccess( + sessionFactory(), + jpaComplianceTransactionSetting + ) ) { + fail( "IllegalStateException should have been thrown." ); + } + } + catch (IllegalStateException ex) { + if ( TestingJtaPlatformImpl.INSTANCE.getTransactionManager().getStatus() == Status.STATUS_ACTIVE ) { + TestingJtaPlatformImpl.INSTANCE.getTransactionManager().setRollbackOnly(); + } + session.close(); + assertEquals( JpaComplianceTransactionSetting.TRUE, jpaComplianceTransactionSetting ); + return; // EARLY RETURN + } + + // Interceptor#afterTransactionBegin is never called when using JTA + assertFalse( interceptor.afterTransactionBeginMethodCalled ); + assertNull( interceptor.afterTransactionBeginAssertionPassed ); + assertNull( interceptor.beforeTransactionCompletionAssertionPassed ); + assertNull( interceptor.afterTransactionCompletionAssertionPassed ); + + SimpleEntity entity = new SimpleEntity( "Hello World" ); + session.save( entity ); + + interceptor.reset(); + + session.getTransaction().commit(); + + assertTrue( interceptor.beforeTransactionCompletionMethodCalled ); + assertTrue( interceptor.afterTransactionCompletionMethodCalled ); + assertEquals( true, interceptor.beforeTransactionCompletionAssertionPassed ); + assertEquals( true, interceptor.afterTransactionCompletionAssertionPassed ); + assertNull( interceptor.afterTransactionBeginAssertionPassed ); + + session.close(); + } + + @Test + public void testJtaApi() throws Exception { + + final TransactionInterceptor interceptor = new TransactionInterceptor(); + + Session session = sessionFactory().withOptions().interceptor( interceptor ).openSession(); + + TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin(); + + // Interceptor#afterTransactionBegin is never called when using JTA + assertFalse( interceptor.afterTransactionBeginMethodCalled ); + assertNull( interceptor.afterTransactionBeginAssertionPassed ); + assertNull( interceptor.beforeTransactionCompletionAssertionPassed ); + assertNull( interceptor.afterTransactionCompletionAssertionPassed ); + + SimpleEntity entity = new SimpleEntity( "Hello World" ); + session.save( entity ); + + interceptor.reset(); + + TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit(); + + assertTrue( interceptor.beforeTransactionCompletionMethodCalled ); + assertTrue( interceptor.afterTransactionCompletionMethodCalled ); + if ( jtaAllowTransactionAccessSetting.allowTransactionAccess( + sessionFactory(), + jpaComplianceTransactionSetting + ) ) { + assertEquals( true, interceptor.beforeTransactionCompletionAssertionPassed ); + assertEquals( true, interceptor.afterTransactionCompletionAssertionPassed ); + } + else { + assertEquals( null, interceptor.beforeTransactionCompletionAssertionPassed ); + assertEquals( null, interceptor.afterTransactionCompletionAssertionPassed ); + } + + assertNull( interceptor.afterTransactionBeginAssertionPassed ); + + session.close(); + } + + @Test + public void testJtaApiWithSharedTransactionCoordinator() throws Exception { + + final TransactionInterceptor interceptor = new TransactionInterceptor(); + + Session originalSession = openSession(); + + Session session = originalSession.sessionWithOptions().connection().interceptor( interceptor ).openSession(); + + TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin(); + + // Interceptor#afterTransactionBegin is never called when using JTA + assertFalse( interceptor.afterTransactionBeginMethodCalled ); + assertNull( interceptor.afterTransactionBeginAssertionPassed ); + assertNull( interceptor.beforeTransactionCompletionAssertionPassed ); + assertNull( interceptor.afterTransactionCompletionAssertionPassed ); + + SimpleEntity entity = new SimpleEntity( "Hello World" ); + session.save( entity ); + + interceptor.reset(); + + TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit(); + + assertTrue( interceptor.beforeTransactionCompletionMethodCalled ); + assertTrue( interceptor.afterTransactionCompletionMethodCalled ); + if ( jtaAllowTransactionAccessSetting.allowTransactionAccess( + sessionFactory(), + jpaComplianceTransactionSetting + ) ) { + assertEquals( true, interceptor.beforeTransactionCompletionAssertionPassed ); + assertEquals( true, interceptor.afterTransactionCompletionAssertionPassed ); + } + else { + assertEquals( null, interceptor.beforeTransactionCompletionAssertionPassed ); + assertEquals( null, interceptor.afterTransactionCompletionAssertionPassed ); + } + + assertNull( interceptor.afterTransactionBeginAssertionPassed ); + + session.close(); + + originalSession.close(); + } + + @Entity(name = "SimpleEntity") + public static class SimpleEntity { + @Id + @GeneratedValue + private Integer id; + private String name; + + SimpleEntity() { + + } + + SimpleEntity(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + private class TransactionInterceptor extends EmptyInterceptor { + private boolean afterTransactionBeginMethodCalled; + private Boolean afterTransactionBeginAssertionPassed; + + + private boolean beforeTransactionCompletionMethodCalled; + private Boolean beforeTransactionCompletionAssertionPassed; + + private boolean afterTransactionCompletionMethodCalled; + private Boolean afterTransactionCompletionAssertionPassed; + + public void reset() { + afterTransactionBeginMethodCalled = false; + afterTransactionBeginAssertionPassed = null; + beforeTransactionCompletionMethodCalled = false; + beforeTransactionCompletionAssertionPassed = null; + afterTransactionCompletionMethodCalled = false; + afterTransactionCompletionAssertionPassed = null; + } + + @Override + public void afterTransactionBegin(org.hibernate.Transaction tx) { + afterTransactionBeginMethodCalled = true; + if ( tx != null ) { + afterTransactionBeginAssertionPassed = false; + assertEquals( TransactionStatus.ACTIVE, tx.getStatus() ); + afterTransactionBeginAssertionPassed = true; + } + } + @Override + public void beforeTransactionCompletion(org.hibernate.Transaction tx) { + beforeTransactionCompletionMethodCalled = true; + if ( tx != null ) { + beforeTransactionCompletionAssertionPassed = false; + assertEquals( TransactionStatus.ACTIVE, tx.getStatus() ); + beforeTransactionCompletionAssertionPassed = true; + } + } + @Override + public void afterTransactionCompletion(org.hibernate.Transaction tx) { + afterTransactionCompletionMethodCalled = true; + if ( tx != null ) { + afterTransactionCompletionAssertionPassed = false; + assertEquals( TransactionStatus.COMMITTED, tx.getStatus() ); + afterTransactionCompletionAssertionPassed = true; + } + } + }; +} From 7559ecf19662ff5f21aba85e7df96533fe185f28 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 19 Mar 2019 21:40:32 -0700 Subject: [PATCH 264/772] HHH-13326 : Transaction passed to Hibernate Interceptor methods is null when JTA is used (cherry picked from commit 883465f52512d24bb282c010c9fdf758ecf7984b) --- .../AbstractSharedSessionContract.java | 24 ++++++++++++------- .../org/hibernate/internal/SessionImpl.java | 16 +++++++++---- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index ee523b802dea..8d9d530512f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -401,19 +401,25 @@ public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { @Override public Transaction getTransaction() throws HibernateException { - if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { - // JPA requires that we throw IllegalStateException if this is called - // on a JTA EntityManager - if ( getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta() ) { - if ( !getFactory().getSessionFactoryOptions().isJtaTransactionAccessEnabled() ) { - throw new IllegalStateException( "A JTA EntityManager cannot use getTransaction()" ); - } - } + if ( !isTransactionAccessible() ) { + throw new IllegalStateException( + "Transaction is not accessible when using JTA with JPA-compliant transaction access enabled" + ); } - return accessTransaction(); } + protected boolean isTransactionAccessible() { + // JPA requires that access not be provided to the transaction when using JTA. + // This is overridden when SessionFactoryOptions isJtaTransactionAccessEnabled() is true. + if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() && + getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta() && + !getFactory().getSessionFactoryOptions().isJtaTransactionAccessEnabled() ) { + return false; + } + return true; + } + @Override public Transaction accessTransaction() { if ( this.currentHibernateTransaction == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 9a5b59b601ca..4f5a01f8db6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -69,6 +69,7 @@ import org.hibernate.SessionException; import org.hibernate.SharedSessionBuilder; import org.hibernate.SimpleNaturalIdLoadAccess; +import org.hibernate.Transaction; import org.hibernate.TransientObjectException; import org.hibernate.TypeHelper; import org.hibernate.TypeMismatchException; @@ -2472,13 +2473,20 @@ public LobHelper getLobHelper() { private transient LobHelperImpl lobHelper; + private Transaction getTransactionIfAccessible() { + // We do not want an exception to be thrown if the transaction + // is not accessible. If the transaction is not accessible, + // then return null. + return isTransactionAccessible() ? accessTransaction() : null; + } + @Override public void beforeTransactionCompletion() { log.tracef( "SessionImpl#beforeTransactionCompletion()" ); flushBeforeTransactionCompletion(); actionQueue.beforeTransactionCompletion(); try { - getInterceptor().beforeTransactionCompletion( getCurrentTransaction() ); + getInterceptor().beforeTransactionCompletion( getTransactionIfAccessible() ); } catch (Throwable t) { log.exceptionInBeforeTransactionCompletionInterceptor( t ); @@ -2506,7 +2514,7 @@ public void afterTransactionCompletion(boolean successful, boolean delayed) { } try { - getInterceptor().afterTransactionCompletion( getCurrentTransaction() ); + getInterceptor().afterTransactionCompletion( getTransactionIfAccessible() ); } catch (Throwable t) { log.exceptionInAfterTransactionCompletionInterceptor( t ); @@ -2727,7 +2735,7 @@ public void beforeCompletion() { } actionQueue.beforeTransactionCompletion(); try { - getInterceptor().beforeTransactionCompletion( getCurrentTransaction() ); + getInterceptor().beforeTransactionCompletion( getTransactionIfAccessible() ); } catch (Throwable t) { log.exceptionInBeforeTransactionCompletionInterceptor( t ); @@ -3269,7 +3277,7 @@ public void startTransactionBoundary() { @Override public void afterTransactionBegin() { checkOpenOrWaitingForAutoClose(); - getInterceptor().afterTransactionBegin( getCurrentTransaction() ); + getInterceptor().afterTransactionBegin( getTransactionIfAccessible() ); } @Override From 8d4c47bffc0586ee424078dd6ea9c18922a40baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 5 Mar 2019 10:18:18 +0100 Subject: [PATCH 265/772] HHH-13300 Test behavior when query.getSingleResult() throws an exception (cherry picked from commit 53f70ab213b45e2445b9bd2e1026ff0c80b7ad57) --- .../ExceptionExpectations.java | 35 +++++++ .../QueryExceptionHandlingTest.java | 99 +++++++++++++++++++ .../QuerySyntaxExceptionHandlingTest.java | 67 ------------- 3 files changed, 134 insertions(+), 67 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QueryExceptionHandlingTest.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java index 26daa5688e72..e78d850c24bf 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java @@ -7,6 +7,7 @@ package org.hibernate.test.exceptionhandling; import java.sql.SQLException; +import javax.persistence.NoResultException; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import javax.persistence.RollbackException; @@ -54,6 +55,16 @@ public void onInvalidQueryExecuted(RuntimeException e) { assertThat( e.getCause(), instanceOf( QuerySyntaxException.class ) ); } + @Override + public void onGetSingleResultWithMultipleResults(RuntimeException e) { + assertThat( e, instanceOf( javax.persistence.NonUniqueResultException.class ) ); + } + + @Override + public void onGetSingleResultWithNoResults(RuntimeException e) { + assertThat( e, instanceOf( NoResultException.class ) ); + } + @Override public void onStaleObjectMergeAndUpdateFlush(RuntimeException e) { assertThat( e, instanceOf( OptimisticLockException.class ) ); @@ -115,6 +126,16 @@ public void onInvalidQueryExecuted(RuntimeException e) { assertThat( e, instanceOf( QuerySyntaxException.class ) ); } + @Override + public void onGetSingleResultWithMultipleResults(RuntimeException e) { + assertThat( e, instanceOf( org.hibernate.NonUniqueResultException.class ) ); + } + + @Override + public void onGetSingleResultWithNoResults(RuntimeException e) { + assertThat( e, instanceOf( NoResultException.class ) ); + } + @Override public void onStaleObjectMergeAndUpdateFlush(RuntimeException e) { assertThat( e, instanceOf( StaleObjectStateException.class ) ); @@ -174,6 +195,16 @@ public void onInvalidQueryExecuted(RuntimeException e) { assertThat( e.getCause(), instanceOf( QuerySyntaxException.class ) ); } + @Override + public void onGetSingleResultWithMultipleResults(RuntimeException e) { + assertThat( e, instanceOf( javax.persistence.NonUniqueResultException.class ) ); + } + + @Override + public void onGetSingleResultWithNoResults(RuntimeException e) { + assertThat( e, instanceOf( NoResultException.class ) ); + } + @Override public void onStaleObjectMergeAndUpdateFlush(RuntimeException e) { assertThat( e, instanceOf( OptimisticLockException.class ) ); @@ -215,6 +246,10 @@ public void onTransactionExceptionOnCommit(RuntimeException e) { void onInvalidQueryExecuted(RuntimeException e); + void onGetSingleResultWithMultipleResults(RuntimeException e); + + void onGetSingleResultWithNoResults(RuntimeException e); + void onStaleObjectMergeAndUpdateFlush(RuntimeException e); void onIdentifierGeneratorFailure(RuntimeException e); diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QueryExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QueryExceptionHandlingTest.java new file mode 100644 index 000000000000..ee38985c15fe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QueryExceptionHandlingTest.java @@ -0,0 +1,99 @@ +/* + * 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.exceptionhandling; + +import static org.junit.Assert.fail; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.transaction.TransactionUtil2; +import org.junit.Before; +import org.junit.Test; + +@RequiresDialect(H2Dialect.class) +public class QueryExceptionHandlingTest extends BaseExceptionHandlingTest { + + public QueryExceptionHandlingTest( + BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + A.class + }; + } + + @Before + public void initData() { + TransactionUtil2.inTransaction( sessionFactory(), s -> { + s.createQuery( "delete from A" ).executeUpdate(); + A a1 = new A(); + a1.id = 1; + s.persist( a1 ); + A a2 = new A(); + a2.id = 2; + s.persist( a2 ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12666") + public void testInvalidQuery() { + try { + TransactionUtil2.inSession( sessionFactory(), s -> { + s.createQuery( "from A where blahblahblah" ).list(); + } ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onInvalidQueryExecuted( expected ); + } + } + + @Test + @TestForIssue(jiraKey = "HHH-13300") + public void testGetSingleResultWithMultipleResults() { + try { + TransactionUtil2.inSession( sessionFactory(), s -> { + s.createQuery( "from A where id in (1, 2)" ).getSingleResult(); + } ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onGetSingleResultWithMultipleResults( expected ); + } + } + + @Test + @TestForIssue(jiraKey = "HHH-13300") + public void testGetSingleResultWithNoResults() { + try { + TransactionUtil2.inSession( sessionFactory(), s -> { + s.createQuery( "from A where id = 3" ).getSingleResult(); + } ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onGetSingleResultWithNoResults( expected ); + } + } + + @Entity(name = "A") + public static class A { + @Id + private long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java deleted file mode 100644 index 6f3dca8679b9..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QuerySyntaxExceptionHandlingTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.exceptionhandling; - -import static org.junit.Assert.fail; - -import javax.persistence.Entity; -import javax.persistence.Id; - -import org.hibernate.Session; -import org.hibernate.Transaction; -import org.hibernate.dialect.H2Dialect; -import org.hibernate.testing.RequiresDialect; -import org.hibernate.testing.TestForIssue; -import org.junit.Test; - -@TestForIssue(jiraKey = "HHH-12666") -@RequiresDialect(H2Dialect.class) -public class QuerySyntaxExceptionHandlingTest extends BaseExceptionHandlingTest { - - public QuerySyntaxExceptionHandlingTest( - BootstrapMethod bootstrapMethod, - ExceptionHandlingSetting exceptionHandlingSetting, - ExceptionExpectations exceptionExpectations) { - super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); - } - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - A.class - }; - } - - @Test - public void testInvalidQuery() { - Session s = openSession(); - Transaction tx = s.beginTransaction(); - A a = new A(); - a.id = 1; - s.persist( a ); - s.flush(); - s.clear(); - - try { - s.createQuery( "from A where blahblahblah" ).list(); - fail( "should have thrown an exception" ); - } - catch (RuntimeException expected) { - exceptionExpectations.onInvalidQueryExecuted( expected ); - } - finally { - tx.rollback(); - s.close(); - } - } - - @Entity(name = "A") - public static class A { - @Id - private long id; - } -} From a3433be82268bc9401715d826a4fc825dd48636a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 5 Mar 2019 10:26:37 +0100 Subject: [PATCH 266/772] HHH-13300 Correctly convert Hibernate exceptions to JPA in getSingleResult() (cherry picked from commit 4665fd9cd94787e3fbdd3a11a1614394c6c0fbd8) --- .../hibernate/query/internal/AbstractProducedQuery.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index e0e249d1162b..cca1c234dd4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -1557,12 +1557,7 @@ public R getSingleResult() { return uniqueElement( list ); } catch ( HibernateException e ) { - if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { - throw getExceptionConverter().convert( e ); - } - else { - throw e; - } + throw getExceptionConverter().convert( e ); } } From a93a5183ba6371fdd3c0291ffac79dde44b2df7d Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 19 Mar 2019 23:41:27 -0700 Subject: [PATCH 267/772] HHH-13300 Test behavior when Query.executeUpdate() throws an exception (cherry picked from commit 5a1efe883eefcb8fe1bf0325936a8684eca19071) --- .../ExceptionExpectations.java | 22 +++++++++++++++++++ .../QueryExceptionHandlingTest.java | 14 ++++++++++++ 2 files changed, 36 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java index e78d850c24bf..7c8dbc608192 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java @@ -94,6 +94,13 @@ public void onTransactionExceptionOnCommit(RuntimeException e) { assertThat( e.getCause(), instanceOf( PersistenceException.class ) ); assertThat( e.getCause().getCause(), instanceOf( TransactionException.class ) ); } + + @Override + public void onExecuteUpdateWithConstraintViolation(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause().getCause(), instanceOf( SQLException.class ) ); + } }; } @@ -160,6 +167,12 @@ public void onTransactionExceptionOnPersistAndMergeAndFlush(RuntimeException e) public void onTransactionExceptionOnCommit(RuntimeException e) { assertThat( e, instanceOf( TransactionException.class ) ); } + + @Override + public void onExecuteUpdateWithConstraintViolation(RuntimeException e) { + assertThat( e, instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause(), instanceOf( SQLException.class ) ); + } }; } @@ -233,6 +246,13 @@ public void onTransactionExceptionOnCommit(RuntimeException e) { assertThat( e, instanceOf( PersistenceException.class ) ); assertThat( e.getCause(), instanceOf( TransactionException.class ) ); } + + @Override + public void onExecuteUpdateWithConstraintViolation(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause().getCause(), instanceOf( SQLException.class ) ); + } }; } @@ -259,4 +279,6 @@ public void onTransactionExceptionOnCommit(RuntimeException e) { void onTransactionExceptionOnPersistAndMergeAndFlush(RuntimeException e); void onTransactionExceptionOnCommit(RuntimeException e); + + void onExecuteUpdateWithConstraintViolation(RuntimeException e); } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QueryExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QueryExceptionHandlingTest.java index ee38985c15fe..d408229fe730 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QueryExceptionHandlingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/QueryExceptionHandlingTest.java @@ -91,6 +91,20 @@ public void testGetSingleResultWithNoResults() { } } + @Test + @TestForIssue(jiraKey = "HHH-13300") + public void testExecuteUpdateWithConstraintViolation() { + try { + TransactionUtil2.inTransaction( sessionFactory(), s -> { + s.createQuery( "update A set id = 1 where id = 2" ).executeUpdate(); + } ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onExecuteUpdateWithConstraintViolation( expected ); + } + } + @Entity(name = "A") public static class A { @Id From bf85bfcf9963120f2b2f6e67216271d13bb3690a Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 19 Mar 2019 23:42:51 -0700 Subject: [PATCH 268/772] HHH-13300 Correctly convert Hibernate exceptions to JPA in executeUpdate() (cherry picked from commit 634782fef69ed0dc80d49fae27c8e5d7fb2c8b84) --- .../query/internal/AbstractProducedQuery.java | 7 +------ ...eEntityUpdateQueryHandlingModeExceptionTest.java | 13 ++++++++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index cca1c234dd4f..1b41ac7ef462 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -1590,12 +1590,7 @@ public int executeUpdate() throws HibernateException { throw new IllegalArgumentException( e ); } catch ( HibernateException e) { - if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { - throw getExceptionConverter().convert( e ); - } - else { - throw e; - } + throw getExceptionConverter().convert( e ); } finally { afterQuery(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java index 60ed5a7dc8f7..32412378a29c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java @@ -8,6 +8,8 @@ import java.util.Map; +import javax.persistence.PersistenceException; + import org.hibernate.HibernateException; import org.hibernate.cfg.AvailableSettings; @@ -18,6 +20,7 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -54,10 +57,14 @@ public void testBulkUpdate(){ .setParameter( "name", "N/A" ) .executeUpdate(); } ); - fail("Should throw HibernateException"); + fail("Should throw PersistenceException"); } - catch (HibernateException e) { - assertEquals( "The query: [update Country set name = :name] attempts to update an immutable entity: [Country]", e.getMessage() ); + catch (PersistenceException e) { + assertTrue( e.getCause() instanceof HibernateException ); + assertEquals( + "The query: [update Country set name = :name] attempts to update an immutable entity: [Country]", + e.getCause().getMessage() + ); } doInHibernate( this::sessionFactory, session -> { From b2707589b390abad4f2e993a60a6b5f49840e587 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 11 Apr 2019 19:24:12 -0700 Subject: [PATCH 269/772] HHH-13364 : Added test cases (cherry picked from commit 288afa35e0ccd7b36feec4063309faef8300e83e) --- .../org/hibernate/jpa/test/lock/LockTest.java | 102 +++++++++++++++++- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java index 9f07cd127201..29902c33c98b 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java @@ -116,6 +116,53 @@ public void testFindWithPessimisticWriteLockTimeoutException() { catch (PessimisticLockException pe) { fail( "Find with immediate timeout should have thrown LockTimeoutException." ); } + catch (PersistenceException pe) { + log.info( + "EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass() + .getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe + ); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + + @Test(timeout = 5 * 1000) //5 seconds + @TestForIssue( jiraKey = "HHH-13364" ) + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, + comment = "Test verifies proper exception throwing when a lock timeout is specified for Query#getSingleResult.", + jiraKey = "HHH-13364" ) + public void testQuerySingleResultPessimisticWriteLockTimeoutException() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createQuery( "from Lock_ where id = " + lock.getId(), Lock.class ) + .setLockMode( LockModeType.PESSIMISTIC_WRITE ) + .setHint( "javax.persistence.lock.timeout", 0 ) + .getSingleResult(); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } catch (PersistenceException pe) { log.info("EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + "This is likely a consequence of " + getDialect().getClass().getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + @@ -126,9 +173,12 @@ public void testFindWithPessimisticWriteLockTimeoutException() { } ); } - @Test - @RequiresDialectFeature( value = DialectChecks.SupportSkipLocked.class ) - public void testUpdateWithPessimisticReadLockSkipLocked() { + @Test(timeout = 5 * 1000) //5 seconds + @TestForIssue( jiraKey = "HHH-13364" ) + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, + comment = "Test verifies proper exception throwing when a lock timeout is specified for Query#getResultList.", + jiraKey = "HHH-13364" ) + public void testQueryResultListPessimisticWriteLockTimeoutException() { Lock lock = new Lock(); lock.setName( "name" ); @@ -136,6 +186,52 @@ public void testUpdateWithPessimisticReadLockSkipLocked() { entityManager.persist( lock ); } ); + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createQuery( "from Lock_ where id = " + lock.getId(), Lock.class ) + .setLockMode( LockModeType.PESSIMISTIC_WRITE ) + .setHint( "javax.persistence.lock.timeout", 0 ) + .getResultList(); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } + catch (PersistenceException pe) { + log.info( + "EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass() + .getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe + ); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + + @Test + @RequiresDialectFeature( value = DialectChecks.SupportSkipLocked.class ) + public void testUpdateWithPessimisticReadLockSkipLocked() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( + this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } + ); + doInJPA( this::entityManagerFactory, _entityManagaer -> { Map properties = new HashMap<>(); properties.put( org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT, LockOptions.SKIP_LOCKED ); From e9fe3df6bd7fbe54b4f04da69d04a8efacc9e35f Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 11 Apr 2019 19:38:04 -0700 Subject: [PATCH 270/772] HHH-13364 : Query.getSingleResult and getResultList() throw PessimisticLockException when pessimistic lock fails with timeout (cherry picked from commit f62913ba1cd53f58930156858e9a459210584967) --- .../org/hibernate/query/internal/AbstractProducedQuery.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index 1b41ac7ef462..3143fc10aab5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -1511,7 +1511,7 @@ public List list() { throw new IllegalArgumentException( e ); } catch (HibernateException he) { - throw getExceptionConverter().convert( he ); + throw getExceptionConverter().convert( he, getLockOptions() ); } finally { afterQuery(); @@ -1557,7 +1557,7 @@ public R getSingleResult() { return uniqueElement( list ); } catch ( HibernateException e ) { - throw getExceptionConverter().convert( e ); + throw getExceptionConverter().convert( e, getLockOptions() ); } } From 78a593b05bf44f02ac8192af65732adb4520c897 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 11 Apr 2019 20:11:08 -0700 Subject: [PATCH 271/772] HHH-13364 : Added a test using a named query (cherry picked from commit 800b60e648b5696e00d006e70f8e0fa2c296fcc0) --- .../org/hibernate/jpa/test/lock/Lock.java | 9 ++++ .../org/hibernate/jpa/test/lock/LockTest.java | 44 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java index 93f41f3c2837..ee50e511f558 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java @@ -10,12 +10,21 @@ import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.LockModeType; +import javax.persistence.NamedQuery; +import javax.persistence.QueryHint; import javax.persistence.Version; /** * @author Emmanuel Bernard */ @Entity(name="Lock_") +@NamedQuery( + name="AllLocks", + query="from Lock_", + lockMode = LockModeType.PESSIMISTIC_WRITE, + hints = { @QueryHint( name = "javax.persistence.lock.timeout", value = "0")} +) public class Lock { private Integer id; private Integer version; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java index 29902c33c98b..dc6e41c28277 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java @@ -220,6 +220,50 @@ public void testQueryResultListPessimisticWriteLockTimeoutException() { } ); } + @Test(timeout = 5 * 1000) //5 seconds + @TestForIssue( jiraKey = "HHH-13364" ) + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, + comment = "Test verifies proper exception throwing when a lock timeout is specified for NamedQuery#getResultList.", + jiraKey = "HHH-13364" ) + public void testNamedQueryResultListPessimisticWriteLockTimeoutException() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createNamedQuery( "AllLocks", Lock.class ).getResultList(); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } + catch (PersistenceException pe) { + log.info( + "EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass() + .getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe + ); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + @Test @RequiresDialectFeature( value = DialectChecks.SupportSkipLocked.class ) public void testUpdateWithPessimisticReadLockSkipLocked() { From cfdcb201b1e5ce337a6b5b8c8d1dbdb334946985 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 18 Apr 2019 14:44:18 -0700 Subject: [PATCH 272/772] HHH-13376 : Upgrade Javassist to 3.23.2-GA --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 35bb364d1883..52190ce0e33e 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -22,7 +22,7 @@ ext { cdiVersion = '2.0' weldVersion = '3.0.0.Final' - javassistVersion = '3.23.1-GA' + javassistVersion = '3.23.2-GA' byteBuddyVersion = '1.9.5' geolatteVersion = '1.3.0' From 0add2d42ba0710076307a02c1314846fd603812e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 26 Feb 2019 12:08:39 +0100 Subject: [PATCH 273/772] HHH-13266 Test LocalDateTime serialization, in particular around 1900-01-01T00:00:00 --- .../test/type/LocalDateTimeTest.java | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java new file mode 100644 index 000000000000..bfc2ea5aac23 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java @@ -0,0 +1,224 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.type; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.PostgreSQL81Dialect; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for storage of LocalDateTime properties. + */ +@RunWith(CustomParameterized.class) +public class LocalDateTimeTest extends BaseCoreFunctionalTestCase { + + private static Dialect DIALECT; + private static Dialect determineDialect() { + try { + return Dialect.getDialect(); + } + catch (Exception e) { + return new Dialect() { + }; + } + } + + /* + * The default timezone affects conversions done using java.util, + * which is why we take it into account even when testing LocalDateTime. + */ + @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6} [JVM TZ: {7}]") + public static List data() { + DIALECT = determineDialect(); + return Arrays.asList( + // Not affected by HHH-13266 (JDK-8061577) + data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 500, ZoneId.of( "Europe/Paris" ) ), + data( 1970, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), + data( 1900, 1, 2, 0, 9, 21, 0, ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 2, 0, 19, 32, 0, ZoneId.of( "Europe/Amsterdam" ) ), + // Affected by HHH-13266 (JDK-8061577) + data( 1892, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), + data( 1900, 1, 1, 0, 9, 20, 0, ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 31, 0, ZoneId.of( "Europe/Amsterdam" ) ), + data( 1600, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Amsterdam" ) ) + ); + } + + private static Object[] data(int year, int month, int day, + int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + if ( DIALECT instanceof PostgreSQL81Dialect ) { + // PostgreSQL apparently doesn't support nanosecond precision correctly + nanosecond = 0; + } + return new Object[] { year, month, day, hour, minute, second, nanosecond, defaultTimeZone }; + } + + private final int year; + private final int month; + private final int day; + private final int hour; + private final int minute; + private final int second; + private final int nanosecond; + private final ZoneId defaultTimeZone; + + public LocalDateTimeTest(int year, int month, int day, + int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.minute = minute; + this.second = second; + this.nanosecond = nanosecond; + this.defaultTimeZone = defaultTimeZone; + } + + private LocalDateTime getExpectedLocalDateTime() { + return LocalDateTime.of( year, month, day, hour, minute, second, nanosecond ); + } + + private Timestamp getExpectedTimestamp() { + return new Timestamp( + year - 1900, month - 1, day, + hour, minute, second, nanosecond + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EntityWithLocalDateTime.class }; + } + + @Before + public void cleanup() { + inTransaction( session -> { + session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithLocalDateTime( 1, getExpectedLocalDateTime() ) ); + } ); + inTransaction( session -> { + LocalDateTime read = session.find( EntityWithLocalDateTime.class, 1 ).value; + assertEquals( + "Writing then reading a value should return the original value", + getExpectedLocalDateTime(), read + ); + } ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenNativeRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithLocalDateTime( 1, getExpectedLocalDateTime() ) ); + } ); + inTransaction( session -> { + Timestamp nativeRead = (Timestamp) session.createNativeQuery( + "SELECT thevalue FROM theentity WHERE theid = :id" + ) + .setParameter( "id", 1 ) + .uniqueResult(); + assertEquals( + "Raw values written in database should match the original value (same day, hour, ...)", + getExpectedTimestamp(), nativeRead + ); + } ); + } ); + } + + private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { + TimeZone timeZoneBefore = TimeZone.getDefault(); + TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); + /* + * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) + * cache data dependent on the default timezone in thread local variables, + * and we want this data to be reinitialized with the new default time zone. + */ + try { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit( runnable ); + executor.shutdown(); + future.get(); + } + catch (InterruptedException e) { + throw new IllegalStateException( "Interrupted while testing", e ); + } + catch (ExecutionException e) { + Throwable cause = e.getCause(); + if ( cause instanceof RuntimeException ) { + throw (RuntimeException) cause; + } + else if ( cause instanceof Error ) { + throw (Error) cause; + } + else { + throw new IllegalStateException( "Unexpected exception while testing", cause ); + } + } + finally { + TimeZone.setDefault( timeZoneBefore ); + } + } + + @Entity + @Table(name = "theentity") + private static final class EntityWithLocalDateTime { + @Id + @Column(name = "theid") + private Integer id; + + @Basic + @Column(name = "thevalue") + private LocalDateTime value; + + protected EntityWithLocalDateTime() { + } + + private EntityWithLocalDateTime(int id, LocalDateTime value) { + this.id = id; + this.value = value; + } + } +} From bc4553b44e7e57191264c5703aa13ba0b481149b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 26 Feb 2019 12:55:14 +0100 Subject: [PATCH 274/772] HHH-13266 Fix LocalDateTime serialization by using the proper conversion methods between LocalDateTime and Timestamp --- .../java/LocalDateTimeJavaDescriptor.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java index 1760393c2532..aa3c760fef29 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java @@ -55,8 +55,14 @@ public X unwrap(LocalDateTime value, Class type, WrapperOptions options) } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - Instant instant = value.atZone( ZoneId.systemDefault() ).toInstant(); - return (X) java.sql.Timestamp.from( instant ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * We used to do Timestamp.from( value.atZone( ZoneId.systemDefault() ).toInstant() ), + * but on top of being more complex than the line below, it won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return (X) Timestamp.valueOf( value ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -98,7 +104,14 @@ public LocalDateTime wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * We used to do LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ), + * but on top of being more complex than the line below, it won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime(); } if ( Long.class.isInstance( value ) ) { From a19a4d3340e11261745cf95c0cb481864b99bfca Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 27 Feb 2019 22:09:27 -0800 Subject: [PATCH 275/772] HHH-13266 Change test to use raw JDBC for executing native query --- .../test/type/LocalDateTimeTest.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java index bfc2ea5aac23..a0543bf19240 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java @@ -6,6 +6,8 @@ */ package org.hibernate.test.type; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneId; @@ -155,15 +157,21 @@ public void writeThenNativeRead() { session.persist( new EntityWithLocalDateTime( 1, getExpectedLocalDateTime() ) ); } ); inTransaction( session -> { - Timestamp nativeRead = (Timestamp) session.createNativeQuery( - "SELECT thevalue FROM theentity WHERE theid = :id" - ) - .setParameter( "id", 1 ) - .uniqueResult(); - assertEquals( - "Raw values written in database should match the original value (same day, hour, ...)", - getExpectedTimestamp(), nativeRead - ); + session.doWork( connection -> { + final PreparedStatement statement = connection.prepareStatement( + "SELECT thevalue FROM theentity WHERE theid = ?" + ); + statement.setInt( 1, 1 ); + statement.execute(); + final ResultSet resultSet = statement.getResultSet(); + resultSet.next(); + Timestamp nativeRead = resultSet.getTimestamp( 1 ); + assertEquals( + "Raw values written in database should match the original value (same day, hour, ...)", + getExpectedTimestamp(), + nativeRead + ); + } ); } ); } ); } From c409c3305fab008e2cb33f6447e33ac1c3ae324c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 28 Feb 2019 17:48:07 +0100 Subject: [PATCH 276/772] HHH-13266 Test LocalDate serialization, in particular around 1900-01-01 --- .../hibernate/test/type/LocalDateTest.java | 217 +++++++++++++----- 1 file changed, 157 insertions(+), 60 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index da6bbb7461be..4d0393d6cb5f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -1,103 +1,200 @@ /* * 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 . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ package org.hibernate.test.type; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; -import org.hibernate.Session; -import org.hibernate.resource.transaction.spi.TransactionStatus; - import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.CustomParameterized; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertEquals; /** - * @author Andrea Boriero + * Tests for storage of LocalDate properties. */ +@RunWith(CustomParameterized.class) @TestForIssue(jiraKey = "HHH-10371") -public class LocalDateTest extends BaseNonConfigCoreFunctionalTestCase { +public class LocalDateTest extends BaseCoreFunctionalTestCase { + + /* + * The default timezone affects conversions done using java.util, + * which is why we take it into account even when testing LocalDateTime. + */ + @Parameterized.Parameters(name = "{0}-{1}-{2} [JVM TZ: {3}]") + public static List data() { + return Arrays.asList( + // Not affected by HHH-13266 (JDK-8061577) + data( 2017, 11, 6, ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, ZoneId.of( "Europe/Paris" ) ), + data( 1970, 1, 1, ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, ZoneId.of( "Europe/Oslo" ) ), + data( 1900, 1, 2, ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 2, ZoneId.of( "Europe/Amsterdam" ) ), + // Could have been affected by HHH-13266 (JDK-8061577), but was not + data( 1892, 1, 1, ZoneId.of( "Europe/Oslo" ) ), + data( 1900, 1, 1, ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, ZoneId.of( "Europe/Amsterdam" ) ), + data( 1600, 1, 1, ZoneId.of( "Europe/Amsterdam" ) ) + ); + } + + private static Object[] data(int year, int month, int day, ZoneId defaultTimeZone) { + return new Object[] { year, month, day, defaultTimeZone }; + } + + private final int year; + private final int month; + private final int day; + private final ZoneId defaultTimeZone; + + public LocalDateTest(int year, int month, int day, ZoneId defaultTimeZone) { + this.year = year; + this.month = month; + this.day = day; + this.defaultTimeZone = defaultTimeZone; + } + + private LocalDate getExpectedLocalDate() { + return LocalDate.of( year, month, day ); + } - private static final LocalDate expectedLocalDate = LocalDate.of( 1, 1, 1 ); + private Date getExpectedSqlDate() { + return new Date( year - 1900, month - 1, day ); + } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] {LocalDateEvent.class}; + protected Class[] getAnnotatedClasses() { + return new Class[] { EntityWithLocalDate.class }; } @Before - public void setUp() { - final Session s = openSession(); - s.getTransaction().begin(); - try { - s.save( new LocalDateEvent( 1L, expectedLocalDate ) ); - s.getTransaction().commit(); - } - catch (Exception e) { - if ( s.getTransaction() != null && s.getTransaction().getStatus() == TransactionStatus.ACTIVE ) { - s.getTransaction().rollback(); - } - fail( e.getMessage() ); - } - finally { - s.close(); - } + public void cleanup() { + inTransaction( session -> { + session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); + } ); } @Test - public void testLocalDate() { - final Session s = openSession(); - try { - final LocalDateEvent localDateEvent = s.get( LocalDateEvent.class, 1L ); - assertThat( localDateEvent.getStartDate(), is( expectedLocalDate ) ); - } - finally { - s.close(); - } + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithLocalDate( 1, getExpectedLocalDate() ) ); + } ); + inTransaction( session -> { + LocalDate read = session.find( EntityWithLocalDate.class, 1 ).value; + assertEquals( + "Writing then reading a value should return the original value", + getExpectedLocalDate(), read + ); + } ); + } ); } - @Entity(name = "LocalDateEvent") - @Table(name = "LOCAL_DATE_EVENT") - public static class LocalDateEvent { - private Long id; - private LocalDate startDate; + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenNativeRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithLocalDate( 1, getExpectedLocalDate() ) ); + } ); + inTransaction( session -> { + session.doWork( connection -> { + final PreparedStatement statement = connection.prepareStatement( + "SELECT thevalue FROM theentity WHERE theid = ?" + ); + statement.setInt( 1, 1 ); + statement.execute(); + final ResultSet resultSet = statement.getResultSet(); + resultSet.next(); + Date nativeRead = resultSet.getDate( 1 ); + assertEquals( + "Raw values written in database should match the original value (same day, hour, ...)", + getExpectedSqlDate(), + nativeRead + ); + } ); + } ); + } ); + } - public LocalDateEvent() { + private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { + TimeZone timeZoneBefore = TimeZone.getDefault(); + TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); + /* + * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) + * cache data dependent on the default timezone in thread local variables, + * and we want this data to be reinitialized with the new default time zone. + */ + try { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit( runnable ); + executor.shutdown(); + future.get(); } - - public LocalDateEvent(Long id, LocalDate startDate) { - this.id = id; - this.startDate = startDate; + catch (InterruptedException e) { + throw new IllegalStateException( "Interrupted while testing", e ); + } + catch (ExecutionException e) { + Throwable cause = e.getCause(); + if ( cause instanceof RuntimeException ) { + throw (RuntimeException) cause; + } + else if ( cause instanceof Error ) { + throw (Error) cause; + } + else { + throw new IllegalStateException( "Unexpected exception while testing", cause ); + } } + finally { + TimeZone.setDefault( timeZoneBefore ); + } + } + @Entity + @Table(name = "theentity") + private static final class EntityWithLocalDate { @Id - public Long getId() { - return id; - } + @Column(name = "theid") + private Integer id; - public void setId(Long id) { - this.id = id; - } + @Basic + @Column(name = "thevalue") + private LocalDate value; - @Column(name = "START_DATE") - public LocalDate getStartDate() { - return startDate; + protected EntityWithLocalDate() { } - public void setStartDate(LocalDate startDate) { - this.startDate = startDate; + private EntityWithLocalDate(int id, LocalDate value) { + this.id = id; + this.value = value; } } } From 08bb8e149f0ab195e56b3b64ff3808281545ee6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Mar 2019 13:03:00 +0100 Subject: [PATCH 277/772] HHH-13266 Test OffsetDateTime serialization, in particular around 1900-01-01 --- .../test/type/OffsetDateTimeTest.java | 332 ++++++++++++------ 1 file changed, 233 insertions(+), 99 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java index 0cae420d9cb5..76ebb98a21fc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java @@ -6,160 +6,294 @@ */ package org.hibernate.test.type; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Timestamp; +import java.time.LocalDateTime; import java.time.OffsetDateTime; +import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.GregorianCalendar; import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.Query; -import org.hibernate.Session; -import org.hibernate.resource.transaction.spi.TransactionStatus; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.type.OffsetDateTimeType; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertTrue; /** * @author Andrea Boriero */ +@RunWith(CustomParameterized.class) @TestForIssue(jiraKey = "HHH-10372") public class OffsetDateTimeTest extends BaseNonConfigCoreFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] {OffsetDateTimeEvent.class}; + private static Dialect DIALECT; + private static Dialect determineDialect() { + try { + return Dialect.getDialect(); + } + catch (Exception e) { + return new Dialect() { + }; + } } - @Test - public void testOffsetDateTimeWithHoursZoneOffset() { - final OffsetDateTime expectedStartDate = OffsetDateTime.of( - 2015, - 1, - 1, - 0, - 0, - 0, - 0, - ZoneOffset.ofHours( 5 ) + /* + * The default timezone affects conversions done using java.util, + * which is why we take it into account even when testing OffsetDateTime. + */ + @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6}[{7}] [JVM TZ: {8}]") + public static List data() { + DIALECT = determineDialect(); + return Arrays.asList( + // Not affected by HHH-13266 + data( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 500, "+01:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZoneId.of( "Europe/Paris" ) ), + data( 1970, 1, 1, 0, 0, 0, 0, "+01:00", ZoneId.of( "GMT" ) ), + data( 1970, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "GMT" ) ), + data( 1970, 1, 1, 0, 0, 0, 0, "-01:00", ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, "+01:00", ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, "-01:00", ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "Europe/Oslo" ) ), + data( 1900, 1, 1, 0, 9, 21, 0, "+00:09:21", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), + // Affected by HHH-13266 + data( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "Europe/Oslo" ) ), + data( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), + data( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ) ); + } - saveOffsetDateTimeEventWithStartDate( expectedStartDate ); - - checkSavedOffsetDateTimeIsEqual( expectedStartDate ); - compareSavedOffsetDateTimeWith( expectedStartDate ); + private static Object[] data(int year, int month, int day, + int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { + if ( DIALECT instanceof PostgreSQL81Dialect ) { + // PostgreSQL apparently doesn't support nanosecond precision correctly + nanosecond = 0; + } + return new Object[] { year, month, day, hour, minute, second, nanosecond, offset, defaultTimeZone }; } - @Test - public void testOffsetDateTimeWithUTCZoneOffset() { - final OffsetDateTime expectedStartDate = OffsetDateTime.of( - 1, - 1, - 1, - 0, - 0, - 0, - 0, - ZoneOffset.UTC - ); + private final int year; + private final int month; + private final int day; + private final int hour; + private final int minute; + private final int second; + private final int nanosecond; + private final String offset; + private final ZoneId defaultTimeZone; - saveOffsetDateTimeEventWithStartDate( expectedStartDate ); + public OffsetDateTimeTest(int year, int month, int day, + int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.minute = minute; + this.second = second; + this.nanosecond = nanosecond; + this.offset = offset; + this.defaultTimeZone = defaultTimeZone; + } - checkSavedOffsetDateTimeIsEqual( expectedStartDate ); - compareSavedOffsetDateTimeWith( expectedStartDate ); + private OffsetDateTime getOriginalOffsetDateTime() { + return OffsetDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneOffset.of( offset ) ); } - @Test - public void testRetrievingEntityByOffsetDateTime() { + private OffsetDateTime getExpectedOffsetDateTime() { + return getOriginalOffsetDateTime().atZoneSameInstant( ZoneId.systemDefault() ).toOffsetDateTime(); + } - final OffsetDateTime startDate = OffsetDateTime.of( - 1, - 1, - 1, - 0, - 0, - 0, - 0, - ZoneOffset.ofHours( 3 ) + private Timestamp getExpectedTimestamp() { + LocalDateTime dateTimeInDefaultTimeZone = getOriginalOffsetDateTime().atZoneSameInstant( ZoneId.systemDefault() ) + .toLocalDateTime(); + return new Timestamp( + dateTimeInDefaultTimeZone.getYear() - 1900, dateTimeInDefaultTimeZone.getMonthValue() - 1, + dateTimeInDefaultTimeZone.getDayOfMonth(), + dateTimeInDefaultTimeZone.getHour(), dateTimeInDefaultTimeZone.getMinute(), + dateTimeInDefaultTimeZone.getSecond(), + dateTimeInDefaultTimeZone.getNano() ); + } - saveOffsetDateTimeEventWithStartDate( startDate ); + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EntityWithOffsetDateTime.class }; + } - final Session s = openSession(); - try { - Query query = s.createQuery( "from OffsetDateTimeEvent o where o.startDate = :date" ); - query.setParameter( "date", startDate, OffsetDateTimeType.INSTANCE ); - List list = query.list(); - assertThat( list.size(), is( 1 ) ); - } - finally { - s.close(); - } + @Before + public void cleanup() { + inTransaction( session -> { + session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); + } ); } - private void checkSavedOffsetDateTimeIsEqual(OffsetDateTime startdate) { - final Session s = openSession(); - try { - final OffsetDateTimeEvent offsetDateEvent = s.get( OffsetDateTimeEvent.class, 1L ); - assertThat( offsetDateEvent.startDate.isEqual( startdate ), is( true ) ); - } - finally { - s.close(); - } + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithOffsetDateTime( 1, getOriginalOffsetDateTime() ) ); + } ); + inTransaction( session -> { + OffsetDateTime read = session.find( org.hibernate.test.type.OffsetDateTimeTest.EntityWithOffsetDateTime.class, 1 ).value; + assertEquals( + "Writing then reading a value should return the original value", + getExpectedOffsetDateTime(), read + ); + assertTrue( + getExpectedOffsetDateTime().isEqual( read ) + ); + assertEquals( + 0, + OffsetDateTimeType.INSTANCE.getComparator().compare( getExpectedOffsetDateTime(), read ) + ); + } ); + } ); } - private void compareSavedOffsetDateTimeWith(OffsetDateTime startdate) { - final Session s = openSession(); - try { - final OffsetDateTimeEvent offsetDateEvent = s.get( OffsetDateTimeEvent.class, 1L ); - assertThat( - OffsetDateTimeType.INSTANCE.getComparator().compare( offsetDateEvent.startDate, startdate ), - is( 0 ) - ); - } - finally { - s.close(); - } + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenNativeRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithOffsetDateTime( 1, getOriginalOffsetDateTime() ) ); + } ); + inTransaction( session -> { + session.doWork( connection -> { + final PreparedStatement statement = connection.prepareStatement( + "SELECT thevalue FROM theentity WHERE theid = ?" + ); + statement.setInt( 1, 1 ); + statement.execute(); + final ResultSet resultSet = statement.getResultSet(); + resultSet.next(); + Timestamp nativeRead = resultSet.getTimestamp( 1 ); + assertEquals( + "Raw values written in database should match the original value (same instant)", + getExpectedTimestamp(), + nativeRead + ); + } ); + } ); + } ); } - private void saveOffsetDateTimeEventWithStartDate(OffsetDateTime startdate) { - final OffsetDateTimeEvent event = new OffsetDateTimeEvent(); - event.id = 1L; - event.startDate = startdate; + @Test + public void testRetrievingEntityByOffsetDateTime() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithOffsetDateTime( 1, getOriginalOffsetDateTime() ) ); + } ); + Consumer checkOneMatch = expected -> inSession( s -> { + Query query = s.createQuery( "from EntityWithOffsetDateTime o where o.value = :date" ); + query.setParameter( "date", expected, OffsetDateTimeType.INSTANCE ); + List list = query.list(); + assertThat( list.size(), is( 1 ) ); + } ); + checkOneMatch.accept( getOriginalOffsetDateTime() ); + checkOneMatch.accept( getExpectedOffsetDateTime() ); + checkOneMatch.accept( getExpectedOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ); + } ); + } - final Session s = openSession(); - s.getTransaction().begin(); + private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { + TimeZone timeZoneBefore = TimeZone.getDefault(); + TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); + /* + * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) + * cache data dependent on the default timezone in thread local variables, + * and we want this data to be reinitialized with the new default time zone. + */ try { - s.save( event ); - s.getTransaction().commit(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit( runnable ); + executor.shutdown(); + future.get(); } - catch (Exception e) { - if ( s.getTransaction() != null && s.getTransaction().getStatus() == TransactionStatus.ACTIVE ) { - s.getTransaction().rollback(); + catch (InterruptedException e) { + throw new IllegalStateException( "Interrupted while testing", e ); + } + catch (ExecutionException e) { + Throwable cause = e.getCause(); + if ( cause instanceof RuntimeException ) { + throw (RuntimeException) cause; + } + else if ( cause instanceof Error ) { + throw (Error) cause; + } + else { + throw new IllegalStateException( "Unexpected exception while testing", cause ); } - fail( e.getMessage() ); } finally { - s.close(); + TimeZone.setDefault( timeZoneBefore ); } } - @Entity(name = "OffsetDateTimeEvent") - @Table(name = "OFFSET_DATE_TIME_EVENT") - public static class OffsetDateTimeEvent { - + @Entity(name = "EntityWithOffsetDateTime") + @Table(name = "theentity") + private static final class EntityWithOffsetDateTime { @Id - private Long id; + @Column(name = "theid") + private Integer id; + + @Basic + @Column(name = "thevalue") + private OffsetDateTime value; - @Column(name = "START_DATE") - private OffsetDateTime startDate; + protected EntityWithOffsetDateTime() { + } + + private EntityWithOffsetDateTime(int id, OffsetDateTime value) { + this.id = id; + this.value = value; + } } @Override From 527e3a25ca039261c438df92dcdce0ec98c51ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Mar 2019 13:03:23 +0100 Subject: [PATCH 278/772] HHH-13266 Fix OffsetDateTime serialization by using the proper conversion methods between OffsetDateTime and Timestamp --- .../java/OffsetDateTimeJavaDescriptor.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java index df3698429563..61f0ccc4766e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java @@ -59,7 +59,13 @@ public X unwrap(OffsetDateTime offsetDateTime, Class type, WrapperOptions } if ( Timestamp.class.isAssignableFrom( type ) ) { - return (X) Timestamp.from( offsetDateTime.toInstant() ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use Timestamp.from( offsetDateTime.toInstant() ), but this won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return (X) Timestamp.valueOf( offsetDateTime.atZoneSameInstant( ZoneId.systemDefault() ).toLocalDateTime() ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -93,7 +99,13 @@ public OffsetDateTime wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return OffsetDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use OffsetDateTime.ofInstant( ts.toInstant(), ... ), but this won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime().atZone( ZoneId.systemDefault() ).toOffsetDateTime(); } if ( Date.class.isInstance( value ) ) { From 8a782b1759f4b15fcecb810fc03c86e0483776d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Mar 2019 13:13:45 +0100 Subject: [PATCH 279/772] HHH-13266 Test ZonedDateTime serialization, in particular around 1900-01-01 --- .../test/type/ZonedDateTimeTest.java | 350 +++++++++++++----- 1 file changed, 249 insertions(+), 101 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java index 978b21d04593..55680baaeed1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java @@ -6,157 +6,305 @@ */ package org.hibernate.test.type; -import java.time.ZoneOffset; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Timestamp; +import java.time.LocalDateTime; import java.time.ZonedDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Arrays; import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.Query; -import org.hibernate.Session; -import org.hibernate.resource.transaction.spi.TransactionStatus; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.type.ZonedDateTimeType; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertTrue; /** * @author Andrea Boriero */ +@RunWith(CustomParameterized.class) +@TestForIssue(jiraKey = "HHH-10372") public class ZonedDateTimeTest extends BaseNonConfigCoreFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] {ZonedDateTimeEvent.class}; + + private static Dialect DIALECT; + private static Dialect determineDialect() { + try { + return Dialect.getDialect(); + } + catch (Exception e) { + return new Dialect() { + }; + } } - @Test - public void testZoneDateTimeWithHoursZoneOffset() { - final ZonedDateTime expectedStartDate = ZonedDateTime.of( - 2015, - 1, - 1, - 0, - 0, - 0, - 0, - ZoneOffset.ofHours( 5 ) + /* + * The default timezone affects conversions done using java.util, + * which is why we take it into account even when testing ZonedDateTime. + */ + @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6}[{7}] [JVM TZ: {8}]") + public static List data() { + DIALECT = determineDialect(); + return Arrays.asList( + // Not affected by HHH-13266 + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 500, "GMT+01:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZoneId.of( "Europe/Paris" ) ), + data( 1970, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZoneId.of( "GMT" ) ), + data( 1970, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "GMT" ) ), + data( 1970, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "Europe/Oslo" ) ), + data( 1900, 1, 1, 0, 9, 21, 0, "GMT+00:09:21", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 9, 21, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 31, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), + data( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Amsterdam" ) ), + // Affected by HHH-13266 + data( 1892, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "Europe/Oslo" ) ), + data( 1892, 1, 1, 0, 0, 0, 0, "Europe/Oslo", ZoneId.of( "Europe/Oslo" ) ), + data( 1900, 1, 1, 0, 9, 20, 0, "GMT+00:09:21", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 9, 20, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), + data( 1900, 1, 1, 0, 19, 31, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Amsterdam" ) ), + data( 1600, 1, 1, 0, 0, 0, 0, "GMT+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), + data( 1600, 1, 1, 0, 0, 0, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Amsterdam" ) ) ); + } - saveZoneDateTimeEventWithStartDate( expectedStartDate ); - - checkSavedZonedDateTimeIsEqual( expectedStartDate ); - compareSavedZonedDateTimeWith( expectedStartDate ); + private static Object[] data(int year, int month, int day, + int hour, int minute, int second, int nanosecond, String zone, ZoneId defaultTimeZone) { + if ( DIALECT instanceof PostgreSQL81Dialect ) { + // PostgreSQL apparently doesn't support nanosecond precision correctly + nanosecond = 0; + } + return new Object[] { year, month, day, hour, minute, second, nanosecond, zone, defaultTimeZone }; } - @Test - public void testZoneDateTimeWithUTCZoneOffset() { - final ZonedDateTime expectedStartDate = ZonedDateTime.of( - 1, - 1, - 1, - 0, - 0, - 0, - 0, - ZoneOffset.UTC - ); + private final int year; + private final int month; + private final int day; + private final int hour; + private final int minute; + private final int second; + private final int nanosecond; + private final String zone; + private final ZoneId defaultTimeZone; + + public ZonedDateTimeTest(int year, int month, int day, + int hour, int minute, int second, int nanosecond, String zone, ZoneId defaultTimeZone) { + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.minute = minute; + this.second = second; + this.nanosecond = nanosecond; + this.zone = zone; + this.defaultTimeZone = defaultTimeZone; + } - saveZoneDateTimeEventWithStartDate( expectedStartDate ); + private ZonedDateTime getOriginalZonedDateTime() { + return ZonedDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneId.of( zone ) ); + } - checkSavedZonedDateTimeIsEqual( expectedStartDate ); - compareSavedZonedDateTimeWith( expectedStartDate ); + private ZonedDateTime getExpectedZonedDateTime() { + return getOriginalZonedDateTime().withZoneSameInstant( ZoneId.systemDefault() ); } - @Test - public void testRetrievingEntityByZoneDateTime() { - - final ZonedDateTime startDate = ZonedDateTime.of( - 1, - 1, - 1, - 0, - 0, - 0, - 0, - ZoneOffset.ofHours( 3 ) + private Timestamp getExpectedTimestamp() { + LocalDateTime dateTimeInDefaultTimeZone = getOriginalZonedDateTime().withZoneSameInstant( ZoneId.systemDefault() ) + .toLocalDateTime(); + return new Timestamp( + dateTimeInDefaultTimeZone.getYear() - 1900, dateTimeInDefaultTimeZone.getMonthValue() - 1, + dateTimeInDefaultTimeZone.getDayOfMonth(), + dateTimeInDefaultTimeZone.getHour(), dateTimeInDefaultTimeZone.getMinute(), + dateTimeInDefaultTimeZone.getSecond(), + dateTimeInDefaultTimeZone.getNano() ); + } - saveZoneDateTimeEventWithStartDate( startDate ); + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EntityWithZonedDateTime.class }; + } - final Session s = openSession(); - try { - Query query = s.createQuery( "from ZonedDateTimeEvent o where o.startDate = :date" ); - query.setParameter( "date", startDate, ZonedDateTimeType.INSTANCE ); - List list = query.list(); - assertThat( list.size(), is( 1 ) ); - } - finally { - s.close(); - } + @Before + public void cleanup() { + inTransaction( session -> { + session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); + } ); } - private void checkSavedZonedDateTimeIsEqual(ZonedDateTime startdate) { - final Session s = openSession(); - try { - final ZonedDateTimeEvent zonedDateTimeEvent = s.get( ZonedDateTimeEvent.class, 1L ); - assertThat( zonedDateTimeEvent.startDate.isEqual( startdate ), is( true ) ); - } - finally { - s.close(); - } + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithZonedDateTime( 1, getOriginalZonedDateTime() ) ); + } ); + inTransaction( session -> { + ZonedDateTime read = session.find( ZonedDateTimeTest.EntityWithZonedDateTime.class, 1 ).value; + assertEquals( + "Writing then reading a value should return the original value", + getExpectedZonedDateTime(), read + ); + assertTrue( + getExpectedZonedDateTime().isEqual( read ) + ); + assertEquals( + 0, + ZonedDateTimeType.INSTANCE.getComparator().compare( getExpectedZonedDateTime(), read ) + ); + } ); + } ); } - private void compareSavedZonedDateTimeWith(ZonedDateTime startdate) { - final Session s = openSession(); - try { - final ZonedDateTimeEvent zonedDateTimeEvent = s.get( ZonedDateTimeEvent.class, 1L ); - assertThat( - ZonedDateTimeType.INSTANCE.getComparator().compare( zonedDateTimeEvent.startDate, startdate ), - is( 0 ) - ); - } - finally { - s.close(); - } + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenNativeRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithZonedDateTime( 1, getOriginalZonedDateTime() ) ); + } ); + inTransaction( session -> { + session.doWork( connection -> { + final PreparedStatement statement = connection.prepareStatement( + "SELECT thevalue FROM theentity WHERE theid = ?" + ); + statement.setInt( 1, 1 ); + statement.execute(); + final ResultSet resultSet = statement.getResultSet(); + resultSet.next(); + Timestamp nativeRead = resultSet.getTimestamp( 1 ); + assertEquals( + "Raw values written in database should match the original value (same instant)", + getExpectedTimestamp(), + nativeRead + ); + } ); + } ); + } ); } - private void saveZoneDateTimeEventWithStartDate(ZonedDateTime startdate) { - final ZonedDateTimeEvent event = new ZonedDateTimeEvent(); - event.id = 1L; - event.startDate = startdate; + @Test + public void testRetrievingEntityByZonedDateTime() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithZonedDateTime( 1, getOriginalZonedDateTime() ) ); + } ); + Consumer checkOneMatch = expected -> inSession( s -> { + Query query = s.createQuery( "from EntityWithZonedDateTime o where o.value = :date" ); + query.setParameter( "date", expected, ZonedDateTimeType.INSTANCE ); + List list = query.list(); + assertThat( list.size(), is( 1 ) ); + } ); + checkOneMatch.accept( getOriginalZonedDateTime() ); + checkOneMatch.accept( getExpectedZonedDateTime() ); + checkOneMatch.accept( getExpectedZonedDateTime().withZoneSameInstant( ZoneOffset.UTC ) ); + } ); + } - final Session s = openSession(); - s.getTransaction().begin(); + private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { + TimeZone timeZoneBefore = TimeZone.getDefault(); + TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); + /* + * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) + * cache data dependent on the default timezone in thread local variables, + * and we want this data to be reinitialized with the new default time zone. + */ try { - s.save( event ); - s.getTransaction().commit(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit( runnable ); + executor.shutdown(); + future.get(); } - catch (Exception e) { - if ( s.getTransaction() != null && s.getTransaction().getStatus() == TransactionStatus.ACTIVE ) { - s.getTransaction().rollback(); + catch (InterruptedException e) { + throw new IllegalStateException( "Interrupted while testing", e ); + } + catch (ExecutionException e) { + Throwable cause = e.getCause(); + if ( cause instanceof RuntimeException ) { + throw (RuntimeException) cause; + } + else if ( cause instanceof Error ) { + throw (Error) cause; + } + else { + throw new IllegalStateException( "Unexpected exception while testing", cause ); } - fail( e.getMessage() ); } finally { - s.close(); + TimeZone.setDefault( timeZoneBefore ); } } - @Entity(name = "ZonedDateTimeEvent") - @Table(name = "ZONE_DATE_TIME_EVENT") - public static class ZonedDateTimeEvent { - + @Entity(name = "EntityWithZonedDateTime") + @Table(name = "theentity") + private static final class EntityWithZonedDateTime { @Id - private Long id; + @Column(name = "theid") + private Integer id; + + @Basic + @Column(name = "thevalue") + private ZonedDateTime value; - @Column(name = "START_DATE") - private ZonedDateTime startDate; + protected EntityWithZonedDateTime() { + } + + private EntityWithZonedDateTime(int id, ZonedDateTime value) { + this.id = id; + this.value = value; + } } @Override From 6085310025c3135846bdd7f494d503352774cf27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Mar 2019 13:15:27 +0100 Subject: [PATCH 280/772] HHH-13266 Fix ZonedDateTime serialization by using the proper conversion methods between ZonedDateTime and Timestamp --- .../java/ZonedDateTimeJavaDescriptor.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java index 26cdd1e410b1..fafb2e25262a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java @@ -59,7 +59,13 @@ public X unwrap(ZonedDateTime zonedDateTime, Class type, WrapperOptions o } if ( Timestamp.class.isAssignableFrom( type ) ) { - return (X) Timestamp.from( zonedDateTime.toInstant() ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use Timestamp.from( zonedDateTime.toInstant() ), but this won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return (X) Timestamp.valueOf( zonedDateTime.withZoneSameInstant( ZoneId.systemDefault() ).toLocalDateTime() ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -93,7 +99,13 @@ public ZonedDateTime wrap(X value, WrapperOptions options) { if ( java.sql.Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return ZonedDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use ZonedDateTime.ofInstant( ts.toInstant(), ... ), but this won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime().atZone( ZoneId.systemDefault() ); } if ( java.util.Date.class.isInstance( value ) ) { From c6cea4ce3daec9e19d1565d2a79ba785ed3b3011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Mar 2019 13:23:34 +0100 Subject: [PATCH 281/772] HHH-13266 Test Instant serialization, in particular around 1900-01-01 --- .../org/hibernate/test/type/InstantTest.java | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java new file mode 100644 index 000000000000..a638d96c7981 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java @@ -0,0 +1,242 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.type; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.PostgreSQL81Dialect; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for storage of Instant properties. + */ +@RunWith(CustomParameterized.class) +public class InstantTest extends BaseCoreFunctionalTestCase { + + private static Dialect DIALECT; + private static Dialect determineDialect() { + try { + return Dialect.getDialect(); + } + catch (Exception e) { + return new Dialect() { + }; + } + } + + /* + * The default timezone affects conversions done using java.util, + * which is why we take it into account even when testing Instant. + */ + @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6}Z [JVM TZ: {7}]") + public static List data() { + DIALECT = determineDialect(); + return Arrays.asList( + // Not affected by HHH-13266 (JDK-8061577) + data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "UTC-8" ) ), + data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "Europe/Paris" ) ), + data( 2017, 11, 6, 19, 19, 1, 500, ZoneId.of( "Europe/Paris" ) ), + data( 1970, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 2, 0, 9, 21, 0, ZoneId.of( "Europe/Paris" ) ), + data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Amsterdam" ) ), + data( 1900, 1, 2, 0, 19, 32, 0, ZoneId.of( "Europe/Amsterdam" ) ), + // Affected by HHH-13266 (JDK-8061577) + data( 1892, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), + data( 1899, 12, 31, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Paris" ) ), + data( 1899, 12, 31, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Amsterdam" ) ), + data( 1600, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Amsterdam" ) ) + ); + } + + private static Object[] data(int year, int month, int day, + int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + if ( DIALECT instanceof PostgreSQL81Dialect ) { + // PostgreSQL apparently doesn't support nanosecond precision correctly + nanosecond = 0; + } + return new Object[] { year, month, day, hour, minute, second, nanosecond, defaultTimeZone }; + } + + private final int year; + private final int month; + private final int day; + private final int hour; + private final int minute; + private final int second; + private final int nanosecond; + private final ZoneId defaultTimeZone; + + public InstantTest(int year, int month, int day, + int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.minute = minute; + this.second = second; + this.nanosecond = nanosecond; + this.defaultTimeZone = defaultTimeZone; + } + + private Instant getExpectedInstant() { + return OffsetDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneOffset.UTC ).toInstant(); + } + + private Timestamp getExpectedTimestamp() { + LocalDateTime dateTimeInDefaultTimeZone = getExpectedInstant().atZone( ZoneId.systemDefault() ) + .toLocalDateTime(); + return new Timestamp( + dateTimeInDefaultTimeZone.getYear() - 1900, dateTimeInDefaultTimeZone.getMonthValue() - 1, + dateTimeInDefaultTimeZone.getDayOfMonth(), + dateTimeInDefaultTimeZone.getHour(), dateTimeInDefaultTimeZone.getMinute(), + dateTimeInDefaultTimeZone.getSecond(), + dateTimeInDefaultTimeZone.getNano() + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EntityWithInstant.class }; + } + + @Before + public void cleanup() { + inTransaction( session -> { + session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithInstant( 1, getExpectedInstant() ) ); + } ); + inTransaction( session -> { + Instant read = session.find( EntityWithInstant.class, 1 ).value; + assertEquals( + "Writing then reading a value should return the original value", + getExpectedInstant(), read + ); + } ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenNativeRead() { + withDefaultTimeZone( defaultTimeZone, () -> { + inTransaction( session -> { + session.persist( new EntityWithInstant( 1, getExpectedInstant() ) ); + } ); + inTransaction( session -> { + session.doWork( connection -> { + final PreparedStatement statement = connection.prepareStatement( + "SELECT thevalue FROM theentity WHERE theid = ?" + ); + statement.setInt( 1, 1 ); + statement.execute(); + final ResultSet resultSet = statement.getResultSet(); + resultSet.next(); + Timestamp nativeRead = resultSet.getTimestamp( 1 ); + assertEquals( + "Raw values written in database should match the original value (same day, hour, ...)", + getExpectedTimestamp(), + nativeRead + ); + } ); + } ); + } ); + } + + private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { + TimeZone timeZoneBefore = TimeZone.getDefault(); + TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); + /* + * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) + * cache data dependent on the default timezone in thread local variables, + * and we want this data to be reinitialized with the new default time zone. + */ + try { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit( runnable ); + executor.shutdown(); + future.get(); + } + catch (InterruptedException e) { + throw new IllegalStateException( "Interrupted while testing", e ); + } + catch (ExecutionException e) { + Throwable cause = e.getCause(); + if ( cause instanceof RuntimeException ) { + throw (RuntimeException) cause; + } + else if ( cause instanceof Error ) { + throw (Error) cause; + } + else { + throw new IllegalStateException( "Unexpected exception while testing", cause ); + } + } + finally { + TimeZone.setDefault( timeZoneBefore ); + } + } + + @Entity + @Table(name = "theentity") + private static final class EntityWithInstant { + @Id + @Column(name = "theid") + private Integer id; + + @Basic + @Column(name = "thevalue") + private Instant value; + + protected EntityWithInstant() { + } + + private EntityWithInstant(int id, Instant value) { + this.id = id; + this.value = value; + } + } +} From a17e9fc494287880edfe053751144c2ea24b5514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Mar 2019 13:32:16 +0100 Subject: [PATCH 282/772] HHH-13266 Fix Instant serialization by using the proper conversion methods between Instant and Timestamp --- .../descriptor/java/InstantJavaDescriptor.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java index bdd0659b4a39..db84b82ac3e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java @@ -61,7 +61,13 @@ public X unwrap(Instant instant, Class type, WrapperOptions options) { } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - return (X) Timestamp.from( instant ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use Timestamp.from(), but this won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return (X) Timestamp.valueOf( instant.atZone( ZoneId.systemDefault() ).toLocalDateTime() ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -95,7 +101,13 @@ public Instant wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return ts.toInstant(); + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use ts.toInstant(), but this won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime().atZone( ZoneId.systemDefault() ).toInstant(); } if ( Long.class.isInstance( value ) ) { From ece5f1a180579b4557278e9bfc2525d36f20baf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 11 Mar 2019 16:32:44 +0100 Subject: [PATCH 283/772] HHH-13266 Add an abstract class for all java.time tests for this ticket So that we can hopefully factorize the upcoming additions. --- .../test/type/AbstractJavaTimeTypeTest.java | 222 +++++++++++++ .../org/hibernate/test/type/InstantTest.java | 214 ++++--------- .../hibernate/test/type/LocalDateTest.java | 177 +++-------- .../test/type/LocalDateTimeTest.java | 206 +++--------- .../test/type/OffsetDateTimeTest.java | 266 +++++----------- .../test/type/ZonedDateTimeTest.java | 294 ++++++------------ 6 files changed, 541 insertions(+), 838 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java new file mode 100644 index 000000000000..1b1315cc4e30 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -0,0 +1,222 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.type; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.PostgreSQL81Dialect; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for storage of Instant properties. + * + * @param The time type being tested. + * @param The entity type used in tests. + */ +@RunWith(CustomParameterized.class) +abstract class AbstractJavaTimeTypeTest extends BaseCoreFunctionalTestCase { + + private static Dialect determineDialect() { + try { + return Dialect.getDialect(); + } + catch (Exception e) { + return new Dialect() { + }; + } + } + + protected static final String ENTITY_NAME = "theentity"; + protected static final String ID_COLUMN_NAME = "theid"; + protected static final String PROPERTY_COLUMN_NAME = "thevalue"; + + protected static final ZoneId ZONE_UTC_MINUS_8 = ZoneId.of( "UTC-8" ); + protected static final ZoneId ZONE_PARIS = ZoneId.of( "Europe/Paris" ); + protected static final ZoneId ZONE_GMT = ZoneId.of( "GMT" ); + protected static final ZoneId ZONE_OSLO = ZoneId.of( "Europe/Oslo" ); + protected static final ZoneId ZONE_AMSTERDAM = ZoneId.of( "Europe/Amsterdam" ); + + private final EnvironmentParameters env; + + public AbstractJavaTimeTypeTest(EnvironmentParameters env) { + this.env = env; + } + + @Override + protected final Class[] getAnnotatedClasses() { + return new Class[] { getEntityType() }; + } + + protected abstract Class getEntityType(); + + protected abstract E createEntity(int id); + + protected abstract T getExpectedPropertyValue(); + + protected abstract T getActualPropertyValue(E entity); + + protected abstract Object getExpectedJdbcValue(); + + protected abstract Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException; + + @Before + public void cleanup() { + inTransaction( session -> { + session.createNativeQuery( "DELETE FROM " + ENTITY_NAME ).executeUpdate(); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenRead() { + withDefaultTimeZone( () -> { + inTransaction( session -> { + session.persist( createEntity( 1 ) ); + } ); + inTransaction( session -> { + T read = getActualPropertyValue( session.find( getEntityType(), 1 ) ); + assertEquals( + "Writing then reading a value should return the original value", + getExpectedPropertyValue(), read + ); + } ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void writeThenNativeRead() { + withDefaultTimeZone( () -> { + inTransaction( session -> { + session.persist( createEntity( 1 ) ); + } ); + inTransaction( session -> { + session.doWork( connection -> { + final PreparedStatement statement = connection.prepareStatement( + "SELECT " + PROPERTY_COLUMN_NAME + " FROM " + ENTITY_NAME + " WHERE " + ID_COLUMN_NAME + " = ?" + ); + statement.setInt( 1, 1 ); + statement.execute(); + final ResultSet resultSet = statement.getResultSet(); + resultSet.next(); + Object nativeRead = getActualJdbcValue( resultSet, 1 ); + assertEquals( + "Values written by Hibernate ORM should match the original value (same day, hour, ...)", + getExpectedJdbcValue(), + nativeRead + ); + } ); + } ); + } ); + } + + protected final void withDefaultTimeZone(Runnable runnable) { + TimeZone timeZoneBefore = TimeZone.getDefault(); + TimeZone.setDefault( TimeZone.getTimeZone( env.defaultJvmTimeZone ) ); + /* + * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) + * cache data dependent on the default timezone in thread local variables, + * and we want this data to be reinitialized with the new default time zone. + */ + try { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit( runnable ); + executor.shutdown(); + future.get(); + } + catch (InterruptedException e) { + throw new IllegalStateException( "Interrupted while testing", e ); + } + catch (ExecutionException e) { + Throwable cause = e.getCause(); + if ( cause instanceof RuntimeException ) { + throw (RuntimeException) cause; + } + else if ( cause instanceof Error ) { + throw (Error) cause; + } + else { + throw new IllegalStateException( "Unexpected exception while testing", cause ); + } + } + finally { + TimeZone.setDefault( timeZoneBefore ); + } + } + + protected static abstract class AbstractParametersBuilder> { + + private final Dialect dialect; + + private final List result = new ArrayList<>(); + + protected AbstractParametersBuilder() { + dialect = determineDialect(); + } + + protected final boolean isNanosecondPrecisionSupported() { + // PostgreSQL apparently doesn't support nanosecond precision correctly + return !( dialect instanceof PostgreSQL81Dialect ); + } + + protected final S add(ZoneId defaultJvmTimeZone, Object ... subClassParameters) { + List parameters = new ArrayList<>(); + parameters.add( new EnvironmentParameters( defaultJvmTimeZone ) ); + Collections.addAll( parameters, subClassParameters ); + result.add( parameters.toArray() ); + return thisAsS(); + } + + private S thisAsS() { + return (S) this; + } + + public List build() { + return result; + } + + } + + protected final static class EnvironmentParameters { + + /* + * The default timezone affects conversions done using java.util, + * which is why we take it into account even with timezone-independent types such as Instant. + */ + private final ZoneId defaultJvmTimeZone; + + private EnvironmentParameters(ZoneId defaultJvmTimeZone) { + this.defaultJvmTimeZone = defaultJvmTimeZone; + } + + @Override + public String toString() { + return String.format( "[JVM TZ: %s]", defaultJvmTimeZone ); + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java index a638d96c7981..8bd4a9592698 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java @@ -6,91 +6,57 @@ */ package org.hibernate.test.type; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.Arrays; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQL81Dialect; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static org.junit.Assert.assertEquals; - /** * Tests for storage of Instant properties. */ -@RunWith(CustomParameterized.class) -public class InstantTest extends BaseCoreFunctionalTestCase { +public class InstantTest extends AbstractJavaTimeTypeTest { - private static Dialect DIALECT; - private static Dialect determineDialect() { - try { - return Dialect.getDialect(); - } - catch (Exception e) { - return new Dialect() { - }; + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, + int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, year, month, day, hour, minute, second, nanosecond ); } } - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing Instant. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6}Z [JVM TZ: {7}]") + @Parameterized.Parameters(name = "{1}-{2}-{3}T{4}:{5}:{6}.{7}Z {0}") public static List data() { - DIALECT = determineDialect(); - return Arrays.asList( + return new ParametersBuilder() // Not affected by HHH-13266 (JDK-8061577) - data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 500, ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 2, 0, 9, 21, 0, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Amsterdam" ) ), - data( 1900, 1, 2, 0, 19, 32, 0, ZoneId.of( "Europe/Amsterdam" ) ), + .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 500, ZONE_PARIS ) + .add( 1970, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_PARIS ) + .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) // Affected by HHH-13266 (JDK-8061577) - data( 1892, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), - data( 1899, 12, 31, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Paris" ) ), - data( 1899, 12, 31, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Amsterdam" ) ) - ); - } - - private static Object[] data(int year, int month, int day, - int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { - if ( DIALECT instanceof PostgreSQL81Dialect ) { - // PostgreSQL apparently doesn't support nanosecond precision correctly - nanosecond = 0; - } - return new Object[] { year, month, day, hour, minute, second, nanosecond, defaultTimeZone }; + .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_PARIS ) + .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .build(); } private final int year; @@ -100,10 +66,10 @@ private static Object[] data(int year, int month, int day, private final int minute; private final int second; private final int nanosecond; - private final ZoneId defaultTimeZone; - public InstantTest(int year, int month, int day, - int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + public InstantTest(EnvironmentParameters env, int year, int month, int day, + int hour, int minute, int second, int nanosecond) { + super( env ); this.year = year; this.month = month; this.day = day; @@ -111,15 +77,31 @@ public InstantTest(int year, int month, int day, this.minute = minute; this.second = second; this.nanosecond = nanosecond; - this.defaultTimeZone = defaultTimeZone; } - private Instant getExpectedInstant() { + @Override + protected Class getEntityType() { + return EntityWithInstant.class; + } + + @Override + protected EntityWithInstant createEntity(int id) { + return new EntityWithInstant( id, getExpectedPropertyValue() ); + } + + @Override + protected Instant getExpectedPropertyValue() { return OffsetDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneOffset.UTC ).toInstant(); } - private Timestamp getExpectedTimestamp() { - LocalDateTime dateTimeInDefaultTimeZone = getExpectedInstant().atZone( ZoneId.systemDefault() ) + @Override + protected Instant getActualPropertyValue(EntityWithInstant entity) { + return entity.value; + } + + @Override + protected Object getExpectedJdbcValue() { + LocalDateTime dateTimeInDefaultTimeZone = getExpectedPropertyValue().atZone( ZoneId.systemDefault() ) .toLocalDateTime(); return new Timestamp( dateTimeInDefaultTimeZone.getYear() - 1900, dateTimeInDefaultTimeZone.getMonthValue() - 1, @@ -131,104 +113,18 @@ private Timestamp getExpectedTimestamp() { } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithInstant.class }; - } - - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithInstant( 1, getExpectedInstant() ) ); - } ); - inTransaction( session -> { - Instant read = session.find( EntityWithInstant.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedInstant(), read - ); - } ); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithInstant( 1, getExpectedInstant() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Timestamp nativeRead = resultSet.getTimestamp( 1 ); - assertEquals( - "Raw values written in database should match the original value (same day, hour, ...)", - getExpectedTimestamp(), - nativeRead - ); - } ); - } ); - } ); - } - - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); } - @Entity - @Table(name = "theentity") - private static final class EntityWithInstant { + @Entity(name = ENTITY_NAME) + static final class EntityWithInstant { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private Instant value; protected EntityWithInstant() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index 4d0393d6cb5f..bb3e023eec4c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -7,186 +7,99 @@ package org.hibernate.test.type; import java.sql.Date; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.time.LocalDate; import java.time.ZoneId; -import java.util.Arrays; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static org.junit.Assert.assertEquals; - /** * Tests for storage of LocalDate properties. */ -@RunWith(CustomParameterized.class) @TestForIssue(jiraKey = "HHH-10371") -public class LocalDateTest extends BaseCoreFunctionalTestCase { +public class LocalDateTest extends AbstractJavaTimeTypeTest { + + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, ZoneId defaultTimeZone) { + return add( defaultTimeZone, year, month, day ); + } + } - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing LocalDateTime. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2} [JVM TZ: {3}]") + @Parameterized.Parameters(name = "{1}-{2}-{3} {0}") public static List data() { - return Arrays.asList( + return new ParametersBuilder() // Not affected by HHH-13266 (JDK-8061577) - data( 2017, 11, 6, ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 2, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 2, ZoneId.of( "Europe/Amsterdam" ) ), + .add( 2017, 11, 6, ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, ZONE_PARIS ) + .add( 1970, 1, 1, ZONE_GMT ) + .add( 1900, 1, 1, ZONE_GMT ) + .add( 1900, 1, 1, ZONE_OSLO ) + .add( 1900, 1, 2, ZONE_PARIS ) + .add( 1900, 1, 2, ZONE_AMSTERDAM ) // Could have been affected by HHH-13266 (JDK-8061577), but was not - data( 1892, 1, 1, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, ZoneId.of( "Europe/Amsterdam" ) ) - ); - } - - private static Object[] data(int year, int month, int day, ZoneId defaultTimeZone) { - return new Object[] { year, month, day, defaultTimeZone }; + .add( 1892, 1, 1, ZONE_OSLO ) + .add( 1900, 1, 1, ZONE_PARIS ) + .add( 1900, 1, 1, ZONE_AMSTERDAM ) + .add( 1600, 1, 1, ZONE_AMSTERDAM ) + .build(); } private final int year; private final int month; private final int day; - private final ZoneId defaultTimeZone; - public LocalDateTest(int year, int month, int day, ZoneId defaultTimeZone) { + public LocalDateTest(EnvironmentParameters env, int year, int month, int day) { + super( env ); this.year = year; this.month = month; this.day = day; - this.defaultTimeZone = defaultTimeZone; } - private LocalDate getExpectedLocalDate() { - return LocalDate.of( year, month, day ); - } - - private Date getExpectedSqlDate() { - return new Date( year - 1900, month - 1, day ); + @Override + protected Class getEntityType() { + return EntityWithLocalDate.class; } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithLocalDate.class }; + protected EntityWithLocalDate createEntity(int id) { + return new EntityWithLocalDate( id, getExpectedPropertyValue() ); } - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); + @Override + protected LocalDate getExpectedPropertyValue() { + return LocalDate.of( year, month, day ); } - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithLocalDate( 1, getExpectedLocalDate() ) ); - } ); - inTransaction( session -> { - LocalDate read = session.find( EntityWithLocalDate.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedLocalDate(), read - ); - } ); - } ); + @Override + protected LocalDate getActualPropertyValue(EntityWithLocalDate entity) { + return entity.value; } - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithLocalDate( 1, getExpectedLocalDate() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Date nativeRead = resultSet.getDate( 1 ); - assertEquals( - "Raw values written in database should match the original value (same day, hour, ...)", - getExpectedSqlDate(), - nativeRead - ); - } ); - } ); - } ); + @Override + protected Object getExpectedJdbcValue() { + return new Date( year - 1900, month - 1, day ); } - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } + @Override + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getDate( columnIndex ); } - @Entity - @Table(name = "theentity") - private static final class EntityWithLocalDate { + @Entity(name = ENTITY_NAME) + static final class EntityWithLocalDate { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private LocalDate value; protected EntityWithLocalDate() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java index a0543bf19240..a2827ae2d065 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java @@ -6,86 +6,52 @@ */ package org.hibernate.test.type; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.Arrays; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQL81Dialect; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static org.junit.Assert.assertEquals; - /** * Tests for storage of LocalDateTime properties. */ -@RunWith(CustomParameterized.class) -public class LocalDateTimeTest extends BaseCoreFunctionalTestCase { +public class LocalDateTimeTest extends AbstractJavaTimeTypeTest { - private static Dialect DIALECT; - private static Dialect determineDialect() { - try { - return Dialect.getDialect(); - } - catch (Exception e) { - return new Dialect() { - }; + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, + int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, year, month, day, hour, minute, second, nanosecond ); } } - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing LocalDateTime. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6} [JVM TZ: {7}]") + @Parameterized.Parameters(name = "{1}-{2}-{3}T{4}:{5}:{6}.{7} {0}") public static List data() { - DIALECT = determineDialect(); - return Arrays.asList( + return new ParametersBuilder() // Not affected by HHH-13266 (JDK-8061577) - data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 500, ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 2, 0, 9, 21, 0, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 2, 0, 19, 32, 0, ZoneId.of( "Europe/Amsterdam" ) ), + .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 500, ZONE_PARIS ) + .add( 1970, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) + .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) // Affected by HHH-13266 (JDK-8061577) - data( 1892, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 20, 0, ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, ZoneId.of( "Europe/Amsterdam" ) ) - ); - } - - private static Object[] data(int year, int month, int day, - int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { - if ( DIALECT instanceof PostgreSQL81Dialect ) { - // PostgreSQL apparently doesn't support nanosecond precision correctly - nanosecond = 0; - } - return new Object[] { year, month, day, hour, minute, second, nanosecond, defaultTimeZone }; + .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 20, 0, ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .build(); } private final int year; @@ -95,10 +61,10 @@ private static Object[] data(int year, int month, int day, private final int minute; private final int second; private final int nanosecond; - private final ZoneId defaultTimeZone; - public LocalDateTimeTest(int year, int month, int day, - int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + public LocalDateTimeTest(EnvironmentParameters env, int year, int month, int day, + int hour, int minute, int second, int nanosecond) { + super( env ); this.year = year; this.month = month; this.day = day; @@ -106,119 +72,49 @@ public LocalDateTimeTest(int year, int month, int day, this.minute = minute; this.second = second; this.nanosecond = nanosecond; - this.defaultTimeZone = defaultTimeZone; } - private LocalDateTime getExpectedLocalDateTime() { - return LocalDateTime.of( year, month, day, hour, minute, second, nanosecond ); - } - - private Timestamp getExpectedTimestamp() { - return new Timestamp( - year - 1900, month - 1, day, - hour, minute, second, nanosecond - ); + @Override + protected Class getEntityType() { + return EntityWithLocalDateTime.class; } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithLocalDateTime.class }; + protected EntityWithLocalDateTime createEntity(int id) { + return new EntityWithLocalDateTime( id, getExpectedPropertyValue() ); } - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); + @Override + protected LocalDateTime getExpectedPropertyValue() { + return LocalDateTime.of( year, month, day, hour, minute, second, nanosecond ); } - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithLocalDateTime( 1, getExpectedLocalDateTime() ) ); - } ); - inTransaction( session -> { - LocalDateTime read = session.find( EntityWithLocalDateTime.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedLocalDateTime(), read - ); - } ); - } ); + @Override + protected LocalDateTime getActualPropertyValue(EntityWithLocalDateTime entity) { + return entity.value; } - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithLocalDateTime( 1, getExpectedLocalDateTime() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Timestamp nativeRead = resultSet.getTimestamp( 1 ); - assertEquals( - "Raw values written in database should match the original value (same day, hour, ...)", - getExpectedTimestamp(), - nativeRead - ); - } ); - } ); - } ); + @Override + protected Object getExpectedJdbcValue() { + return new Timestamp( + year - 1900, month - 1, day, + hour, minute, second, nanosecond + ); } - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } + @Override + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); } - @Entity - @Table(name = "theentity") - private static final class EntityWithLocalDateTime { + @Entity(name = ENTITY_NAME) + static final class EntityWithLocalDateTime { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private LocalDateTime value; protected EntityWithLocalDateTime() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java index 76ebb98a21fc..59fecaa6dbf3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java @@ -6,116 +6,84 @@ */ package org.hibernate.test.type; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.Arrays; -import java.util.GregorianCalendar; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.function.Consumer; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; import org.hibernate.Query; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.type.OffsetDateTimeType; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; /** * @author Andrea Boriero */ -@RunWith(CustomParameterized.class) @TestForIssue(jiraKey = "HHH-10372") -public class OffsetDateTimeTest extends BaseNonConfigCoreFunctionalTestCase { +public class OffsetDateTimeTest extends AbstractJavaTimeTypeTest { - private static Dialect DIALECT; - private static Dialect determineDialect() { - try { - return Dialect.getDialect(); - } - catch (Exception e) { - return new Dialect() { - }; + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, + int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, year, month, day, hour, minute, second, nanosecond, offset ); } } - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing OffsetDateTime. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6}[{7}] [JVM TZ: {8}]") + @Parameterized.Parameters(name = "{1}-{2}-{3}T{4}:{5}:{6}.{7}[{8}] {0}") public static List data() { - DIALECT = determineDialect(); - return Arrays.asList( + return new ParametersBuilder() // Not affected by HHH-13266 - data( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 500, "+01:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "+01:00", ZoneId.of( "GMT" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "GMT" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "-01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "+01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "-01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 21, 0, "+00:09:21", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), + .add( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+10:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+07:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+01:30", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 500, "+01:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+01:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "+00:30", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZONE_PARIS ) + .add( 1970, 1, 1, 0, 0, 0, 0, "+01:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "+01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 21, 0, "+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_AMSTERDAM ) // Affected by HHH-13266 - data( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ) - ); - } - - private static Object[] data(int year, int month, int day, - int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { - if ( DIALECT instanceof PostgreSQL81Dialect ) { - // PostgreSQL apparently doesn't support nanosecond precision correctly - nanosecond = 0; - } - return new Object[] { year, month, day, hour, minute, second, nanosecond, offset, defaultTimeZone }; + .add( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) + .build(); } private final int year; @@ -126,10 +94,10 @@ private static Object[] data(int year, int month, int day, private final int second; private final int nanosecond; private final String offset; - private final ZoneId defaultTimeZone; - public OffsetDateTimeTest(int year, int month, int day, - int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { + public OffsetDateTimeTest(EnvironmentParameters env, int year, int month, int day, + int hour, int minute, int second, int nanosecond, String offset) { + super( env ); this.year = year; this.month = month; this.day = day; @@ -138,18 +106,34 @@ public OffsetDateTimeTest(int year, int month, int day, this.second = second; this.nanosecond = nanosecond; this.offset = offset; - this.defaultTimeZone = defaultTimeZone; + } + + @Override + protected Class getEntityType() { + return EntityWithOffsetDateTime.class; + } + + @Override + protected EntityWithOffsetDateTime createEntity(int id) { + return new EntityWithOffsetDateTime( id, getOriginalOffsetDateTime() ); } private OffsetDateTime getOriginalOffsetDateTime() { return OffsetDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneOffset.of( offset ) ); } - private OffsetDateTime getExpectedOffsetDateTime() { + @Override + protected OffsetDateTime getExpectedPropertyValue() { return getOriginalOffsetDateTime().atZoneSameInstant( ZoneId.systemDefault() ).toOffsetDateTime(); } - private Timestamp getExpectedTimestamp() { + @Override + protected OffsetDateTime getActualPropertyValue(EntityWithOffsetDateTime entity) { + return entity.value; + } + + @Override + protected Object getExpectedJdbcValue() { LocalDateTime dateTimeInDefaultTimeZone = getOriginalOffsetDateTime().atZoneSameInstant( ZoneId.systemDefault() ) .toLocalDateTime(); return new Timestamp( @@ -162,129 +146,36 @@ private Timestamp getExpectedTimestamp() { } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithOffsetDateTime.class }; - } - - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithOffsetDateTime( 1, getOriginalOffsetDateTime() ) ); - } ); - inTransaction( session -> { - OffsetDateTime read = session.find( org.hibernate.test.type.OffsetDateTimeTest.EntityWithOffsetDateTime.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedOffsetDateTime(), read - ); - assertTrue( - getExpectedOffsetDateTime().isEqual( read ) - ); - assertEquals( - 0, - OffsetDateTimeType.INSTANCE.getComparator().compare( getExpectedOffsetDateTime(), read ) - ); - } ); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithOffsetDateTime( 1, getOriginalOffsetDateTime() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Timestamp nativeRead = resultSet.getTimestamp( 1 ); - assertEquals( - "Raw values written in database should match the original value (same instant)", - getExpectedTimestamp(), - nativeRead - ); - } ); - } ); - } ); + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); } @Test public void testRetrievingEntityByOffsetDateTime() { - withDefaultTimeZone( defaultTimeZone, () -> { + withDefaultTimeZone( () -> { inTransaction( session -> { session.persist( new EntityWithOffsetDateTime( 1, getOriginalOffsetDateTime() ) ); } ); Consumer checkOneMatch = expected -> inSession( s -> { - Query query = s.createQuery( "from EntityWithOffsetDateTime o where o.value = :date" ); + Query query = s.createQuery( "from " + ENTITY_NAME + " o where o.value = :date" ); query.setParameter( "date", expected, OffsetDateTimeType.INSTANCE ); List list = query.list(); assertThat( list.size(), is( 1 ) ); } ); checkOneMatch.accept( getOriginalOffsetDateTime() ); - checkOneMatch.accept( getExpectedOffsetDateTime() ); - checkOneMatch.accept( getExpectedOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ); + checkOneMatch.accept( getExpectedPropertyValue() ); + checkOneMatch.accept( getExpectedPropertyValue().withOffsetSameInstant( ZoneOffset.UTC ) ); } ); } - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } - } - - @Entity(name = "EntityWithOffsetDateTime") - @Table(name = "theentity") - private static final class EntityWithOffsetDateTime { + @Entity(name = ENTITY_NAME) + static final class EntityWithOffsetDateTime { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private OffsetDateTime value; protected EntityWithOffsetDateTime() { @@ -295,9 +186,4 @@ private EntityWithOffsetDateTime(int id, OffsetDateTime value) { this.value = value; } } - - @Override - protected boolean isCleanupTestDataRequired() { - return true; - } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java index 55680baaeed1..52d603c02aa9 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java @@ -6,127 +6,96 @@ */ package org.hibernate.test.type; -import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Timestamp; import java.time.LocalDateTime; -import java.time.ZonedDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.Arrays; +import java.time.ZonedDateTime; import java.util.List; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.function.Consumer; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; import org.hibernate.Query; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.type.ZonedDateTimeType; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.hibernate.testing.junit4.CustomParameterized; -import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; /** * @author Andrea Boriero */ -@RunWith(CustomParameterized.class) @TestForIssue(jiraKey = "HHH-10372") -public class ZonedDateTimeTest extends BaseNonConfigCoreFunctionalTestCase { +public class ZonedDateTimeTest extends AbstractJavaTimeTypeTest { - private static Dialect DIALECT; - private static Dialect determineDialect() { - try { - return Dialect.getDialect(); - } - catch (Exception e) { - return new Dialect() { - }; + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int year, int month, int day, + int hour, int minute, int second, int nanosecond, String zone, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, year, month, day, hour, minute, second, nanosecond, zone ); } } - /* - * The default timezone affects conversions done using java.util, - * which is why we take it into account even when testing ZonedDateTime. - */ - @Parameterized.Parameters(name = "{0}-{1}-{2}T{3}:{4}:{5}.{6}[{7}] [JVM TZ: {8}]") + @Parameterized.Parameters(name = "{1}-{2}-{3}T{4}:{5}:{6}.{7}[{8}] {0}") public static List data() { - DIALECT = determineDialect(); - return Arrays.asList( + return new ParametersBuilder() // Not affected by HHH-13266 - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZoneId.of( "UTC-8" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 500, "GMT+01:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZoneId.of( "Europe/Paris" ) ), - data( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZoneId.of( "Europe/Paris" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZoneId.of( "GMT" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "GMT" ) ), - data( 1970, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZoneId.of( "GMT" ) ), - data( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 21, 0, "GMT+00:09:21", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 9, 21, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Amsterdam" ) ), + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZONE_UTC_MINUS_8 ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+10:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+07:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:30", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+01:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 500, "GMT+01:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "Europe/Paris", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "Europe/London", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT+00:30", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZONE_PARIS ) + .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZONE_PARIS ) + .add( 1970, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 21, 0, "GMT+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 9, 21, 0, "Europe/Paris", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Paris", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) + .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) // Affected by HHH-13266 - data( 1892, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZoneId.of( "Europe/Oslo" ) ), - data( 1892, 1, 1, 0, 0, 0, 0, "Europe/Oslo", ZoneId.of( "Europe/Oslo" ) ), - data( 1900, 1, 1, 0, 9, 20, 0, "GMT+00:09:21", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 9, 20, 0, "Europe/Paris", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZoneId.of( "Europe/Paris" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1900, 1, 1, 0, 19, 31, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, "GMT+00:19:32", ZoneId.of( "Europe/Amsterdam" ) ), - data( 1600, 1, 1, 0, 0, 0, 0, "Europe/Amsterdam", ZoneId.of( "Europe/Amsterdam" ) ) - ); - } - - private static Object[] data(int year, int month, int day, - int hour, int minute, int second, int nanosecond, String zone, ZoneId defaultTimeZone) { - if ( DIALECT instanceof PostgreSQL81Dialect ) { - // PostgreSQL apparently doesn't support nanosecond precision correctly - nanosecond = 0; - } - return new Object[] { year, month, day, hour, minute, second, nanosecond, zone, defaultTimeZone }; + .add( 1892, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) + .add( 1892, 1, 1, 0, 0, 0, 0, "Europe/Oslo", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 20, 0, "GMT+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 9, 20, 0, "Europe/Paris", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) + .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) + .build(); } private final int year; @@ -137,10 +106,10 @@ private static Object[] data(int year, int month, int day, private final int second; private final int nanosecond; private final String zone; - private final ZoneId defaultTimeZone; - public ZonedDateTimeTest(int year, int month, int day, - int hour, int minute, int second, int nanosecond, String zone, ZoneId defaultTimeZone) { + public ZonedDateTimeTest(EnvironmentParameters env, int year, int month, int day, + int hour, int minute, int second, int nanosecond, String zone) { + super( env ); this.year = year; this.month = month; this.day = day; @@ -149,18 +118,37 @@ public ZonedDateTimeTest(int year, int month, int day, this.second = second; this.nanosecond = nanosecond; this.zone = zone; - this.defaultTimeZone = defaultTimeZone; + } + + @Override + protected Class getEntityType() { + return EntityWithZonedDateTime.class; + } + + @Override + protected EntityWithZonedDateTime createEntity(int id) { + return new EntityWithZonedDateTime( + id, + getOriginalZonedDateTime() + ); } private ZonedDateTime getOriginalZonedDateTime() { return ZonedDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneId.of( zone ) ); } - private ZonedDateTime getExpectedZonedDateTime() { + @Override + protected ZonedDateTime getExpectedPropertyValue() { return getOriginalZonedDateTime().withZoneSameInstant( ZoneId.systemDefault() ); } - private Timestamp getExpectedTimestamp() { + @Override + protected ZonedDateTime getActualPropertyValue(EntityWithZonedDateTime entity) { + return entity.value; + } + + @Override + protected Object getExpectedJdbcValue() { LocalDateTime dateTimeInDefaultTimeZone = getOriginalZonedDateTime().withZoneSameInstant( ZoneId.systemDefault() ) .toLocalDateTime(); return new Timestamp( @@ -173,129 +161,36 @@ private Timestamp getExpectedTimestamp() { } @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { EntityWithZonedDateTime.class }; - } - - @Before - public void cleanup() { - inTransaction( session -> { - session.createNativeQuery( "DELETE FROM theentity" ).executeUpdate(); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithZonedDateTime( 1, getOriginalZonedDateTime() ) ); - } ); - inTransaction( session -> { - ZonedDateTime read = session.find( ZonedDateTimeTest.EntityWithZonedDateTime.class, 1 ).value; - assertEquals( - "Writing then reading a value should return the original value", - getExpectedZonedDateTime(), read - ); - assertTrue( - getExpectedZonedDateTime().isEqual( read ) - ); - assertEquals( - 0, - ZonedDateTimeType.INSTANCE.getComparator().compare( getExpectedZonedDateTime(), read ) - ); - } ); - } ); - } - - @Test - @TestForIssue(jiraKey = "HHH-13266") - public void writeThenNativeRead() { - withDefaultTimeZone( defaultTimeZone, () -> { - inTransaction( session -> { - session.persist( new EntityWithZonedDateTime( 1, getOriginalZonedDateTime() ) ); - } ); - inTransaction( session -> { - session.doWork( connection -> { - final PreparedStatement statement = connection.prepareStatement( - "SELECT thevalue FROM theentity WHERE theid = ?" - ); - statement.setInt( 1, 1 ); - statement.execute(); - final ResultSet resultSet = statement.getResultSet(); - resultSet.next(); - Timestamp nativeRead = resultSet.getTimestamp( 1 ); - assertEquals( - "Raw values written in database should match the original value (same instant)", - getExpectedTimestamp(), - nativeRead - ); - } ); - } ); - } ); + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); } @Test public void testRetrievingEntityByZonedDateTime() { - withDefaultTimeZone( defaultTimeZone, () -> { + withDefaultTimeZone( () -> { inTransaction( session -> { session.persist( new EntityWithZonedDateTime( 1, getOriginalZonedDateTime() ) ); } ); Consumer checkOneMatch = expected -> inSession( s -> { - Query query = s.createQuery( "from EntityWithZonedDateTime o where o.value = :date" ); + Query query = s.createQuery( "from " + ENTITY_NAME + " o where o.value = :date" ); query.setParameter( "date", expected, ZonedDateTimeType.INSTANCE ); List list = query.list(); assertThat( list.size(), is( 1 ) ); } ); checkOneMatch.accept( getOriginalZonedDateTime() ); - checkOneMatch.accept( getExpectedZonedDateTime() ); - checkOneMatch.accept( getExpectedZonedDateTime().withZoneSameInstant( ZoneOffset.UTC ) ); + checkOneMatch.accept( getExpectedPropertyValue() ); + checkOneMatch.accept( getExpectedPropertyValue().withZoneSameInstant( ZoneOffset.UTC ) ); } ); } - private static void withDefaultTimeZone(ZoneId zoneId, Runnable runnable) { - TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( zoneId ) ); - /* - * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) - * cache data dependent on the default timezone in thread local variables, - * and we want this data to be reinitialized with the new default time zone. - */ - try { - ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = executor.submit( runnable ); - executor.shutdown(); - future.get(); - } - catch (InterruptedException e) { - throw new IllegalStateException( "Interrupted while testing", e ); - } - catch (ExecutionException e) { - Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) { - throw (RuntimeException) cause; - } - else if ( cause instanceof Error ) { - throw (Error) cause; - } - else { - throw new IllegalStateException( "Unexpected exception while testing", cause ); - } - } - finally { - TimeZone.setDefault( timeZoneBefore ); - } - } - - @Entity(name = "EntityWithZonedDateTime") - @Table(name = "theentity") - private static final class EntityWithZonedDateTime { + @Entity(name = ENTITY_NAME) + static final class EntityWithZonedDateTime { @Id - @Column(name = "theid") + @Column(name = ID_COLUMN_NAME) private Integer id; @Basic - @Column(name = "thevalue") + @Column(name = PROPERTY_COLUMN_NAME) private ZonedDateTime value; protected EntityWithZonedDateTime() { @@ -306,9 +201,4 @@ private EntityWithZonedDateTime(int id, ZonedDateTime value) { this.value = value; } } - - @Override - protected boolean isCleanupTestDataRequired() { - return true; - } } From a065bffe905fcddff83df1b045e85171b99ba9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 12 Mar 2019 12:34:30 +0100 Subject: [PATCH 284/772] HHH-13266 Test reading of values written without Hibernate ORM in AbstractJavaTimeTypeTest --- .../test/type/AbstractJavaTimeTypeTest.java | 41 +++++++++++++++---- .../org/hibernate/test/type/InstantTest.java | 16 +++++--- .../hibernate/test/type/LocalDateTest.java | 14 +++++-- .../test/type/LocalDateTimeTest.java | 14 +++++-- .../test/type/OffsetDateTimeTest.java | 16 +++++--- .../test/type/ZonedDateTimeTest.java | 16 +++++--- 6 files changed, 87 insertions(+), 30 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java index 1b1315cc4e30..32b980d145da 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -73,13 +73,15 @@ protected final Class[] getAnnotatedClasses() { protected abstract Class getEntityType(); - protected abstract E createEntity(int id); + protected abstract E createEntityForHibernateWrite(int id); - protected abstract T getExpectedPropertyValue(); + protected abstract T getExpectedPropertyValueAfterHibernateRead(); protected abstract T getActualPropertyValue(E entity); - protected abstract Object getExpectedJdbcValue(); + protected abstract void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) throws SQLException; + + protected abstract Object getExpectedJdbcValueAfterHibernateWrite(); protected abstract Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException; @@ -95,13 +97,13 @@ public void cleanup() { public void writeThenRead() { withDefaultTimeZone( () -> { inTransaction( session -> { - session.persist( createEntity( 1 ) ); + session.persist( createEntityForHibernateWrite( 1 ) ); } ); inTransaction( session -> { T read = getActualPropertyValue( session.find( getEntityType(), 1 ) ); assertEquals( "Writing then reading a value should return the original value", - getExpectedPropertyValue(), read + getExpectedPropertyValueAfterHibernateRead(), read ); } ); } ); @@ -112,7 +114,7 @@ public void writeThenRead() { public void writeThenNativeRead() { withDefaultTimeZone( () -> { inTransaction( session -> { - session.persist( createEntity( 1 ) ); + session.persist( createEntityForHibernateWrite( 1 ) ); } ); inTransaction( session -> { session.doWork( connection -> { @@ -126,7 +128,7 @@ public void writeThenNativeRead() { Object nativeRead = getActualJdbcValue( resultSet, 1 ); assertEquals( "Values written by Hibernate ORM should match the original value (same day, hour, ...)", - getExpectedJdbcValue(), + getExpectedJdbcValueAfterHibernateWrite(), nativeRead ); } ); @@ -134,6 +136,31 @@ public void writeThenNativeRead() { } ); } + @Test + @TestForIssue(jiraKey = "HHH-13266") + public void nativeWriteThenRead() { + withDefaultTimeZone( () -> { + inTransaction( session -> { + session.doWork( connection -> { + final PreparedStatement statement = connection.prepareStatement( + "INSERT INTO " + ENTITY_NAME + " (" + ID_COLUMN_NAME + ", " + PROPERTY_COLUMN_NAME + ") " + + " VALUES ( ? , ? )" + ); + statement.setInt( 1, 1 ); + setJdbcValueForNonHibernateWrite( statement, 2 ); + statement.execute(); + } ); + } ); + inTransaction( session -> { + T read = getActualPropertyValue( session.find( getEntityType(), 1 ) ); + assertEquals( + "Values written without Hibernate ORM should be read correctly by Hibernate ORM", + getExpectedPropertyValueAfterHibernateRead(), read + ); + } ); + } ); + } + protected final void withDefaultTimeZone(Runnable runnable) { TimeZone timeZoneBefore = TimeZone.getDefault(); TimeZone.setDefault( TimeZone.getTimeZone( env.defaultJvmTimeZone ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java index 8bd4a9592698..1bb7bb5f2dad 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.type; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; @@ -85,12 +86,12 @@ protected Class getEntityType() { } @Override - protected EntityWithInstant createEntity(int id) { - return new EntityWithInstant( id, getExpectedPropertyValue() ); + protected EntityWithInstant createEntityForHibernateWrite(int id) { + return new EntityWithInstant( id, getExpectedPropertyValueAfterHibernateRead() ); } @Override - protected Instant getExpectedPropertyValue() { + protected Instant getExpectedPropertyValueAfterHibernateRead() { return OffsetDateTime.of( year, month, day, hour, minute, second, nanosecond, ZoneOffset.UTC ).toInstant(); } @@ -100,8 +101,13 @@ protected Instant getActualPropertyValue(EntityWithInstant entity) { } @Override - protected Object getExpectedJdbcValue() { - LocalDateTime dateTimeInDefaultTimeZone = getExpectedPropertyValue().atZone( ZoneId.systemDefault() ) + protected void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) throws SQLException { + statement.setTimestamp( parameterIndex, getExpectedJdbcValueAfterHibernateWrite() ); + } + + @Override + protected Timestamp getExpectedJdbcValueAfterHibernateWrite() { + LocalDateTime dateTimeInDefaultTimeZone = getExpectedPropertyValueAfterHibernateRead().atZone( ZoneId.systemDefault() ) .toLocalDateTime(); return new Timestamp( dateTimeInDefaultTimeZone.getYear() - 1900, dateTimeInDefaultTimeZone.getMonthValue() - 1, diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index bb3e023eec4c..aa2b79a466f9 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -7,6 +7,7 @@ package org.hibernate.test.type; import java.sql.Date; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.time.LocalDate; @@ -68,12 +69,12 @@ protected Class getEntityType() { } @Override - protected EntityWithLocalDate createEntity(int id) { - return new EntityWithLocalDate( id, getExpectedPropertyValue() ); + protected EntityWithLocalDate createEntityForHibernateWrite(int id) { + return new EntityWithLocalDate( id, getExpectedPropertyValueAfterHibernateRead() ); } @Override - protected LocalDate getExpectedPropertyValue() { + protected LocalDate getExpectedPropertyValueAfterHibernateRead() { return LocalDate.of( year, month, day ); } @@ -83,7 +84,12 @@ protected LocalDate getActualPropertyValue(EntityWithLocalDate entity) { } @Override - protected Object getExpectedJdbcValue() { + protected void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) throws SQLException { + statement.setDate( parameterIndex, getExpectedJdbcValueAfterHibernateWrite() ); + } + + @Override + protected Date getExpectedJdbcValueAfterHibernateWrite() { return new Date( year - 1900, month - 1, day ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java index a2827ae2d065..877ab318c978 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.type; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; @@ -80,12 +81,12 @@ protected Class getEntityType() { } @Override - protected EntityWithLocalDateTime createEntity(int id) { - return new EntityWithLocalDateTime( id, getExpectedPropertyValue() ); + protected EntityWithLocalDateTime createEntityForHibernateWrite(int id) { + return new EntityWithLocalDateTime( id, getExpectedPropertyValueAfterHibernateRead() ); } @Override - protected LocalDateTime getExpectedPropertyValue() { + protected LocalDateTime getExpectedPropertyValueAfterHibernateRead() { return LocalDateTime.of( year, month, day, hour, minute, second, nanosecond ); } @@ -95,7 +96,12 @@ protected LocalDateTime getActualPropertyValue(EntityWithLocalDateTime entity) { } @Override - protected Object getExpectedJdbcValue() { + protected void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) throws SQLException { + statement.setTimestamp( parameterIndex, getExpectedJdbcValueAfterHibernateWrite() ); + } + + @Override + protected Timestamp getExpectedJdbcValueAfterHibernateWrite() { return new Timestamp( year - 1900, month - 1, day, hour, minute, second, nanosecond diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java index 59fecaa6dbf3..c61fe1d9b5a1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.type; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; @@ -114,7 +115,7 @@ protected Class getEntityType() { } @Override - protected EntityWithOffsetDateTime createEntity(int id) { + protected EntityWithOffsetDateTime createEntityForHibernateWrite(int id) { return new EntityWithOffsetDateTime( id, getOriginalOffsetDateTime() ); } @@ -123,7 +124,7 @@ private OffsetDateTime getOriginalOffsetDateTime() { } @Override - protected OffsetDateTime getExpectedPropertyValue() { + protected OffsetDateTime getExpectedPropertyValueAfterHibernateRead() { return getOriginalOffsetDateTime().atZoneSameInstant( ZoneId.systemDefault() ).toOffsetDateTime(); } @@ -133,7 +134,12 @@ protected OffsetDateTime getActualPropertyValue(EntityWithOffsetDateTime entity) } @Override - protected Object getExpectedJdbcValue() { + protected void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) throws SQLException { + statement.setTimestamp( parameterIndex, getExpectedJdbcValueAfterHibernateWrite() ); + } + + @Override + protected Timestamp getExpectedJdbcValueAfterHibernateWrite() { LocalDateTime dateTimeInDefaultTimeZone = getOriginalOffsetDateTime().atZoneSameInstant( ZoneId.systemDefault() ) .toLocalDateTime(); return new Timestamp( @@ -163,8 +169,8 @@ public void testRetrievingEntityByOffsetDateTime() { assertThat( list.size(), is( 1 ) ); } ); checkOneMatch.accept( getOriginalOffsetDateTime() ); - checkOneMatch.accept( getExpectedPropertyValue() ); - checkOneMatch.accept( getExpectedPropertyValue().withOffsetSameInstant( ZoneOffset.UTC ) ); + checkOneMatch.accept( getExpectedPropertyValueAfterHibernateRead() ); + checkOneMatch.accept( getExpectedPropertyValueAfterHibernateRead().withOffsetSameInstant( ZoneOffset.UTC ) ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java index 52d603c02aa9..6621b9a85402 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.type; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; @@ -126,7 +127,7 @@ protected Class getEntityType() { } @Override - protected EntityWithZonedDateTime createEntity(int id) { + protected EntityWithZonedDateTime createEntityForHibernateWrite(int id) { return new EntityWithZonedDateTime( id, getOriginalZonedDateTime() @@ -138,7 +139,7 @@ private ZonedDateTime getOriginalZonedDateTime() { } @Override - protected ZonedDateTime getExpectedPropertyValue() { + protected ZonedDateTime getExpectedPropertyValueAfterHibernateRead() { return getOriginalZonedDateTime().withZoneSameInstant( ZoneId.systemDefault() ); } @@ -148,7 +149,12 @@ protected ZonedDateTime getActualPropertyValue(EntityWithZonedDateTime entity) { } @Override - protected Object getExpectedJdbcValue() { + protected void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) throws SQLException { + statement.setTimestamp( parameterIndex, getExpectedJdbcValueAfterHibernateWrite() ); + } + + @Override + protected Timestamp getExpectedJdbcValueAfterHibernateWrite() { LocalDateTime dateTimeInDefaultTimeZone = getOriginalZonedDateTime().withZoneSameInstant( ZoneId.systemDefault() ) .toLocalDateTime(); return new Timestamp( @@ -178,8 +184,8 @@ public void testRetrievingEntityByZonedDateTime() { assertThat( list.size(), is( 1 ) ); } ); checkOneMatch.accept( getOriginalZonedDateTime() ); - checkOneMatch.accept( getExpectedPropertyValue() ); - checkOneMatch.accept( getExpectedPropertyValue().withZoneSameInstant( ZoneOffset.UTC ) ); + checkOneMatch.accept( getExpectedPropertyValueAfterHibernateRead() ); + checkOneMatch.accept( getExpectedPropertyValueAfterHibernateRead().withZoneSameInstant( ZoneOffset.UTC ) ); } ); } From a3c318523dc98d578b1d3528a751b1f485c66132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 12 Mar 2019 11:04:30 +0100 Subject: [PATCH 285/772] HHH-13266 Allow to override the SQL type mappings in AbstractJavaTimeTypeTest --- .../test/type/AbstractJavaTimeTypeTest.java | 70 +++++++++++++++++-- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java index 32b980d145da..32ce3d6eac29 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -19,8 +19,12 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -71,6 +75,14 @@ protected final Class[] getAnnotatedClasses() { return new Class[] { getEntityType() }; } + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + if ( env.remappingDialectClass != null ) { + configuration.setProperty( AvailableSettings.DIALECT, env.remappingDialectClass.getName() ); + } + } + protected abstract Class getEntityType(); protected abstract E createEntityForHibernateWrite(int id); @@ -195,14 +207,30 @@ else if ( cause instanceof Error ) { } } + protected final Class getRemappingDialectClass() { + return env.remappingDialectClass; + } + protected static abstract class AbstractParametersBuilder> { private final Dialect dialect; private final List result = new ArrayList<>(); + private final List> remappingDialectClasses = new ArrayList<>(); + protected AbstractParametersBuilder() { dialect = determineDialect(); + remappingDialectClasses.add( null ); // Always test without remapping + } + + @SafeVarargs + public final S alsoTestRemappingsWithH2(Class ... dialectClasses) { + if ( dialect instanceof H2Dialect ) { + // Only test remappings with H2 + Collections.addAll( remappingDialectClasses, dialectClasses ); + } + return thisAsS(); } protected final boolean isNanosecondPrecisionSupported() { @@ -211,10 +239,12 @@ protected final boolean isNanosecondPrecisionSupported() { } protected final S add(ZoneId defaultJvmTimeZone, Object ... subClassParameters) { - List parameters = new ArrayList<>(); - parameters.add( new EnvironmentParameters( defaultJvmTimeZone ) ); - Collections.addAll( parameters, subClassParameters ); - result.add( parameters.toArray() ); + for ( Class remappingDialectClass : remappingDialectClasses ) { + List parameters = new ArrayList<>(); + parameters.add( new EnvironmentParameters( defaultJvmTimeZone, remappingDialectClass ) ); + Collections.addAll( parameters, subClassParameters ); + result.add( parameters.toArray() ); + } return thisAsS(); } @@ -236,13 +266,41 @@ protected final static class EnvironmentParameters { */ private final ZoneId defaultJvmTimeZone; - private EnvironmentParameters(ZoneId defaultJvmTimeZone) { + private final Class remappingDialectClass; + + private EnvironmentParameters(ZoneId defaultJvmTimeZone, + Class remappingDialectClass) { this.defaultJvmTimeZone = defaultJvmTimeZone; + this.remappingDialectClass = remappingDialectClass; } @Override public String toString() { - return String.format( "[JVM TZ: %s]", defaultJvmTimeZone ); + return String.format( + "[JVM TZ: %s, remapping dialect: %s]", + defaultJvmTimeZone, + remappingDialectClass == null ? null : remappingDialectClass.getSimpleName() + ); + } + } + + protected static class AbstractRemappingH2Dialect extends H2Dialect { + private final int overriddenSqlTypeCode; + private final SqlTypeDescriptor overriddenSqlTypeDescriptor; + + public AbstractRemappingH2Dialect(int overriddenSqlTypeCode, SqlTypeDescriptor overriddenSqlTypeDescriptor) { + this.overriddenSqlTypeCode = overriddenSqlTypeCode; + this.overriddenSqlTypeDescriptor = overriddenSqlTypeDescriptor; + } + + @Override + protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { + if ( overriddenSqlTypeCode == sqlCode ) { + return overriddenSqlTypeDescriptor; + } + else { + return super.getSqlTypeDescriptorOverride( sqlCode ); + } } } From 9a8d4f0e5d85702c7774b33c204e556cd4633217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 12 Mar 2019 11:04:30 +0100 Subject: [PATCH 286/772] HHH-13266 Test LocalDate serialization when dates are remapped as Timestamp, in particular around 1900-01-01 --- .../hibernate/test/type/LocalDateTest.java | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index aa2b79a466f9..e2176c2d8fb9 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -10,6 +10,8 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; import java.time.LocalDate; import java.time.ZoneId; import java.util.List; @@ -18,6 +20,8 @@ import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; + import org.hibernate.testing.TestForIssue; import org.junit.runners.Parameterized; @@ -36,6 +40,7 @@ public ParametersBuilder add(int year, int month, int day, ZoneId defaultTimeZon @Parameterized.Parameters(name = "{1}-{2}-{3} {0}") public static List data() { return new ParametersBuilder() + .alsoTestRemappingsWithH2( DateAsTimestampRemappingH2Dialect.class ) // Not affected by HHH-13266 (JDK-8061577) .add( 2017, 11, 6, ZONE_UTC_MINUS_8 ) .add( 2017, 11, 6, ZONE_PARIS ) @@ -44,7 +49,7 @@ public static List data() { .add( 1900, 1, 1, ZONE_OSLO ) .add( 1900, 1, 2, ZONE_PARIS ) .add( 1900, 1, 2, ZONE_AMSTERDAM ) - // Could have been affected by HHH-13266 (JDK-8061577), but was not + // Affected by HHH-13266 (JDK-8061577), but only when remapping dates as timestamps .add( 1892, 1, 1, ZONE_OSLO ) .add( 1900, 1, 1, ZONE_PARIS ) .add( 1900, 1, 1, ZONE_AMSTERDAM ) @@ -85,17 +90,32 @@ protected LocalDate getActualPropertyValue(EntityWithLocalDate entity) { @Override protected void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) throws SQLException { - statement.setDate( parameterIndex, getExpectedJdbcValueAfterHibernateWrite() ); + if ( DateAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + statement.setTimestamp( parameterIndex, new Timestamp( year - 1900, month - 1, day, 0, 0, 0, 0 ) ); + } + else { + statement.setDate( parameterIndex, new Date( year - 1900, month - 1, day ) ); + } } @Override - protected Date getExpectedJdbcValueAfterHibernateWrite() { - return new Date( year - 1900, month - 1, day ); + protected Object getExpectedJdbcValueAfterHibernateWrite() { + if ( DateAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + return new Timestamp( year - 1900, month - 1, day, 0, 0, 0, 0 ); + } + else { + return new Date( year - 1900, month - 1, day ); + } } @Override protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { - return resultSet.getDate( columnIndex ); + if ( DateAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + return resultSet.getTimestamp( columnIndex ); + } + else { + return resultSet.getDate( columnIndex ); + } } @Entity(name = ENTITY_NAME) @@ -116,4 +136,10 @@ private EntityWithLocalDate(int id, LocalDate value) { this.value = value; } } + + public static class DateAsTimestampRemappingH2Dialect extends AbstractRemappingH2Dialect { + public DateAsTimestampRemappingH2Dialect() { + super( Types.DATE, TimestampTypeDescriptor.INSTANCE ); + } + } } From 1293b5bf70ab6f14680f60935fa761b1eef17580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 12 Mar 2019 11:07:58 +0100 Subject: [PATCH 287/772] HHH-13266 Fix LocalDate serialization by using the proper conversion methods between LocalDate and Timestamp --- .../descriptor/java/LocalDateJavaDescriptor.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java index 61f9c3458dc1..44018d2a92f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java @@ -63,6 +63,13 @@ public X unwrap(LocalDate value, Class type, WrapperOptions options) { final LocalDateTime localDateTime = value.atStartOfDay(); if ( Timestamp.class.isAssignableFrom( type ) ) { + /* + * Workaround for HHH-13266 (JDK-8061577). + * We could have done Timestamp.from( localDateTime.atZone( ZoneId.systemDefault() ).toInstant() ), + * but on top of being more complex than the line below, it won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ return (X) Timestamp.valueOf( localDateTime ); } @@ -97,7 +104,14 @@ public LocalDate wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ).toLocalDate(); + /* + * Workaround for HHH-13266 (JDK-8061577). + * We used to do LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ).toLocalDate(), + * but on top of being more complex than the line below, it won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime().toLocalDate(); } if ( Long.class.isInstance( value ) ) { From 759237fb9ff65ecb74add84f698f692392881d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 12 Mar 2019 11:23:00 +0100 Subject: [PATCH 288/772] HHH-13266 Test LocalTime serialization --- .../hibernate/test/type/LocalTimeTest.java | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java new file mode 100644 index 000000000000..567865842741 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java @@ -0,0 +1,186 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.type; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.List; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; + +import org.junit.runners.Parameterized; + +/** + * Tests for storage of LocalTime properties. + */ +public class LocalTimeTest extends AbstractJavaTimeTypeTest { + + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, hour, minute, second, nanosecond, 1970, 1, 1 ); + } + + public ParametersBuilder addPersistedWithoutHibernate(int yearWhenPersistedWithoutHibernate, + int monthWhenPersistedWithoutHibernate, int dayWhenPersistedWithoutHibernate, + int hour, int minute, int second, int nanosecond, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( + defaultTimeZone, hour, minute, second, nanosecond, + yearWhenPersistedWithoutHibernate, + monthWhenPersistedWithoutHibernate, dayWhenPersistedWithoutHibernate + ); + } + } + + @Parameterized.Parameters(name = "{1}:{2}:{3}.{4} (JDBC write date: {5}-{6}-{7}) {0}") + public static List data() { + return new ParametersBuilder() + .alsoTestRemappingsWithH2( TimeAsTimestampRemappingH2Dialect.class ) + // None of these values was affected by HHH-13266 (JDK-8061577) + .add( 19, 19, 1, 0, ZONE_UTC_MINUS_8 ) + .add( 19, 19, 1, 0, ZONE_PARIS ) + .add( 19, 19, 1, 500, ZONE_PARIS ) + .add( 0, 0, 0, 0, ZONE_GMT ) + .add( 0, 0, 0, 0, ZONE_OSLO ) + .add( 0, 9, 20, 0, ZONE_PARIS ) + .add( 0, 19, 31, 0, ZONE_AMSTERDAM ) + .add( 0, 0, 0, 0, ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .addPersistedWithoutHibernate( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) + .addPersistedWithoutHibernate( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 9, 20, 0, ZONE_PARIS ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 19, 31, 0, ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .build(); + } + + private final int hour; + private final int minute; + private final int second; + private final int nanosecond; + + private final int yearWhenPersistedWithoutHibernate; + private final int monthWhenPersistedWithoutHibernate; + private final int dayWhenPersistedWithoutHibernate; + + public LocalTimeTest(EnvironmentParameters env, int hour, int minute, int second, int nanosecond, + int yearWhenPersistedWithoutHibernate, int monthWhenPersistedWithoutHibernate, int dayWhenPersistedWithoutHibernate) { + super( env ); + this.hour = hour; + this.minute = minute; + this.second = second; + this.nanosecond = nanosecond; + this.yearWhenPersistedWithoutHibernate = yearWhenPersistedWithoutHibernate; + this.monthWhenPersistedWithoutHibernate = monthWhenPersistedWithoutHibernate; + this.dayWhenPersistedWithoutHibernate = dayWhenPersistedWithoutHibernate; + } + + @Override + protected Class getEntityType() { + return EntityWithLocalTime.class; + } + + @Override + protected EntityWithLocalTime createEntityForHibernateWrite(int id) { + return new EntityWithLocalTime( id, getOriginalPropertyValue() ); + } + + protected LocalTime getOriginalPropertyValue() { + return LocalTime.of( hour, minute, second, nanosecond ); + } + + @Override + protected LocalTime getExpectedPropertyValueAfterHibernateRead() { + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + return getOriginalPropertyValue(); + } + else { + // When storing time as java.sql.Time, we only get second precision (not nanosecond) + return getOriginalPropertyValue().withNano( 0 ); + } + } + + @Override + protected LocalTime getActualPropertyValue(EntityWithLocalTime entity) { + return entity.value; + } + + @Override + protected void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) + throws SQLException { + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + statement.setTimestamp( + parameterIndex, + new Timestamp( + yearWhenPersistedWithoutHibernate - 1900, + monthWhenPersistedWithoutHibernate - 1, + dayWhenPersistedWithoutHibernate, + hour, minute, second, nanosecond + ) + ); + } + else { + statement.setTime( parameterIndex, new Time( hour, minute, second ) ); + } + } + + @Override + protected Object getExpectedJdbcValueAfterHibernateWrite() { + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + return new Timestamp( 1970 - 1900, 0, 1, hour, minute, second, nanosecond ); + } + else { + return new Time( hour, minute, second ); + } + } + + @Override + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); + } + + @Entity(name = ENTITY_NAME) + static final class EntityWithLocalTime { + @Id + @Column(name = ID_COLUMN_NAME) + private Integer id; + + @Basic + @Column(name = PROPERTY_COLUMN_NAME) + private LocalTime value; + + protected EntityWithLocalTime() { + } + + private EntityWithLocalTime(int id, LocalTime value) { + this.id = id; + this.value = value; + } + } + + public static class TimeAsTimestampRemappingH2Dialect extends AbstractRemappingH2Dialect { + public TimeAsTimestampRemappingH2Dialect() { + super( Types.TIME, TimestampTypeDescriptor.INSTANCE ); + } + } +} From 09618a2174072cf61b3fd905207c851ca7df199f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 12 Mar 2019 12:12:11 +0100 Subject: [PATCH 289/772] HHH-13266 Test OffsetTime serialization --- .../hibernate/test/type/OffsetTimeTest.java | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java new file mode 100644 index 000000000000..6f14192208da --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java @@ -0,0 +1,203 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.type; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.List; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.type.descriptor.sql.BigIntTypeDescriptor; +import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; + +import org.junit.runners.Parameterized; + +/** + * Tests for storage of LocalTime properties. + */ +public class OffsetTimeTest extends AbstractJavaTimeTypeTest { + + private static class ParametersBuilder extends AbstractParametersBuilder { + public ParametersBuilder add(int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( defaultTimeZone, hour, minute, second, nanosecond, offset, 1970, 1, 1 ); + } + + public ParametersBuilder addPersistedWithoutHibernate(int yearWhenPersistedWithoutHibernate, + int monthWhenPersistedWithoutHibernate, int dayWhenPersistedWithoutHibernate, + int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { + if ( !isNanosecondPrecisionSupported() ) { + nanosecond = 0; + } + return add( + defaultTimeZone, hour, minute, second, nanosecond, offset, + yearWhenPersistedWithoutHibernate, + monthWhenPersistedWithoutHibernate, dayWhenPersistedWithoutHibernate + ); + } + } + + @Parameterized.Parameters(name = "{1}:{2}:{3}.{4}[{5}] (JDBC write date: {6}-{7}-{8}) {0}") + public static List data() { + return new ParametersBuilder() + .alsoTestRemappingsWithH2( TimeAsTimestampRemappingH2Dialect.class ) + // None of these values was affected by HHH-13266 (JDK-8061577) + .add( 19, 19, 1, 0, "+10:00", ZONE_UTC_MINUS_8 ) + .add( 19, 19, 1, 0, "+01:30", ZONE_UTC_MINUS_8 ) + .add( 19, 19, 1, 0, "-06:00", ZONE_UTC_MINUS_8 ) + .add( 19, 19, 1, 0, "+10:00", ZONE_PARIS ) + .add( 19, 19, 1, 0, "+01:30", ZONE_PARIS ) + .add( 19, 19, 1, 500, "+01:00", ZONE_PARIS ) + .add( 19, 19, 1, 0, "-08:00", ZONE_PARIS ) + .add( 0, 0, 0, 0, "+01:00", ZONE_GMT ) + .add( 0, 0, 0, 0, "+00:00", ZONE_GMT ) + .add( 0, 0, 0, 0, "-01:00", ZONE_GMT ) + .add( 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .add( 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) + .add( 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) + .add( 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) + .add( 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) + .build(); + } + + private final int hour; + private final int minute; + private final int second; + private final int nanosecond; + private final String offset; + + private final int yearWhenPersistedWithoutHibernate; + private final int monthWhenPersistedWithoutHibernate; + private final int dayWhenPersistedWithoutHibernate; + + public OffsetTimeTest(EnvironmentParameters env, int hour, int minute, int second, int nanosecond, String offset, + int yearWhenPersistedWithoutHibernate, int monthWhenPersistedWithoutHibernate, int dayWhenPersistedWithoutHibernate) { + super( env ); + this.hour = hour; + this.minute = minute; + this.second = second; + this.nanosecond = nanosecond; + this.offset = offset; + this.yearWhenPersistedWithoutHibernate = yearWhenPersistedWithoutHibernate; + this.monthWhenPersistedWithoutHibernate = monthWhenPersistedWithoutHibernate; + this.dayWhenPersistedWithoutHibernate = dayWhenPersistedWithoutHibernate; + } + + @Override + protected Class getEntityType() { + return EntityWithOffsetTime.class; + } + + @Override + protected EntityWithOffsetTime createEntityForHibernateWrite(int id) { + return new EntityWithOffsetTime( id, getOriginalPropertyValue() ); + } + + protected OffsetTime getOriginalPropertyValue() { + return OffsetTime.of( hour, minute, second, nanosecond, ZoneOffset.of( offset ) ); + } + + @Override + protected OffsetTime getExpectedPropertyValueAfterHibernateRead() { + // For some reason, the offset is not stored, so the restored values use the offset from the default JVM timezone. + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + return getOriginalPropertyValue().withOffsetSameLocal( OffsetDateTime.now().getOffset() ); + } + else { + // When storing time as java.sql.Time, we only get second precision (not nanosecond) + return getOriginalPropertyValue().withNano( 0 ).withOffsetSameLocal( OffsetDateTime.now().getOffset() ); + } + } + + @Override + protected OffsetTime getActualPropertyValue(EntityWithOffsetTime entity) { + return entity.value; + } + + @Override + protected void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) + throws SQLException { + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + statement.setTimestamp( + parameterIndex, + new Timestamp( + yearWhenPersistedWithoutHibernate - 1900, + monthWhenPersistedWithoutHibernate - 1, + dayWhenPersistedWithoutHibernate, + hour, minute, second, nanosecond + ) + ); + } + else { + statement.setTime( parameterIndex, new Time( hour, minute, second ) ); + } + } + + @Override + protected Object getExpectedJdbcValueAfterHibernateWrite() { + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + return new Timestamp( 1970 - 1900, 0, 1, hour, minute, second, nanosecond ); + } + else { + return new Time( hour, minute, second ); + } + } + + @Override + protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + return resultSet.getTimestamp( columnIndex ); + } + + @Entity(name = ENTITY_NAME) + static final class EntityWithOffsetTime { + @Id + @Column(name = ID_COLUMN_NAME) + private Integer id; + + @Basic + @Column(name = PROPERTY_COLUMN_NAME) + private OffsetTime value; + + protected EntityWithOffsetTime() { + } + + private EntityWithOffsetTime(int id, OffsetTime value) { + this.id = id; + this.value = value; + } + } + + public static class TimeAsTimestampRemappingH2Dialect extends AbstractRemappingH2Dialect { + public TimeAsTimestampRemappingH2Dialect() { + super( Types.TIME, TimestampTypeDescriptor.INSTANCE ); + } + } + + public static class TimeAsBigIntRemappingH2Dialect extends AbstractRemappingH2Dialect { + public TimeAsBigIntRemappingH2Dialect() { + super( Types.TIME, BigIntTypeDescriptor.INSTANCE ); + } + } +} From 34b42844847c4a8051da4568ef59364ce99bf469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 12 Mar 2019 13:36:27 +0100 Subject: [PATCH 290/772] HHH-13266 Test serialization of java.time types when hibernate.jdbc.time_zone is set --- .../test/type/AbstractJavaTimeTypeTest.java | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java index 32ce3d6eac29..a2968470718f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.time.ZoneId; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.TimeZone; @@ -18,6 +19,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; @@ -29,6 +32,7 @@ import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -78,6 +82,9 @@ protected final Class[] getAnnotatedClasses() { @Override protected void configure(Configuration configuration) { super.configure( configuration ); + if ( env.hibernateJdbcTimeZone != null ) { + configuration.setProperty( AvailableSettings.JDBC_TIME_ZONE, env.hibernateJdbcTimeZone.getId() ); + } if ( env.remappingDialectClass != null ) { configuration.setProperty( AvailableSettings.DIALECT, env.remappingDialectClass.getName() ); } @@ -124,6 +131,8 @@ public void writeThenRead() { @Test @TestForIssue(jiraKey = "HHH-13266") public void writeThenNativeRead() { + assumeNoJdbcTimeZone(); + withDefaultTimeZone( () -> { inTransaction( session -> { session.persist( createEntityForHibernateWrite( 1 ) ); @@ -151,6 +160,8 @@ public void writeThenNativeRead() { @Test @TestForIssue(jiraKey = "HHH-13266") public void nativeWriteThenRead() { + assumeNoJdbcTimeZone(); + withDefaultTimeZone( () -> { inTransaction( session -> { session.doWork( connection -> { @@ -211,6 +222,16 @@ protected final Class getRemappingDialectC return env.remappingDialectClass; } + protected void assumeNoJdbcTimeZone() { + Assume.assumeTrue( + "Tests with native read/writes are only relevant when not using " + AvailableSettings.JDBC_TIME_ZONE + + ", because the expectations do not take that time zone into account." + + " When this property is set, we only test that a write by Hibernate followed by " + + " a read by Hibernate returns the same value.", + env.hibernateJdbcTimeZone == null + ); + } + protected static abstract class AbstractParametersBuilder> { private final Dialect dialect; @@ -241,13 +262,35 @@ protected final boolean isNanosecondPrecisionSupported() { protected final S add(ZoneId defaultJvmTimeZone, Object ... subClassParameters) { for ( Class remappingDialectClass : remappingDialectClasses ) { List parameters = new ArrayList<>(); - parameters.add( new EnvironmentParameters( defaultJvmTimeZone, remappingDialectClass ) ); + parameters.add( + new EnvironmentParameters( + defaultJvmTimeZone, + null, + remappingDialectClass + ) + ); + Collections.addAll( parameters, subClassParameters ); + result.add( parameters.toArray() ); + } + for ( ZoneId hibernateJdbcTimeZone : getHibernateJdbcTimeZonesToTest() ) { + List parameters = new ArrayList<>(); + parameters.add( + new EnvironmentParameters( + defaultJvmTimeZone, + hibernateJdbcTimeZone, + null + ) + ); Collections.addAll( parameters, subClassParameters ); result.add( parameters.toArray() ); } return thisAsS(); } + protected Iterable getHibernateJdbcTimeZonesToTest() { + return Arrays.asList( ZONE_GMT, ZONE_OSLO ); + } + private S thisAsS() { return (S) this; } @@ -266,19 +309,28 @@ protected final static class EnvironmentParameters { */ private final ZoneId defaultJvmTimeZone; + /** + * The Hibernate setting, {@link AvailableSettings#JDBC_TIME_ZONE}, + * may affect a lot of time-related types, + * which is why we take it into account even with timezone-independent types such as Instant. + */ + private final ZoneId hibernateJdbcTimeZone; + private final Class remappingDialectClass; - private EnvironmentParameters(ZoneId defaultJvmTimeZone, + private EnvironmentParameters(ZoneId defaultJvmTimeZone, ZoneId hibernateJdbcTimeZone, Class remappingDialectClass) { this.defaultJvmTimeZone = defaultJvmTimeZone; + this.hibernateJdbcTimeZone = hibernateJdbcTimeZone; this.remappingDialectClass = remappingDialectClass; } @Override public String toString() { return String.format( - "[JVM TZ: %s, remapping dialect: %s]", + "[JVM TZ: %s, JDBC TZ: %s, remapping dialect: %s]", defaultJvmTimeZone, + hibernateJdbcTimeZone, remappingDialectClass == null ? null : remappingDialectClass.getSimpleName() ); } From 9380520681521ef81061775532ee21051e860d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 14 Mar 2019 10:41:36 +0100 Subject: [PATCH 291/772] HHH-13266 Mark most databases as not supporting nanosecond-precision storage for timestamps At least PostgreSQL, Oracle, MySQL and HANA don't support it. --- .../org/hibernate/test/type/AbstractJavaTimeTypeTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java index a2968470718f..0e9d38cefbda 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -26,6 +26,7 @@ import org.hibernate.cfg.Configuration; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -255,8 +256,8 @@ public final S alsoTestRemappingsWithH2(Class Date: Thu, 14 Mar 2019 15:24:32 +0100 Subject: [PATCH 292/772] HHH-13266 Skip tests that involve timestamps before epoch with MySQL/Mariadb --- .../test/type/AbstractJavaTimeTypeTest.java | 15 +++++ .../org/hibernate/test/type/InstantTest.java | 33 +++++++---- .../hibernate/test/type/LocalDateTest.java | 28 ++++++---- .../test/type/LocalDateTimeTest.java | 29 ++++++---- .../hibernate/test/type/LocalTimeTest.java | 28 ++++++---- .../test/type/OffsetDateTimeTest.java | 40 +++++++------ .../hibernate/test/type/OffsetTimeTest.java | 28 ++++++---- .../test/type/ZonedDateTimeTest.java | 56 +++++++++++-------- 8 files changed, 165 insertions(+), 92 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java index 0e9d38cefbda..1c83b62cc4e6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -19,6 +19,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -26,6 +27,7 @@ import org.hibernate.cfg.Configuration; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MariaDB10Dialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -246,6 +248,19 @@ protected AbstractParametersBuilder() { remappingDialectClasses.add( null ); // Always test without remapping } + public S skippedForDialects(List> dialectClasses, Consumer skippedIfDialectMatchesClasses) { + boolean skip = false; + for ( Class dialectClass : dialectClasses ) { + if ( dialectClass.isInstance( dialect ) ) { + skip = true; + } + } + if ( !skip ) { + skippedIfDialectMatchesClasses.accept( thisAsS() ); + } + return thisAsS(); + } + @SafeVarargs public final S alsoTestRemappingsWithH2(Class ... dialectClasses) { if ( dialect instanceof H2Dialect ) { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java index 1bb7bb5f2dad..b88e8782b916 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java @@ -15,12 +15,16 @@ import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.Arrays; import java.util.List; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQLDialect; + import org.junit.runners.Parameterized; /** @@ -45,18 +49,23 @@ public static List data() { .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_UTC_MINUS_8 ) .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_PARIS ) .add( 2017, 11, 6, 19, 19, 1, 500, ZONE_PARIS ) - .add( 1970, 1, 1, 0, 0, 0, 0, ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) - .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_PARIS ) - .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) - .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) - .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) - // Affected by HHH-13266 (JDK-8061577) - .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) - .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_PARIS ) - .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_AMSTERDAM ) - .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class ), + b -> b + .add( 1970, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_PARIS ) + .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) + // Affected by HHH-13266 (JDK-8061577) + .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_PARIS ) + .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + ) .build(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index e2176c2d8fb9..417636b875d4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -14,12 +14,15 @@ import java.sql.Types; import java.time.LocalDate; import java.time.ZoneId; +import java.util.Arrays; import java.util.List; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; import org.hibernate.testing.TestForIssue; @@ -44,16 +47,21 @@ public static List data() { // Not affected by HHH-13266 (JDK-8061577) .add( 2017, 11, 6, ZONE_UTC_MINUS_8 ) .add( 2017, 11, 6, ZONE_PARIS ) - .add( 1970, 1, 1, ZONE_GMT ) - .add( 1900, 1, 1, ZONE_GMT ) - .add( 1900, 1, 1, ZONE_OSLO ) - .add( 1900, 1, 2, ZONE_PARIS ) - .add( 1900, 1, 2, ZONE_AMSTERDAM ) - // Affected by HHH-13266 (JDK-8061577), but only when remapping dates as timestamps - .add( 1892, 1, 1, ZONE_OSLO ) - .add( 1900, 1, 1, ZONE_PARIS ) - .add( 1900, 1, 1, ZONE_AMSTERDAM ) - .add( 1600, 1, 1, ZONE_AMSTERDAM ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class ), + b -> b + .add( 1970, 1, 1, ZONE_GMT ) + .add( 1900, 1, 1, ZONE_GMT ) + .add( 1900, 1, 1, ZONE_OSLO ) + .add( 1900, 1, 2, ZONE_PARIS ) + .add( 1900, 1, 2, ZONE_AMSTERDAM ) + // Affected by HHH-13266 (JDK-8061577), but only when remapping dates as timestamps + .add( 1892, 1, 1, ZONE_OSLO ) + .add( 1900, 1, 1, ZONE_PARIS ) + .add( 1900, 1, 1, ZONE_AMSTERDAM ) + .add( 1600, 1, 1, ZONE_AMSTERDAM ) + ) .build(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java index 877ab318c978..7224460f4b82 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java @@ -12,12 +12,16 @@ import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneId; +import java.util.Arrays; import java.util.List; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQLDialect; + import org.junit.runners.Parameterized; /** @@ -42,16 +46,21 @@ public static List data() { .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_UTC_MINUS_8 ) .add( 2017, 11, 6, 19, 19, 1, 0, ZONE_PARIS ) .add( 2017, 11, 6, 19, 19, 1, 500, ZONE_PARIS ) - .add( 1970, 1, 1, 0, 0, 0, 0, ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) - .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) - .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) - // Affected by HHH-13266 (JDK-8061577) - .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) - .add( 1900, 1, 1, 0, 9, 20, 0, ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 31, 0, ZONE_AMSTERDAM ) - .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class ), + b -> b + .add( 1970, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) + .add( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) + // Affected by HHH-13266 (JDK-8061577) + .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 20, 0, ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + ) .build(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java index 567865842741..64c3baa580c4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java @@ -14,12 +14,15 @@ import java.sql.Types; import java.time.LocalTime; import java.time.ZoneId; +import java.util.Arrays; import java.util.List; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; import org.junit.runners.Parameterized; @@ -59,18 +62,23 @@ public static List data() { .add( 19, 19, 1, 0, ZONE_UTC_MINUS_8 ) .add( 19, 19, 1, 0, ZONE_PARIS ) .add( 19, 19, 1, 500, ZONE_PARIS ) - .add( 0, 0, 0, 0, ZONE_GMT ) - .add( 0, 0, 0, 0, ZONE_OSLO ) .add( 0, 9, 20, 0, ZONE_PARIS ) .add( 0, 19, 31, 0, ZONE_AMSTERDAM ) - .add( 0, 0, 0, 0, ZONE_AMSTERDAM ) - .addPersistedWithoutHibernate( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) - .addPersistedWithoutHibernate( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) - .addPersistedWithoutHibernate( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) - .addPersistedWithoutHibernate( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) - .addPersistedWithoutHibernate( 1900, 1, 1, 0, 9, 20, 0, ZONE_PARIS ) - .addPersistedWithoutHibernate( 1900, 1, 1, 0, 19, 31, 0, ZONE_AMSTERDAM ) - .addPersistedWithoutHibernate( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class ), + b -> b + .add( 0, 0, 0, 0, ZONE_GMT ) + .add( 0, 0, 0, 0, ZONE_OSLO ) + .add( 0, 0, 0, 0, ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .addPersistedWithoutHibernate( 1900, 1, 2, 0, 9, 21, 0, ZONE_PARIS ) + .addPersistedWithoutHibernate( 1900, 1, 2, 0, 19, 32, 0, ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 9, 20, 0, ZONE_PARIS ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 19, 31, 0, ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) + ) .build(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java index c61fe1d9b5a1..9926fea2f515 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java @@ -14,6 +14,7 @@ import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import javax.persistence.Basic; @@ -22,6 +23,8 @@ import javax.persistence.Id; import org.hibernate.Query; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.OffsetDateTimeType; import org.hibernate.testing.TestForIssue; @@ -68,22 +71,27 @@ public static List data() { .add( 2017, 11, 6, 19, 19, 1, 0, "-02:00", ZONE_PARIS ) .add( 2017, 11, 6, 19, 19, 1, 0, "-06:00", ZONE_PARIS ) .add( 2017, 11, 6, 19, 19, 1, 0, "-08:00", ZONE_PARIS ) - .add( 1970, 1, 1, 0, 0, 0, 0, "+01:00", ZONE_GMT ) - .add( 1970, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_GMT ) - .add( 1970, 1, 1, 0, 0, 0, 0, "-01:00", ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, "+01:00", ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, "-01:00", ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) - .add( 1900, 1, 1, 0, 9, 21, 0, "+00:09:21", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_AMSTERDAM ) - // Affected by HHH-13266 - .add( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) - .add( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) - .add( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class ), + b -> b + .add( 1970, 1, 1, 0, 0, 0, 0, "+01:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "+01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 21, 0, "+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "+00:19:32", ZONE_AMSTERDAM ) + // Affected by HHH-13266 + .add( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) + ) .build(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java index 6f14192208da..448a74ea2921 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java @@ -16,12 +16,15 @@ import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.Arrays; import java.util.List; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.descriptor.sql.BigIntTypeDescriptor; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; @@ -66,19 +69,24 @@ public static List data() { .add( 19, 19, 1, 0, "+01:30", ZONE_PARIS ) .add( 19, 19, 1, 500, "+01:00", ZONE_PARIS ) .add( 19, 19, 1, 0, "-08:00", ZONE_PARIS ) - .add( 0, 0, 0, 0, "+01:00", ZONE_GMT ) - .add( 0, 0, 0, 0, "+00:00", ZONE_GMT ) - .add( 0, 0, 0, 0, "-01:00", ZONE_GMT ) - .add( 0, 0, 0, 0, "+00:00", ZONE_OSLO ) .add( 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) .add( 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) .add( 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) - .add( 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) - .addPersistedWithoutHibernate( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) - .addPersistedWithoutHibernate( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) - .addPersistedWithoutHibernate( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) - .addPersistedWithoutHibernate( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) - .addPersistedWithoutHibernate( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class ), + b -> b + .add( 0, 0, 0, 0, "+01:00", ZONE_GMT ) + .add( 0, 0, 0, 0, "+00:00", ZONE_GMT ) + .add( 0, 0, 0, 0, "-01:00", ZONE_GMT ) + .add( 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .add( 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1892, 1, 1, 0, 0, 0, 0, "+00:00", ZONE_OSLO ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) + .addPersistedWithoutHibernate( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) + .addPersistedWithoutHibernate( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) + ) .build(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java index 6621b9a85402..de7fe39ae080 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java @@ -14,6 +14,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import javax.persistence.Basic; @@ -22,6 +23,8 @@ import javax.persistence.Id; import org.hibernate.Query; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.ZonedDateTimeType; import org.hibernate.testing.TestForIssue; @@ -72,30 +75,35 @@ public static List data() { .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-02:00", ZONE_PARIS ) .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-06:00", ZONE_PARIS ) .add( 2017, 11, 6, 19, 19, 1, 0, "GMT-08:00", ZONE_PARIS ) - .add( 1970, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZONE_GMT ) - .add( 1970, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_GMT ) - .add( 1970, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZONE_GMT ) - .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) - .add( 1900, 1, 1, 0, 9, 21, 0, "GMT+00:09:21", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 9, 21, 0, "Europe/Paris", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Paris", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) - .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) - // Affected by HHH-13266 - .add( 1892, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) - .add( 1892, 1, 1, 0, 0, 0, 0, "Europe/Oslo", ZONE_OSLO ) - .add( 1900, 1, 1, 0, 9, 20, 0, "GMT+00:09:21", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 9, 20, 0, "Europe/Paris", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_PARIS ) - .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) - .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) - .add( 1600, 1, 1, 0, 0, 0, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) - .add( 1600, 1, 1, 0, 0, 0, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) + .skippedForDialects( + // MySQL/Mariadb cannot store values equal to epoch exactly, or less, in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class ), + b -> b + .add( 1970, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_GMT ) + .add( 1970, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT-01:00", ZONE_GMT ) + .add( 1900, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 21, 0, "GMT+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 9, 21, 0, "Europe/Paris", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Paris", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 32, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) + .add( 1900, 1, 1, 0, 19, 32, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) + // Affected by HHH-13266 + .add( 1892, 1, 1, 0, 0, 0, 0, "GMT+00:00", ZONE_OSLO ) + .add( 1892, 1, 1, 0, 0, 0, 0, "Europe/Oslo", ZONE_OSLO ) + .add( 1900, 1, 1, 0, 9, 20, 0, "GMT+00:09:21", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 9, 20, 0, "Europe/Paris", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_PARIS ) + .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) + .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) + .add( 1600, 1, 1, 0, 0, 0, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) + ) .build(); } From b328544dc5ebc98717289ce6bcb409c4d55eca7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 14 Mar 2019 16:03:42 +0100 Subject: [PATCH 293/772] HHH-13266 Skip some tests that apparently trigger bugs in HANA --- .../java/org/hibernate/test/type/LocalDateTest.java | 3 +++ .../java/org/hibernate/test/type/LocalTimeTest.java | 10 ++++++++++ .../java/org/hibernate/test/type/OffsetTimeTest.java | 10 ++++++++++ 3 files changed, 23 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index 417636b875d4..8cf5dabc4ca2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -21,10 +21,12 @@ import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.runners.Parameterized; @@ -32,6 +34,7 @@ * Tests for storage of LocalDate properties. */ @TestForIssue(jiraKey = "HHH-10371") +@SkipForDialect(value = AbstractHANADialect.class, comment = "HANA systematically returns the wrong date when the JVM default timezone is not UTC") public class LocalDateTest extends AbstractJavaTimeTypeTest { private static class ParametersBuilder extends AbstractParametersBuilder { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java index 64c3baa580c4..a7aa5628be8f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java @@ -21,10 +21,13 @@ import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; +import org.hibernate.testing.SkipForDialect; +import org.junit.Test; import org.junit.runners.Parameterized; /** @@ -167,6 +170,13 @@ protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws return resultSet.getTimestamp( columnIndex ); } + @Override + @Test + @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA seems to return a java.sql.Timestamp instead of a java.sql.Time") + public void writeThenNativeRead() { + super.writeThenNativeRead(); + } + @Entity(name = ENTITY_NAME) static final class EntityWithLocalTime { @Id diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java index 448a74ea2921..8a4203cbfc16 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java @@ -23,11 +23,14 @@ import javax.persistence.Entity; import javax.persistence.Id; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.descriptor.sql.BigIntTypeDescriptor; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; +import org.hibernate.testing.SkipForDialect; +import org.junit.Test; import org.junit.runners.Parameterized; /** @@ -178,6 +181,13 @@ protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws return resultSet.getTimestamp( columnIndex ); } + @Override + @Test + @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA seems to return a java.sql.Timestamp instead of a java.sql.Time") + public void writeThenNativeRead() { + super.writeThenNativeRead(); + } + @Entity(name = ENTITY_NAME) static final class EntityWithOffsetTime { @Id From 664d435b2726dd860419b68b861551c3e3de96c0 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 19 Apr 2019 00:00:52 -0700 Subject: [PATCH 294/772] HHH-13266 : Skip OffsetTimeTest#nativeWriteThenRead and #writeThenRead in some cases due to HHH-13357 when Time is remapped as Timestamp and default JVM is not GMT --- .../test/type/AbstractJavaTimeTypeTest.java | 9 ++++--- .../hibernate/test/type/OffsetTimeTest.java | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java index 1c83b62cc4e6..1c5db51aeab8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -20,16 +20,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.H2Dialect; -import org.hibernate.dialect.MariaDB10Dialect; -import org.hibernate.dialect.Oracle8iDialect; -import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.testing.TestForIssue; @@ -221,6 +216,10 @@ else if ( cause instanceof Error ) { } } + protected final ZoneId getDefaultJvmTimeZone() { + return env.defaultJvmTimeZone; + } + protected final Class getRemappingDialectClass() { return env.remappingDialectClass; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java index 8a4203cbfc16..c2e2df87e710 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java @@ -18,6 +18,7 @@ import java.time.ZoneOffset; import java.util.Arrays; import java.util.List; +import java.util.TimeZone; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; @@ -30,6 +31,7 @@ import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.SkipLog; import org.junit.Test; import org.junit.runners.Parameterized; @@ -181,6 +183,17 @@ protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws return resultSet.getTimestamp( columnIndex ); } + @Override + @Test + public void nativeWriteThenRead() { + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) && + !ZONE_GMT.equals( getDefaultJvmTimeZone() ) ) { + SkipLog.reportSkip( "OffsetTimeType remapped as timestamp only works reliably with GMT default JVM for nativeWriteThenRead; see HHH-13357" ); + return; + } + super.nativeWriteThenRead(); + } + @Override @Test @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA seems to return a java.sql.Timestamp instead of a java.sql.Time") @@ -188,6 +201,17 @@ public void writeThenNativeRead() { super.writeThenNativeRead(); } + @Override + @Test + public void writeThenRead() { + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) && + !ZONE_GMT.equals( getDefaultJvmTimeZone() ) ) { + SkipLog.reportSkip( "OffsetTimeType remapped as timestamp only works reliably with GMT default JVM for writeThenRead; see HHH-13357" ); + return; + } + super.writeThenRead(); + } + @Entity(name = ENTITY_NAME) static final class EntityWithOffsetTime { @Id From b43d7375683e013dc53ca3c14774072a2f595b84 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 19 Apr 2019 22:12:52 +0200 Subject: [PATCH 295/772] 5.3.10.Final --- changelog.txt | 21 +++++++++++++++++++++ gradle/base-information.gradle | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 3609af669334..68629a3d4865 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,27 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.3.10.final (April 19th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31759/tab/release-report-done + +** Bug + * [HHH-12939] - Database name not quoted at schema update + * [HHH-13138] - Work around class loading issues so that bytecode enhanced tests can run as expected + * [HHH-13241] - Constraint violation when deleting entites in bi-directional, lazy OneToMany association with bytecode enhancement + * [HHH-13266] - LocalDateTime values are wrong around 1900 (caused by JDK-8061577) + * [HHH-13277] - HibernateMethodLookupDispatcher - Issue with Security Manager + * [HHH-13300] - query.getSingleResult() throws org.hibernate.NonUniqueResultException instead of javax.persistence.NonUniqueResultException + * [HHH-13326] - Transaction passed to Hibernate Interceptor methods is null when JTA is used + * [HHH-13343] - Bytecode enhancement using ByteBuddy fails when the class is not available from the provided ClassLoader + * [HHH-13364] - Query.getSingleResult and getResultList() throw PessimisticLockException when pessimistic lock fails with timeout + +** Task + * [HHH-13376] - Upgrade Javassist dependency to 3.23.2-GA + + + Changes in 5.3.9.final (February 25th, 2019) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index d9af8ac32d8d..31b34ccee001 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.10-SNAPSHOT', project ) + ormVersion = new HibernateVersion( '5.3.10.Final', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From e9d2bfeec5979eac144ce312c68c110f47eeeebb Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 19 Apr 2019 23:33:10 +0200 Subject: [PATCH 296/772] Prepare for next development iteration --- gradle/base-information.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 31b34ccee001..8e8c1e1df936 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -8,7 +8,7 @@ apply plugin: 'base' ext { - ormVersion = new HibernateVersion( '5.3.10.Final', project ) + ormVersion = new HibernateVersion( '5.3.11-SNAPSHOT', project ) baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } From 784341682dc2621afea5d8ecc6207cb530e2aab8 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 27 May 2019 16:12:39 +0100 Subject: [PATCH 297/772] HHH-13416 Unguarded debug message being rendered in org.hibernate.engine.internal.Collections.processReachableCollection --- .../org/hibernate/engine/internal/Collections.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java index fec7aea00a8b..1153f9d1f3f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java @@ -167,10 +167,12 @@ public static void processReachableCollection( final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getInstrumentationMetadata().isEnhancedForLazyLoading(); if ( isBytecodeEnhanced && !collection.wasInitialized() ) { // skip it - LOG.debugf( - "Skipping uninitialized bytecode-lazy collection: %s", - MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ) - ); + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Skipping uninitialized bytecode-lazy collection: %s", + MessageHelper.collectionInfoString(persister, collection, ce.getCurrentKey(), session) + ); + } ce.setReached( true ); ce.setProcessed( true ); } From e4a35863166ddc4f87b619618b0a496feb0ff450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 13 Jun 2019 16:39:19 +0200 Subject: [PATCH 298/772] Force the use of Ubuntu Trusty in the Travis build If we don't force it, Travis will pick a release at random, either Trusty or Xenial at the moment. Using Oracle JDK 8 on Xenial is not easy, for some reason. See: * https://travis-ci.community/t/oracle-jdk-11-and-10-are-pre-installed-not-the-openjdk-builds/785/15 * https://github.com/travis-ci/travis-ci/issues/10289 (cherry picked from commit d6bd291934b2c90de6666f73a486d95f1c02a8c7) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cfa43e211c27..d6e5a06939e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +dist: trusty language: java jdk: From 5d518cb67365970d5efd9e14d4fb4686d6b09dec Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 18 Apr 2019 02:28:29 -0500 Subject: [PATCH 299/772] HHH-11147 - Allow enhanced entities to be returned in a completely uninitialized state --- .../main/java/org/hibernate/Hibernate.java | 20 +- .../SessionFactoryOptionsBuilder.java | 11 +- ...stractDelegatingSessionFactoryOptions.java | 5 + .../boot/spi/SessionFactoryOptions.java | 7 + .../hibernate/bytecode/BytecodeLogger.java | 22 + .../internal/bytebuddy/EnhancerImpl.java | 5 + .../PersistentAttributeTransformer.java | 5 +- .../enhance/spi/LazyPropertyInitializer.java | 15 +- .../spi/interceptor/AbstractInterceptor.java | 160 ++ .../AbstractLazyLoadInterceptor.java | 26 + .../BytecodeLazyAttributeInterceptor.java | 40 + ...EnhancementAsProxyLazinessInterceptor.java | 267 +++ .../spi/interceptor/EnhancementHelper.java | 217 +++ .../enhance/spi/interceptor/Helper.java | 179 -- .../LazyAttributeLoadingInterceptor.java | 274 +-- .../interceptor/LazyAttributesMetadata.java | 19 +- .../SessionAssociableInterceptor.java | 25 + .../spi/BytecodeEnhancementMetadata.java | 17 + .../org/hibernate/cfg/AvailableSettings.java | 21 + .../engine/internal/AbstractEntityEntry.java | 12 + .../hibernate/engine/internal/Cascade.java | 4 +- .../engine/internal/Collections.java | 98 +- .../engine/internal/ForeignKeys.java | 5 +- .../internal/StatefulPersistenceContext.java | 42 +- .../engine/internal/TwoPhaseLoad.java | 2 +- .../internal/CollectionLoadContext.java | 2 +- .../spi/PersistentAttributeInterceptor.java | 65 +- .../spi/SharedSessionContractImplementor.java | 10 + .../internal/AbstractSaveEventListener.java | 2 +- .../internal/DefaultLoadEventListener.java | 126 +- .../internal/DefaultMergeEventListener.java | 116 +- .../event/internal/FlushVisitor.java | 18 +- .../hibernate/event/internal/WrapVisitor.java | 32 +- .../org/hibernate/event/spi/LoadEvent.java | 17 + .../internal/ScrollableResultsImpl.java | 38 +- .../org/hibernate/internal/SessionImpl.java | 38 +- .../internal/StatelessSessionImpl.java | 72 +- .../util/collections/ArrayHelper.java | 6 + .../java/org/hibernate/loader/Loader.java | 83 +- .../EntityReferenceInitializerImpl.java | 28 + .../java/org/hibernate/mapping/Property.java | 43 +- .../entity/AbstractEntityPersister.java | 106 +- .../persister/entity/EntityPersister.java | 22 +- .../org/hibernate/tuple/PropertyFactory.java | 22 +- .../tuple/entity/AbstractEntityTuplizer.java | 17 +- ...ytecodeEnhancementMetadataNonPojoImpl.java | 25 + .../BytecodeEnhancementMetadataPojoImpl.java | 137 +- .../tuple/entity/EntityMetamodel.java | 63 +- .../tuple/entity/EntityTuplizer.java | 5 + .../tuple/entity/PojoEntityInstantiator.java | 1 + .../tuple/entity/PojoEntityTuplizer.java | 25 +- .../java/org/hibernate/type/EntityType.java | 3 +- .../TestLazyPropertyOnPreUpdate.java | 2 +- .../basic/BasicEnhancementTest.java | 9 - .../lazy/BidirectionalLazyTest.java | 38 +- .../lazy/StatelessQueryScrollingTest.java | 476 ++++++ .../lazy/group/SimpleLazyGroupUpdateTest.java | 4 +- .../enhancement/lazy/proxy/AbstractKey.java | 130 ++ .../enhancement/lazy/proxy/Activity.java | 79 + .../enhancement/lazy/proxy/Address.java | 49 + .../enhancement/lazy/proxy/BaseEntity.java | 45 + .../lazy/proxy/BidirectionalProxyTest.java | 364 ++++ .../lazy/proxy/CreditCardPayment.java | 35 + .../enhancement/lazy/proxy/Customer.java | 106 ++ .../lazy/proxy/DebitCardPayment.java | 35 + .../lazy/proxy/DeepInheritanceProxyTest.java | 629 +++++++ ...epInheritanceWithNonEntitiesProxyTest.java | 1514 +++++++++++++++++ .../lazy/proxy/DomesticCustomer.java | 40 + .../EntitySharedInCollectionAndToOneTest.java | 252 +++ .../lazy/proxy/FetchGraphTest.java | 926 ++++++++++ .../lazy/proxy/ForeignCustomer.java | 48 + .../enhancement/lazy/proxy/GenericKey.java | 27 + .../enhancement/lazy/proxy/Instruction.java | 41 + ...azyGroupWithInheritanceAllowProxyTest.java | 297 ++++ .../proxy/LazyGroupWithInheritanceTest.java | 267 +++ .../lazy/proxy/MergeDetachedToProxyTest.java | 192 +++ .../lazy/proxy/MergeProxyTest.java | 230 +++ .../enhancement/lazy/proxy/ModelEntity.java | 72 + .../lazy/proxy/MoreSpecializedKey.java | 24 + .../enhancement/lazy/proxy/Order.java | 109 ++ .../lazy/proxy/OrderSupplemental.java | 48 + .../lazy/proxy/OrderSupplemental2.java | 63 + .../enhancement/lazy/proxy/Payment.java | 49 + .../lazy/proxy/ProxyDeletionTest.java | 461 +++++ .../enhancement/lazy/proxy/RoleEntity.java | 74 + .../SimpleUpdateTestWithLazyLoading.java | 329 ++++ ...WithLazyLoadingAndInlineDirtyTracking.java | 284 ++++ .../lazy/proxy/SpecializedEntity.java | 72 + .../lazy/proxy/SpecializedKey.java | 51 + .../lazy/proxy/WebApplication.java | 72 + .../enhancement/lazy/proxy/package-info.java | 11 + .../test/legacy/CustomPersister.java | 2 +- .../test/stateless/fetching/Producer.java | 57 + .../test/stateless/fetching/Product.java | 67 + .../StatelessSessionFetchingTest.java | 159 +- .../test/stateless/fetching/Vendor.java | 56 + .../test/type/LobUnfetchedPropertyTest.java | 1 + .../src/test/resources/log4j.properties | 4 +- .../enhancement/BytecodeEnhancerRunner.java | 196 ++- .../enhancement/ClassEnhancementSelector.java | 25 + .../ClassEnhancementSelectors.java | 23 + .../bytecode/enhancement/ClassSelector.java | 25 + .../enhancement/EnhancementOptions.java | 26 + .../enhancement/EnhancementSelector.java | 20 + .../enhancement/EverythingSelector.java | 22 + .../enhancement/ImplEnhancementSelector.java | 25 + .../enhancement/ImplEnhancementSelectors.java | 23 + .../PackageEnhancementSelector.java | 27 + .../PackageEnhancementSelectors.java | 23 + .../bytecode/enhancement/PackageSelector.java | 25 + .../BaseNonConfigCoreFunctionalTestCase.java | 22 + .../testing/transaction/TransactionUtil2.java | 162 +- 112 files changed, 10436 insertions(+), 728 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java create mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java create mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java create mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java create mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java create mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java create mode 100644 hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceAllowProxyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeDetachedToProxyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeProxyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ModelEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MoreSpecializedKey.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Order.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental2.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Payment.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyDeletionTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoading.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoadingAndInlineDirtyTracking.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedKey.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/WebApplication.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/package-info.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Producer.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Product.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Vendor.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelector.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelectors.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassSelector.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementSelector.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EverythingSelector.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelector.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelectors.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelector.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelectors.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageSelector.java diff --git a/hibernate-core/src/main/java/org/hibernate/Hibernate.java b/hibernate-core/src/main/java/org/hibernate/Hibernate.java index 6644d420f4b5..c46f9d219f9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/Hibernate.java +++ b/hibernate-core/src/main/java/org/hibernate/Hibernate.java @@ -8,6 +8,7 @@ import java.util.Iterator; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.HibernateIterator; @@ -63,6 +64,13 @@ public static void initialize(Object proxy) throws HibernateException { else if ( proxy instanceof PersistentCollection ) { ( (PersistentCollection) proxy ).forceInitialization(); } + else if ( proxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) proxy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( proxy, null ); + } + } } /** @@ -76,6 +84,13 @@ public static boolean isInitialized(Object proxy) { if ( proxy instanceof HibernateProxy ) { return !( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUninitialized(); } + else if ( proxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) proxy ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return false; + } + return true; + } else if ( proxy instanceof PersistentCollection ) { return ( (PersistentCollection) proxy ).wasInitialized(); } @@ -187,7 +202,10 @@ public static boolean isPropertyInitialized(Object proxy, String propertyName) { if ( entity instanceof PersistentAttributeInterceptable ) { PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); - if ( interceptor != null && interceptor instanceof LazyAttributeLoadingInterceptor ) { + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return false; + } + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { return ( (LazyAttributeLoadingInterceptor) interceptor ).isAttributeLoaded( propertyName ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index a8b393a626aa..de2b94b64573 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -63,9 +63,8 @@ import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.tuple.entity.EntityTuplizerFactory; -import org.jboss.logging.Logger; - import static org.hibernate.cfg.AvailableSettings.ACQUIRE_CONNECTIONS; +import static org.hibernate.cfg.AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY; import static org.hibernate.cfg.AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS; import static org.hibernate.cfg.AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY; import static org.hibernate.cfg.AvailableSettings.ALLOW_UPDATE_OUTSIDE_TRANSACTION; @@ -193,6 +192,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private NullPrecedence defaultNullPrecedence; private boolean orderUpdatesEnabled; private boolean orderInsertsEnabled; + private boolean enhancementAsProxyEnabled; // multi-tenancy @@ -244,6 +244,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private boolean nativeExceptionHandling51Compliance; + @SuppressWarnings({"WeakerAccess", "deprecation"}) public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) { this.serviceRegistry = serviceRegistry; @@ -340,6 +341,7 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo this.defaultNullPrecedence = NullPrecedence.parse( defaultNullPrecedence ); this.orderUpdatesEnabled = ConfigurationHelper.getBoolean( ORDER_UPDATES, configurationSettings ); this.orderInsertsEnabled = ConfigurationHelper.getBoolean( ORDER_INSERTS, configurationSettings ); + this.enhancementAsProxyEnabled = ConfigurationHelper.getBoolean( ALLOW_ENHANCEMENT_AS_PROXY, configurationSettings ); this.jtaTrackByThread = cfgService.getSetting( JTA_TRACK_BY_THREAD, BOOLEAN, true ); @@ -1031,6 +1033,11 @@ public boolean nativeExceptionHandling51Compliance() { return nativeExceptionHandling51Compliance; } + @Override + public boolean isEnhancementAsProxyEnabled() { + return enhancementAsProxyEnabled; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 44b8c5f24cd2..f1f9acd23566 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -432,4 +432,9 @@ public boolean inClauseParameterPaddingEnabled() { public boolean nativeExceptionHandling51Compliance() { return delegate.nativeExceptionHandling51Compliance(); } + + @Override + public boolean isEnhancementAsProxyEnabled() { + return delegate.isEnhancementAsProxyEnabled(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 08c0d66bedbb..8e92af0def06 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -290,4 +290,11 @@ default boolean inClauseParameterPaddingEnabled() { default boolean nativeExceptionHandling51Compliance() { return false; } + + /** + * Can bytecode-enhanced entity classes be used as a "proxy"? + */ + default boolean isEnhancementAsProxyEnabled() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java b/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java new file mode 100644 index 000000000000..203d7d2929bf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java @@ -0,0 +1,22 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public interface BytecodeLogger extends BasicLogger { + String NAME = "org.hibernate.orm.bytecode"; + + Logger LOGGER = Logger.getLogger( NAME ); + + boolean TRACE_ENABLED = LOGGER.isTraceEnabled(); + boolean DEBUG_ENABLED = LOGGER.isDebugEnabled(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index cec2f0f0d118..2c7827f02d66 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -459,6 +459,11 @@ public boolean hasAnnotation(Class annotationType) { return getAnnotations().isAnnotationPresent( annotationType ); } + @Override + public String toString() { + return fieldDescription.toString(); + } + AnnotationDescription.Loadable getAnnotation(Class annotationType) { return getAnnotations().ofType( annotationType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 18050ac72f35..3b9eb4114b02 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -101,7 +101,10 @@ private static Collection collectInheritPersistentFie } TypeDefinition managedCtSuperclass = managedCtClass.getSuperClass(); - if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { + if ( enhancementContext.isEntityClass( managedCtSuperclass.asErasure() ) ) { + return Collections.emptyList(); + } + else if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { return collectInheritPersistentFields( managedCtSuperclass, enhancementContext ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java index 2f8ddce0b879..4e5da5c87d05 100755 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java @@ -7,8 +7,10 @@ package org.hibernate.bytecode.enhance.spi; import java.io.Serializable; +import java.util.Collections; import java.util.Set; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -32,9 +34,18 @@ public Object readResolve() { } }; + /** + * @deprecated Prefer the form of these methods defined on + * {@link BytecodeLazyAttributeInterceptor} instead + */ + @Deprecated interface InterceptorImplementor { - Set getInitializedLazyAttributeNames(); - void attributeInitialized(String name); + default Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + + default void attributeInitialized(String name) { + } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java new file mode 100644 index 000000000000..a201fdc31682 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java @@ -0,0 +1,160 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractInterceptor implements SessionAssociableInterceptor { + private final String entityName; + + private transient SharedSessionContractImplementor session; + private boolean allowLoadOutsideTransaction; + private String sessionFactoryUuid; + + @SuppressWarnings("WeakerAccess") + public AbstractInterceptor(String entityName) { + this.entityName = entityName; + } + + public String getEntityName() { + return entityName; + } + + @Override + public SharedSessionContractImplementor getLinkedSession() { + return session; + } + + @Override + public void setSession(SharedSessionContractImplementor session) { + this.session = session; + if ( session != null && !allowLoadOutsideTransaction ) { + this.allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); + if ( this.allowLoadOutsideTransaction ) { + this.sessionFactoryUuid = session.getFactory().getUuid(); + } + } + } + + @Override + public void unsetSession() { + this.session = null; + } + + @Override + public boolean allowLoadOutsideTransaction() { + return allowLoadOutsideTransaction; + } + + @Override + public String getSessionFactoryUuid() { + return sessionFactoryUuid; + } + + /** + * Handle the case of reading an attribute. The result is what is returned to the caller + */ + protected abstract Object handleRead(Object target, String attributeName, Object value); + + /** + * Handle the case of writing an attribute. The result is what is set as the entity state + */ + protected abstract Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue); + + @Override + public boolean readBoolean(Object obj, String name, boolean oldValue) { + return (Boolean) handleRead( obj, name, oldValue ); + } + + @Override + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { + return (Boolean) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public byte readByte(Object obj, String name, byte oldValue) { + return (Byte) handleRead( obj, name, oldValue ); + } + + @Override + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { + return (Byte) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public char readChar(Object obj, String name, char oldValue) { + return (Character) handleRead( obj, name, oldValue ); + } + + @Override + public char writeChar(Object obj, String name, char oldValue, char newValue) { + return (char) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public short readShort(Object obj, String name, short oldValue) { + return (Short) handleRead( obj, name, oldValue ); + } + + @Override + public short writeShort(Object obj, String name, short oldValue, short newValue) { + return (Short) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public int readInt(Object obj, String name, int oldValue) { + return (Integer) handleRead( obj, name, oldValue ); + } + + @Override + public int writeInt(Object obj, String name, int oldValue, int newValue) { + return (Integer) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public float readFloat(Object obj, String name, float oldValue) { + return (Float) handleRead( obj, name, oldValue ); + } + + @Override + public float writeFloat(Object obj, String name, float oldValue, float newValue) { + return (Float) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public double readDouble(Object obj, String name, double oldValue) { + return (Double) handleRead( obj, name, oldValue ); + } + + @Override + public double writeDouble(Object obj, String name, double oldValue, double newValue) { + return (Double) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public long readLong(Object obj, String name, long oldValue) { + return (Long) handleRead( obj, name, oldValue ); + } + + @Override + public long writeLong(Object obj, String name, long oldValue, long newValue) { + return (Long) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public Object readObject(Object obj, String name, Object oldValue) { + return handleRead( obj, name, oldValue ); + } + + @Override + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { + return handleWrite( obj, name, oldValue, newValue ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java new file mode 100644 index 000000000000..3eb53f0261a4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java @@ -0,0 +1,26 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractLazyLoadInterceptor extends AbstractInterceptor implements BytecodeLazyAttributeInterceptor { + + @SuppressWarnings("unused") + public AbstractLazyLoadInterceptor(String entityName) { + super( entityName ); + } + + @SuppressWarnings("WeakerAccess") + public AbstractLazyLoadInterceptor(String entityName, SharedSessionContractImplementor session) { + super( entityName ); + setSession( session ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java new file mode 100644 index 000000000000..ae92635ea736 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java @@ -0,0 +1,40 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import java.util.Set; + +import org.hibernate.Incubating; + +/** + * @author Steve Ebersole + */ +@Incubating +public interface BytecodeLazyAttributeInterceptor extends SessionAssociableInterceptor { + /** + * The name of the entity this interceptor is meant to intercept + */ + String getEntityName(); + + /** + * The id of the entity instance this interceptor is associated with + */ + Object getIdentifier(); + + /** + * The names of all lazy attributes which have been initialized + */ + @Override + Set getInitializedLazyAttributeNames(); + + /** + * Callback from the enhanced class that an attribute has been read or written + */ + void attributeInitialized(String name); + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java new file mode 100644 index 000000000000..d9d79ad734ef --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java @@ -0,0 +1,267 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.EntityMode; +import org.hibernate.LockMode; +import org.hibernate.bytecode.BytecodeLogger; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SelfDirtinessTracker; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.tuple.entity.EntityTuplizer; +import org.hibernate.type.CompositeType; + +/** + * @author Steve Ebersole + */ +public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInterceptor { + private final Set identifierAttributeNames; + private final CompositeType nonAggregatedCidMapper; + + private final EntityKey entityKey; + + private final boolean inLineDirtyChecking; + private Set writtenFieldNames; + + private boolean initialized; + + public EnhancementAsProxyLazinessInterceptor( + String entityName, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, + EntityKey entityKey, + SharedSessionContractImplementor session) { + super( entityName, session ); + + this.identifierAttributeNames = identifierAttributeNames; + assert identifierAttributeNames != null; + + this.nonAggregatedCidMapper = nonAggregatedCidMapper; + assert nonAggregatedCidMapper != null || identifierAttributeNames.size() == 1; + + this.entityKey = entityKey; + + final EntityPersister entityPersister = session.getFactory().getMetamodel().entityPersister( entityName ); + this.inLineDirtyChecking = entityPersister.getEntityMode() == EntityMode.POJO + && SelfDirtinessTracker.class.isAssignableFrom( entityPersister.getMappedClass() ); + } + + public EntityKey getEntityKey() { + return entityKey; + } + + @Override + protected Object handleRead(Object target, String attributeName, Object value) { + // it is illegal for this interceptor to still be attached to the entity after initialization + if ( initialized ) { + throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); + } + + // the attribute being read is an entity-id attribute + // - we already know the id, return that + if ( identifierAttributeNames.contains( attributeName ) ) { + return extractIdValue( target, attributeName ); + } + + // Use `performWork` to group together multiple Session accesses + return EnhancementHelper.performWork( + this, + (session, isTempSession) -> { + final Object[] writtenValues; + + final EntityPersister entityPersister = session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ); + final EntityTuplizer entityTuplizer = entityPersister.getEntityTuplizer(); + + if ( inLineDirtyChecking && writtenFieldNames != null && !writtenFieldNames.isEmpty() ) { + + // enhancement has dirty-tracking available and at least one attribute was explicitly set + + if ( writtenFieldNames.contains( attributeName ) ) { + // the requested attribute was one of the attributes explicitly set, we can just return the explicitly set value + return entityTuplizer.getPropertyValue( target, attributeName ); + } + + // otherwise we want to save all of the explicitly set values in anticipation of + // the force initialization below so that we can "replay" them after the + // initialization + + writtenValues = new Object[writtenFieldNames.size()]; + + int index = 0; + for ( String writtenFieldName : writtenFieldNames ) { + writtenValues[index] = entityTuplizer.getPropertyValue( target, writtenFieldName ); + index++; + } + } + else { + writtenValues = null; + } + + final Object initializedValue = forceInitialize( + target, + attributeName, + session, + isTempSession + ); + + initialized = true; + + if ( writtenValues != null ) { + // here is the replaying of the explicitly set values we prepared above + int index = 0; + for ( String writtenFieldName : writtenFieldNames ) { + entityTuplizer.setPropertyValue( target, writtenFieldName, writtenValues[index++] ); + } + writtenFieldNames.clear(); + } + + return initializedValue; + }, + getEntityName(), + attributeName + ); + } + + private Object extractIdValue(Object target, String attributeName) { + // access to the id or part of it for non-aggregated cid + if ( nonAggregatedCidMapper == null ) { + return getIdentifier(); + } + else { + return nonAggregatedCidMapper.getPropertyValue( + target, + nonAggregatedCidMapper.getPropertyIndex( attributeName ), + getLinkedSession() + ); + } + } + + public Object forceInitialize(Object target, String attributeName) { + BytecodeLogger.LOGGER.tracef( + "EnhancementAsProxyLazinessInterceptor#forceInitialize : %s#%s -> %s )", + entityKey.getEntityName(), + entityKey.getIdentifier(), + attributeName + ); + + return EnhancementHelper.performWork( + this, + (session, isTemporarySession) -> forceInitialize( target, attributeName, session, isTemporarySession ), + getEntityName(), + attributeName + ); + } + + public Object forceInitialize(Object target, String attributeName, SharedSessionContractImplementor session, boolean isTemporarySession) { + BytecodeLogger.LOGGER.tracef( + "EnhancementAsProxyLazinessInterceptor#forceInitialize : %s#%s -> %s )", + entityKey.getEntityName(), + entityKey.getIdentifier(), + attributeName + ); + + final EntityPersister persister = session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ); + + if ( isTemporarySession ) { + // Add an entry for this entity in the PC of the temp Session + session.getPersistenceContext().addEntity( + target, + Status.READ_ONLY, + // loaded state + ArrayHelper.filledArray( + LazyPropertyInitializer.UNFETCHED_PROPERTY, + Object.class, + persister.getPropertyTypes().length + ), + entityKey, + persister.getVersion( target ), + LockMode.NONE, + // we assume an entry exists in the db + true, + persister, + true + ); + } + + return persister.initializeEnhancedEntityUsedAsProxy( + target, + attributeName, + session + ); + } + + @Override + protected Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue) { + if ( initialized ) { + throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); + } + + if ( identifierAttributeNames.contains( attributeName ) ) { + EnhancementHelper.performWork( + this, + (session, isTempSession) -> session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ) + .getEntityTuplizer() + .getPropertyValue( target, attributeName ), + getEntityName(), + attributeName + ); + } + + if ( ! inLineDirtyChecking ) { + // we need to force-initialize the proxy - the fetch group to which the `attributeName` belongs + try { + forceInitialize( target, attributeName ); + } + finally { + initialized = true; + } + } + else { + // because of the entity being enhanced with `org.hibernate.engine.spi.SelfDirtinessTracker` + // we can skip forcing the initialization. However, in the case of a subsequent read we + // need to know which attributes had been explicitly set so that we can re-play the setters + // after the force-initialization there + if ( writtenFieldNames == null ) { + writtenFieldNames = new HashSet<>(); + } + writtenFieldNames.add( attributeName ); + } + + return newValue; + } + + @Override + public Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + + @Override + public void attributeInitialized(String name) { + if ( initialized ) { + throw new UnsupportedOperationException( "Expected call to EnhancementAsProxyLazinessInterceptor#attributeInitialized" ); + } + } + + @Override + public Object getIdentifier() { + return entityKey.getIdentifier(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java new file mode 100644 index 000000000000..a958e5e8f903 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java @@ -0,0 +1,217 @@ +/* + * 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.bytecode.enhance.spi.interceptor; + +import java.util.Locale; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.hibernate.FlushMode; +import org.hibernate.LazyInitializationException; +import org.hibernate.bytecode.BytecodeLogger; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.SessionFactoryRegistry; +import org.hibernate.mapping.OneToOne; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.Value; + +/** + * @author Steve Ebersole + */ +public class EnhancementHelper { + /** + * Should the given property be included in the owner's base fetch group? + */ + public static boolean includeInBaseFetchGroup( + Property bootMapping, + boolean isEnhanced, + boolean allowEnhancementAsProxy, + Function hasSubclassChecker) { + final Value value = bootMapping.getValue(); + + if ( ! isEnhanced ) { + if ( value instanceof ToOne ) { + if ( ( (ToOne) value ).isUnwrapProxy() ) { + BytecodeLogger.LOGGER.debugf( + "To-one property `%s#%s` was mapped with LAZY + NO_PROXY but the class was not enhanced", + bootMapping.getPersistentClass().getEntityName(), + bootMapping.getName() + ); + } + } + return true; + } + + if ( value instanceof ToOne ) { + final ToOne toOne = (ToOne) value; + if ( toOne.isLazy() ) { + if ( toOne.isUnwrapProxy() ) { + if ( toOne instanceof OneToOne ) { + return false; + } + // include it in the base fetch group so long as the config allows + // using the FK to create an "enhancement proxy" +// return allowEnhancementAsProxy && hasSubclassChecker.apply( toOne.getReferencedEntityName() ); + return allowEnhancementAsProxy; + } + + } + + return true; + } + + return ! bootMapping.isLazy(); + } + + public static T performWork( + BytecodeLazyAttributeInterceptor interceptor, + BiFunction work, + String entityName, + String attributeName) { + SharedSessionContractImplementor session = interceptor.getLinkedSession(); + + boolean isTempSession = false; + boolean isJta = false; + + // first figure out which Session to use + if ( session == null ) { + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); + isTempSession = true; + } + else { + throwLazyInitializationException( Cause.NO_SESSION, entityName, attributeName ); + } + } + else if ( !session.isOpen() ) { + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); + isTempSession = true; + } + else { + throwLazyInitializationException( Cause.CLOSED_SESSION, entityName, attributeName ); + } + } + else if ( !session.isConnected() ) { + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); + isTempSession = true; + } + else { + throwLazyInitializationException( Cause.DISCONNECTED_SESSION, entityName, attributeName); + } + } + + // If we are using a temporary Session, begin a transaction if necessary + if ( isTempSession ) { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork started temporary Session" ); + + isJta = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); + + if ( !isJta ) { + // Explicitly handle the transactions only if we're not in + // a JTA environment. A lazy loading temporary session can + // be created even if a current session and transaction are + // open (ex: session.clear() was used). We must prevent + // multiple transactions. + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork starting transaction on temporary Session" ); + session.beginTransaction(); + } + } + + try { + // do the actual work + return work.apply( session, isTempSession ); + } + finally { + if ( isTempSession ) { + try { + // Commit the JDBC transaction is we started one. + if ( !isJta ) { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork committing transaction on temporary Session" ); + session.getTransaction().commit(); + } + } + catch (Exception e) { + BytecodeLogger.LOGGER.warn( + "Unable to commit JDBC transaction on temporary session used to load lazy " + + "collection associated to no session" + ); + } + + // Close the just opened temp Session + try { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork closing temporary Session" ); + session.close(); + } + catch (Exception e) { + BytecodeLogger.LOGGER.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); + } + } + } + } + + enum Cause { + NO_SESSION, + CLOSED_SESSION, + DISCONNECTED_SESSION, + NO_SF_UUID + } + + private static void throwLazyInitializationException(Cause cause, String entityName, String attributeName) { + final String reason; + switch ( cause ) { + case NO_SESSION: { + reason = "no session and settings disallow loading outside the Session"; + break; + } + case CLOSED_SESSION: { + reason = "session is closed and settings disallow loading outside the Session"; + break; + } + case DISCONNECTED_SESSION: { + reason = "session is disconnected and settings disallow loading outside the Session"; + break; + } + case NO_SF_UUID: { + reason = "could not determine SessionFactory UUId to create temporary Session for loading"; + break; + } + default: { + reason = ""; + } + } + + final String message = String.format( + Locale.ROOT, + "Unable to perform requested lazy initialization [%s.%s] - %s", + entityName, + attributeName, + reason + ); + + throw new LazyInitializationException( message ); + } + + private static SharedSessionContractImplementor openTemporarySessionForLoading( + BytecodeLazyAttributeInterceptor interceptor, + String entityName, + String attributeName) { + if ( interceptor.getSessionFactoryUuid() == null ) { + throwLazyInitializationException( Cause.NO_SF_UUID, entityName, attributeName ); + } + + final SessionFactoryImplementor sf = (SessionFactoryImplementor) + SessionFactoryRegistry.INSTANCE.getSessionFactory( interceptor.getSessionFactoryUuid() ); + final SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession(); + session.getPersistenceContext().setDefaultReadOnly( true ); + session.setHibernateFlushMode( FlushMode.MANUAL ); + return session; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java deleted file mode 100644 index 9459288a1739..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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.bytecode.enhance.spi.interceptor; - -import java.util.Locale; - -import org.hibernate.FlushMode; -import org.hibernate.LazyInitializationException; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.SessionFactoryRegistry; - -import org.jboss.logging.Logger; - -/** - * @author Steve Ebersole - */ -public class Helper { - private static final Logger log = Logger.getLogger( Helper.class ); - - interface Consumer { - SharedSessionContractImplementor getLinkedSession(); - boolean allowLoadOutsideTransaction(); - String getSessionFactoryUuid(); - } - - interface LazyInitializationWork { - T doWork(SharedSessionContractImplementor session, boolean isTemporarySession); - - // informational details - String getEntityName(); - String getAttributeName(); - } - - - private final Consumer consumer; - - public Helper(Consumer consumer) { - this.consumer = consumer; - } - - public T performWork(LazyInitializationWork lazyInitializationWork) { - SharedSessionContractImplementor session = consumer.getLinkedSession(); - - boolean isTempSession = false; - boolean isJta = false; - - // first figure out which Session to use - if ( session == null ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); - isTempSession = true; - } - else { - throwLazyInitializationException( Cause.NO_SESSION, lazyInitializationWork ); - } - } - else if ( !session.isOpen() ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); - isTempSession = true; - } - else { - throwLazyInitializationException( Cause.CLOSED_SESSION, lazyInitializationWork ); - } - } - else if ( !session.isConnected() ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); - isTempSession = true; - } - else { - throwLazyInitializationException( Cause.DISCONNECTED_SESSION, lazyInitializationWork ); - } - } - - // If we are using a temporary Session, begin a transaction if necessary - if ( isTempSession ) { - isJta = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); - - if ( !isJta ) { - // Explicitly handle the transactions only if we're not in - // a JTA environment. A lazy loading temporary session can - // be created even if a current session and transaction are - // open (ex: session.clear() was used). We must prevent - // multiple transactions. - session.beginTransaction(); - } - } - - try { - // do the actual work - return lazyInitializationWork.doWork( session, isTempSession ); - } - finally { - if ( isTempSession ) { - try { - // Commit the JDBC transaction is we started one. - if ( !isJta ) { - session.getTransaction().commit(); - } - } - catch (Exception e) { - log.warn( - "Unable to commit JDBC transaction on temporary session used to load lazy " + - "collection associated to no session" - ); - } - - // Close the just opened temp Session - try { - session.close(); - } - catch (Exception e) { - log.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); - } - } - } - } - - enum Cause { - NO_SESSION, - CLOSED_SESSION, - DISCONNECTED_SESSION, - NO_SF_UUID - } - - private void throwLazyInitializationException(Cause cause, LazyInitializationWork work) { - final String reason; - switch ( cause ) { - case NO_SESSION: { - reason = "no session and settings disallow loading outside the Session"; - break; - } - case CLOSED_SESSION: { - reason = "session is closed and settings disallow loading outside the Session"; - break; - } - case DISCONNECTED_SESSION: { - reason = "session is disconnected and settings disallow loading outside the Session"; - break; - } - case NO_SF_UUID: { - reason = "could not determine SessionFactory UUId to create temporary Session for loading"; - break; - } - default: { - reason = ""; - } - } - - final String message = String.format( - Locale.ROOT, - "Unable to perform requested lazy initialization [%s.%s] - %s", - work.getEntityName(), - work.getAttributeName(), - reason - ); - - throw new LazyInitializationException( message ); - } - - private SharedSessionContractImplementor openTemporarySessionForLoading(LazyInitializationWork lazyInitializationWork) { - if ( consumer.getSessionFactoryUuid() == null ) { - throwLazyInitializationException( Cause.NO_SF_UUID, lazyInitializationWork ); - } - - final SessionFactoryImplementor sf = (SessionFactoryImplementor) - SessionFactoryRegistry.INSTANCE.getSessionFactory( consumer.getSessionFactoryUuid() ); - final SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession(); - session.getPersistenceContext().setDefaultReadOnly( true ); - session.setHibernateFlushMode( FlushMode.MANUAL ); - return session; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java index 58672cc9bf35..86f9b4b1f209 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java @@ -16,47 +16,39 @@ import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.CollectionTracker; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.InterceptorImplementor; -import org.hibernate.bytecode.enhance.spi.interceptor.Helper.Consumer; -import org.hibernate.bytecode.enhance.spi.interceptor.Helper.LazyInitializationWork; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.persister.entity.EntityPersister; -import org.jboss.logging.Logger; - /** * Interceptor that loads attributes lazily * * @author Luis Barreiro * @author Steve Ebersole */ -public class LazyAttributeLoadingInterceptor - implements PersistentAttributeInterceptor, Consumer, InterceptorImplementor { - private static final Logger log = Logger.getLogger( LazyAttributeLoadingInterceptor.class ); - - private final String entityName; +public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor { + private final Object identifier; private final Set lazyFields; - private Set initializedLazyFields; - private transient SharedSessionContractImplementor session; - private boolean allowLoadOutsideTransaction; - private String sessionFactoryUuid; - public LazyAttributeLoadingInterceptor( String entityName, + Object identifier, Set lazyFields, SharedSessionContractImplementor session) { - this.entityName = entityName; + super( entityName, session ); + this.identifier = identifier; this.lazyFields = lazyFields; + } - setSession( session ); + @Override + public Object getIdentifier() { + return identifier; } - protected final Object intercept(Object target, String attributeName, Object value) { + @Override + protected Object handleRead(Object target, String attributeName, Object value) { if ( !isAttributeLoaded( attributeName ) ) { Object loadedValue = fetchAttribute( target, attributeName ); attributeInitialized( attributeName ); @@ -65,6 +57,14 @@ protected final Object intercept(Object target, String attributeName, Object val return value; } + @Override + protected Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue) { + if ( !isAttributeLoaded( attributeName ) ) { + attributeInitialized( attributeName ); + } + return newValue; + } + /** * Fetches the lazy attribute. The attribute does not get associated with the entity. (To be used by hibernate methods) */ @@ -73,72 +73,48 @@ public Object fetchAttribute(final Object target, final String attributeName) { } protected Object loadAttribute(final Object target, final String attributeName) { - return new Helper( this ).performWork( - new LazyInitializationWork() { - @Override - public Object doWork(SharedSessionContractImplementor session, boolean isTemporarySession) { - final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( getEntityName() ); - - if ( isTemporarySession ) { - final Serializable id = persister.getIdentifier( target, null ); - - // Add an entry for this entity in the PC of the temp Session - // NOTE : a few arguments that would be nice to pass along here... - // 1) loadedState if we know any - final Object[] loadedState = null; - // 2) does a row exist in the db for this entity? - final boolean existsInDb = true; - session.getPersistenceContext().addEntity( - target, - Status.READ_ONLY, - loadedState, - session.generateEntityKey( id, persister ), - persister.getVersion( target ), - LockMode.NONE, - existsInDb, - persister, - true - ); - } - - final LazyPropertyInitializer initializer = (LazyPropertyInitializer) persister; - final Object loadedValue = initializer.initializeLazyProperty( - attributeName, + return EnhancementHelper.performWork( + this, + (session, isTemporarySession) -> { + final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( getEntityName() ); + + if ( isTemporarySession ) { + final Serializable id = persister.getIdentifier( target, null ); + + // Add an entry for this entity in the PC of the temp Session + // NOTE : a few arguments that would be nice to pass along here... + // 1) loadedState if we know any + final Object[] loadedState = null; + // 2) does a row exist in the db for this entity? + final boolean existsInDb = true; + session.getPersistenceContext().addEntity( target, - session + Status.READ_ONLY, + loadedState, + session.generateEntityKey( id, persister ), + persister.getVersion( target ), + LockMode.NONE, + existsInDb, + persister, + true ); - - takeCollectionSizeSnapshot( target, attributeName, loadedValue ); - return loadedValue; } - @Override - public String getEntityName() { - return entityName; - } - - @Override - public String getAttributeName() { - return attributeName; - } - } + final LazyPropertyInitializer initializer = (LazyPropertyInitializer) persister; + final Object loadedValue = initializer.initializeLazyProperty( + attributeName, + target, + session + ); + + takeCollectionSizeSnapshot( target, attributeName, loadedValue ); + return loadedValue; + }, + getEntityName(), + attributeName ); } - public final void setSession(SharedSessionContractImplementor session) { - this.session = session; - if ( session != null && !allowLoadOutsideTransaction ) { - this.allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); - if ( this.allowLoadOutsideTransaction ) { - this.sessionFactoryUuid = session.getFactory().getUuid(); - } - } - } - - public final void unsetSession() { - this.session = null; - } - public boolean isAttributeLoaded(String fieldName) { return !isLazyAttribute( fieldName ) || isInitializedLazyField( fieldName ); } @@ -171,13 +147,11 @@ public boolean hasAnyUninitializedAttributes() { @Override public String toString() { - return "LazyAttributeLoader(entityName=" + entityName + " ,lazyFields=" + lazyFields + ')'; + return "LazyAttributeLoader(entityName=" + getEntityName() + " ,lazyFields=" + lazyFields + ')'; } - // - private void takeCollectionSizeSnapshot(Object target, String fieldName, Object value) { - if ( value != null && value instanceof Collection && target instanceof SelfDirtinessTracker ) { + if ( value instanceof Collection && target instanceof SelfDirtinessTracker ) { CollectionTracker tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker(); if ( tracker == null ) { ( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes(); @@ -187,152 +161,20 @@ private void takeCollectionSizeSnapshot(Object target, String fieldName, Object } } - @Override - public boolean readBoolean(Object obj, String name, boolean oldValue) { - return (Boolean) intercept( obj, name, oldValue ); - } - - @Override - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public byte readByte(Object obj, String name, byte oldValue) { - return (Byte) intercept( obj, name, oldValue ); - } - - @Override - public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public char readChar(Object obj, String name, char oldValue) { - return (Character) intercept( obj, name, oldValue ); - } - - @Override - public char writeChar(Object obj, String name, char oldValue, char newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public short readShort(Object obj, String name, short oldValue) { - return (Short) intercept( obj, name, oldValue ); - } - - @Override - public short writeShort(Object obj, String name, short oldValue, short newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public int readInt(Object obj, String name, int oldValue) { - return (Integer) intercept( obj, name, oldValue ); - } - - @Override - public int writeInt(Object obj, String name, int oldValue, int newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public float readFloat(Object obj, String name, float oldValue) { - return (Float) intercept( obj, name, oldValue ); - } - - @Override - public float writeFloat(Object obj, String name, float oldValue, float newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public double readDouble(Object obj, String name, double oldValue) { - return (Double) intercept( obj, name, oldValue ); - } - - @Override - public double writeDouble(Object obj, String name, double oldValue, double newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public long readLong(Object obj, String name, long oldValue) { - return (Long) intercept( obj, name, oldValue ); - } - - @Override - public long writeLong(Object obj, String name, long oldValue, long newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public Object readObject(Object obj, String name, Object oldValue) { - return intercept( obj, name, oldValue ); - } - - @Override - public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public SharedSessionContractImplementor getLinkedSession() { - return session; - } - - @Override - public boolean allowLoadOutsideTransaction() { - return allowLoadOutsideTransaction; - } - - @Override - public String getSessionFactoryUuid() { - return sessionFactoryUuid; - } - @Override public void attributeInitialized(String name) { if ( !isLazyAttribute( name ) ) { return; } if ( initializedLazyFields == null ) { - initializedLazyFields = new HashSet(); + initializedLazyFields = new HashSet<>(); } initializedLazyFields.add( name ); } @Override public Set getInitializedLazyAttributeNames() { - return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields; + return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields; } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java index 312bea07fdad..380b17711ce1 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; @@ -29,12 +30,12 @@ public class LazyAttributesMetadata implements Serializable { /** * Build a LazyFetchGroupMetadata based on the attributes defined for the * PersistentClass - * - * @param mappedEntity The entity definition - * - * @return The built LazyFetchGroupMetadata */ - public static LazyAttributesMetadata from(PersistentClass mappedEntity) { + public static LazyAttributesMetadata from( + PersistentClass mappedEntity, + boolean isEnhanced, + boolean allowEnhancementAsProxy, + Function hasSubclassChecker) { final Map lazyAttributeDescriptorMap = new LinkedHashMap<>(); final Map> fetchGroupToAttributesMap = new HashMap<>(); @@ -44,7 +45,13 @@ public static LazyAttributesMetadata from(PersistentClass mappedEntity) { while ( itr.hasNext() ) { i++; final Property property = (Property) itr.next(); - if ( property.isLazy() ) { + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + property, + isEnhanced, + allowEnhancementAsProxy, + hasSubclassChecker + ); + if ( lazy ) { final LazyAttributeDescriptor lazyAttributeDescriptor = LazyAttributeDescriptor.from( property, i, x++ ); lazyAttributeDescriptorMap.put( lazyAttributeDescriptor.getName(), lazyAttributeDescriptor ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java new file mode 100644 index 000000000000..7f60ffddbc19 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java @@ -0,0 +1,25 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public interface SessionAssociableInterceptor extends PersistentAttributeInterceptor { + SharedSessionContractImplementor getLinkedSession(); + + void setSession(SharedSessionContractImplementor session); + + void unsetSession(); + + boolean allowLoadOutsideTransaction(); + + String getSessionFactoryUuid(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java index b71ba5aad200..3e932fce024d 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java @@ -6,8 +6,11 @@ */ package org.hibernate.bytecode.spi; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -37,6 +40,7 @@ public interface BytecodeEnhancementMetadata { * Build and inject an interceptor instance into the enhanced entity. * * @param entity The entity into which built interceptor should be injected + * @param identifier * @param session The session to which the entity instance belongs. * * @return The built and injected interceptor @@ -45,8 +49,19 @@ public interface BytecodeEnhancementMetadata { */ LazyAttributeLoadingInterceptor injectInterceptor( Object entity, + Object identifier, SharedSessionContractImplementor session) throws NotInstrumentedException; + void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session); + + void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session); + /** * Extract the field interceptor instance from the enhanced entity. * @@ -58,6 +73,8 @@ LazyAttributeLoadingInterceptor injectInterceptor( */ LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException; + BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException; + boolean hasUnFetchedAttributes(Object entity); boolean isAttributeLoaded(Object entity, String attributeName); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index e8634f160f36..3ee971e385d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -859,6 +859,27 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String ENFORCE_LEGACY_PROXY_CLASSNAMES = "hibernate.bytecode.enforce_legacy_proxy_classnames"; + /** + * Should Hibernate use enhanced entities "as a proxy"? + * + * E.g., when an application uses {@link org.hibernate.Session#load} against an enhanced + * class, enabling this will allow Hibernate to create an "empty" instance of the enhanced + * class to act as the proxy - it contains just the identifier which is later used to + * trigger the base initialization but no other data is loaded + * + * Not enabling this (the legacy default behavior) would cause the "base" attributes to + * be loaded. Any lazy-group attributes would not be initialized. + * + * Applications using bytecode enhancement and switching to allowing this should be careful + * in use of the various {@link org.hibernate.Hibernate} methods such as + * {@link org.hibernate.Hibernate#isInitialized}, + * {@link org.hibernate.Hibernate#isPropertyInitialized}, etc - enabling this setting changes + * the results of those methods + * + * @implSpec See {@link org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor} + */ + String ALLOW_ENHANCEMENT_AS_PROXY = "hibernate.bytecode.allow_enhancement_as_proxy"; + /** * The classname of the HQL query parser factory */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java index bb87bced99ba..714f2f932b15 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java @@ -16,12 +16,15 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.Session; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CachedNaturalIdValueSource; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryExtraState; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -345,6 +348,15 @@ private boolean isUnequivocallyNonDirty(Object entity) { return ! persister.hasCollections() && ! ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes(); } + if ( entity instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // we never have to check an uninitialized proxy + return true; + } + } + final CustomEntityDirtinessStrategy customEntityDirtinessStrategy = getPersistenceContext().getSession().getFactory().getCustomEntityDirtinessStrategy(); if ( customEntityDirtinessStrategy.canDirtyCheck( entity, getPersister(), (Session) getPersistenceContext().getSession() ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index 6afa9ce858d5..7d9d97cecaa4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -94,7 +94,7 @@ public static void cascade( final String propertyName = propertyNames[ i ]; final boolean isUninitializedProperty = hasUninitializedLazyProperties && - !persister.getInstrumentationMetadata().isAttributeLoaded( parent, propertyName ); + !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); if ( style.doCascade( action ) ) { final Object child; @@ -133,7 +133,7 @@ else if ( types[ i ].isComponentType() ) { else if ( action.performOnLazyProperty() && types[ i ].isEntityType() ) { // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() // returns true. - LazyAttributeLoadingInterceptor interceptor = persister.getInstrumentationMetadata() + LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() .extractInterceptor( parent ); child = interceptor.fetchAttribute( parent, propertyName ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java index 1153f9d1f3f1..f4b4a61a961f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java @@ -164,9 +164,10 @@ public static void processReachableCollection( //TODO: better to pass the id in as an argument? ce.setCurrentKey( type.getKeyOfOwner( entity, session ) ); - final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getInstrumentationMetadata().isEnhancedForLazyLoading(); + final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); if ( isBytecodeEnhanced && !collection.wasInitialized() ) { - // skip it + // the class of the collection owner is enhanced for lazy loading and we found an un-initialized PersistentCollection + // - skip it if ( LOG.isDebugEnabled() ) { LOG.debugf( "Skipping uninitialized bytecode-lazy collection: %s", @@ -175,57 +176,58 @@ public static void processReachableCollection( } ce.setReached( true ); ce.setProcessed( true ); + return; } - else { - // The CollectionEntry.isReached() stuff is just to detect any silly users - // who set up circular or shared references between/to collections. - if ( ce.isReached() ) { - // We've been here before - throw new HibernateException( - "Found shared references to a collection: " + type.getRole() + + // The CollectionEntry.isReached() stuff is just to detect any silly users + // who set up circular or shared references between/to collections. + if ( ce.isReached() ) { + // We've been here before + throw new HibernateException( + "Found shared references to a collection: " + type.getRole() + ); + } + + ce.setReached( true ); + + if ( LOG.isDebugEnabled() ) { + if ( collection.wasInitialized() ) { + LOG.debugf( + "Collection found: %s, was: %s (initialized)", + MessageHelper.collectionInfoString( + persister, + collection, + ce.getCurrentKey(), + session + ), + MessageHelper.collectionInfoString( + ce.getLoadedPersister(), + collection, + ce.getLoadedKey(), + session + ) ); } - ce.setReached( true ); - - if ( LOG.isDebugEnabled() ) { - if ( collection.wasInitialized() ) { - LOG.debugf( - "Collection found: %s, was: %s (initialized)", - MessageHelper.collectionInfoString( - persister, - collection, - ce.getCurrentKey(), - session - ), - MessageHelper.collectionInfoString( - ce.getLoadedPersister(), - collection, - ce.getLoadedKey(), - session - ) - ); - } - else { - LOG.debugf( - "Collection found: %s, was: %s (uninitialized)", - MessageHelper.collectionInfoString( - persister, - collection, - ce.getCurrentKey(), - session - ), - MessageHelper.collectionInfoString( - ce.getLoadedPersister(), - collection, - ce.getLoadedKey(), - session - ) - ); - } + else { + LOG.debugf( + "Collection found: %s, was: %s (uninitialized)", + MessageHelper.collectionInfoString( + persister, + collection, + ce.getCurrentKey(), + session + ), + MessageHelper.collectionInfoString( + ce.getLoadedPersister(), + collection, + ce.getLoadedKey(), + session + ) + ); } - - prepareCollectionForUpdate( collection, ce, factory ); } + + prepareCollectionForUpdate( collection, ce, factory ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java index 78f6b97cc3b4..879533baffb4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java @@ -177,10 +177,7 @@ private Object initializeIfNecessary( // superclass or the same as the entity type of a nullifiable entity). // It is unclear if a more complicated check would impact performance // more than just initializing the associated entity. - return persister - .getInstrumentationMetadata() - .extractInterceptor( self ) - .fetchAttribute( self, propertyName ); + return ( (LazyPropertyInitializer) persister ).initializeLazyProperty( propertyName, self, session ); } else { return value; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 28bf7af324db..629f5e3a9e2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -31,6 +31,8 @@ import org.hibernate.PersistentObjectException; import org.hibernate.TransientObjectException; import org.hibernate.action.spi.AfterTransactionCompletionProcess; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.access.SoftLock; @@ -495,6 +497,8 @@ public EntityEntry addEntry( final boolean existsInDatabase, final EntityPersister persister, final boolean disableVersionIncrement) { + assert lockMode != null; + final EntityEntry e; /* @@ -571,15 +575,29 @@ public boolean containsProxy(Object entity) { @Override public boolean reassociateIfUninitializedProxy(Object value) throws MappingException { - if ( !Hibernate.isInitialized( value ) ) { - final HibernateProxy proxy = (HibernateProxy) value; - final LazyInitializer li = proxy.getHibernateLazyInitializer(); - reassociateProxy( li, proxy ); - return true; - } - else { - return false; + if ( ! Hibernate.isInitialized( value ) ) { + + // could be a proxy.... + if ( value instanceof HibernateProxy ) { + final HibernateProxy proxy = (HibernateProxy) value; + final LazyInitializer li = proxy.getHibernateLazyInitializer(); + reassociateProxy( li, proxy ); + return true; + } + + // or an uninitialized enhanced entity ("bytecode proxy")... + if ( value instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable bytecodeProxy = (PersistentAttributeInterceptable) value; + final BytecodeLazyAttributeInterceptor interceptor = (BytecodeLazyAttributeInterceptor) bytecodeProxy.$$_hibernate_getInterceptor(); + if ( interceptor != null ) { + interceptor.setSession( getSession() ); + } + return true; + } + } + + return false; } @Override @@ -636,6 +654,14 @@ public Object unproxyAndReassociate(Object maybeProxy) throws HibernateException //initialize + unwrap the object and return it return li.getImplementation(); } + else if ( maybeProxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) maybeProxy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( maybeProxy, null ); + } + return maybeProxy; + } else { return maybeProxy; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java index 3b426effe5ef..f687a6f2aab8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java @@ -341,7 +341,7 @@ private static Boolean isEagerFetchProfile(SharedSessionContractImplementor sess return true; } } - + return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java index 4a448e8bb488..9e3dccaed94e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java @@ -256,7 +256,7 @@ private void endLoadingCollection(LoadingCollectionEntry lce, CollectionPersiste // If the owner is bytecode-enhanced and the owner's collection value is uninitialized, // then go ahead and set it to the newly initialized collection. final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = - persister.getOwnerEntityPersister().getInstrumentationMetadata(); + persister.getOwnerEntityPersister().getBytecodeEnhancementMetadata(); if ( bytecodeEnhancementMetadata.isEnhancedForLazyLoading() ) { // Lazy properties in embeddables/composites are not currently supported for embeddables (HHH-10480), // so check to make sure the collection is not in an embeddable before checking to see if diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java index 38bf8090beac..61e80eca6da2 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java @@ -6,47 +6,76 @@ */ package org.hibernate.engine.spi; +import java.util.Collections; +import java.util.Set; + +import org.hibernate.Incubating; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.InterceptorImplementor; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; /** + * The base contract for interceptors that can be injected into + * enhanced entities for the purpose of intercepting attribute access + * * @author Steve Ebersole + * + * @see PersistentAttributeInterceptable */ +@Incubating +@SuppressWarnings("unused") public interface PersistentAttributeInterceptor extends InterceptorImplementor { + boolean readBoolean(Object obj, String name, boolean oldValue); - public boolean readBoolean(Object obj, String name, boolean oldValue); + boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); + byte readByte(Object obj, String name, byte oldValue); - public byte readByte(Object obj, String name, byte oldValue); + byte writeByte(Object obj, String name, byte oldValue, byte newValue); - public byte writeByte(Object obj, String name, byte oldValue, byte newValue); + char readChar(Object obj, String name, char oldValue); - public char readChar(Object obj, String name, char oldValue); + char writeChar(Object obj, String name, char oldValue, char newValue); - public char writeChar(Object obj, String name, char oldValue, char newValue); + short readShort(Object obj, String name, short oldValue); - public short readShort(Object obj, String name, short oldValue); + short writeShort(Object obj, String name, short oldValue, short newValue); - public short writeShort(Object obj, String name, short oldValue, short newValue); + int readInt(Object obj, String name, int oldValue); - public int readInt(Object obj, String name, int oldValue); + int writeInt(Object obj, String name, int oldValue, int newValue); - public int writeInt(Object obj, String name, int oldValue, int newValue); + float readFloat(Object obj, String name, float oldValue); - public float readFloat(Object obj, String name, float oldValue); + float writeFloat(Object obj, String name, float oldValue, float newValue); - public float writeFloat(Object obj, String name, float oldValue, float newValue); + double readDouble(Object obj, String name, double oldValue); - public double readDouble(Object obj, String name, double oldValue); + double writeDouble(Object obj, String name, double oldValue, double newValue); - public double writeDouble(Object obj, String name, double oldValue, double newValue); + long readLong(Object obj, String name, long oldValue); - public long readLong(Object obj, String name, long oldValue); + long writeLong(Object obj, String name, long oldValue, long newValue); - public long writeLong(Object obj, String name, long oldValue, long newValue); + Object readObject(Object obj, String name, Object oldValue); - public Object readObject(Object obj, String name, Object oldValue); + Object writeObject(Object obj, String name, Object oldValue, Object newValue); - public Object writeObject(Object obj, String name, Object oldValue, Object newValue); + /** + * @deprecated Just as the method it overrides. Interceptors that deal with + * lazy state should implement {@link BytecodeLazyAttributeInterceptor} + */ + @Deprecated + @Override + default Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + /** + * @deprecated Just as the method it overrides. Interceptors that deal with + * lazy state should implement {@link BytecodeLazyAttributeInterceptor} + */ + @Override + @Deprecated + default void attributeInitialized(String name) { + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java index d0db9689f6e0..4b205bc5246e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java @@ -246,12 +246,22 @@ void initializeCollection(PersistentCollection collection, boolean writing) Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) throws HibernateException; + default Object internalLoad( + String entityName, + Serializable id, + boolean eager, + boolean nullable, + Boolean unwrapProxy) throws HibernateException { + return internalLoad( entityName, id, eager, nullable ); + } + /** * Load an instance immediately. This method is only called when lazily initializing a proxy. * Do not return the proxy. */ Object immediateLoad(String entityName, Serializable id) throws HibernateException; + /** * Execute a find() query */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java index 39806b7d69f8..76ce2d161164 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java @@ -363,7 +363,7 @@ protected boolean visitCollectionsBeforeSave( Object[] values, Type[] types, EventSource source) { - WrapVisitor visitor = new WrapVisitor( source ); + WrapVisitor visitor = new WrapVisitor( entity, id, source ); // substitutes into values by side-effect visitor.processEntityPropertyValues( values, types ); return visitor.isSubstitutionRequired(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java index e160b24e3144..d715abd10fe7 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java @@ -69,8 +69,6 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i * Handle the given load event. * * @param event The load event to be handled. - * - * @throws HibernateException */ public void onLoad( final LoadEvent event, @@ -102,7 +100,7 @@ protected EntityPersister getPersister( final LoadEvent event ) { ); } else { - return event.getSession().getFactory().getEntityPersister( event.getEntityClassName() ); + return event.getSession().getFactory().getMetamodel().entityPersister( event.getEntityClassName() ); } } @@ -157,7 +155,7 @@ private void checkIdClass( loadType, persister, dependentIdType, - event.getSession().getFactory().getEntityPersister( dependentParentType.getAssociatedEntityName() ) + event.getSession().getFactory().getMetamodel().entityPersister( dependentParentType.getAssociatedEntityName() ) ); return; } @@ -196,8 +194,6 @@ private void loadByDerivedIdentitySimplePkValue( * @param options The defined load options * * @return The loaded entity. - * - * @throws HibernateException */ private Object load( final LoadEvent event, @@ -260,27 +256,108 @@ private Object proxyOrLoad( ); } - // this class has no proxies (so do a shortcut) - if ( !persister.hasProxy() ) { - return load( event, persister, keyToLoad, options ); - } - final PersistenceContext persistenceContext = event.getSession().getPersistenceContext(); - // look for a proxy - Object proxy = persistenceContext.getProxy( keyToLoad ); - if ( proxy != null ) { - return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy ); + final boolean allowBytecodeProxy = event.getSession() + .getFactory() + .getSessionFactoryOptions() + .isEnhancementAsProxyEnabled(); + + final boolean entityHasHibernateProxyFactory = persister.getEntityMetamodel() + .getTuplizer() + .getProxyFactory() != null; + + // Check for the case where we can use the entity itself as a proxy + if ( options.isAllowProxyCreation() + && allowBytecodeProxy + && persister.getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + // if there is already a managed entity instance associated with the PC, return it + final Object managed = persistenceContext.getEntity( keyToLoad ); + if ( managed != null ) { + if ( options.isCheckDeleted() ) { + final EntityEntry entry = persistenceContext.getEntry( managed ); + final Status status = entry.getStatus(); + if ( status == Status.DELETED || status == Status.GONE ) { + return null; + } + } + return managed; + } + + // if the entity defines a HibernateProxy factory, see if there is an + // existing proxy associated with the PC - and if so, use it + if ( entityHasHibernateProxyFactory ) { + final Object proxy = persistenceContext.getProxy( keyToLoad ); + + if ( proxy != null ) { + LOG.trace( "Entity proxy found in session cache" ); + + LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer(); + + if ( li.isUnwrap() || event.getShouldUnwrapProxy() ) { + return li.getImplementation(); + } + + + return persistenceContext.narrowProxy( proxy, persister, keyToLoad, null ); + } + + // specialized handling for entities with subclasses with a HibernateProxy factory + if ( persister.getEntityMetamodel().hasSubclasses() ) { + // entities with subclasses that define a ProxyFactory can create + // a HibernateProxy so long as NO_PROXY was not specified. + if ( event.getShouldUnwrapProxy() != null && event.getShouldUnwrapProxy() ) { + LOG.debugf( "Ignoring NO_PROXY for to-one association with subclasses to honor laziness" ); + } + return createProxy( event, persister, keyToLoad, persistenceContext ); + } + } + + // This is the crux of HHH-11147 + // create the (uninitialized) entity instance - has only id set + final Object entity = persister.getEntityTuplizer().instantiate( + keyToLoad.getIdentifier(), + event.getSession() + ); + + // add the entity instance to the persistence context + persistenceContext.addEntity( + entity, + Status.MANAGED, + null, + keyToLoad, + null, + LockMode.NONE, + true, + persister, + true + ); + + persister.getEntityMetamodel() + .getBytecodeEnhancementMetadata() + .injectEnhancedEntityAsProxyInterceptor( entity, keyToLoad, event.getSession() ); + + return entity; } + else { + if ( persister.hasProxy() ) { + // look for a proxy + Object proxy = persistenceContext.getProxy( keyToLoad ); + if ( proxy != null ) { + return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy ); + } - if ( options.isAllowProxyCreation() ) { - return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext ); + if ( options.isAllowProxyCreation() ) { + return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext ); + } + } } // return a newly loaded object return load( event, persister, keyToLoad, options ); } + /** * Given a proxy, initialize it and/or narrow it provided either * is necessary. @@ -304,10 +381,13 @@ private Object returnNarrowedProxy( if ( traceEnabled ) { LOG.trace( "Entity proxy found in session cache" ); } + LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer(); + if ( li.isUnwrap() ) { return li.getImplementation(); } + Object impl = null; if ( !options.isAllowProxyCreation() ) { impl = load( event, persister, keyToLoad, options ); @@ -318,6 +398,7 @@ private Object returnNarrowedProxy( .handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier() ); } } + return persistenceContext.narrowProxy( proxy, persister, keyToLoad, impl ); } @@ -358,6 +439,14 @@ private Object createProxyIfNecessary( if ( traceEnabled ) { LOG.trace( "Creating new proxy for entity" ); } + return createProxy( event, persister, keyToLoad, persistenceContext ); + } + + private Object createProxy( + LoadEvent event, + EntityPersister persister, + EntityKey keyToLoad, + PersistenceContext persistenceContext) { // return new uninitialized proxy Object proxy = persister.createProxy( event.getEntityId(), event.getSession() ); persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad ); @@ -376,8 +465,6 @@ private Object createProxyIfNecessary( * @param source The originating session * * @return The loaded entity - * - * @throws HibernateException */ private Object lockAndLoad( final LoadEvent event, @@ -505,6 +592,7 @@ private Object doLoad( * * @return The object loaded from the datasource, or null if not found. */ + @SuppressWarnings("WeakerAccess") protected Object loadFromDatasource( final LoadEvent event, final EntityPersister persister) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java index 7f587cb585f0..f7458af096ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java @@ -17,12 +17,15 @@ import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -109,26 +112,41 @@ public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateExcepti final EventSource source = event.getSession(); final Object original = event.getOriginal(); - if ( original != null ) { + // NOTE : `original` is the value being merged + if ( original != null ) { final Object entity; if ( original instanceof HibernateProxy ) { LazyInitializer li = ( (HibernateProxy) original ).getHibernateLazyInitializer(); if ( li.isUninitialized() ) { LOG.trace( "Ignoring uninitialized proxy" ); event.setResult( source.load( li.getEntityName(), li.getIdentifier() ) ); - return; //EARLY EXIT! + //EARLY EXIT! + return; } else { entity = li.getImplementation(); } } + else if ( original instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) original; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor; + LOG.trace( "Ignoring uninitialized enhanced-proxy" ); + event.setResult( source.load( proxyInterceptor.getEntityName(), (Serializable) proxyInterceptor.getIdentifier() ) ); + //EARLY EXIT! + return; + } + else { + entity = original; + } + } else { entity = original; } - if ( copyCache.containsKey( entity ) && - ( copyCache.isOperatedOn( entity ) ) ) { + if ( copyCache.containsKey( entity ) && ( copyCache.isOperatedOn( entity ) ) ) { LOG.trace( "Already in merge process" ); event.setResult( entity ); } @@ -210,36 +228,50 @@ protected void entityIsTransient(MergeEvent event, Map copyCache) { LOG.trace( "Merging transient instance" ); final Object entity = event.getEntity(); - final EventSource source = event.getSession(); + final EventSource session = event.getSession(); final String entityName = event.getEntityName(); - final EntityPersister persister = source.getEntityPersister( entityName, entity ); + final EntityPersister persister = session.getEntityPersister( entityName, entity ); - final Serializable id = persister.hasIdentifierProperty() ? - persister.getIdentifier( entity, source ) : - null; - if ( copyCache.containsKey( entity ) ) { - persister.setIdentifier( copyCache.get( entity ), id, source ); + final Serializable id = persister.hasIdentifierProperty() + ? persister.getIdentifier( entity, session ) + : null; + + final Object copy; + final Object existingCopy = copyCache.get( entity ); + if ( existingCopy != null ) { + persister.setIdentifier( copyCache.get( entity ), id, session ); + copy = existingCopy; } else { - ( (MergeContext) copyCache ).put( entity, source.instantiate( persister, id ), true ); //before cascade! + copy = session.instantiate( persister, id ); + + //before cascade! + ( (MergeContext) copyCache ).put( entity, copy, true ); } - final Object copy = copyCache.get( entity ); // cascade first, so that all unsaved objects get their // copy created before we actually copy //cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE); - super.cascadeBeforeSave( source, persister, entity, copyCache ); - copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.FROM_PARENT ); + super.cascadeBeforeSave( session, persister, entity, copyCache ); + copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.FROM_PARENT ); - saveTransientEntity( copy, entityName, event.getRequestedId(), source, copyCache ); + saveTransientEntity( copy, entityName, event.getRequestedId(), session, copyCache ); // cascade first, so that all unsaved objects get their // copy created before we actually copy - super.cascadeAfterSave( source, persister, entity, copyCache ); - copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.TO_PARENT ); + super.cascadeAfterSave( session, persister, entity, copyCache ); + copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.TO_PARENT ); event.setResult( copy ); + + if ( copy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) copy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor == null ) { + persister.getBytecodeEnhancementMetadata().injectInterceptor( copy, id, session ); + } + } } private void saveTransientEntity( @@ -283,11 +315,12 @@ protected void entityIsDetached(MergeEvent event, Map copyCache) { String previousFetchProfile = source.getLoadQueryInfluencers().getInternalFetchProfile(); source.getLoadQueryInfluencers().setInternalFetchProfile( "merge" ); + //we must clone embedded composite identifiers, or //we will get back the same instance that we pass in - final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType() - .deepCopy( id, source.getFactory() ); + final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType().deepCopy( id, source.getFactory() ); final Object result = source.get( entityName, clonedIdentifier ); + source.getLoadQueryInfluencers().setInternalFetchProfile( previousFetchProfile ); if ( result == null ) { @@ -301,9 +334,11 @@ protected void entityIsDetached(MergeEvent event, Map copyCache) { entityIsTransient( event, copyCache ); } else { - ( (MergeContext) copyCache ).put( entity, result, true ); //before cascade! + // before cascade! + ( (MergeContext) copyCache ).put( entity, result, true ); + + final Object target = unproxyManagedForDetachedMerging( entity, result, persister, source ); - final Object target = source.getPersistenceContext().unproxy( result ); if ( target == entity ) { throw new AssertionFailure( "entity was not detached" ); } @@ -334,6 +369,43 @@ else if ( isVersionChanged( entity, source, persister, target ) ) { } + private Object unproxyManagedForDetachedMerging( + Object incoming, + Object managed, + EntityPersister persister, + EventSource source) { + if ( incoming instanceof HibernateProxy ) { + return source.getPersistenceContext().unproxy( managed ); + } + + if ( incoming instanceof PersistentAttributeInterceptable + && persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() + && source.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() ) { + + final PersistentAttributeInterceptor incomingInterceptor = ( (PersistentAttributeInterceptable) incoming ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor managedInterceptor = ( (PersistentAttributeInterceptable) managed ).$$_hibernate_getInterceptor(); + + // todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but + // with different attributes initialized? + // - for now, assume we do not... + + // if the managed entity is not a proxy, we can just return it + if ( ! ( managedInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) ) { + return managed; + } + + // if the incoming entity is still a proxy there is no need to force initialization of the managed one + if ( incomingInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return managed; + } + + // otherwise, force initialization + persister.initializeEnhancedEntityUsedAsProxy( managed, null, source ); + } + + return managed; + } + private void markInterceptorDirty(final Object entity, final Object target, EntityPersister persister) { // for enhanced entities, copy over the dirty attributes if ( entity instanceof SelfDirtinessTracker && target instanceof SelfDirtinessTracker ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java index a506004105a0..c5cb4caada6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java @@ -21,17 +21,20 @@ * @author Gavin King */ public class FlushVisitor extends AbstractVisitor { - private Object owner; - Object processCollection(Object collection, CollectionType type) - throws HibernateException { + FlushVisitor(EventSource session, Object owner) { + super(session); + this.owner = owner; + } + + Object processCollection(Object collection, CollectionType type) throws HibernateException { - if (collection==CollectionType.UNFETCHED_COLLECTION) { + if ( collection == CollectionType.UNFETCHED_COLLECTION ) { return null; } - if (collection!=null) { + if ( collection != null ) { final PersistentCollection coll; if ( type.hasHolder() ) { coll = getSession().getPersistenceContext().getCollectionHolder(collection); @@ -55,9 +58,4 @@ boolean includeEntityProperty(Object[] values, int i) { return true; } - FlushVisitor(EventSource session, Object owner) { - super(session); - this.owner = owner; - } - } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java index 923d9389d1c1..0ddbc19b8bdf 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java @@ -6,8 +6,11 @@ */ package org.hibernate.event.internal; +import java.io.Serializable; + import org.hibernate.EntityMode; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionImplementor; @@ -25,10 +28,19 @@ * * @author Gavin King */ +@SuppressWarnings("WeakerAccess") public class WrapVisitor extends ProxyVisitor { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( WrapVisitor.class ); + private Object entity; + private Serializable id; + + private boolean substitute; - boolean substitute; + public WrapVisitor(Object entity, Serializable id, EventSource session) { + super( session ); + this.entity = entity; + this.id = id; + } boolean isSubstitutionRequired() { return substitute; @@ -42,20 +54,26 @@ boolean isSubstitutionRequired() { Object processCollection(Object collection, CollectionType collectionType) throws HibernateException { - if ( collection != null && ( collection instanceof PersistentCollection ) ) { + if ( collection == null ) { + return null; + } + if ( collection == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + return null; + } + + if ( collection instanceof PersistentCollection ) { + final PersistentCollection coll = (PersistentCollection) collection; final SessionImplementor session = getSession(); - PersistentCollection coll = (PersistentCollection) collection; + if ( coll.setCurrentSession( session ) ) { reattachCollection( coll, collectionType ); } - return null; - } - else { - return processArrayOrNewCollection( collection, collectionType ); + return null; } + return processArrayOrNewCollection( collection, collectionType ); } final Object processArrayOrNewCollection(Object collection, CollectionType collectionType) diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java index ed186ab39527..eccbe18a34cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java @@ -47,6 +47,8 @@ public LockOptions setScope(boolean scope) { private Object result; private PostLoadEvent postLoadEvent; + private Boolean shouldUnwrapProxy; + public LoadEvent(Serializable entityId, Object instanceToLoad, EventSource source) { this( entityId, null, instanceToLoad, DEFAULT_LOCK_OPTIONS, false, source ); } @@ -190,4 +192,19 @@ public PostLoadEvent getPostLoadEvent() { public void setPostLoadEvent(PostLoadEvent postLoadEvent) { this.postLoadEvent = postLoadEvent; } + + public Boolean getShouldUnwrapProxy() { + if ( shouldUnwrapProxy == null ) { + final boolean enabled = getSession().getFactory() + .getSessionFactoryOptions() + .isEnhancementAsProxyEnabled(); + return enabled; + } + + return shouldUnwrapProxy; + } + + public void setShouldUnwrapProxy(Boolean shouldUnwrapProxy) { + this.shouldUnwrapProxy = shouldUnwrapProxy; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java index f0350117446f..63a0d933756c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java @@ -13,6 +13,7 @@ import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.ScrollableResults; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.hql.internal.HolderInstantiator; @@ -189,21 +190,28 @@ private void prepareCurrentRow(boolean underlyingScrollSuccessful) { return; } - final Object result = getLoader().loadSingleRow( - getResultSet(), - getSession(), - getQueryParameters(), - true - ); - if ( result != null && result.getClass().isArray() ) { - currentRow = (Object[]) result; - } - else { - currentRow = new Object[] {result}; - } - - if ( getHolderInstantiator() != null ) { - currentRow = new Object[] {getHolderInstantiator().instantiate( currentRow )}; + final PersistenceContext persistenceContext = getSession().getPersistenceContext(); + persistenceContext.beforeLoad(); + try { + final Object result = getLoader().loadSingleRow( + getResultSet(), + getSession(), + getQueryParameters(), + true + ); + if ( result != null && result.getClass().isArray() ) { + currentRow = (Object[]) result; + } + else { + currentRow = new Object[] {result}; + } + + if ( getHolderInstantiator() != null ) { + currentRow = new Object[] { getHolderInstantiator().instantiate( currentRow ) }; + } + } + finally { + persistenceContext.afterLoad(); } afterScrollOperation(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 4f5a01f8db6f..a7f997a0c2c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1123,25 +1123,40 @@ public Object immediateLoad(String entityName, Serializable id) throws Hibernate } return result; } + @Override + public final Object internalLoad( + String entityName, + Serializable id, + boolean eager, + boolean nullable) throws HibernateException { + return internalLoad( entityName, id, eager, nullable, null ); + } @Override - public final Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) - throws HibernateException { - // todo : remove - LoadEventListener.LoadType type = nullable - ? LoadEventListener.INTERNAL_LOAD_NULLABLE - : eager - ? LoadEventListener.INTERNAL_LOAD_EAGER - : LoadEventListener.INTERNAL_LOAD_LAZY; + public final Object internalLoad( + String entityName, + Serializable id, + boolean eager, + boolean nullable, + Boolean unwrapProxy) { + final LoadEventListener.LoadType type; + if ( nullable ) { + type = LoadEventListener.INTERNAL_LOAD_NULLABLE; + } + else { + type = eager + ? LoadEventListener.INTERNAL_LOAD_EAGER + : LoadEventListener.INTERNAL_LOAD_LAZY; + } LoadEvent event = loadEvent; loadEvent = null; event = recycleEventInstance( event, id, entityName ); - fireLoad( event, type ); + event.setShouldUnwrapProxy( unwrapProxy );fireLoad( event, type ); Object result = event.getResult(); if ( !nullable ) { - UnresolvableObjectException.throwIfNull( result, id, entityName ); - } + UnresolvableObjectException.throwIfNull( result, id, entityName );} + if ( loadEvent == null ) { event.setEntityClassName( null ); event.setEntityId( null ); @@ -1166,6 +1181,7 @@ private LoadEvent recycleEventInstance(final LoadEvent event, final Serializable event.setLockMode( LoadEvent.DEFAULT_LOCK_MODE ); event.setLockScope( LoadEvent.DEFAULT_LOCK_OPTIONS.getScope() ); event.setLockTimeout( LoadEvent.DEFAULT_LOCK_OPTIONS.getTimeOut() ); + event.setShouldUnwrapProxy( null ); return event; } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index 375ed7a2c990..a9d98b6fe4cb 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -65,13 +65,15 @@ public void setInternalFetchProfile(String internalFetchProfile) { } }; - private PersistenceContext temporaryPersistenceContext = new StatefulPersistenceContext( this ); + private final PersistenceContext temporaryPersistenceContext = new StatefulPersistenceContext( this ); - private boolean connectionProvided; + private final boolean connectionProvided; + private final boolean allowBytecodeProxy; StatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { super( factory, options ); connectionProvided = options.getConnection() != null; + allowBytecodeProxy = getFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled(); } @Override @@ -248,9 +250,12 @@ public void refresh(String entityName, Object entity, LockMode lockMode) { } @Override - public Object immediateLoad(String entityName, Serializable id) - throws HibernateException { - throw new SessionException( "proxies cannot be fetched by a stateless session" ); + public Object immediateLoad(String entityName, Serializable id) throws HibernateException { + if ( getPersistenceContext().isLoadFinished() ) { + throw new SessionException( "proxies cannot be fetched by a stateless session" ); + } + // unless we are still in the process of handling a top-level load + return get( entityName, id ); } @Override @@ -275,19 +280,54 @@ public Object internalLoad( boolean eager, boolean nullable) throws HibernateException { checkOpen(); + EntityPersister persister = getFactory().getMetamodel().entityPersister( entityName ); + final EntityKey entityKey = generateEntityKey( id, persister ); + // first, try to load it from the temp PC associated to this SS - Object loaded = temporaryPersistenceContext.getEntity( generateEntityKey( id, persister ) ); + Object loaded = temporaryPersistenceContext.getEntity( entityKey ); if ( loaded != null ) { // we found it in the temp PC. Should indicate we are in the midst of processing a result set // containing eager fetches via join fetch return loaded; } - if ( !eager && persister.hasProxy() ) { - // if the metadata allowed proxy creation and caller did not request forceful eager loading, - // generate a proxy - return persister.createProxy( id, this ); + + if ( !eager ) { + // caller did not request forceful eager loading, see if we can create + // some form of proxy + + // first, check to see if we can use "bytecode proxies" + + if ( allowBytecodeProxy + && persister.getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + + // we cannot use bytecode proxy for entities with subclasses + if ( !persister.getEntityMetamodel().hasSubclasses() ) { + final Object entity = persister.getEntityTuplizer().instantiate( id, this ); + + persister.getEntityMetamodel() + .getBytecodeEnhancementMetadata() + .injectEnhancedEntityAsProxyInterceptor( entity, entityKey, this ); + + getPersistenceContext().addEntity( entityKey, entity ); + return entity; + } + } + + // we could not use bytecode proxy, check to see if we can use HibernateProxy + if ( persister.hasProxy() ) { + final Object existingProxy = getPersistenceContext().getProxy( entityKey ); + if ( existingProxy != null ) { + return getPersistenceContext().narrowProxy( existingProxy, persister, entityKey, null ); + } + else { + final Object proxy = persister.createProxy( id, this ); + getPersistenceContext().addProxy( entityKey, proxy ); + return proxy; + } + } } + // otherwise immediately materialize it return get( entityName, id ); } @@ -424,6 +464,18 @@ public EntityPersister getEntityPersister(String entityName, Object object) @Override public Object getEntityUsingInterceptor(EntityKey key) throws HibernateException { checkOpen(); + + final Object result = getPersistenceContext().getEntity( key ); + if ( result != null ) { + return result; + } + + final Object newObject = getInterceptor().getEntity( key.getEntityName(), key.getIdentifier() ); + if ( newObject != null ) { + getPersistenceContext().addEntity( key, newObject ); + return newObject; + } + return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java index 4576328131eb..a7f48dd26e2b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java @@ -35,6 +35,12 @@ public static int indexOf(Object[] array, Object object) { return -1; } + public static T[] filledArray(T value, Class valueJavaType, int size) { + final T[] array = (T[]) Array.newInstance( valueJavaType, size ); + Arrays.fill( array, value ); + return array; + } + public static String[] toStringArray(Object[] objects) { int length = objects.length; String[] result = new String[length]; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index a3ebcd2069d7..5cf761cf10e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -31,6 +31,7 @@ import org.hibernate.Session; import org.hibernate.StaleObjectStateException; import org.hibernate.WrongClassException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.cache.spi.FilterKey; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; @@ -50,6 +51,8 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.RowSelection; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -66,6 +69,7 @@ import org.hibernate.internal.ScrollableResultsImpl; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.loader.entity.CascadeEntityLoader; import org.hibernate.loader.spi.AfterLoadAction; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -107,12 +111,14 @@ public abstract class Loader { private volatile ColumnNameCache columnNameCache; private final boolean referenceCachingEnabled; + private final boolean enhancementAsProxyEnabled; private boolean isJdbc4 = true; public Loader(SessionFactoryImplementor factory) { this.factory = factory; this.referenceCachingEnabled = factory.getSessionFactoryOptions().isDirectReferenceCacheEntriesEnabled(); + this.enhancementAsProxyEnabled = factory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(); } /** @@ -829,6 +835,7 @@ protected void extractKeysFromResultSet( keys[targetIndex], object, lockModes[targetIndex], + hydratedObjects, session ); } @@ -1491,7 +1498,8 @@ private void checkVersion( Object version = session.getPersistenceContext().getEntry( entity ).getVersion(); - if ( version != null ) { //null version means the object is in the process of being loaded somewhere else in the ResultSet + if ( version != null ) { + // null version means the object is in the process of being loaded somewhere else in the ResultSet final VersionType versionType = persister.getVersionType(); final Object currentVersion = versionType.nullSafeGet( rs, @@ -1526,7 +1534,7 @@ private Object[] getRow( final List hydratedObjects, final SharedSessionContractImplementor session) throws HibernateException, SQLException { final int cols = persisters.length; - final EntityAliases[] descriptors = getEntityAliases(); + final EntityAliases[] entityAliases = getEntityAliases(); if ( LOG.isDebugEnabled() ) { LOG.debugf( "Result row: %s", StringHelper.toString( keys ) ); @@ -1546,7 +1554,6 @@ private Object[] getRow( //If the object is already loaded, return the loaded one object = session.getEntityUsingInterceptor( key ); if ( object != null ) { - //its already loaded so don't need to hydrate it instanceAlreadyLoaded( rs, i, @@ -1554,6 +1561,7 @@ private Object[] getRow( key, object, lockModes[i], + hydratedObjects, session ); } @@ -1562,7 +1570,7 @@ private Object[] getRow( rs, i, persisters[i], - descriptors[i].getRowIdAlias(), + entityAliases[i].getRowIdAlias(), key, lockModes[i], optionalObjectKey, @@ -1590,6 +1598,7 @@ private void instanceAlreadyLoaded( final EntityKey key, final Object object, final LockMode requestedLockMode, + List hydratedObjects, final SharedSessionContractImplementor session) throws HibernateException, SQLException { if ( !persister.isInstance( object ) ) { @@ -1600,7 +1609,42 @@ private void instanceAlreadyLoaded( ); } - if ( LockMode.NONE != requestedLockMode && upgradeLocks() ) { //no point doing this if NONE was requested + if ( persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() && enhancementAsProxyEnabled ) { + if ( "merge".equals( session.getLoadQueryInfluencers().getInternalFetchProfile() ) ) { + assert this instanceof CascadeEntityLoader; + // we are processing a merge and have found an existing "managed copy" in the + // session - we need to check if this copy is an enhanced-proxy and, if so, + // perform the hydration just as if it were "not yet loaded" + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) object; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + hydrateEntityState( + rs, + i, + persister, + getEntityAliases()[i].getRowIdAlias(), + key, + hydratedObjects, + session, + getInstanceClass( + rs, + i, + persister, + key.getIdentifier(), + session + ), + object, + requestedLockMode + ); + + // EARLY EXIT!!! + // - to skip the version check + return; + } + } + } + + if ( LockMode.NONE != requestedLockMode && upgradeLocks() ) { final EntityEntry entry = session.getPersistenceContext().getEntry( object ); if ( entry.getLockMode().lessThan( requestedLockMode ) ) { //we only check the version when _upgrading_ lock modes @@ -1669,6 +1713,33 @@ private Object instanceNotYetLoaded( // (but don't yet initialize the object itself) // note that we acquire LockMode.READ even if it was not requested LockMode acquiredLockMode = lockMode == LockMode.NONE ? LockMode.READ : lockMode; + hydrateEntityState( + rs, + i, + persister, + rowIdAlias, + key, + hydratedObjects, + session, + instanceClass, + object, + acquiredLockMode + ); + + return object; + } + + private void hydrateEntityState( + ResultSet rs, + int i, + Loadable persister, + String rowIdAlias, + EntityKey key, + List hydratedObjects, + SharedSessionContractImplementor session, + String instanceClass, + Object object, + LockMode acquiredLockMode) throws SQLException { loadFromResultSet( rs, i, @@ -1683,8 +1754,6 @@ private Object instanceNotYetLoaded( //materialize associations (and initialize the object) later hydratedObjects.add( object ); - - return object; } private boolean isEagerPropertyFetchEnabled(int i) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java index 297673459768..f327ed410233 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java @@ -14,6 +14,9 @@ import org.hibernate.LockMode; import org.hibernate.StaleObjectStateException; import org.hibernate.WrongClassException; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.engine.internal.TwoPhaseLoad; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.EntityKey; @@ -195,6 +198,31 @@ public void hydrateEntityState(ResultSet resultSet, ResultSetProcessingContextIm // use the existing association as the hydrated state processingState.registerEntityInstance( existing ); //context.registerHydratedEntity( entityReference, entityKey, existing ); + + // see if the entity is enhanced and is being used as a "proxy" (is fully uninitialized) + final BytecodeEnhancementMetadata enhancementMetadata = entityReference.getEntityPersister() + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + + if ( enhancementMetadata.isEnhancedForLazyLoading() ) { + final BytecodeLazyAttributeInterceptor interceptor = enhancementMetadata.extractLazyInterceptor( existing ); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final LockMode requestedLockMode = context.resolveLockMode( entityReference ); + final LockMode lockModeToAcquire = requestedLockMode == LockMode.NONE + ? LockMode.READ + : requestedLockMode; + + loadFromResultSet( + resultSet, + context, + existing, + getConcreteEntityTypeName( resultSet, context, entityKey ), + entityKey, + lockModeToAcquire + ); + } + } + return; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index ebf86b44e977..f18d27046b63 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -14,6 +14,7 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.PropertyNotFoundException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.Mapping; @@ -233,29 +234,31 @@ public String toString() { public void setLazy(boolean lazy) { this.lazy=lazy; } - + + /** + * Is this property lazy in the "bytecode" sense? + * + * Lazy here means whether we should push *something* to the entity + * instance for this field in its "base fetch group". Mainly it affects + * whether we should list this property's columns in the SQL select + * for the owning entity when we load its "base fetch group". + * + * The "something" we push varies based on the nature (basic, etc) of + * the property. + * + * @apiNote This form reports whether the property is considered part of the + * base fetch group based solely on the mapping information. However, + * {@link EnhancementHelper#includeInBaseFetchGroup} is used internally to make that + * decision to account for {@link org.hibernate.cfg.AvailableSettings#ALLOW_ENHANCEMENT_AS_PROXY} + */ public boolean isLazy() { if ( value instanceof ToOne ) { - // both many-to-one and one-to-one are represented as a - // Property. EntityPersister is relying on this value to - // determine "lazy fetch groups" in terms of field-level - // interception. So we need to make sure that we return - // true here for the case of many-to-one and one-to-one - // with lazy="no-proxy" - // - // * impl note - lazy="no-proxy" currently forces both - // lazy and unwrap to be set to true. The other case we - // are extremely interested in here is that of lazy="proxy" - // where lazy is set to true, but unwrap is set to false. - // thus we use both here under the assumption that this - // return is really only ever used during persister - // construction to determine the lazy property/field fetch - // groupings. If that assertion changes then this check - // needs to change as well. Partially, this is an issue with - // the overloading of the term "lazy" here... - ToOne toOneValue = ( ToOne ) value; - return toOneValue.isLazy() && toOneValue.isUnwrapProxy(); + // For a many-to-one, this is always false. Whether the + // association is EAGER, PROXY or NO-PROXY we want the fk + // selected + return false; } + return lazy; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index d0b794d91843..2f2ccbd76ded 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -38,6 +38,9 @@ import org.hibernate.StaleObjectStateException; import org.hibernate.StaleStateException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; @@ -74,6 +77,7 @@ import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.PersistenceContext.NaturalIdHelper; import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.ValueInclusion; @@ -551,7 +555,7 @@ public AbstractEntityPersister( this.naturalIdRegionAccessStrategy = null; } - this.entityMetamodel = new EntityMetamodel( persistentClass, this, factory ); + this.entityMetamodel = new EntityMetamodel( persistentClass, this, creationContext ); this.entityTuplizer = this.entityMetamodel.getTuplizer(); if ( entityMetamodel.isMutable() ) { @@ -684,7 +688,20 @@ public AbstractEntityPersister( propertyColumnWriters[i] = colWriters; propertyColumnAliases[i] = colAliases; - if ( lazyAvailable && prop.isLazy() ) { + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + entityMetamodel.isInstrumented(), + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } + ); + + if ( lazy ) { lazyNames.add( prop.getName() ); lazyNumbers.add( i ); lazyTypes.add( prop.getValue().getType() ); @@ -754,7 +771,18 @@ public AbstractEntityPersister( int[] colnos = new int[prop.getColumnSpan()]; int[] formnos = new int[prop.getColumnSpan()]; int l = 0; - Boolean lazy = Boolean.valueOf( prop.isLazy() && lazyAvailable ); + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + entityMetamodel.isInstrumented(), + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } + ); while ( colIter.hasNext() ) { Selectable thing = (Selectable) colIter.next(); if ( thing.isFormula() ) { @@ -1025,7 +1053,7 @@ protected Map generateLazySelectStringsByFetchGroup() { public Object initializeLazyProperty(String fieldName, Object entity, SharedSessionContractImplementor session) { final EntityEntry entry = session.getPersistenceContext().getEntry( entity ); - final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); assert interceptor != null : "Expecting bytecode interceptor to be non-null"; if ( hasCollections() ) { @@ -1052,10 +1080,10 @@ public Object initializeLazyProperty(String fieldName, Object entity, SharedSess session.getPersistenceContext().addUninitializedCollection( persister, collection, key ); } - // HHH-11161 Initialize, if the collection is not extra lazy - if ( !persister.isExtraLazy() ) { - session.initializeCollection( collection, false ); - } +// // HHH-11161 Initialize, if the collection is not extra lazy +// if ( !persister.isExtraLazy() ) { +// session.initializeCollection( collection, false ); +// } interceptor.attributeInitialized( fieldName ); if ( collectionType.isArrayType() ) { @@ -1146,10 +1174,10 @@ private Object initializeLazyPropertiesFromDatastore( throw new AssertionFailure( "no lazy properties" ); } - final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); assert interceptor != null : "Expecting bytecode interceptor to be non-null"; - LOG.trace( "Initializing lazy properties from datastore" ); + LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName ); final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata() .getLazyAttributesMetadata() @@ -4273,6 +4301,50 @@ public Object load(Serializable id, Object optionalObject, LockOptions lockOptio return loader.load( id, optionalObject, session, lockOptions ); } + @Override + public Object initializeEnhancedEntityUsedAsProxy( + Object entity, + String nameOfAttributeBeingAccessed, + SharedSessionContractImplementor session) { + final BytecodeEnhancementMetadata enhancementMetadata = getEntityMetamodel().getBytecodeEnhancementMetadata(); + final BytecodeLazyAttributeInterceptor currentInterceptor = enhancementMetadata.extractLazyInterceptor( entity ); + if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) currentInterceptor; + + readLockLoader.load( + proxyInterceptor.getEntityKey().getIdentifier(), + entity, + session, + LockOptions.READ + ); + + final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata.injectInterceptor( + entity, + proxyInterceptor.getEntityKey().getIdentifier(), + session + ); + + final Object value; + if ( nameOfAttributeBeingAccessed == null ) { + return null; + } + else if ( interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) ) { + value = getEntityTuplizer().getPropertyValue( entity, nameOfAttributeBeingAccessed ); + } + else { + value = ( (LazyPropertyInitializer) this ).initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ); + } + + return interceptor.readObject( + entity, + nameOfAttributeBeingAccessed, + value + ); + } + + throw new IllegalStateException( ); + } + @Override public List multiLoad(Serializable[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) { return DynamicBatchingEntityLoaderBuilder.INSTANCE.multiLoad( @@ -4582,9 +4654,14 @@ public boolean hasLazyProperties() { public void afterReassociate(Object entity, SharedSessionContractImplementor session) { if ( getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { - LazyAttributeLoadingInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractInterceptor( entity ); + final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata() + .extractLazyInterceptor( entity ); if ( interceptor == null ) { - getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( entity, session ); + getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( + entity, + getIdentifier( entity, session ), + session + ); } else { interceptor.setSession( session ); @@ -5453,6 +5530,11 @@ public EntityTuplizer getEntityTuplizer() { @Override public BytecodeEnhancementMetadata getInstrumentationMetadata() { + return getBytecodeEnhancementMetadata(); + } + + @Override + public BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() { return entityMetamodel.getBytecodeEnhancementMetadata(); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index eb47a19382cd..dac30cd60e57 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -15,7 +15,9 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.bytecode.spi.NotInstrumentedException; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.entry.CacheEntry; @@ -132,6 +134,20 @@ public interface EntityPersister extends EntityDefinition { */ EntityMetamodel getEntityMetamodel(); + /** + * Called from {@link EnhancementAsProxyLazinessInterceptor} to trigger load of + * the entity's non-lazy state as well as the named attribute we are accessing + * if it is still uninitialized after fetching non-lazy state + */ + default Object initializeEnhancedEntityUsedAsProxy( + Object entity, + String nameOfAttributeBeingAccessed, + SharedSessionContractImplementor session) { + throw new UnsupportedOperationException( + "Initialization of entity enhancement used to act like a proxy is not supported by this EntityPersister : " + getClass().getName() + ); + } + /** * Determine whether the given name represents a subclass entity * (or this entity itself) of the entity mapped by this persister. @@ -798,7 +814,11 @@ Object createProxy(Serializable id, SharedSessionContractImplementor session) EntityTuplizer getEntityTuplizer(); BytecodeEnhancementMetadata getInstrumentationMetadata(); - + + default BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() { + return getInstrumentationMetadata(); + } + FilterAliasGenerator getFilterAliasGenerator(final String rootAlias); /** diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java index 4b770edf44a2..9265687f55e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java @@ -7,9 +7,11 @@ package org.hibernate.tuple; import java.lang.reflect.Constructor; +import java.util.function.Function; import org.hibernate.EntityMode; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.engine.internal.UnsavedValueFactory; import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -153,7 +155,8 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( SessionFactoryImplementor sessionFactory, int attributeNumber, Property property, - boolean lazyAvailable) { + boolean lazyAvailable, + Function hasSubclassChecker) { final Type type = property.getValue().getType(); final NonIdentifierAttributeNature nature = decode( type ); @@ -168,6 +171,13 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( boolean alwaysDirtyCheck = type.isAssociationType() && ( (AssociationType) type ).isAlwaysDirtyChecked(); + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + property, + lazyAvailable, + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + hasSubclassChecker + ); + switch ( nature ) { case BASIC: { return new EntityBasedBasicAttribute( @@ -177,7 +187,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( property.getName(), type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -197,7 +207,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( property.getName(), (CompositeType) type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -219,7 +229,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( property.getName(), (AssociationType) type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -279,7 +289,9 @@ public static StandardProperty buildStandardProperty(Property property, boolean return new StandardProperty( property.getName(), type, - lazyAvailable && property.isLazy(), + // only called for embeddable sub-attributes which are never (yet) lazy + //lazyAvailable && property.isLazy(), + false, property.isInsertable(), property.isUpdateable(), property.getValueGenerationStrategy(), diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java index 07021458237d..076e4ddb15fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java @@ -16,6 +16,7 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; @@ -152,7 +153,8 @@ public AbstractEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass m instantiator = buildInstantiator( entityMetamodel, mappingInfo ); - if ( entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { +// if ( entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + if ( entityMetamodel.isLazy() ) { proxyFactory = buildProxyFactory( mappingInfo, idGetter, idSetter ); if ( proxyFactory == null ) { entityMetamodel.setLazy( false ); @@ -535,18 +537,24 @@ protected boolean shouldGetAllProperties(Object entity) { @Override public Object[] getPropertyValues(Object entity) { final BytecodeEnhancementMetadata enhancementMetadata = entityMetamodel.getBytecodeEnhancementMetadata(); + final LazyAttributesMetadata lazyAttributesMetadata = enhancementMetadata.getLazyAttributesMetadata(); + final int span = entityMetamodel.getPropertySpan(); final Object[] result = new Object[span]; for ( int j = 0; j < span; j++ ) { - NonIdentifierAttribute property = entityMetamodel.getProperties()[j]; - if ( !property.isLazy() || enhancementMetadata.isAttributeLoaded( entity, property.getName() ) ) { + // if the attribute is not lazy (bytecode sense), we can just use the value from the instance + // if the attribute is lazy but has been initialized we can just use the value from the instance + // todo : there should be a third case here when we merge transient instances + if ( ! lazyAttributesMetadata.isLazyAttribute( entityMetamodel.getPropertyNames()[j] ) + || enhancementMetadata.isAttributeLoaded( entity, entityMetamodel.getPropertyNames()[j] ) ) { result[j] = getters[j].get( entity ); } else { result[j] = LazyPropertyInitializer.UNFETCHED_PROPERTY; } } + return result; } @@ -718,7 +726,8 @@ protected final Instantiator getInstantiator() { return instantiator; } - protected final ProxyFactory getProxyFactory() { + @Override + public final ProxyFactory getProxyFactory() { return proxyFactory; } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java index 10286c23a615..6f0709c9ad3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java @@ -6,10 +6,13 @@ */ package org.hibernate.tuple.entity; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.bytecode.spi.NotInstrumentedException; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -44,15 +47,37 @@ public LazyAttributesMetadata getLazyAttributesMetadata() { @Override public LazyAttributeLoadingInterceptor injectInterceptor( Object entity, + Object identifier, SharedSessionContractImplementor session) throws NotInstrumentedException { throw new NotInstrumentedException( errorMsg ); } + @Override + public void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + + @Override + public void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + @Override public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException { throw new NotInstrumentedException( errorMsg ); } + @Override + public BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException { + throw new NotInstrumentedException( errorMsg ); + } + @Override public boolean hasUnFetchedAttributes(Object entity) { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java index 48134c0ceeb4..3e4ba070d779 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java @@ -6,29 +6,46 @@ */ package org.hibernate.tuple.entity; +import java.util.Set; +import java.util.function.Function; + +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.bytecode.spi.NotInstrumentedException; +import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.mapping.PersistentClass; +import org.hibernate.type.CompositeType; /** * @author Steve Ebersole */ public class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhancementMetadata { - public static BytecodeEnhancementMetadata from(PersistentClass persistentClass) { + /** + * Static constructor + */ + public static BytecodeEnhancementMetadata from( + PersistentClass persistentClass, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, + boolean allowEnhancementAsProxy, + Function hasSubclassChecker) { final Class mappedClass = persistentClass.getMappedClass(); final boolean enhancedForLazyLoading = PersistentAttributeInterceptable.class.isAssignableFrom( mappedClass ); final LazyAttributesMetadata lazyAttributesMetadata = enhancedForLazyLoading - ? LazyAttributesMetadata.from( persistentClass ) + ? LazyAttributesMetadata.from( persistentClass, true, allowEnhancementAsProxy, hasSubclassChecker ) : LazyAttributesMetadata.nonEnhanced( persistentClass.getEntityName() ); return new BytecodeEnhancementMetadataPojoImpl( persistentClass.getEntityName(), mappedClass, + identifierAttributeNames, + nonAggregatedCidMapper, enhancedForLazyLoading, lazyAttributesMetadata ); @@ -36,16 +53,26 @@ public static BytecodeEnhancementMetadata from(PersistentClass persistentClass) private final String entityName; private final Class entityClass; + private final Set identifierAttributeNames; + private final CompositeType nonAggregatedCidMapper; private final boolean enhancedForLazyLoading; private final LazyAttributesMetadata lazyAttributesMetadata; - public BytecodeEnhancementMetadataPojoImpl( + @SuppressWarnings("WeakerAccess") + protected BytecodeEnhancementMetadataPojoImpl( String entityName, Class entityClass, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, boolean enhancedForLazyLoading, LazyAttributesMetadata lazyAttributesMetadata) { + this.nonAggregatedCidMapper = nonAggregatedCidMapper; + assert identifierAttributeNames != null; + assert !identifierAttributeNames.isEmpty(); + this.entityName = entityName; this.entityClass = entityClass; + this.identifierAttributeNames = identifierAttributeNames; this.enhancedForLazyLoading = enhancedForLazyLoading; this.lazyAttributesMetadata = lazyAttributesMetadata; } @@ -67,18 +94,47 @@ public LazyAttributesMetadata getLazyAttributesMetadata() { @Override public boolean hasUnFetchedAttributes(Object entity) { - LazyAttributeLoadingInterceptor interceptor = enhancedForLazyLoading ? extractInterceptor( entity ) : null; - return interceptor != null && interceptor.hasAnyUninitializedAttributes(); + if ( ! enhancedForLazyLoading ) { + return false; + } + + final BytecodeLazyAttributeInterceptor interceptor = extractLazyInterceptor( entity ); + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { + return ( (LazyAttributeLoadingInterceptor) interceptor ).hasAnyUninitializedAttributes(); + } + + //noinspection RedundantIfStatement + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return true; + } + + return false; } @Override public boolean isAttributeLoaded(Object entity, String attributeName) { - LazyAttributeLoadingInterceptor interceptor = enhancedForLazyLoading ? extractInterceptor( entity ) : null; - return interceptor == null || interceptor.isAttributeLoaded( attributeName ); + if ( ! enhancedForLazyLoading ) { + return true; + } + + final BytecodeLazyAttributeInterceptor interceptor = extractLazyInterceptor( entity ); + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { + return ( (LazyAttributeLoadingInterceptor) interceptor ).isAttributeLoaded( attributeName ); + } + + return true; } @Override public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException { + return (LazyAttributeLoadingInterceptor) extractLazyInterceptor( entity ); + } + + @Override + public LazyAttributeLoadingInterceptor injectInterceptor( + Object entity, + Object identifier, + SharedSessionContractImplementor session) { if ( !enhancedForLazyLoading ) { throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); } @@ -92,17 +148,41 @@ public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws ) ); } + final LazyAttributeLoadingInterceptor interceptor = new LazyAttributeLoadingInterceptor( + getEntityName(), + identifier, + lazyAttributesMetadata.getLazyAttributeNames(), + session + ); - final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); - if ( interceptor == null ) { - return null; - } + injectInterceptor( entity, interceptor, session ); + + return interceptor; + } - return (LazyAttributeLoadingInterceptor) interceptor; + @Override + public void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session) { + injectInterceptor( + entity, + new EnhancementAsProxyLazinessInterceptor( + entityName, + identifierAttributeNames, + nonAggregatedCidMapper, + entityKey, + session + ), + session + ); } @Override - public LazyAttributeLoadingInterceptor injectInterceptor(Object entity, SharedSessionContractImplementor session) { + public void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session) { if ( !enhancedForLazyLoading ) { throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); } @@ -117,12 +197,31 @@ public LazyAttributeLoadingInterceptor injectInterceptor(Object entity, SharedSe ); } - final LazyAttributeLoadingInterceptor interceptor = new LazyAttributeLoadingInterceptor( - getEntityName(), - lazyAttributesMetadata.getLazyAttributeNames(), - session - ); ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( interceptor ); - return interceptor; } + + @Override + public BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException { + if ( !enhancedForLazyLoading ) { + throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); + } + + if ( !entityClass.isInstance( entity ) ) { + throw new IllegalArgumentException( + String.format( + "Passed entity instance [%s] is not of expected type [%s]", + entity, + getEntityName() + ) + ); + } + + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + if ( interceptor == null ) { + return null; + } + + return (BytecodeLazyAttributeInterceptor) interceptor; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 98d34aba73f3..9bb4d7f1b6a4 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -18,6 +19,7 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.OptimisticLockStyle; @@ -31,6 +33,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.spi.PersisterCreationContext; import org.hibernate.tuple.GenerationTiming; import org.hibernate.tuple.IdentifierProperty; import org.hibernate.tuple.InDatabaseValueGenerationStrategy; @@ -125,8 +128,8 @@ public class EntityMetamodel implements Serializable { public EntityMetamodel( PersistentClass persistentClass, EntityPersister persister, - SessionFactoryImplementor sessionFactory) { - this.sessionFactory = sessionFactory; + final PersisterCreationContext creationContext) { + this.sessionFactory = creationContext.getSessionFactory(); name = persistentClass.getEntityName(); rootName = persistentClass.getRootClass().getEntityName(); @@ -139,7 +142,37 @@ public EntityMetamodel( versioned = persistentClass.isVersioned(); if ( persistentClass.hasPojoRepresentation() ) { - bytecodeEnhancementMetadata = BytecodeEnhancementMetadataPojoImpl.from( persistentClass ); + final Component identifierMapperComponent = persistentClass.getIdentifierMapper(); + final CompositeType nonAggregatedCidMapper; + final Set idAttributeNames; + + if ( identifierMapperComponent != null ) { + nonAggregatedCidMapper = (CompositeType) identifierMapperComponent.getType(); + idAttributeNames = new HashSet<>( ); + //noinspection unchecked + final Iterator propertyItr = identifierMapperComponent.getPropertyIterator(); + while ( propertyItr.hasNext() ) { + idAttributeNames.add( propertyItr.next() ); + } + } + else { + nonAggregatedCidMapper = null; + idAttributeNames = Collections.singleton( identifierAttribute.getName() ); + } + + bytecodeEnhancementMetadata = BytecodeEnhancementMetadataPojoImpl.from( + persistentClass, + idAttributeNames, + nonAggregatedCidMapper, + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } + ); } else { bytecodeEnhancementMetadata = new BytecodeEnhancementMetadataNonPojoImpl( persistentClass.getEntityName() ); @@ -201,7 +234,14 @@ public EntityMetamodel( sessionFactory, i, prop, - bytecodeEnhancementMetadata.isEnhancedForLazyLoading() + bytecodeEnhancementMetadata.isEnhancedForLazyLoading(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } ); } @@ -217,10 +257,23 @@ public EntityMetamodel( } // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - boolean lazy = prop.isLazy() && bytecodeEnhancementMetadata.isEnhancedForLazyLoading(); + boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + bytecodeEnhancementMetadata.isEnhancedForLazyLoading(), + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(), + associatedEntityName -> { + final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); + if ( bootEntityDescriptor == null ) { + return false; + } + return bootEntityDescriptor.hasSubclasses(); + } + ); + if ( lazy ) { hasLazy = true; } + propertyLaziness[i] = lazy; propertyNames[i] = properties[i].getName(); diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java index ecb6970cbcd5..f9a79e2e8c1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java @@ -15,6 +15,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.property.access.spi.Getter; +import org.hibernate.proxy.ProxyFactory; import org.hibernate.tuple.Tuplizer; /** @@ -273,4 +274,8 @@ Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionCon * @return The getter for the version property. */ Getter getVersionGetter(); + + default ProxyFactory getProxyFactory() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java index 47bd979d6612..0910b6d9aea3 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java @@ -46,6 +46,7 @@ protected Object applyInterception(Object entity) { PersistentAttributeInterceptor interceptor = new LazyAttributeLoadingInterceptor( entityMetamodel.getName(), + null, entityMetamodel.getBytecodeEnhancementMetadata() .getLazyAttributesMetadata() .getLazyAttributeNames(), diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java index f186e8a384c2..57c61115c8e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java @@ -16,7 +16,8 @@ import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.Environment; import org.hibernate.classic.Lifecycle; @@ -268,22 +269,14 @@ public Class getConcreteProxyClass() { @Override public void afterInitialize(Object entity, SharedSessionContractImplementor session) { - - // moving to multiple fetch groups, the idea of `lazyPropertiesAreUnfetched` really - // needs to become either: - // 1) the names of all un-fetched fetch groups - // 2) the names of all fetched fetch groups - // probably (2) is best - // - // ultimately this comes from EntityEntry, although usage-search seems to show it is never updated there. - // - // also org.hibernate.persister.entity.AbstractEntityPersister.initializeLazyPropertiesFromDatastore() - // needs to be re-worked - if ( entity instanceof PersistentAttributeInterceptable ) { - final LazyAttributeLoadingInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractInterceptor( entity ); - if ( interceptor == null ) { - getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( entity, session ); + final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractLazyInterceptor( entity ); + if ( interceptor == null || interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( + entity, + getIdentifier( entity, session ), + session + ); } else { if ( interceptor.getLinkedSession() == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index bb078f511fc6..59e1debf336b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -683,7 +683,8 @@ protected final Object resolveIdentifier(Serializable id, SharedSessionContractI getAssociatedEntityName(), id, eager, - isNullable() + isNullable(), + unwrapProxy ); if ( proxyOrEntity instanceof HibernateProxy ) { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java index c1cb4e0f0912..9719a0f67319 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java @@ -51,7 +51,7 @@ protected void addConfigOptions(Map options) { @Before public void prepare() throws Exception { EntityPersister ep = entityManagerFactory().getMetamodel().entityPersister( EntityWithLazyProperty.class.getName() ); - assertTrue( ep.getInstrumentationMetadata().isEnhancedForLazyLoading() ); + assertTrue( ep.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ); byte[] testArray = new byte[]{0x2A}; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java index edae6baf7835..fbe4ef03afea 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java @@ -209,15 +209,6 @@ public Object readObject(Object obj, String name, Object oldValue) { public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { return WRITE_MARKER; } - - @Override - public Set getInitializedLazyAttributeNames() { - return null; - } - - @Override - public void attributeInitialized(String name) { - } } // --- // diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java index ed6e755a17fe..5a636b08aeb7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java @@ -79,8 +79,10 @@ public void testRemoveWithDeletedAssociatedEntity() { doInHibernate( this::sessionFactory, session -> { Employer employer = session.get( Employer.class, "RedHat" ); + // Delete the associated entity first session.remove( employer ); + for ( Employee employee : employer.getEmployees() ) { assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); session.remove( employee ); @@ -188,6 +190,7 @@ public void testRemoveEntityWithNullLazyManyToOne() { doInHibernate( this::sessionFactory, session -> { Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); // Get and delete an Employer that is not associated with employee Employer employer = session.get( Employer.class, "RedHat" ); @@ -196,15 +199,48 @@ public void testRemoveEntityWithNullLazyManyToOne() { // employee.employer is uninitialized. Since the column for employee.employer // is a foreign key, and there is an Employer that has already been removed, // employee.employer will need to be iniitialized to determine if - // employee.employee is nullifiable. + // employee.employer is nullifiable. assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); session.remove( employee ); assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); } ); + } + /** + * @implSpec Same as {@link #testRemoveEntityWithNullLazyManyToOne} but + * deleting the Employer linked to the loaded Employee + */ + @Test + public void testRemoveEntityWithLinkedLazyManyToOne() { + inTransaction( + session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employee.setEmployer( employer ); + session.persist( employee ); + } + ); + inTransaction( + session -> { + Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + // Get and delete an Employer that is not associated with employee + Employer employer = session.get( Employer.class, "RedHat" ); + session.remove( employer ); + + // employee.employer is uninitialized. Since the column for employee.employer + // is a foreign key, and there is an Employer that has already been removed, + // employee.employer will need to be iniitialized to determine if + // employee.employer is nullifiable. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + ); } private void checkEntityEntryState( diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java new file mode 100644 index 000000000000..1e27611c5a8c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java @@ -0,0 +1,476 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.StatelessSession; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.query.Query; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class StatelessQueryScrollingTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testDynamicFetchScroll() { + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + + final Query query = statelessSession.createQuery( "from Task t join fetch t.resource join fetch t.user"); + final ScrollableResults scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY); + while ( scrollableResults.next() ) { + Task taskRef = (Task) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( taskRef ) ); + assertTrue( Hibernate.isInitialized( taskRef.getUser() ) ); + assertTrue( Hibernate.isInitialized( taskRef.getResource() ) ); + assertFalse( Hibernate.isInitialized( taskRef.getResource().getOwner() ) ); + } + } + + @Test + public void testDynamicFetchCollectionScroll() { + StatelessSession statelessSession = sessionFactory().openStatelessSession(); + statelessSession.beginTransaction(); + + final Query query = statelessSession.createQuery( "select p from Producer p join fetch p.products" ); + final ScrollableResults scrollableResults = query.scroll(ScrollMode.FORWARD_ONLY); + while ( scrollableResults.next() ) { + Producer producer = (Producer) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( producer ) ); + assertTrue( Hibernate.isPropertyInitialized( producer, "products" ) ); + assertTrue( Hibernate.isInitialized( producer.getProducts() ) ); + + for (Product product : producer.getProducts()) { + assertTrue( Hibernate.isInitialized( product ) ); + assertFalse( Hibernate.isInitialized( product.getVendor() ) ); + } + } + + statelessSession.getTransaction().commit(); + statelessSession.close(); + + } + + + @Before + public void createTestData() { + inTransaction( + session -> { + Date now = new Date(); + User me = new User( "me" ); + User you = new User( "you" ); + Resource yourClock = new Resource( "clock", you ); + Task task = new Task( me, "clean", yourClock, now ); // :) + + session.save( me ); + session.save( you ); + session.save( yourClock ); + session.save( task ); + + User u3 = new User( "U3" ); + User u4 = new User( "U4" ); + Resource it = new Resource( "it", u4 ); + Task task2 = new Task( u3, "beat", it, now ); // :)) + + session.save( u3 ); + session.save( u4 ); + session.save( it ); + session.save( task2 ); + } + ); + + inTransaction( + session -> { + Producer p1 = new Producer( 1, "Acme" ); + Producer p2 = new Producer( 2, "ABC" ); + + session.save( p1 ); + session.save( p2 ); + + Vendor v1 = new Vendor( 1, "v1" ); + Vendor v2 = new Vendor( 2, "v2" ); + + session.save( v1 ); + session.save( v2 ); + + final Product product1 = new Product(1, "123", v1, p1); + final Product product2 = new Product(2, "456", v1, p1); + final Product product3 = new Product(3, "789", v1, p2); + + session.save( product1 ); + session.save( product2 ); + session.save( product3 ); + } + ); + } + + @After + public void deleteTestData() { + inTransaction( + s -> { + s.createQuery( "delete Task" ).executeUpdate(); + s.createQuery( "delete Resource" ).executeUpdate(); + s.createQuery( "delete User" ).executeUpdate(); + + s.createQuery( "delete Product" ).executeUpdate(); + s.createQuery( "delete Producer" ).executeUpdate(); + s.createQuery( "delete Vendor" ).executeUpdate(); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( Task.class ); + sources.addAnnotatedClass( User.class ); + sources.addAnnotatedClass( Resource.class ); + sources.addAnnotatedClass( Product.class ); + sources.addAnnotatedClass( Producer.class ); + sources.addAnnotatedClass( Vendor.class ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Collection fetch scrolling + + @Entity( name = "Producer" ) + public static class Producer { + @Id + private Integer id; + + private String name; + + @OneToMany( mappedBy = "producer", fetch = FetchType.LAZY ) + private Set products = new HashSet<>(); + + public Producer() { + } + + public Producer(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } + } + + @Entity( name = "Product" ) + public static class Product { + @Id + private Integer id; + private String sku; + + @ManyToOne( fetch = FetchType.LAZY ) + private Vendor vendor; + + @ManyToOne( fetch = FetchType.LAZY ) + private Producer producer; + + public Product() { + } + + public Product(Integer id, String sku, Vendor vendor, Producer producer) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + this.producer = producer; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSku() { + return sku; + } + + public void setSku(String sku) { + this.sku = sku; + } + + public Vendor getVendor() { + return vendor; + } + + public void setVendor(Vendor vendor) { + this.vendor = vendor; + } + + public Producer getProducer() { + return producer; + } + + public void setProducer(Producer producer) { + this.producer = producer; + } + } + + @Entity( name = "Vendor" ) + public static class Vendor { + @Id + private Integer id; + private String name; + + @OneToMany(mappedBy = "vendor", fetch = FetchType.LAZY ) + private Set products = new HashSet<>(); + + public Vendor() { + } + + public Vendor(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity fetch scrolling + + @Entity( name = "Resource" ) + public static class Resource { + @Id + @GeneratedValue( generator = "increment" ) + private Long id; + private String name; + @ManyToOne( fetch = FetchType.LAZY ) + private User owner; + + public Resource() { + } + + public Resource(String name, User owner) { + this.name = name; + this.owner = owner; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + } + + @Entity( name = "User" ) + public static class User { + @Id + @GeneratedValue( generator = "increment" ) + private Long id; + private String name; + + public User() { + } + + public User(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity( name = "Task" ) + public static class Task { + @Id + @GeneratedValue( generator = "increment" ) + private Long id; + private String description; + @ManyToOne( fetch = FetchType.LAZY) + private User user; + @ManyToOne( fetch = FetchType.LAZY) + private Resource resource; + private Date dueDate; + private Date startDate; + private Date completionDate; + + public Task() { + } + + public Task(User user, String description, Resource resource, Date dueDate) { + this( user, description, resource, dueDate, null, null ); + } + + public Task(User user, String description, Resource resource, Date dueDate, Date startDate, Date completionDate) { + this.user = user; + this.resource = resource; + this.description = description; + this.dueDate = dueDate; + this.startDate = startDate; + this.completionDate = completionDate; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Resource getResource() { + return resource; + } + + public void setResource(Resource resource) { + this.resource = resource; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getDueDate() { + return dueDate; + } + + public void setDueDate(Date dueDate) { + this.dueDate = dueDate; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getCompletionDate() { + return completionDate; + } + + public void setCompletionDate(Date completionDate) { + this.completionDate = completionDate; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java index baacfbb3ee3a..93a66e308c65 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java @@ -64,7 +64,7 @@ public void prepare() { @Test public void test() { doInHibernate( this::sessionFactory, s -> { - TestEntity entity = s.load( TestEntity.class, 1L ); + TestEntity entity = s.get( TestEntity.class, 1L ); assertLoaded( entity, "name" ); assertNotLoaded( entity, "lifeStory" ); assertNotLoaded( entity, "reallyBigString" ); @@ -76,7 +76,7 @@ public void test() { } ); doInHibernate( this::sessionFactory, s -> { - TestEntity entity = s.load( TestEntity.class, 1L ); + TestEntity entity = s.get( TestEntity.class, 1L ); assertLoaded( entity, "name" ); assertNotLoaded( entity, "lifeStory" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java new file mode 100644 index 000000000000..ca1ccf7066c3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java @@ -0,0 +1,130 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +@Entity +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name = "PP_DCKey") +public abstract class AbstractKey extends ModelEntity + implements Serializable { + + @Column(name = "Name") + String name; + + @OneToMany(targetEntity = RoleEntity.class, mappedBy = "key", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("R") + protected Set roles = new LinkedHashSet<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PR") + @JoinColumn + protected AbstractKey register; + + @OneToMany(targetEntity = AbstractKey.class, mappedBy = "register", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("RK") + protected Set keys = new LinkedHashSet(); + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PP") + @JoinColumn + protected AbstractKey parent; + + @OneToMany(targetEntity = AbstractKey.class, mappedBy = "parent", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PK") + protected Set otherKeys = new LinkedHashSet(); + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set role) { + this.roles = role; + } + + public void addRole(RoleEntity role) { + this.roles.add( role ); + } + + public AbstractKey getRegister() { + return register; + } + + public void setRegister(AbstractKey register) { + this.register = register; + } + + public Set getKeys() { + return keys; + } + + public void setKeys(Set keys) { + this.keys = keys; + } + + public void addRegisterKey(AbstractKey registerKey) { + keys.add( registerKey ); + } + + public AbstractKey getParent() { + return parent; + } + + public void setParent(AbstractKey parent) { + this.parent = parent; + } + + public Set getOtherKeys() { + return otherKeys; + } + + public void setOtherKeys(Set otherKeys) { + this.otherKeys = otherKeys; + } + + public void addPanelKey(AbstractKey panelKey) { + this.otherKeys.add( panelKey ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java new file mode 100644 index 000000000000..7b8b45a64056 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java @@ -0,0 +1,79 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Activity") +@Table(name = "activity") +@SuppressWarnings("WeakerAccess") +public class Activity extends BaseEntity { + private String description; + private Instruction instruction; + + protected WebApplication webApplication = null; + + /** + * Used by Hibernate + */ + @SuppressWarnings("unused") + public Activity() { + super(); + } + + public Activity(Integer id, String description, Instruction instruction) { + super( id ); + this.description = description; + this.instruction = instruction; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("Instruction") + @JoinColumn(name = "Instruction_Id") + public Instruction getInstruction() { + return instruction; + } + + @SuppressWarnings("unused") + public void setInstruction(Instruction instruction) { + this.instruction = instruction; + } + + @SuppressWarnings("unused") + @ManyToOne(fetch=FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("webApplication") + @JoinColumn(name="web_app_oid") + public WebApplication getWebApplication() { + return webApplication; + } + + public void setWebApplication(WebApplication webApplication) { + this.webApplication = webApplication; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java new file mode 100644 index 000000000000..360a042081c2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java @@ -0,0 +1,49 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Address") +@Table(name = "address") +public class Address { + private Integer id; + + private String text; + + public Address() { + } + + public Address(Integer id, String text) { + this.id = id; + this.text = text; + } + + @Id + @Column(name = "oid") + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java new file mode 100644 index 000000000000..463d1bb7d6c8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java @@ -0,0 +1,45 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * @author Steve Ebersole + */ +@MappedSuperclass +public class BaseEntity { + protected Integer id; + protected String nbr; + + public BaseEntity() { + } + + public BaseEntity(Integer id) { + this.id = id; + } + + @Id + @Column( name = "oid" ) + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getNbr() { + return nbr; + } + + public void setNbr(String nbr) { + this.nbr = nbr; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java new file mode 100644 index 000000000000..fd91eb9cf102 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java @@ -0,0 +1,364 @@ +/* + * 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.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class BidirectionalProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testIt() { + inTransaction( + session -> { + for (BEntity b : session.createQuery("from BEntity b", BEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AChildEntity a = b.getA(); + assertEquals( 0, stats.getPrepareStatementCount() ); + a.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getStringField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getIntegerField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setIntegerField( 1 ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getEntries(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setVersion( new Short( "2" ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setStringField( "this is a string" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + AMappedSuperclass mappedSuperclass = a; + mappedSuperclass.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + } + ); + + inTransaction( + session -> { + for (BEntity b : session.createQuery("from BEntity b", BEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AChildEntity a = b.getA(); + assertEquals( "this is a string", a.getStringField() ); + assertEquals( 2, a.getVersion() ); + assertEquals( new Integer( 1 ), a.getIntegerField() ); + } + } + ); + + inTransaction( + session -> { + for (CEntity c : session.createQuery("from CEntity c", CEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AEntity a = c.getA(); + assertEquals( 0, stats.getPrepareStatementCount() ); + a.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getIntegerField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setIntegerField( 1 ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setVersion( new Short( "2" ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + } + ); + + inTransaction( + session -> { + for (CEntity c : session.createQuery("from CEntity c", CEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AEntity a = c.getA(); + assertEquals( 2, a.getVersion() ); + assertEquals( new Integer( 1 ), a.getIntegerField() ); + } + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AChildEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AChildEntity a = new AChildEntity("a"); + BEntity b = new BEntity("b"); + b.setA(a); + session.persist(a); + session.persist(b); + + AChildEntity a1 = new AChildEntity("a1"); + CEntity c = new CEntity( "c" ); + c.setA( a1 ); + session.persist( a1 ); + session.persist( c ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from BEntity" ).executeUpdate(); + session.createQuery( "delete from CEntity" ).executeUpdate(); + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + @Entity(name="CEntity") + @Table(name="C") + public static class CEntity implements Serializable { + @Id + private String id; + + public CEntity(String id) { + this(); + setId(id); + } + + protected CEntity() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public void setA(AEntity a) { + aChildEntity = a; + a.getcEntries().add(this); + } + + public AEntity getA() { + return aChildEntity; + } + + @ManyToOne(fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("aEntity") + @JoinColumn(name="aEntity") + protected AEntity aChildEntity = null; + } + + @Entity(name="BEntity") + @Table(name="B") + public static class BEntity implements Serializable { + @Id + private String id; + + public BEntity(String id) { + this(); + setId(id); + } + + protected BEntity() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public void setA(AChildEntity a) { + aChildEntity = a; + a.getEntries().add(this); + } + + public AChildEntity getA() { + return aChildEntity; + } + + @ManyToOne(fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("aEntity") + @JoinColumn(name="aEntity") + protected AChildEntity aChildEntity = null; + } + + @MappedSuperclass + public static class AMappedSuperclass implements Serializable { + + @Id + private String id; + + @Basic + private short version; + + @Column(name = "INTEGER_FIELD") + private Integer integerField; + + public AMappedSuperclass(String id) { + setId(id); + } + + protected AMappedSuperclass() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(Integer integerField) { + this.integerField = integerField; + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + @Table(name="A") + public static class AEntity extends AMappedSuperclass { + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + @OneToMany(targetEntity=CEntity.class, mappedBy="aChildEntity", fetch=FetchType.LAZY) + protected Set cEntries = new LinkedHashSet(); + + public Set getcEntries() { + return cEntries; + } + + public void setcEntries(Set cEntries) { + this.cEntries = cEntries; + } + } + + @Entity(name="AChildEntity") + @Table(name="ACChild") + public static class AChildEntity extends AEntity { + + private String stringField; + + @OneToMany(targetEntity=BEntity.class, mappedBy="aChildEntity", fetch=FetchType.LAZY) + protected Set entries = new LinkedHashSet(); + + public AChildEntity(String id) { + super(id); + } + + protected AChildEntity() { + } + + public Set getEntries() { + return entries; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(String stringField) { + this.stringField = stringField; + } + + @Override + public Integer getIntegerField() { + return super.getIntegerField(); + } + + @Override + public void setIntegerField(Integer integerField) { + super.setIntegerField( integerField ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java new file mode 100644 index 000000000000..f0529f83e8ba --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java @@ -0,0 +1,35 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "CreditCardPayment") +@Table(name = "credit_payment") +public class CreditCardPayment extends Payment { + private String transactionId; + + public CreditCardPayment() { + } + + public CreditCardPayment(Integer oid, Float amount, String transactionId) { + super( oid, amount ); + this.transactionId = transactionId; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java new file mode 100644 index 000000000000..a709fe1fea2a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java @@ -0,0 +1,106 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Customer") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public abstract class Customer { + private Integer oid; + private String name; + private Set orders = new HashSet<>(); + + private Address address; + + private Customer parentCustomer; + private Set childCustomers = new HashSet<>(); + + public Customer() { + } + + public Customer(Integer oid, String name, Address address, Customer parentCustomer) { + this.oid = oid; + this.name = name; + this.address = address; + this.parentCustomer = parentCustomer; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "customer") + public Set getOrders() { + return orders; + } + + public void setOrders(Set orders) { + this.orders = orders; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.NO_PROXY) + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.NO_PROXY) + public Customer getParentCustomer() { + return parentCustomer; + } + + public void setParentCustomer(Customer parentCustomer) { + this.parentCustomer = parentCustomer; + } + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentCustomer") + public Set getChildCustomers() { + return childCustomers; + } + + public void setChildCustomers(Set childCustomers) { + this.childCustomers = childCustomers; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java new file mode 100644 index 000000000000..9eb37212be10 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java @@ -0,0 +1,35 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "DebitCardPayment") +@Table(name = "debit_payment") +public class DebitCardPayment extends Payment { + private String transactionId; + + public DebitCardPayment() { + } + + public DebitCardPayment(Integer oid, Float amount, String transactionId) { + super( oid, amount ); + this.transactionId = transactionId; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java new file mode 100644 index 000000000000..6b522ce761cc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java @@ -0,0 +1,629 @@ +/* + * 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.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class DeepInheritanceProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testRootGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity ) ); + aaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + aaaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAEntity( false ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAAAEntity( 4 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AAEntity.class ); + sources.addAnnotatedClass( AAAEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AEntity aEntity = new AEntity( "AEntity" ); + aEntity.setFieldInAMappedSuperclass( (short) 2 ); + aEntity.setFieldInAEntity( true ); + session.persist( aEntity ); + + AAEntity aaEntity = new AAAEntity( "AAEntity" ); + aaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaEntity.setFieldInAEntity( true ); + aaEntity.setFieldInAAEntity( "field in AAEntity" ); + session.persist( aaEntity ); + + AAAEntity aaaEntity = new AAAEntity( "AAAEntity" ); + aaaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaaEntity.setFieldInAEntity( true ); + aaaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaaEntity.setFieldInAAAEntity( 3 ); + session.persist( aaaEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class AMappedSuperclass implements Serializable { + + @Id + private String id; + + private short fieldInAMappedSuperclass; + + public short getFieldInAMappedSuperclass() { + return fieldInAMappedSuperclass; + } + + public void setFieldInAMappedSuperclass(short fieldInAMappedSuperclass) { + this.fieldInAMappedSuperclass = fieldInAMappedSuperclass; + } + + public AMappedSuperclass(String id) { + this.id = id; + } + + protected AMappedSuperclass() { + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class AEntity extends AMappedSuperclass { + + private Boolean fieldInAEntity; + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + public Boolean getFieldInAEntity() { + return fieldInAEntity; + } + public void setFieldInAEntity(Boolean fieldInAEntity) { + this.fieldInAEntity = fieldInAEntity; + } + } + + @Entity(name="AAEntity") + public static class AAEntity extends AEntity { + + private String fieldInAAEntity; + + public AAEntity(String id) { + super(id); + } + + protected AAEntity() { + } + + public String getFieldInAAEntity() { + return fieldInAAEntity; + } + + public void setFieldInAAEntity(String fieldInAAEntity) { + this.fieldInAAEntity = fieldInAAEntity; + } + } + + @Entity(name="AAAEntity") + public static class AAAEntity extends AAEntity { + + private long fieldInAAAEntity; + + public AAAEntity(String id) { + super(id); + } + + protected AAAEntity() { + } + + public long getFieldInAAAEntity() { + return fieldInAAAEntity; + } + + public void setFieldInAAAEntity(long fieldInAAAEntity) { + this.fieldInAAAEntity = fieldInAAAEntity; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java new file mode 100644 index 000000000000..5b1e466e3b10 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java @@ -0,0 +1,1514 @@ +/* + * 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.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class DeepInheritanceWithNonEntitiesProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testRootGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.getFieldInNonEntityAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( (short) 5 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 5, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + assertEquals( 6, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.setFieldInNonEntityAEntitySuperclass( 10L ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 10 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + assertEquals( 6, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property + aaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( (short) 10 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 10, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 4 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAAEntitySuperclass( "xyz" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "xyz", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity only gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAAAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAEntity( false ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAAAEntity( 4 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 99 ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 99, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAEntitySuperclass( 10L ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 10 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "xyz" ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "xyz", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + // aaaEntity only gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( true ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( true, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AAEntity.class ); + sources.addAnnotatedClass( AAAEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AEntity aEntity = new AEntity( "AEntity" ); + aEntity.setFieldInAMappedSuperclass( (short) 2 ); + aEntity.setFieldInAEntity( true ); + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + session.persist( aEntity ); + + AAEntity aaEntity = new AAAEntity( "AAEntity" ); + aaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaEntity.setFieldInAEntity( true ); + aaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "abc" ); + session.persist( aaEntity ); + + AAAEntity aaaEntity = new AAAEntity( "AAAEntity" ); + aaaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaaEntity.setFieldInAEntity( true ); + aaaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaaEntity.setFieldInAAAEntity( 3 ); + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "abc" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( true ); + session.persist( aaaEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + public static class NonEntityAMappedSuperclassSuperclass { + private int fieldInNonEntityAMappedSuperclassSuperclass; + + public int getFieldInNonEntityAMappedSuperclassSuperclass() { + return fieldInNonEntityAMappedSuperclassSuperclass; + } + + public void setFieldInNonEntityAMappedSuperclassSuperclass(int fieldInNonEntityAMappedSuperclassSuperclass) { + this.fieldInNonEntityAMappedSuperclassSuperclass = fieldInNonEntityAMappedSuperclassSuperclass; + } + } + + @MappedSuperclass + public static class AMappedSuperclass extends NonEntityAMappedSuperclassSuperclass implements Serializable { + + @Id + private String id; + + private short fieldInAMappedSuperclass; + + public short getFieldInAMappedSuperclass() { + return fieldInAMappedSuperclass; + } + + public void setFieldInAMappedSuperclass(short fieldInAMappedSuperclass) { + this.fieldInAMappedSuperclass = fieldInAMappedSuperclass; + } + + public AMappedSuperclass(String id) { + this.id = id; + } + + protected AMappedSuperclass() { + } + } + + public static class NonEntityAEntitySuperclass extends AMappedSuperclass { + + private Long fieldInNonEntityAEntitySuperclass; + + public NonEntityAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAEntitySuperclass() { + } + + public Long getFieldInNonEntityAEntitySuperclass() { + return fieldInNonEntityAEntitySuperclass; + } + + public void setFieldInNonEntityAEntitySuperclass(Long fieldInNonEntityAEntitySuperclass) { + this.fieldInNonEntityAEntitySuperclass = fieldInNonEntityAEntitySuperclass; + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class AEntity extends NonEntityAEntitySuperclass { + + private Boolean fieldInAEntity; + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + public Boolean getFieldInAEntity() { + return fieldInAEntity; + } + public void setFieldInAEntity(Boolean fieldInAEntity) { + this.fieldInAEntity = fieldInAEntity; + } + } + + public static class NonEntityAAEntitySuperclass extends AEntity { + + private String fieldInNonEntityAAEntitySuperclass; + + public NonEntityAAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAAEntitySuperclass() { + } + + public String getFieldInNonEntityAAEntitySuperclass() { + return fieldInNonEntityAAEntitySuperclass; + } + + public void setFieldInNonEntityAAEntitySuperclass(String fieldInNonEntityAAEntitySuperclass) { + this.fieldInNonEntityAAEntitySuperclass = fieldInNonEntityAAEntitySuperclass; + } + } + + @Entity(name="AAEntity") + public static class AAEntity extends NonEntityAAEntitySuperclass { + + private String fieldInAAEntity; + + public AAEntity(String id) { + super(id); + } + + protected AAEntity() { + } + + public String getFieldInAAEntity() { + return fieldInAAEntity; + } + + public void setFieldInAAEntity(String fieldInAAEntity) { + this.fieldInAAEntity = fieldInAAEntity; + } + } + + public static class NonEntityAAAEntitySuperclass extends AAEntity { + + private Boolean fieldInNonEntityAAAEntitySuperclass; + + public NonEntityAAAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAAAEntitySuperclass() { + } + + public Boolean getFieldInNonEntityAAAEntitySuperclass() { + return fieldInNonEntityAAAEntitySuperclass; + } + + public void setFieldInNonEntityAAAEntitySuperclass(Boolean fieldInNonEntityAAAEntitySuperclass) { + this.fieldInNonEntityAAAEntitySuperclass = fieldInNonEntityAAAEntitySuperclass; + } + } + + @Entity(name="AAAEntity") + public static class AAAEntity extends NonEntityAAAEntitySuperclass { + + private long fieldInAAAEntity; + + public AAAEntity(String id) { + super(id); + } + + protected AAAEntity() { + } + + public long getFieldInAAAEntity() { + return fieldInAAAEntity; + } + + public void setFieldInAAAEntity(long fieldInAAAEntity) { + this.fieldInAAAEntity = fieldInAAAEntity; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java new file mode 100644 index 000000000000..8647b578e44a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java @@ -0,0 +1,40 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "DomesticCustomer") +@Table(name = "domestic_cust") +public class DomesticCustomer extends Customer { + private String taxId; + + public DomesticCustomer() { + } + + public DomesticCustomer( + Integer oid, + String name, + String taxId, + Address address, + Customer parentCustomer) { + super( oid, name, address, parentCustomer ); + this.taxId = taxId; + } + + public String getTaxId() { + return taxId; + } + + public void setTaxId(String taxId) { + this.taxId = taxId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java new file mode 100644 index 000000000000..1794cb01fd94 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java @@ -0,0 +1,252 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class EntitySharedInCollectionAndToOneTest extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void testIt() { + inTransaction( + session -> { + int passes = 0; + for ( CodeTable codeTable : session.createQuery( "from CodeTable ct where ct.id = 2", CodeTable.class ).list() ) { + assert 0 == passes; + passes++; + Hibernate.initialize( codeTable.getCodeTableItems() ); + } + + assertThat( session.getPersistenceContext().getNumberOfManagedEntities(), is( 2 ) ); + } + ); + } + + @Before + public void createTestData() { + inTransaction( + session -> { + final CodeTable codeTable1 = new CodeTable( 1, 1 ); + final CodeTableItem item1 = new CodeTableItem( 1, 1, "first" ); + final CodeTableItem item2 = new CodeTableItem( 2, 1, "second" ); + final CodeTableItem item3 = new CodeTableItem( 3, 1, "third" ); + + session.save( codeTable1 ); + session.save( item1 ); + session.save( item2 ); + session.save( item3 ); + + codeTable1.getCodeTableItems().add( item1 ); + item1.setCodeTable( codeTable1 ); + + codeTable1.getCodeTableItems().add( item2 ); + item2.setCodeTable( codeTable1 ); + + codeTable1.getCodeTableItems().add( item3 ); + item3.setCodeTable( codeTable1 ); + + codeTable1.setDefaultItem( item1 ); + item1.setDefaultItemInverse( codeTable1 ); + + final CodeTable codeTable2 = new CodeTable( 2, 1 ); + final CodeTableItem item4 = new CodeTableItem( 4, 1, "fourth" ); + + session.save( codeTable2 ); + session.save( item4 ); + + codeTable2.getCodeTableItems().add( item4 ); + item4.setCodeTable( codeTable2 ); + + codeTable2.setDefaultItem( item4 ); + item4.setDefaultItemInverse( codeTable2 ); + } + ); + } + +// @After +// public void deleteTestData() { +// inTransaction( +// session -> { +// for ( CodeTable codeTable : session.createQuery( "from CodeTable", CodeTable.class ).list() ) { +// session.delete( codeTable ); +// } +// } +// ); +// } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( CodeTableItem.class ); + sources.addAnnotatedClass( CodeTable.class ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private Integer oid; + private int version; + + public BaseEntity() { + } + + public BaseEntity(Integer oid, int version) { + this.oid = oid; + this.version = version; + } + + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + } + + @Entity( name = "CodeTable" ) + @Table( name = "code_table" ) + public static class CodeTable extends BaseEntity { + @OneToOne( fetch = FetchType.LAZY ) + @LazyGroup( "defaultCodeTableItem" ) + @JoinColumn( name = "default_code_id" ) + private CodeTableItem defaultItem; + + @OneToMany( mappedBy = "codeTable" ) + private Set codeTableItems = new HashSet<>(); + + public CodeTable() { + } + + public CodeTable(Integer oid, int version) { + super( oid, version ); + } + + public CodeTableItem getDefaultItem() { + return defaultItem; + } + + public void setDefaultItem(CodeTableItem defaultItem) { + this.defaultItem = defaultItem; + } + + public Set getCodeTableItems() { + return codeTableItems; + } + + public void setCodeTableItems(Set codeTableItems) { + this.codeTableItems = codeTableItems; + } + } + + @Entity( name = "CodeTableItem" ) + @Table( name = "code_table_item" ) + public static class CodeTableItem extends BaseEntity { + private String name; + + @ManyToOne( fetch = FetchType.LAZY ) + @LazyGroup( "codeTable" ) + @JoinColumn( name = "code_table_oid" ) + private CodeTable codeTable; + + @OneToOne( mappedBy = "defaultItem", fetch=FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + @LazyGroup( "defaultItemInverse" ) + protected CodeTable defaultItemInverse; + + + public CodeTableItem() { + } + + public CodeTableItem(Integer oid, int version, String name) { + super( oid, version ); + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public CodeTable getCodeTable() { + return codeTable; + } + + public void setCodeTable(CodeTable codeTable) { + this.codeTable = codeTable; + } + + public CodeTable getDefaultItemInverse() { + return defaultItemInverse; + } + + public void setDefaultItemInverse(CodeTable defaultItemInverse) { + this.defaultItemInverse = defaultItemInverse; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java new file mode 100644 index 000000000000..f3d38c16ebcc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java @@ -0,0 +1,926 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.sql.Blob; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollableResults; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class FetchGraphTest extends BaseNonConfigCoreFunctionalTestCase { + + + @Test + public void testLoadNonOwningOneToOne() { + // Test loading D and accessing E + // E is the owner of the FK, not D. When `D#e` is accessed we + // need to actually load E because its table has the FK value, not + // D's table + + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final DEntity entityD = session.load( DEntity.class, 1L ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + + entityD.getE(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isInitialized( entityD.getA() ); + assert Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isInitialized( entityD.getC() ); + assert Hibernate.isPropertyInitialized( entityD, "e" ); + assert Hibernate.isInitialized( entityD.getE() ); + } + ); + } + + @Test + public void testLoadOwningOneToOne() { + // switch it around + + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final EEntity entityE = session.load( EEntity.class, 17L ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + assert !Hibernate.isPropertyInitialized( entityE, "d" ); + + final DEntity entityD = entityE.getD(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + } + ); + } + + @Test + public void testFetchingScroll() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + + inStatelessSession( + session -> { + final String qry = "select e from E e join fetch e.d"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + + inStatelessSession( + session -> { + final String qry = "select d from D d " + + "join fetch d.a " + + "join fetch d.bs " + + "join fetch d.c " + + "join fetch d.e " + + "join fetch d.g"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + + inStatelessSession( + session -> { + final String qry = "select g from G g join fetch g.dEntities"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + } + + + @Test + public void testLazyAssociationSameAsNonLazyInPC() { + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getInstrumentationMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final AEntity entityA = session.get( AEntity.class, 1L ); + + final DEntity entityD = session.load( DEntity.class, 1L ); + assert !Hibernate.isInitialized( entityD ); + Hibernate.initialize( entityD ); + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert entityA.getOid() == entityD.getA().getOid(); + assert session.getPersistenceContext().getEntry( entityA ) == + session.getPersistenceContext().getEntry( entityD.getA() ); + assert entityA == entityD.getA(); + } + ); + } + + @Test + public void testRandomAccess() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + final EntityPersister persister = sessionFactory().getMetamodel().entityPersister( DEntity.class ); + assert persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + + inSession( + session -> { + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Load a D + + final DEntity entityD = session.load( DEntity.class, 1L ); + + assertThat( entityD instanceof HibernateProxy, is(false) ); + assertThat( entityD instanceof PersistentAttributeInterceptable, is(true) ); + assertThat( Hibernate.isInitialized( entityD ), is(false) ); + // Because D is enhanced we should not have executed any SQL + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + // access the id. + // -since entityD is a "enhanced proxy", this should not trigger loading + assertThat( entityD.getOid(), is(1L) ); + assertThat( Hibernate.isInitialized( entityD ), is(false) ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + + // Because D is enhanced we should not have executed any SQL + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + assert !Hibernate.isPropertyInitialized( entityD, "g" ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access C + + final CEntity c = entityD.getC(); + + // make sure interception happened + assertThat( c, notNullValue() ); + + // See `#testLoadNonOwningOneToOne` + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + // The fields themselves are initialized - set to the + // enhanced entity "proxy" instance + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + + assert !Hibernate.isInitialized( entityD.getA() ); + assert !Hibernate.isInitialized( entityD.getC() ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access C again + + entityD.getC(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access E + + final EEntity e1 = entityD.getE(); + assert Hibernate.isPropertyInitialized( entityD, "e" ); + + assert Hibernate.isInitialized( entityD.getE() ); + + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + assert Hibernate.isInitialized( e1 ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access E again + + entityD.getE(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + assertThat( entityD.getE().getOid(), is(17L) ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // now lets access the attribute "proxies" + + // this will load the table C data + entityD.getC().getC1(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + assert Hibernate.isInitialized( entityD.getC() ); + + // this should not - it was already loaded above + entityD.getE().getE1(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + + Set bs = entityD.getBs(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + assertThat( bs.size(), is( 2 ) ); + assertThat( stats.getPrepareStatementCount(), is( 4L ) ); + + entityD.getG().getOid(); + assertThat( stats.getPrepareStatementCount(), is( 4L ) ); + } + ); + } + + @Test + public void testNullManyToOneHql() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + final String qry = "select e from Activity e"; + final List activities = session.createQuery( qry, Activity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + long expectedCount = 1L; + + for ( Activity activity : activities ) { + if ( activity.getInstruction() != null ) { + // do something special + // - here we just access an attribute to trigger + // the initialization of the association + + activity.getInstruction().getSummary(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + } + + if ( activity.getWebApplication() != null ) { + // trigger base group initialization + activity.getWebApplication().getName(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + // trigger initialization + activity.getWebApplication().getSiteUrl(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + } + } + } + ); + } + + @Test + public void testAbstractClassAssociation() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( RoleEntity.class ) + .getInstrumentationMetadata() + .isEnhancedForLazyLoading(); + + inTransaction( + session -> { + final String qry = "select e from RoleEntity e"; + final List keyRoleEntities = session.createQuery( qry, RoleEntity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + for ( RoleEntity keyRoleEntity : keyRoleEntities ) { + Object key = Hibernate.unproxy( keyRoleEntity.getKey() ); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + Set specializedEntities = ( (SpecializedKey) key ) + .getSpecializedEntities(); + + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + Iterator iterator = specializedEntities.iterator(); + while ( iterator.hasNext() ) { + SpecializedEntity specializedEntity = iterator.next(); + assertThat( specializedEntity.getId(), notNullValue() ); + specializedEntity.getValue(); + } + + // but regardless there should not be an additional query + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + } + } + ); + } + + @Test + public void testNonAbstractAssociationWithSubclassValue() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + final String qry = "select e from RoleEntity e"; + final List keyRoleEntities = session.createQuery( qry, RoleEntity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + assertThat( keyRoleEntities.size(), is( 1 ) ); + + RoleEntity roleEntity = keyRoleEntities.get( 0 ); + assertThat( + Hibernate.unproxy( roleEntity.getKey() ).getClass().getName(), + is( SpecializedKey.class.getName() ) + ); + + assertThat( + Hibernate.unproxy( roleEntity.getSpecializedKey() ).getClass().getName(), + is( MoreSpecializedKey.class.getName() ) + ); + } + ); + } + + @Test + public void testQueryAndDeleteDEntity() { + inTransaction( + session -> { + List result = session.createQuery( + "select d from D d ", + DEntity.class + ).list(); + result.forEach( entity -> { + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + + } ); + } + ); + } + + @Test + public void testLoadAndDeleteDEntity() { + inTransaction( + session -> { + DEntity entity = session.load( DEntity.class, 1L ); + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + } + ); + } + + @Test + public void testGetAndDeleteDEntity() { + inTransaction( + session -> { + DEntity entity = session.get( DEntity.class, 1L ); + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + } + ); + } + + @Test + public void testGetAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.get( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Test + public void testLoadAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.load( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Test + public void testQueryAndDeleteEEntity() { + inTransaction( + session -> { + List result = session.createQuery( + "select e from E e", + EEntity.class + ).list(); + result.forEach( entity -> { + session.delete( entity ); + session.delete( entity.getD() ); + } ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( DEntity.class ); + sources.addAnnotatedClass( EEntity.class ); + sources.addAnnotatedClass( GEntity.class ); + + sources.addAnnotatedClass( Activity.class ); + sources.addAnnotatedClass( Instruction.class ); + sources.addAnnotatedClass( WebApplication.class ); + + sources.addAnnotatedClass( SpecializedKey.class ); + sources.addAnnotatedClass( MoreSpecializedKey.class ); + sources.addAnnotatedClass( RoleEntity.class ); + sources.addAnnotatedClass( AbstractKey.class ); + sources.addAnnotatedClass( GenericKey.class ); + sources.addAnnotatedClass( SpecializedEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + DEntity d = new DEntity(); + d.setD( "bla" ); + d.setOid( 1 ); + + byte[] lBytes = "agdfagdfagfgafgsfdgasfdgfgasdfgadsfgasfdgasfdgasdasfdg".getBytes(); + Blob lBlob = Hibernate.getLobCreator( session ).createBlob( lBytes ); + d.setBlob( lBlob ); + + BEntity b1 = new BEntity(); + b1.setOid( 1 ); + b1.setB1( 34 ); + b1.setB2( "huhu" ); + + BEntity b2 = new BEntity(); + b2.setOid( 2 ); + b2.setB1( 37 ); + b2.setB2( "haha" ); + + Set lBs = new HashSet<>(); + lBs.add( b1 ); + lBs.add( b2 ); + d.setBs( lBs ); + + AEntity a = new AEntity(); + a.setOid( 1 ); + a.setA( "hihi" ); + d.setA( a ); + + EEntity e = new EEntity(); + e.setOid( 17 ); + e.setE1( "Balu" ); + e.setE2( "Bär" ); + + e.setD( d ); + d.setE( e ); + + CEntity c = new CEntity(); + c.setOid( 1 ); + c.setC1( "ast" ); + c.setC2( "qwert" ); + c.setC3( "yxcv" ); + d.setC( c ); + + GEntity g = new GEntity(); + g.setOid( 1 ); + g.getdEntities().add( d ); + d.setG( g ); + + + session.save( b1 ); + session.save( b2 ); + session.save( a ); + session.save( c ); + session.save( g ); + session.save( d ); + session.save( e ); + + + // create a slew of Activity objects, some with Instruction reference + // some without. + + for ( int i = 0; i < 30; i++ ) { + final Activity activity = new Activity( i, "Activity #" + i, null ); + if ( i % 2 == 0 ) { + final Instruction instr = new Instruction( i, "Instruction #" + i ); + activity.setInstruction( instr ); + session.save( instr ); + } + else { + final WebApplication webApplication = new WebApplication( i, "http://" + i + ".com" ); + activity.setWebApplication( webApplication ); + webApplication.getActivities().add( activity ); + session.save( webApplication ); + } + + session.save( activity ); + } + + RoleEntity roleEntity = new RoleEntity(); + roleEntity.setOid( 1L ); + + SpecializedKey specializedKey = new SpecializedKey(); + specializedKey.setOid(1L); + + MoreSpecializedKey moreSpecializedKey = new MoreSpecializedKey(); + moreSpecializedKey.setOid( 3L ); + + SpecializedEntity specializedEntity = new SpecializedEntity(); + specializedEntity.setId( 2L ); + specializedKey.addSpecializedEntity( specializedEntity ); + specializedEntity.setSpecializedKey( specializedKey); + + specializedKey.addRole( roleEntity ); + roleEntity.setKey( specializedKey ); + roleEntity.setSpecializedKey( moreSpecializedKey ); + moreSpecializedKey.addRole( roleEntity ); + session.save( specializedEntity ); + session.save( roleEntity ); + session.save( specializedKey ); + session.save( moreSpecializedKey ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from E" ).executeUpdate(); + session.createQuery( "delete from D" ).executeUpdate(); + session.createQuery( "delete from C" ).executeUpdate(); + session.createQuery( "delete from B" ).executeUpdate(); + session.createQuery( "delete from A" ).executeUpdate(); + session.createQuery( "delete from G" ).executeUpdate(); + + session.createQuery( "delete from Activity" ).executeUpdate(); + session.createQuery( "delete from Instruction" ).executeUpdate(); + session.createQuery( "delete from WebApplication" ).executeUpdate(); + + session.createQuery( "delete from SpecializedEntity" ).executeUpdate(); + session.createQuery( "delete from RoleEntity" ).executeUpdate(); + session.createQuery( "delete from MoreSpecializedKey" ).executeUpdate(); + session.createQuery( "delete from SpecializedKey" ).executeUpdate(); + session.createQuery( "delete from GenericKey" ).executeUpdate(); + session.createQuery( "delete from AbstractKey" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private long oid; + private short version; + + public long getOid() { + return oid; + } + + public void setOid(long oid) { + this.oid = oid; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + } + + @Entity(name = "A") + @Table(name = "A") + public static class AEntity extends BaseEntity { + @Column(name = "A") + private String a; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + } + + + @Entity(name = "B") + @Table(name = "B") + public static class BEntity extends BaseEntity { + private Integer b1; + private String b2; + + public Integer getB1() { + return b1; + } + + public void setB1(Integer b1) { + this.b1 = b1; + } + + public String getB2() { + return b2; + } + + public void setB2(String b2) { + this.b2 = b2; + } + } + + + @Entity(name = "C") + @Table(name = "C") + public static class CEntity extends BaseEntity { + private String c1; + private String c2; + private String c3; + private Long c4; + + public String getC1() { + return c1; + } + + public void setC1(String c1) { + this.c1 = c1; + } + + public String getC2() { + return c2; + } + + @Basic(fetch = FetchType.LAZY) + public void setC2(String c2) { + this.c2 = c2; + } + + public String getC3() { + return c3; + } + + public void setC3(String c3) { + this.c3 = c3; + } + + public Long getC4() { + return c4; + } + + public void setC4(Long c4) { + this.c4 = c4; + } + } + + @Entity(name = "D") + @Table(name = "D") + public static class DEntity extends BaseEntity { + private String d; + + // ****** Relations ***************** + @OneToOne(fetch = FetchType.LAZY) +// @LazyToOne(LazyToOneOption.PROXY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("a") + public AEntity a; + + @OneToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) +// @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("c") + public CEntity c; + + @OneToMany(targetEntity = BEntity.class) + public Set bs; + + @OneToOne(mappedBy = "d", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("e") + private EEntity e; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn() + @LazyGroup("g") + public GEntity g; + + @Lob + @Basic(fetch = FetchType.LAZY) + @LazyGroup("blob") + private Blob blob; + + public String getD() { + return d; + } + + public void setD(String d) { + this.d = d; + } + + + public AEntity getA() { + return a; + } + + public void setA(AEntity a) { + this.a = a; + } + + public Set getBs() { + return bs; + } + + public void setBs(Set bs) { + this.bs = bs; + } + + public CEntity getC() { + return c; + } + + public void setC(CEntity c) { + this.c = c; + } + + public Blob getBlob() { + return blob; + } + + public void setBlob(Blob blob) { + this.blob = blob; + } + + public EEntity getE() { + return e; + } + + public void setE(EEntity e) { + this.e = e; + } + + public GEntity getG() { + return g; + } + + public void setG(GEntity g) { + this.g = g; + } + } + + @Entity(name = "E") + @Table(name = "E") + public static class EEntity extends BaseEntity { + private String e1; + private String e2; + + @OneToOne(fetch = FetchType.LAZY) + private DEntity d; + + public String getE1() { + return e1; + } + + public void setE1(String e1) { + this.e1 = e1; + } + + public String getE2() { + return e2; + } + + public void setE2(String e2) { + this.e2 = e2; + } + + public DEntity getD() { + return d; + } + + public void setD(DEntity d) { + this.d = d; + } + } + + @Entity(name = "G") + @Table(name = "G") + public static class GEntity extends BaseEntity { + + @OneToMany(mappedBy = "g") + public Set dEntities = new HashSet<>(); + + public Set getdEntities() { + return dEntities; + } + + public void setdEntities(Set dEntities) { + this.dEntities = dEntities; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java new file mode 100644 index 000000000000..a9b0c6acc3de --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java @@ -0,0 +1,48 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "ForeignCustomer") +@Table(name = "foreign_cust") +public class ForeignCustomer extends Customer { + private String vat; + + public ForeignCustomer() { + } + + public ForeignCustomer( + Integer oid, + String name, + Address address, + String vat, + Customer parentCustomer) { + super( oid, name, address, parentCustomer ); + this.vat = vat; + } + + public ForeignCustomer( + Integer oid, + String name, + Address address, + String vat) { + this( oid, name, address, vat, null ); + } + + public String getVat() { + return vat; + } + + public void setVat(String vat) { + this.vat = vat; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java new file mode 100644 index 000000000000..6f52802abf1e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +@Entity(name="GenericKey") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name="PP_GenericDCKey") +public abstract class GenericKey extends AbstractKey implements Serializable { + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java new file mode 100644 index 000000000000..56d594b368da --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java @@ -0,0 +1,41 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Instruction") +@Table(name = "instruction") +public class Instruction extends BaseEntity { + private String summary; + + /** + * Used by Hibernate + */ + @SuppressWarnings("unused") + public Instruction() { + super(); + } + + @SuppressWarnings("WeakerAccess") + public Instruction(Integer id, String summary) { + super( id ); + this.summary = summary; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceAllowProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceAllowProxyTest.java new file mode 100644 index 000000000000..535a3b529663 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceAllowProxyTest.java @@ -0,0 +1,297 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ + +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions( lazyLoading = true ) +public class LazyGroupWithInheritanceAllowProxyTest extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void baseline() { + inTransaction( + session -> { + final List orders = session.createQuery( "select o from Order o", Order.class ).list(); + for ( Order order : orders ) { + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + + } + + } + ); + } + + @Test + public void testMergingUninitializedProxy() { + inTransaction( + session -> { + final List orders = session.createQuery( "select o from Order o", Order.class ).list(); + for ( Order order : orders ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + assertThat( order.getCustomer().getAddress(), notNullValue() ); + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) order.getCustomer().getAddress(); + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + } + } + ); + } + + + @Test + public void queryEntityWithAssociationToAbstract() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final AtomicInteger expectedQueryCount = new AtomicInteger( 0 ); + + inTransaction( + session -> { + final List orders = session.createQuery( "select o from Order o", Order.class ).list(); + + expectedQueryCount.set( 1 ); + + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + for ( Order order : orders ) { + System.out.println( "############################################" ); + System.out.println( "Starting Order #" + order.getOid() ); + + // accessing the many-to-one's id should not trigger a load + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // accessing the one-to-many should trigger a load + final Set orderPayments = order.getPayments(); + System.out.println( "Number of payments = " + orderPayments.size() ); + expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // access the non-inverse, logical 1-1 + order.getSupplemental(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental() != null ) { + System.out.println( "Got Order#supplemental = " + order.getSupplemental().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + + // access the inverse, logical 1-1 + order.getSupplemental2(); + expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental2() != null ) { + System.out.println( "Got Order#supplemental2 = " + order.getSupplemental2().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + } + } + ); + } + + /** + * Same test as {@link #queryEntityWithAssociationToAbstract()}, but using runtime + * fetching to issues just a single select + */ + @Test + public void queryEntityWithAssociationToAbstractRuntimeFetch() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final AtomicInteger expectedQueryCount = new AtomicInteger( 0 ); + + inTransaction( + session -> { + final String qry = "select o from Order o join fetch o.customer c join fetch o.payments join fetch o.supplemental join fetch o.supplemental2"; + + final List orders = session.createQuery( qry, Order.class ).list(); + + // oh look - just a single query for all the data we will need. hmm, crazy + expectedQueryCount.set( 1 ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + for ( Order order : orders ) { + System.out.println( "############################################" ); + System.out.println( "Starting Order #" + order.getOid() ); + + // accessing the many-to-one's id should not trigger a load + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // accessing the one-to-many should trigger a load + final Set orderPayments = order.getPayments(); + System.out.println( "Number of payments = " + orderPayments.size() ); + + // loaded already + // expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // access the non-inverse, logical 1-1 + order.getSupplemental(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental() != null ) { + System.out.println( "Got Order#supplemental = " + order.getSupplemental().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + + // access the inverse, logical 1-1 + order.getSupplemental2(); + + // loaded already + // expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental2() != null ) { + System.out.println( "Got Order#supplemental2 = " + order.getSupplemental2().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + } + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + + // todo : this is the only difference between this test and LazyGroupWithInheritanceTest + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.USE_QUERY_CACHE, "false" ); + } + + + @Before + public void prepareTestData() { + inTransaction( + session -> { + final Address austin = new Address( 1, "Austin" ); + final Address london = new Address( 2, "London" ); + + session.save( austin ); + session.save( london ); + + final ForeignCustomer acme = new ForeignCustomer( 1, "Acme", london, "1234" ); + final ForeignCustomer acmeBrick = new ForeignCustomer( 2, "Acme Brick", london, "9876", acme ); + + final ForeignCustomer freeBirds = new ForeignCustomer( 3, "Free Birds", austin, "13579" ); + + session.save( acme ); + session.save( acmeBrick ); + session.save( freeBirds ); + + final Order order1 = new Order( 1, "some text", freeBirds ); + freeBirds.getOrders().add( order1 ); + session.save( order1 ); + + final OrderSupplemental orderSupplemental = new OrderSupplemental( 1, 1 ); + order1.setSupplemental( orderSupplemental ); + final OrderSupplemental2 orderSupplemental2_1 = new OrderSupplemental2( 2, 2 ); + order1.setSupplemental2( orderSupplemental2_1 ); + orderSupplemental2_1.setOrder( order1 ); + session.save( orderSupplemental ); + session.save( orderSupplemental2_1 ); + + final Order order2 = new Order( 2, "some text", acme ); + acme.getOrders().add( order2 ); + session.save( order2 ); + + final OrderSupplemental2 orderSupplemental2_2 = new OrderSupplemental2( 3, 3 ); + order2.setSupplemental2( orderSupplemental2_2 ); + orderSupplemental2_2.setOrder( order2 ); + session.save( orderSupplemental2_2 ); + + final CreditCardPayment payment1 = new CreditCardPayment( 1, 1F, "1" ); + session.save( payment1 ); + order1.getPayments().add( payment1 ); + + final DebitCardPayment payment2 = new DebitCardPayment( 2, 2F, "2" ); + session.save( payment2 ); + order1.getPayments().add( payment2 ); + + + + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from CreditCardPayment" ).executeUpdate(); + session.createQuery( "delete from DebitCardPayment" ).executeUpdate(); + + session.createQuery( "delete from OrderSupplemental2" ).executeUpdate(); + + session.createQuery( "delete from Order" ).executeUpdate(); + + session.createQuery( "delete from OrderSupplemental" ).executeUpdate(); + + session.createQuery( "delete from DomesticCustomer" ).executeUpdate(); + session.createQuery( "delete from ForeignCustomer" ).executeUpdate(); + + session.createQuery( "delete from Address" ).executeUpdate(); + } + ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + + sources.addAnnotatedClass( Customer.class ); + sources.addAnnotatedClass( ForeignCustomer.class ); + sources.addAnnotatedClass( DomesticCustomer.class ); + + sources.addAnnotatedClass( Payment.class ); + sources.addAnnotatedClass( CreditCardPayment.class ); + sources.addAnnotatedClass( DebitCardPayment.class ); + + sources.addAnnotatedClass( Address.class ); + + sources.addAnnotatedClass( Order.class ); + sources.addAnnotatedClass( OrderSupplemental.class ); + sources.addAnnotatedClass( OrderSupplemental2.class ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceTest.java new file mode 100644 index 000000000000..fbb13398edfd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyGroupWithInheritanceTest.java @@ -0,0 +1,267 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.test.bytecode.enhancement.lazy.group.BidirectionalLazyGroupsInEmbeddableTest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ + +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions( lazyLoading = true ) +public class LazyGroupWithInheritanceTest extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void queryEntityWithAssociationToAbstract() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final AtomicInteger expectedQueryCount = new AtomicInteger( 0 ); + + inTransaction( + session -> { + final List orders = session.createQuery( "select o from Order o", Order.class ).list(); + + // todo (HHH-11147) : this is a regression from 4.x + // - the condition is that the association from Order to Customer points to the non-root + // entity (Customer) rather than one of its concrete sub-types (DomesticCustomer, + // ForeignCustomer). We'd have to read the "other table" to be able to resolve the + // concrete type. The same holds true for associations to versioned entities as well. + // The only viable solution I see would be to join to the "other side" and read the + // version/discriminator[1]. But of course that means doing the join which is generally + // what the application is trying to avoid in the first place + //expectedQueryCount.set( 1 ); + expectedQueryCount.set( 4 ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + for ( Order order : orders ) { + System.out.println( "############################################" ); + System.out.println( "Starting Order #" + order.getOid() ); + + // accessing the many-to-one's id should not trigger a load + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // accessing the one-to-many should trigger a load + final Set orderPayments = order.getPayments(); + System.out.println( "Number of payments = " + orderPayments.size() ); + expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // access the non-inverse, logical 1-1 + order.getSupplemental(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental() != null ) { + System.out.println( "Got Order#supplemental = " + order.getSupplemental().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + + // access the inverse, logical 1-1 + order.getSupplemental2(); + expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental2() != null ) { + System.out.println( "Got Order#supplemental2 = " + order.getSupplemental2().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + } + } + ); + } + + /** + * Same test as {@link #queryEntityWithAssociationToAbstract()}, but using runtime + * fetching to issues just a single select + */ + @Test + public void queryEntityWithAssociationToAbstractRuntimeFetch() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final AtomicInteger expectedQueryCount = new AtomicInteger( 0 ); + + inTransaction( + session -> { + final String qry = "select o from Order o join fetch o.customer c join fetch o.payments join fetch o.supplemental join fetch o.supplemental2"; + + final List orders = session.createQuery( qry, Order.class ).list(); + + // oh look - just a single query for all the data we will need. hmm, crazy + expectedQueryCount.set( 1 ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + for ( Order order : orders ) { + System.out.println( "############################################" ); + System.out.println( "Starting Order #" + order.getOid() ); + + // accessing the many-to-one's id should not trigger a load + if ( order.getCustomer().getOid() == null ) { + System.out.println( "Got Order#customer: " + order.getCustomer().getOid() ); + } + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // accessing the one-to-many should trigger a load + final Set orderPayments = order.getPayments(); + System.out.println( "Number of payments = " + orderPayments.size() ); + + // loaded already + // expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + + // access the non-inverse, logical 1-1 + order.getSupplemental(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental() != null ) { + System.out.println( "Got Order#supplemental = " + order.getSupplemental().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + + // access the inverse, logical 1-1 + order.getSupplemental2(); + + // loaded already + // expectedQueryCount.getAndIncrement(); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + if ( order.getSupplemental2() != null ) { + System.out.println( "Got Order#supplemental2 = " + order.getSupplemental2().getOid() ); + assertEquals( expectedQueryCount.get(), stats.getPrepareStatementCount() ); + } + } + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.USE_QUERY_CACHE, "false" ); + } + + + @Before + public void prepareTestData() { + inTransaction( + session -> { + final Address austin = new Address( 1, "Austin" ); + final Address london = new Address( 2, "London" ); + + session.save( austin ); + session.save( london ); + + final ForeignCustomer acme = new ForeignCustomer( 1, "Acme", london, "1234" ); + final ForeignCustomer acmeBrick = new ForeignCustomer( 2, "Acme Brick", london, "9876", acme ); + + final ForeignCustomer freeBirds = new ForeignCustomer( 3, "Free Birds", austin, "13579" ); + + session.save( acme ); + session.save( acmeBrick ); + session.save( freeBirds ); + + final Order order1 = new Order( 1, "some text", freeBirds ); + freeBirds.getOrders().add( order1 ); + session.save( order1 ); + + final OrderSupplemental orderSupplemental = new OrderSupplemental( 1, 1 ); + order1.setSupplemental( orderSupplemental ); + final OrderSupplemental2 orderSupplemental2_1 = new OrderSupplemental2( 2, 2 ); + order1.setSupplemental2( orderSupplemental2_1 ); + orderSupplemental2_1.setOrder( order1 ); + session.save( orderSupplemental ); + session.save( orderSupplemental2_1 ); + + final Order order2 = new Order( 2, "some text", acme ); + acme.getOrders().add( order2 ); + session.save( order2 ); + + final OrderSupplemental2 orderSupplemental2_2 = new OrderSupplemental2( 3, 3 ); + order2.setSupplemental2( orderSupplemental2_2 ); + orderSupplemental2_2.setOrder( order2 ); + session.save( orderSupplemental2_2 ); + + final CreditCardPayment payment1 = new CreditCardPayment( 1, 1F, "1" ); + session.save( payment1 ); + order1.getPayments().add( payment1 ); + + final DebitCardPayment payment2 = new DebitCardPayment( 2, 2F, "2" ); + session.save( payment2 ); + order1.getPayments().add( payment2 ); + + + + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from CreditCardPayment" ).executeUpdate(); + session.createQuery( "delete from DebitCardPayment" ).executeUpdate(); + + session.createQuery( "delete from OrderSupplemental2" ).executeUpdate(); + + session.createQuery( "delete from Order" ).executeUpdate(); + + session.createQuery( "delete from OrderSupplemental" ).executeUpdate(); + + session.createQuery( "delete from DomesticCustomer" ).executeUpdate(); + session.createQuery( "delete from ForeignCustomer" ).executeUpdate(); + + session.createQuery( "delete from Address" ).executeUpdate(); + } + ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + + sources.addAnnotatedClass( Customer.class ); + sources.addAnnotatedClass( ForeignCustomer.class ); + sources.addAnnotatedClass( DomesticCustomer.class ); + + sources.addAnnotatedClass( Payment.class ); + sources.addAnnotatedClass( CreditCardPayment.class ); + sources.addAnnotatedClass( DebitCardPayment.class ); + + sources.addAnnotatedClass( Address.class ); + + sources.addAnnotatedClass( Order.class ); + sources.addAnnotatedClass( OrderSupplemental.class ); + sources.addAnnotatedClass( OrderSupplemental2.class ); + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeDetachedToProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeDetachedToProxyTest.java new file mode 100644 index 000000000000..953bde61dd1f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeDetachedToProxyTest.java @@ -0,0 +1,192 @@ +/* + * 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.bytecode.enhancement.lazy.proxy; + + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class MergeDetachedToProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testMergeInitializedDetachedOntoProxy() { + final StatisticsImplementor statistics = sessionFactory().getStatistics(); + + final AEntity aEntityDetached = fromTransaction( + session -> { + AEntity aEntity = session.get( AEntity.class, 1 ); + assertIsEnhancedProxy( aEntity.bEntity ); + Hibernate.initialize( aEntity.bEntity ); + return aEntity; + } + ); + + statistics.clear(); + assertThat( statistics.getPrepareStatementCount(), is( 0L ) ); + + inSession( + session -> { + BEntity bEntity = session.getReference( BEntity.class, 2 ); + assertIsEnhancedProxy( bEntity ); + assertThat( statistics.getPrepareStatementCount(), is( 0L ) ); + + AEntity aEntityMerged = (AEntity) session.merge( aEntityDetached ); + assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); + + assertSame( bEntity, aEntityMerged.bEntity ); + assertEquals( "a description", aEntityDetached.bEntity.description ); + assertTrue( Hibernate.isInitialized( bEntity ) ); + } + ); + + assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); + } + + @Test + public void testMergeUpdatedDetachedOntoProxy() { + final StatisticsImplementor statistics = sessionFactory().getStatistics(); + + final AEntity aEntityDetached = fromTransaction( + session -> { + AEntity aEntity = session.get( AEntity.class, 1 ); + assertIsEnhancedProxy( aEntity.bEntity ); + Hibernate.initialize( aEntity.bEntity ); + return aEntity; + } + ); + + aEntityDetached.bEntity.description = "new description"; + + statistics.clear(); + assertThat( statistics.getPrepareStatementCount(), is( 0L ) ); + + inSession( + session -> { + BEntity bEntity = session.getReference( BEntity.class, 2 ); + assertIsEnhancedProxy( bEntity ); + assertThat( statistics.getPrepareStatementCount(), is( 0L ) ); + + AEntity aEntityMerged = (AEntity) session.merge( aEntityDetached ); + assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); + + assertSame( bEntity, aEntityMerged.bEntity ); + assertEquals( "new description", aEntityDetached.bEntity.description ); + assertTrue( Hibernate.isInitialized( bEntity ) ); + } + ); + + assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( BEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + final AEntity aEntity = new AEntity(); + aEntity.id = 1; + final BEntity bEntity = new BEntity(); + bEntity.id = 2; + bEntity.description = "a description"; + aEntity.bEntity = bEntity; + session.persist( aEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + session.createQuery( "delete from BEntity" ).executeUpdate(); + } + ); + } + + private void assertIsEnhancedProxy(Object entity) { + assertTrue( PersistentAttributeInterceptable.class.isInstance( entity ) ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + assertTrue( EnhancementAsProxyLazinessInterceptor.class.isInstance( interceptor ) ); + } + + @Entity(name = "AEntity") + public static class AEntity { + @Id + private int id; + + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + private BEntity bEntity; + } + + @Entity(name = "BEntity") + public static class BEntity { + @Id + private int id; + + private String description; + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeProxyTest.java new file mode 100644 index 000000000000..c68b8c7a68ba --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MergeProxyTest.java @@ -0,0 +1,230 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.proxy.HibernateProxy; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.hamcrest.CoreMatchers; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + * @author Gail Badner + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class MergeProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testMergeDetachUninitializedProxy() { + final Activity activity = fromTransaction( + session -> session.get( Activity.class, 0 ) + ); + + assertThat( Hibernate.isInitialized( activity ), is( true ) ); + assertThat( Hibernate.isPropertyInitialized( activity, "instruction" ), is( true ) ); + final Instruction instruction = activity.getInstruction(); + assertThat( Hibernate.isInitialized( instruction ), is( false ) ); + assertThat( instruction, instanceOf( PersistentAttributeInterceptable.class ) ); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) instruction ).$$_hibernate_getInterceptor(); + assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + + activity.setNbr( "2" ); + + final Activity mergedActivity = fromTransaction( + session -> (Activity) session.merge( activity ) + ); + + assertTrue( Hibernate.isInitialized( mergedActivity ) ); + assertThat( activity, not( CoreMatchers.sameInstance( mergedActivity ) ) ); + + inTransaction( + session -> { + final Instruction persistentInstruction = session.get( Instruction.class, 0 ); + assertThat( persistentInstruction.getSummary(), is( "Instruction #0" ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testMergeDetachInitializedProxy() { + final Activity activityProxy = fromTransaction( + session -> { + final Activity activity = session.load( Activity.class, 0 ); + assertFalse( Hibernate.isInitialized( activity) ); + Hibernate.initialize( activity ); + return activity; + } + ); + + assertThat( activityProxy, not( instanceOf( HibernateProxy.class ) ) ); + assertThat( Hibernate.isInitialized( activityProxy), is( true ) ); + + { + assertThat( Hibernate.isPropertyInitialized( activityProxy, "instruction" ), is( true ) ); + final Instruction instruction = activityProxy.getInstruction(); + assertThat( instruction, instanceOf( PersistentAttributeInterceptable.class ) ); + final PersistentAttributeInterceptor instructionInterceptor = ( (PersistentAttributeInterceptable) instruction ).$$_hibernate_getInterceptor(); + assertThat( instructionInterceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + } + + { + assertThat( Hibernate.isPropertyInitialized( activityProxy, "webApplication" ), is( true ) ); + assertThat( activityProxy.getWebApplication(), nullValue() ); + } + + activityProxy.setNbr( "2" ); + + final Activity mergedActivity = fromTransaction( + session -> (Activity) session.merge( activityProxy ) + ); + + assertTrue( Hibernate.isInitialized( mergedActivity ) ); + assertThat( activityProxy, not( CoreMatchers.sameInstance( mergedActivity ) ) ); + + inTransaction( + session -> { + final Instruction instruction = session.get( Instruction.class, 0 ); + assertThat( instruction.getSummary(), is( "Instruction #0" ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testMergeDetachInitializedByAccessProxy() { + final Activity activityProxy = fromTransaction( + session -> { + final Activity activity = session.load( Activity.class, 0 ); + assertFalse( Hibernate.isInitialized( activity) ); + activity.getDescription(); + return activity; + } + ); + + assertThat( activityProxy, not( instanceOf( HibernateProxy.class ) ) ); + assertThat( Hibernate.isInitialized( activityProxy), is( true ) ); + + { + assertThat( Hibernate.isPropertyInitialized( activityProxy, "instruction" ), is( true ) ); + final Instruction instruction = activityProxy.getInstruction(); + assertThat( instruction, instanceOf( PersistentAttributeInterceptable.class ) ); + final PersistentAttributeInterceptor instructionInterceptor = ( (PersistentAttributeInterceptable) instruction ).$$_hibernate_getInterceptor(); + assertThat( instructionInterceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + } + + { + assertThat( Hibernate.isPropertyInitialized( activityProxy, "webApplication" ), is( true ) ); + assertThat( activityProxy.getWebApplication(), nullValue() ); + } + + activityProxy.setNbr( "2" ); + + final Activity mergedActivity = fromTransaction( + session -> (Activity) session.merge( activityProxy ) + ); + + assertTrue( Hibernate.isInitialized( mergedActivity ) ); + assertThat( activityProxy, not( CoreMatchers.sameInstance( mergedActivity ) ) ); + + inTransaction( + session -> { + final Instruction instruction = session.get( Instruction.class, 0 ); + assertThat( instruction.getSummary(), is( "Instruction #0" ) ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( Activity.class ); + sources.addAnnotatedClass( Instruction.class ); + sources.addAnnotatedClass( WebApplication.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + // create a slew of Activity objects, some with Instruction reference + // some without. + + for ( int i = 0; i < 30; i++ ) { + final Activity activity = new Activity( i, "Activity #" + i, null ); + if ( i % 2 == 0 ) { + final Instruction instr = new Instruction( i, "Instruction #" + i ); + activity.setInstruction( instr ); + session.save( instr ); + } + else { + final WebApplication webApplication = new WebApplication( i, "http://" + i + ".com" ); + activity.setWebApplication( webApplication ); + webApplication.getActivities().add( activity ); + session.save( webApplication ); + } + + session.save( activity ); + } + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from Activity" ).executeUpdate(); + session.createQuery( "delete from Instruction" ).executeUpdate(); + session.createQuery( "delete from WebApplication" ).executeUpdate(); + } + ); + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ModelEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ModelEntity.java new file mode 100644 index 000000000000..1bf041596799 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ModelEntity.java @@ -0,0 +1,72 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.sql.Timestamp; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +@MappedSuperclass +public abstract class ModelEntity { + + @Id + @Column(name="Oid") + private Long Oid = null; + + @Basic + @Column(name="CreatedAt") + private Timestamp CreatedAt = null; + + @Basic + @Column(name="CreatedBy") + private String CreatedBy = null; + + @Basic + @Column(name="VersionNr") + private short VersionNr = 0; + + public short getVersionNr() { + return VersionNr; + } + + public void setVersionNr(short versionNr) { + this.VersionNr = versionNr; + } + + public Long getOid() { + return Oid; + } + + public void setOid(Long oid) { + this.Oid = oid; + } + + public Timestamp getCreatedAt() { + return CreatedAt; + } + + public void setCreatedAt(Timestamp createdAt) { + this.CreatedAt = createdAt; + } + + public String getCreatedBy() { + return CreatedBy; + } + + public void setCreatedBy(String createdBy) { + this.CreatedBy = createdBy; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MoreSpecializedKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MoreSpecializedKey.java new file mode 100644 index 000000000000..4eecb7acbb0f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/MoreSpecializedKey.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +@Entity(name="MoreSpecializedKey") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public class MoreSpecializedKey extends SpecializedKey implements Serializable { +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Order.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Order.java new file mode 100644 index 000000000000..b5fbf1bf99b8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Order.java @@ -0,0 +1,109 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Order") +@Table(name = "`order`") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public class Order { + private Integer oid; + + private String theText; + + private Customer customer; + private OrderSupplemental supplemental; + private OrderSupplemental2 supplemental2; + + private Set payments = new HashSet(); + + public Order() { + } + + public Order(Integer oid, String theText, Customer customer) { + this.oid = oid; + this.theText = theText; + this.customer = customer; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public String getTheText() { + return theText; + } + + public void setTheText(String theText) { + this.theText = theText; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn +// @LazyToOne( LazyToOneOption.NO_PROXY ) + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + + @OneToMany(fetch = FetchType.LAZY) + public Set getPayments() { + return payments; + } + + public void setPayments(Set payments) { + this.payments = payments; + } + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "supp_info_id") + public OrderSupplemental getSupplemental() { + return supplemental; + } + + public void setSupplemental(OrderSupplemental supplemental) { + this.supplemental = supplemental; + } + + @OneToOne(fetch = FetchType.LAZY, mappedBy = "order") + @LazyToOne(LazyToOneOption.NO_PROXY) + public OrderSupplemental2 getSupplemental2() { + return supplemental2; + } + + public void setSupplemental2(OrderSupplemental2 supplemental2) { + this.supplemental2 = supplemental2; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental.java new file mode 100644 index 000000000000..50f2fe5409b2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental.java @@ -0,0 +1,48 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "OrderSupplemental") +@Table(name = "order_supp") +public class OrderSupplemental { + private Integer oid; + private Integer receivablesId; + + public OrderSupplemental() { + } + + public OrderSupplemental(Integer oid, Integer receivablesId) { + this.oid = oid; + this.receivablesId = receivablesId; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public Integer getReceivablesId() { + return receivablesId; + } + + public void setReceivablesId(Integer receivablesId) { + this.receivablesId = receivablesId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental2.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental2.java new file mode 100644 index 000000000000..c2bf3baaa5ef --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/OrderSupplemental2.java @@ -0,0 +1,63 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "OrderSupplemental2") +@Table(name = "order_supp2") +public class OrderSupplemental2 { + private Integer oid; + private Integer receivablesId; + + private Order order; + + public OrderSupplemental2() { + } + + public OrderSupplemental2(Integer oid, Integer receivablesId) { + this.oid = oid; + this.receivablesId = receivablesId; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public Integer getReceivablesId() { + return receivablesId; + } + + public void setReceivablesId(Integer receivablesId) { + this.receivablesId = receivablesId; + } + + @OneToOne(fetch = FetchType.LAZY) + @MapsId + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Payment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Payment.java new file mode 100644 index 000000000000..cac4adc2db72 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Payment.java @@ -0,0 +1,49 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Payment") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public abstract class Payment { + private Integer oid; + private Float amount; + + public Payment() { + } + + public Payment(Integer oid, Float amount) { + this.oid = oid; + this.amount = amount; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public Float getAmount() { + return amount; + } + + public void setAmount(Float amount) { + this.amount = amount; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyDeletionTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyDeletionTest.java new file mode 100644 index 000000000000..05926d24b74d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ProxyDeletionTest.java @@ -0,0 +1,461 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.sql.Blob; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Steve Ebersole + */ +public class ProxyDeletionTest extends BaseNonConfigCoreFunctionalTestCase { + + + @Test + public void testGetAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.get( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( DEntity.class ); + sources.addAnnotatedClass( EEntity.class ); + sources.addAnnotatedClass( GEntity.class ); + + sources.addAnnotatedClass( Activity.class ); + sources.addAnnotatedClass( Instruction.class ); + sources.addAnnotatedClass( WebApplication.class ); + + sources.addAnnotatedClass( SpecializedKey.class ); + sources.addAnnotatedClass( MoreSpecializedKey.class ); + sources.addAnnotatedClass( RoleEntity.class ); + sources.addAnnotatedClass( AbstractKey.class ); + sources.addAnnotatedClass( GenericKey.class ); + sources.addAnnotatedClass( SpecializedEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + DEntity d = new DEntity(); + d.setD( "bla" ); + d.setOid( 1 ); + + byte[] lBytes = "agdfagdfagfgafgsfdgasfdgfgasdfgadsfgasfdgasfdgasdasfdg".getBytes(); + Blob lBlob = Hibernate.getLobCreator( session ).createBlob( lBytes ); + d.setBlob( lBlob ); + + BEntity b1 = new BEntity(); + b1.setOid( 1 ); + b1.setB1( 34 ); + b1.setB2( "huhu" ); + + BEntity b2 = new BEntity(); + b2.setOid( 2 ); + b2.setB1( 37 ); + b2.setB2( "haha" ); + + Set lBs = new HashSet<>(); + lBs.add( b1 ); + lBs.add( b2 ); + d.setBs( lBs ); + + AEntity a = new AEntity(); + a.setOid( 1 ); + a.setA( "hihi" ); + d.setA( a ); + + EEntity e = new EEntity(); + e.setOid( 17 ); + e.setE1( "Balu" ); + e.setE2( "Bär" ); + + e.setD( d ); + d.setE( e ); + + CEntity c = new CEntity(); + c.setOid( 1 ); + c.setC1( "ast" ); + c.setC2( "qwert" ); + c.setC3( "yxcv" ); + d.setC( c ); + + GEntity g = new GEntity(); + g.setOid( 1 ); + g.getdEntities().add( d ); + d.setG( g ); + + + session.save( b1 ); + session.save( b2 ); + session.save( a ); + session.save( c ); + session.save( g ); + session.save( d ); + session.save( e ); + + + // create a slew of Activity objects, some with Instruction reference + // some without. + + for ( int i = 0; i < 30; i++ ) { + final Activity activity = new Activity( i, "Activity #" + i, null ); + if ( i % 2 == 0 ) { + final Instruction instr = new Instruction( i, "Instruction #" + i ); + activity.setInstruction( instr ); + session.save( instr ); + } + else { + final WebApplication webApplication = new WebApplication( i, "http://" + i + ".com" ); + activity.setWebApplication( webApplication ); + webApplication.getActivities().add( activity ); + session.save( webApplication ); + } + + session.save( activity ); + } + + RoleEntity roleEntity = new RoleEntity(); + roleEntity.setOid( 1L ); + + SpecializedKey specializedKey = new SpecializedKey(); + specializedKey.setOid(1L); + + MoreSpecializedKey moreSpecializedKey = new MoreSpecializedKey(); + moreSpecializedKey.setOid( 3L ); + + SpecializedEntity specializedEntity = new SpecializedEntity(); + specializedEntity.setId( 2L ); + specializedKey.addSpecializedEntity( specializedEntity ); + specializedEntity.setSpecializedKey( specializedKey); + + specializedKey.addRole( roleEntity ); + roleEntity.setKey( specializedKey ); + roleEntity.setSpecializedKey( moreSpecializedKey ); + moreSpecializedKey.addRole( roleEntity ); + session.save( specializedEntity ); + session.save( roleEntity ); + session.save( specializedKey ); + session.save( moreSpecializedKey ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from E" ).executeUpdate(); + session.createQuery( "delete from D" ).executeUpdate(); + session.createQuery( "delete from C" ).executeUpdate(); + session.createQuery( "delete from B" ).executeUpdate(); + session.createQuery( "delete from A" ).executeUpdate(); + session.createQuery( "delete from G" ).executeUpdate(); + + session.createQuery( "delete from Activity" ).executeUpdate(); + session.createQuery( "delete from Instruction" ).executeUpdate(); + session.createQuery( "delete from WebApplication" ).executeUpdate(); + + session.createQuery( "delete from SpecializedEntity" ).executeUpdate(); + session.createQuery( "delete from RoleEntity" ).executeUpdate(); + session.createQuery( "delete from MoreSpecializedKey" ).executeUpdate(); + session.createQuery( "delete from SpecializedKey" ).executeUpdate(); + session.createQuery( "delete from GenericKey" ).executeUpdate(); + session.createQuery( "delete from AbstractKey" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private long oid; + private short version; + + public long getOid() { + return oid; + } + + public void setOid(long oid) { + this.oid = oid; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + } + + @Entity(name = "A") + @Table(name = "A") + public static class AEntity extends BaseEntity { + @Column(name = "A") + private String a; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + } + + @Entity(name = "B") + @Table(name = "B") + public static class BEntity extends BaseEntity { + private Integer b1; + private String b2; + + public Integer getB1() { + return b1; + } + + public void setB1(Integer b1) { + this.b1 = b1; + } + + public String getB2() { + return b2; + } + + public void setB2(String b2) { + this.b2 = b2; + } + } + + @Entity(name = "C") + @Table(name = "C") + public static class CEntity extends BaseEntity { + private String c1; + private String c2; + private String c3; + private Long c4; + + public String getC1() { + return c1; + } + + public void setC1(String c1) { + this.c1 = c1; + } + + public String getC2() { + return c2; + } + + @Basic(fetch = FetchType.LAZY) + public void setC2(String c2) { + this.c2 = c2; + } + + public String getC3() { + return c3; + } + + public void setC3(String c3) { + this.c3 = c3; + } + + public Long getC4() { + return c4; + } + + public void setC4(Long c4) { + this.c4 = c4; + } + } + + @Entity(name = "D") + @Table(name = "D") + public static class DEntity extends BaseEntity { + private String d; + + @OneToOne(fetch = FetchType.LAZY) + public AEntity a; + + @OneToOne(fetch = FetchType.LAZY) + public CEntity c; + + @OneToMany(targetEntity = BEntity.class) + public Set bs; + + @OneToOne(mappedBy = "d", fetch = FetchType.LAZY) + private EEntity e; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn() + @LazyGroup("g") + public GEntity g; + + @Lob + @Basic(fetch = FetchType.LAZY) + @LazyGroup("blob") + private Blob blob; + + public String getD() { + return d; + } + + public void setD(String d) { + this.d = d; + } + + + public AEntity getA() { + return a; + } + + public void setA(AEntity a) { + this.a = a; + } + + public Set getBs() { + return bs; + } + + public void setBs(Set bs) { + this.bs = bs; + } + + public CEntity getC() { + return c; + } + + public void setC(CEntity c) { + this.c = c; + } + + public Blob getBlob() { + return blob; + } + + public void setBlob(Blob blob) { + this.blob = blob; + } + + public EEntity getE() { + return e; + } + + public void setE(EEntity e) { + this.e = e; + } + + public GEntity getG() { + return g; + } + + public void setG(GEntity g) { + this.g = g; + } + } + + @Entity(name = "E") + @Table(name = "E") + public static class EEntity extends BaseEntity { + private String e1; + private String e2; + + @OneToOne(fetch = FetchType.LAZY) + private DEntity d; + + public String getE1() { + return e1; + } + + public void setE1(String e1) { + this.e1 = e1; + } + + public String getE2() { + return e2; + } + + public void setE2(String e2) { + this.e2 = e2; + } + + public DEntity getD() { + return d; + } + + public void setD(DEntity d) { + this.d = d; + } + } + + @Entity(name = "G") + @Table(name = "G") + public static class GEntity extends BaseEntity { + + @OneToMany(mappedBy = "g") + public Set dEntities = new HashSet<>(); + + public Set getdEntities() { + return dEntities; + } + + public void setdEntities(Set dEntities) { + this.dEntities = dEntities; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java new file mode 100644 index 000000000000..18ae96adb2e3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/RoleEntity.java @@ -0,0 +1,74 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + + +import java.io.Serializable; +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +@Entity(name = "RoleEntity") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name = "PP_DCRolleKey") +public class RoleEntity extends ModelEntity implements Serializable { + + @Basic + Short value; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("Key") + @JoinColumn + protected AbstractKey key = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("Key") + @JoinColumn + protected SpecializedKey specializedKey = null; + + public Short getValue() { + return value; + } + + public void setvalue(Short value) { + this.value = value; + } + + public AbstractKey getKey() { + return key; + } + + public void setKey(AbstractKey key) { + this.key = key; + } + + public SpecializedKey getSpecializedKey() { + return specializedKey; + } + + public void setSpecializedKey(SpecializedKey specializedKey) { + this.specializedKey = specializedKey; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoading.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoading.java new file mode 100644 index 000000000000..6f31d92af6c7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoading.java @@ -0,0 +1,329 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.hamcrest.MatcherAssert; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true) +public class SimpleUpdateTestWithLazyLoading extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + private static final int CHILDREN_SIZE = 10; + private Long lastChildID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class, Person.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = new Parent(); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { + Child child = new Child(); + // Association management should kick in here + child.parent = parent; + + Person relative = new Person(); + relative.setName( "Luigi" ); + child.addRelative( relative ); + + s.persist( relative ); + + s.persist( child ); + lastChildID = child.id; + } + s.persist( parent ); + } ); + } + + + @After + public void tearDown() { + doInHibernate( this::sessionFactory, s -> { + s.createQuery( "delete from Child" ).executeUpdate(); + s.createQuery( "delete from Parent" ).executeUpdate(); + } ); + } + + @Test + public void updateSimpleField() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + final String updatedName = "Barrabas_"; + + final EntityPersister childPersister = sessionFactory().getMetamodel().entityPersister( Child.class.getName() ); + + final int relativesAttributeIndex = childPersister.getEntityMetamodel().getPropertyIndex( "relatives" ); + + inTransaction( + session -> { + stats.clear(); + Child loadedChild = session.load( Child.class, lastChildID ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) loadedChild; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor; + + loadedChild.setName( updatedName ); + + // ^ should have triggered "base fetch group" initialization which would mean a SQL select + assertEquals( 1, stats.getPrepareStatementCount() ); + + // check that the `#setName` "persisted" + assertThat( loadedChild.getName(), is( updatedName ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + final EntityEntry entry = session.getPersistenceContext().getEntry( loadedChild ); + assertThat( + entry.getLoadedState()[ relativesAttributeIndex ], + is( LazyPropertyInitializer.UNFETCHED_PROPERTY ) + ); + + // force a flush - the relatives collection should still be UNFETCHED_PROPERTY afterwards + session.flush(); + + final EntityEntry updatedEntry = session.getPersistenceContext().getEntry( loadedChild ); + assertThat( updatedEntry, sameInstance( entry ) ); + + assertThat( + entry.getLoadedState()[ relativesAttributeIndex ], + is( LazyPropertyInitializer.UNFETCHED_PROPERTY ) + ); + + session.getEventListenerManager(); + } + ); + + inTransaction( + session -> { + Child loadedChild = session.load( Child.class, lastChildID ); + assertThat( loadedChild.getName(), is( updatedName ) ); + + final EntityEntry entry = session.getPersistenceContext().getEntry( loadedChild ); + assertThat( + entry.getLoadedState()[ relativesAttributeIndex ], + is( LazyPropertyInitializer.UNFETCHED_PROPERTY ) + ); + } + ); + + } + + @Test + public void testUpdateAssociation() { + String updatedName = "Barrabas_"; + String parentName = "Yodit"; + doInHibernate( this::sessionFactory, s -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + loadedChild.setName( updatedName ); + + Parent parent = new Parent(); + parent.setName( parentName ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + loadedChild.setParent( parent ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertThat( loadedChild.getParent().getName(), is( parentName ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + s.save( parent ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getName(), is( updatedName ) ); + assertThat( loadedChild.getParent().getName(), is( parentName ) ); + } ); + } + + @Test + public void testUpdateCollection() { + doInHibernate( this::sessionFactory, s -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + + assertEquals( 0, stats.getPrepareStatementCount() ); + Person relative = new Person(); + relative.setName( "Luis" ); + loadedChild.addRelative( relative ); + assertEquals( 2, stats.getPrepareStatementCount() ); + s.persist( relative ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getRelatives().size(), is( 2 ) ); + } ); + } + + @Entity(name = "Parent") + @Table(name = "PARENT") + private static class Parent { + + String name; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + List children; + + void setChildren(List children) { + this.children = children; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Person") + @Table(name = "Person") + private static class Person { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Child") + @Table(name = "CHILD") + private static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + Parent parent; + + @OneToMany + List relatives; + + String name; + + Child() { + } + + Child(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + public List getRelatives() { + return relatives; + } + + public void setRelatives(List relatives) { + this.relatives = relatives; + } + + public void addRelative(Person person) { + if ( this.relatives == null ) { + this.relatives = new ArrayList<>(); + } + this.relatives.add( person ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoadingAndInlineDirtyTracking.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoadingAndInlineDirtyTracking.java new file mode 100644 index 000000000000..15b49622e016 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SimpleUpdateTestWithLazyLoadingAndInlineDirtyTracking.java @@ -0,0 +1,284 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.hamcrest.MatcherAssert; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions( lazyLoading = true, inlineDirtyChecking = true ) +public class SimpleUpdateTestWithLazyLoadingAndInlineDirtyTracking extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + private static final int CHILDREN_SIZE = 10; + private Long lastChildID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class, Person.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = new Parent(); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { + Child child = new Child(); + // Association management should kick in here + child.parent = parent; + + Person relative = new Person(); + relative.setName( "Luigi" ); + child.addRelative( relative ); + + s.persist( relative ); + + s.persist( child ); + lastChildID = child.id; + } + s.persist( parent ); + } ); + } + + + @After + public void tearDown() { + doInHibernate( this::sessionFactory, s -> { + s.createQuery( "delete from Child" ).executeUpdate(); + s.createQuery( "delete from Parent" ).executeUpdate(); + } ); + } + + @Test + public void updateSimpleField() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + String updatedName = "Barrabas_"; + doInHibernate( this::sessionFactory, s -> { + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) loadedChild; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + + loadedChild.setName( updatedName ); + assertEquals( 0, stats.getPrepareStatementCount() ); + assertThat( loadedChild.getName(), is( updatedName ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + } ); + + // the UPDATE + assertEquals( 1, stats.getPrepareStatementCount() ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getName(), is( updatedName ) ); + } ); + } + + @Test + public void testUpdateAssociation() { + String updatedName = "Barrabas_"; + String parentName = "Yodit"; + doInHibernate( this::sessionFactory, s -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + loadedChild.setName( updatedName ); + + Parent parent = new Parent(); + parent.setName( parentName ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + loadedChild.setParent( parent ); + assertEquals( 0, stats.getPrepareStatementCount() ); + assertThat( loadedChild.getParent().getName(), is( parentName ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + s.save( parent ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getName(), is( updatedName ) ); + assertThat( loadedChild.getParent().getName(), is( parentName ) ); + } ); + } + + @Test + public void testUpdateCollection() { + doInHibernate( this::sessionFactory, s -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + Child loadedChild = s.load( Child.class, lastChildID ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + Person relative = new Person(); + relative.setName( "Luis" ); + loadedChild.addRelative( relative ); + + // forces SELECT related to uninitialized Child, and then forces + // SELECT of Child#relatives collection for the add + assertEquals( 2, stats.getPrepareStatementCount() ); + s.persist( relative ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Child loadedChild = s.load( Child.class, lastChildID ); + assertThat( loadedChild.getRelatives().size(), is( 2 ) ); + } ); + } + + @Entity(name = "Parent") + @Table(name = "PARENT") + private static class Parent { + + String name; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + List children; + + void setChildren(List children) { + this.children = children; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Person") + @Table(name = "Person") + private static class Person { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Child") + @Table(name = "CHILD") + private static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + Parent parent; + + @OneToMany + List relatives; + + String name; + + Child() { + } + + Child(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + public List getRelatives() { + return relatives; + } + + public void setRelatives(List relatives) { + this.relatives = relatives; + } + + public void addRelative(Person person) { + if ( this.relatives == null ) { + this.relatives = new ArrayList<>(); + } + this.relatives.add( person ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java new file mode 100644 index 000000000000..875cd594b29a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedEntity.java @@ -0,0 +1,72 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + + +@Entity(name="SpecializedEntity") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name="PP_PartnerZusatzKuerzelZR") +public class SpecializedEntity implements Serializable { + + @Id + Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Column(name="Value") + String value; + + @ManyToOne(fetch=FetchType.LAZY) + @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("SpecializedKey") + @JoinColumn + protected SpecializedKey specializedKey = null; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public SpecializedKey getSpecializedKey() { + return specializedKey; + } + + public void setSpecializedKey(SpecializedKey specializedKey) { + this.specializedKey = specializedKey; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedKey.java new file mode 100644 index 000000000000..682f0a4fb0a2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SpecializedKey.java @@ -0,0 +1,51 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; + +@Entity(name="SpecializedKey") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name="PP_PartnerDCKey") +public class SpecializedKey extends GenericKey implements Serializable +{ + + @OneToMany(targetEntity= SpecializedEntity.class, mappedBy="specializedKey", fetch=FetchType.LAZY) +// @LazyCollection( LazyCollectionOption.EXTRA ) + protected Set specializedEntities = new LinkedHashSet(); + + public Set getSpecializedEntities() { + return specializedEntities; + } + + public void setSpecializedEntities(Set specializedEntities) { + this. specializedEntities = specializedEntities; + } + + public void addSpecializedEntity(SpecializedEntity pPartnerZusatzkuerzelZR) { + this.specializedEntities.add( pPartnerZusatzkuerzelZR); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/WebApplication.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/WebApplication.java new file mode 100644 index 000000000000..d25377109cca --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/WebApplication.java @@ -0,0 +1,72 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.net.URL; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.NaturalId; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name="web_app" ) +public class WebApplication extends BaseEntity { + private String name; + private String siteUrl; + + private Set activities = new HashSet<>(); + + @SuppressWarnings("unused") + public WebApplication() { + } + + public WebApplication(Integer id, String siteUrl) { + super( id ); + this.siteUrl = siteUrl; + } + + @NaturalId + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Basic( fetch = FetchType.LAZY ) + public String getSiteUrl() { + return siteUrl; + } + + public void setSiteUrl(String siteUrl) { + this.siteUrl = siteUrl; + } + + @OneToMany(mappedBy="webApplication", fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("app_activity_group") +// @CollectionType(type="baseutil.technology.hibernate.IskvLinkedSetCollectionType") + public Set getActivities() { + return activities; + } + + public void setActivities(Set activities) { + this.activities = activities; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/package-info.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/package-info.java new file mode 100644 index 000000000000..54d07339c77f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/package-info.java @@ -0,0 +1,11 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ + +/** + * Tests for lazy-enhancement-as-proxy support + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java index 68d676d84abb..2f8d8d191126 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java @@ -71,7 +71,7 @@ public CustomPersister( NaturalIdDataAccess naturalIdRegionAccessStrategy, PersisterCreationContext creationContext) { this.factory = creationContext.getSessionFactory(); - this.entityMetamodel = new EntityMetamodel( model, this, factory ); + this.entityMetamodel = new EntityMetamodel( model, this, creationContext ); } public boolean hasLazyProperties() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Producer.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Producer.java new file mode 100644 index 000000000000..af8864bd07df --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Producer.java @@ -0,0 +1,57 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.stateless.fetching; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.HashSet; +import java.util.Set; + +@Entity +public class Producer { + @Id + private Integer id; + + private String name; + + @OneToMany( mappedBy = "producer", fetch = FetchType.LAZY ) + private Set products = new HashSet<>(); + + public Producer() { + } + + public Producer(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Product.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Product.java new file mode 100644 index 000000000000..bc384a6f07ca --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Product.java @@ -0,0 +1,67 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.stateless.fetching; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity +public class Product { + @Id + private Integer id; + private String sku; + + @ManyToOne( fetch = FetchType.LAZY ) + private Vendor vendor; + + @ManyToOne( fetch = FetchType.LAZY ) + private Producer producer; + + public Product() { + } + + public Product(Integer id, String sku, Vendor vendor, Producer producer) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + this.producer = producer; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSku() { + return sku; + } + + public void setSku(String sku) { + this.sku = sku; + } + + public Vendor getVendor() { + return vendor; + } + + public void setVendor(Vendor vendor) { + this.vendor = vendor; + } + + public Producer getProducer() { + return producer; + } + + public void setProducer(Producer producer) { + this.producer = producer; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java index fabd46cf3622..01f7d0e75f98 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java @@ -10,6 +10,9 @@ import java.util.Locale; import org.hibernate.Hibernate; +import org.hibernate.Query; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; import org.hibernate.Session; import org.hibernate.StatelessSession; import org.hibernate.boot.model.naming.Identifier; @@ -21,8 +24,6 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; -import org.jboss.logging.Logger; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -36,6 +37,11 @@ public String[] getMappings() { return new String[] { "stateless/fetching/Mappings.hbm.xml" }; } + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Producer.class, Product.class, Vendor.class }; + } + @Override public void configure(Configuration cfg) { super.configure( cfg ); @@ -91,12 +97,161 @@ public void testDynamicFetch() { cleanup(); } + @Test + public void testDynamicFetchScroll() { + Session s = openSession(); + s.beginTransaction(); + Date now = new Date(); + + User me = new User( "me" ); + User you = new User( "you" ); + Resource yourClock = new Resource( "clock", you ); + Task task = new Task( me, "clean", yourClock, now ); // :) + + s.save( me ); + s.save( you ); + s.save( yourClock ); + s.save( task ); + + User u3 = new User( "U3" ); + User u4 = new User( "U4" ); + Resource it = new Resource( "it", u4 ); + Task task2 = new Task( u3, "beat", it, now ); // :)) + + s.save( u3 ); + s.save( u4 ); + s.save( it ); + s.save( task2 ); + + s.getTransaction().commit(); + s.close(); + + StatelessSession ss = sessionFactory().openStatelessSession(); + ss.beginTransaction(); + + final Query query = ss.createQuery( "from Task t join fetch t.resource join fetch t.user"); + final ScrollableResults scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY); + while ( scrollableResults.next() ) { + Task taskRef = (Task) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( taskRef ) ); + assertTrue( Hibernate.isInitialized( taskRef.getUser() ) ); + assertTrue( Hibernate.isInitialized( taskRef.getResource() ) ); + assertFalse( Hibernate.isInitialized( taskRef.getResource().getOwner() ) ); + } + + ss.getTransaction().commit(); + ss.close(); + + cleanup(); + } + + @Test + public void testDynamicFetchScrollSession() { + Session s = openSession(); + s.beginTransaction(); + Date now = new Date(); + + User me = new User( "me" ); + User you = new User( "you" ); + Resource yourClock = new Resource( "clock", you ); + Task task = new Task( me, "clean", yourClock, now ); // :) + + s.save( me ); + s.save( you ); + s.save( yourClock ); + s.save( task ); + + User u3 = new User( "U3" ); + User u4 = new User( "U4" ); + Resource it = new Resource( "it", u4 ); + Task task2 = new Task( u3, "beat", it, now ); // :)) + + s.save( u3 ); + s.save( u4 ); + s.save( it ); + s.save( task2 ); + + s.getTransaction().commit(); + s.close(); + + inTransaction( + session -> { + final Query query = session.createQuery( "from Task t join fetch t.resource join fetch t.user"); + final ScrollableResults scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + while ( scrollableResults.next() ) { + Task taskRef = (Task) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( taskRef ) ); + assertTrue( Hibernate.isInitialized( taskRef.getUser() ) ); + assertTrue( Hibernate.isInitialized( taskRef.getResource() ) ); + assertFalse( Hibernate.isInitialized( taskRef.getResource().getOwner() ) ); + } + + } + ); + + cleanup(); + } + + @Test + public void testDynamicFetchCollectionScroll() { + Session s = openSession(); + s.beginTransaction(); + + Producer p1 = new Producer( 1, "Acme" ); + Producer p2 = new Producer( 2, "ABC" ); + + session.save( p1 ); + session.save( p2 ); + + Vendor v1 = new Vendor( 1, "v1" ); + Vendor v2 = new Vendor( 2, "v2" ); + + session.save( v1 ); + session.save( v2 ); + + final Product product1 = new Product(1, "123", v1, p1); + final Product product2 = new Product(2, "456", v1, p1); + final Product product3 = new Product(3, "789", v1, p2); + + session.save( product1 ); + session.save( product2 ); + session.save( product3 ); + + s.getTransaction().commit(); + s.close(); + + StatelessSession ss = sessionFactory().openStatelessSession(); + ss.beginTransaction(); + + final Query query = ss.createQuery( "select p from Producer p join fetch p.products" ); + final ScrollableResults scrollableResults = query.scroll(ScrollMode.FORWARD_ONLY); + while ( scrollableResults.next() ) { + Producer producer = (Producer) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( producer ) ); + assertTrue( Hibernate.isInitialized( producer.getProducts() ) ); + + for (Product product : producer.getProducts()) { + assertTrue( Hibernate.isInitialized( product ) ); + assertFalse( Hibernate.isInitialized( product.getVendor() ) ); + } + } + + ss.getTransaction().commit(); + ss.close(); + + cleanup(); + } + private void cleanup() { Session s = openSession(); s.beginTransaction(); s.createQuery( "delete Task" ).executeUpdate(); s.createQuery( "delete Resource" ).executeUpdate(); s.createQuery( "delete User" ).executeUpdate(); + + s.createQuery( "delete Product" ).executeUpdate(); + s.createQuery( "delete Producer" ).executeUpdate(); + s.createQuery( "delete Vendor" ).executeUpdate(); s.getTransaction().commit(); s.close(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Vendor.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Vendor.java new file mode 100644 index 000000000000..5f022d2dd65e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/Vendor.java @@ -0,0 +1,56 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.stateless.fetching; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.HashSet; +import java.util.Set; + +@Entity +public class Vendor { + @Id + private Integer id; + private String name; + + @OneToMany(mappedBy = "vendor", fetch = FetchType.LAZY ) + private Set products = new HashSet<>(); + + public Vendor() { + } + + public Vendor(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java index 25ce2fcade6d..092e268d471b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java @@ -115,6 +115,7 @@ public void testNClob() { FileNClob file = s.get( FileNClob.class, id ); assertFalse( Hibernate.isPropertyInitialized( file, "clob" ) ); NClob nClob = file.getClob(); + assertTrue( Hibernate.isPropertyInitialized( file, "clob" ) ); try { final char[] chars = new char[(int) file.getClob().length()]; nClob.getCharacterStream().read( chars ); diff --git a/hibernate-core/src/test/resources/log4j.properties b/hibernate-core/src/test/resources/log4j.properties index ce6c1821422c..77b4eac553d8 100644 --- a/hibernate-core/src/test/resources/log4j.properties +++ b/hibernate-core/src/test/resources/log4j.properties @@ -49,9 +49,9 @@ log4j.logger.org.hibernate.engine.internal.StatisticalLoggingSessionEventListene log4j.logger.org.hibernate.boot.model.source.internal.hbm.ModelBinder=debug log4j.logger.org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry=debug -log4j.logger.org.hibernate.action.internal.EntityAction=debug -log4j.logger.org.hibernate.engine.internal.Cascade=trace +#log4j.logger.org.hibernate.action.internal.EntityAction=debug +#log4j.logger.org.hibernate.engine.internal.Cascade=trace ### When entity copy merge functionality is enabled using: ### hibernate.event.merge.entity_copy_observer=log, the following will diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java index 4801752bc50c..af5626531d57 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java @@ -10,13 +10,20 @@ import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.bytecode.enhance.spi.UnloadedField; import org.hibernate.cfg.Environment; + import org.hibernate.testing.junit4.CustomRunner; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; @@ -46,7 +53,16 @@ private static Class[] enhanceTestClass(Class klass) throws ClassNotFoundE List> classList = new ArrayList<>(); try { - if ( klass.isAnnotationPresent( CustomEnhancementContext.class ) ) { + if ( klass.isAnnotationPresent( EnhancementOptions.class ) + || klass.isAnnotationPresent( ClassEnhancementSelector.class ) + || klass.isAnnotationPresent( ClassEnhancementSelectors.class ) + || klass.isAnnotationPresent( PackageEnhancementSelector.class ) + || klass.isAnnotationPresent( PackageEnhancementSelectors.class ) + || klass.isAnnotationPresent( ImplEnhancementSelector.class ) + || klass.isAnnotationPresent( ImplEnhancementSelectors.class ) ) { + classList.add( buildEnhancerClassLoader( klass ).loadClass( klass.getName() ) ); + } + else if ( klass.isAnnotationPresent( CustomEnhancementContext.class ) ) { for ( Class contextClass : klass.getAnnotation( CustomEnhancementContext.class ).value() ) { EnhancementContext enhancementContextInstance = contextClass.getConstructor().newInstance(); classList.add( getEnhancerClassLoader( enhancementContextInstance, packageName ).loadClass( klass.getName() ) ); @@ -65,52 +81,166 @@ private static Class[] enhanceTestClass(Class klass) throws ClassNotFoundE // --- // - private static ClassLoader getEnhancerClassLoader(EnhancementContext context, String packageName) { - return new ClassLoader() { - private final String debugOutputDir = System.getProperty( "java.io.tmpdir" ); + private static ClassLoader buildEnhancerClassLoader(Class klass) { + final EnhancementOptions options = klass.getAnnotation( EnhancementOptions.class ); + final EnhancementContext enhancerContext; + if ( options == null ) { + enhancerContext = new EnhancerTestContext(); + } + else { + enhancerContext = new EnhancerTestContext() { + @Override + public boolean doBiDirectionalAssociationManagement(UnloadedField field) { + return options.biDirectionalAssociationManagement() && super.doBiDirectionalAssociationManagement( field ); + } - private final Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( context ); + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return options.inlineDirtyChecking() && super.doDirtyCheckingInline( classDescriptor ); + } - @SuppressWarnings( "ResultOfMethodCallIgnored" ) - @Override - public Class loadClass(String name) throws ClassNotFoundException { - if ( !name.startsWith( packageName ) ) { - return getParent().loadClass( name ); + @Override + public boolean doExtendedEnhancement(UnloadedClass classDescriptor) { + return options.extendedEnhancement() && super.doExtendedEnhancement( classDescriptor ); } - Class c = findLoadedClass( name ); - if ( c != null ) { - return c; + + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + return options.lazyLoading() && super.hasLazyLoadableAttributes( classDescriptor ); } - try ( InputStream is = getResourceAsStream( name.replace( '.', '/' ) + ".class" ) ) { - if ( is == null ) { - throw new ClassNotFoundException( name + " not found" ); - } + @Override + public boolean isLazyLoadable(UnloadedField field) { + return options.lazyLoading() && super.isLazyLoadable( field ); + } + }; + } - byte[] original = new byte[is.available()]; - try ( BufferedInputStream bis = new BufferedInputStream( is ) ) { - bis.read( original ); + final List selectors = new ArrayList<>(); + selectors.add( new PackageSelector( klass.getPackage().getName() ) ); + applySelectors( + klass, + ClassEnhancementSelector.class, + ClassEnhancementSelectors.class, + selectorAnnotation -> selectors.add( new ClassSelector( selectorAnnotation.value().getName() ) ) + ); + applySelectors( + klass, + PackageEnhancementSelector.class, + PackageEnhancementSelectors.class, + selectorAnnotation -> selectors.add( new PackageSelector( selectorAnnotation.value() ) ) + ); + applySelectors( + klass, + ImplEnhancementSelector.class, + ImplEnhancementSelectors.class, + selectorAnnotation -> { + try { + selectors.add( selectorAnnotation.impl().newInstance() ); } + catch ( RuntimeException re ) { + throw re; + } + catch ( Exception e ) { + throw new RuntimeException( e ); + } + } + ); + + return buildEnhancerClassLoader( enhancerContext, selectors ); + } + + private static void applySelectors( + Class klass, + Class selectorAnnotationType, + Class selectorsAnnotationType, + Consumer action) { + final A selectorAnnotation = klass.getAnnotation( selectorAnnotationType ); + final Annotation selectorsAnnotation = klass.getAnnotation( selectorsAnnotationType ); + + if ( selectorAnnotation != null ) { + action.accept( selectorAnnotation ); + } + else if ( selectorsAnnotation != null ) { + try { + final Method valuesMethod = selectorsAnnotationType.getDeclaredMethods()[0]; + //noinspection unchecked + final A[] selectorAnnotations = (A[]) valuesMethod.invoke( selectorsAnnotation ); + for ( A groupedSelectorAnnotation : selectorAnnotations ) { + action.accept( groupedSelectorAnnotation ); + } + + } + catch (Exception e) { + throw new RuntimeException( e ); + } + } + } + + private static ClassLoader buildEnhancerClassLoader( + EnhancementContext enhancerContext, + List selectors) { + return new EnhancingClassLoader( + Environment.getBytecodeProvider().getEnhancer( enhancerContext ), + selectors + ); + } + + private static class EnhancingClassLoader extends ClassLoader { + private static final String debugOutputDir = System.getProperty( "java.io.tmpdir" ); + + private final Enhancer enhancer; + private final List selectors; + + public EnhancingClassLoader(Enhancer enhancer, List selectors) { + this.enhancer = enhancer; + this.selectors = selectors; + } - byte[] enhanced = enhancer.enhance( name, original ); - if ( enhanced == null ) { - return defineClass( name, original, 0, original.length ); + public Class loadClass(String name) throws ClassNotFoundException { + for ( EnhancementSelector selector : selectors ) { + if ( selector.select( name ) ) { + final Class c = findLoadedClass( name ); + if ( c != null ) { + return c; } - File f = new File( debugOutputDir + File.separator + name.replace( ".", File.separator ) + ".class" ); - f.getParentFile().mkdirs(); - f.createNewFile(); - try ( FileOutputStream out = new FileOutputStream( f ) ) { - out.write( enhanced ); + try ( InputStream is = getResourceAsStream( name.replace( '.', '/' ) + ".class" ) ) { + if ( is == null ) { + throw new ClassNotFoundException( name + " not found" ); + } + + byte[] original = new byte[is.available()]; + try ( BufferedInputStream bis = new BufferedInputStream( is ) ) { + bis.read( original ); + } + + byte[] enhanced = enhancer.enhance( name, original ); + if ( enhanced == null ) { + return defineClass( name, original, 0, original.length ); + } + + File f = new File( debugOutputDir + File.separator + name.replace( ".", File.separator ) + ".class" ); + f.getParentFile().mkdirs(); + f.createNewFile(); + try ( FileOutputStream out = new FileOutputStream( f ) ) { + out.write( enhanced ); + } + return defineClass( name, enhanced, 0, enhanced.length ); + } + catch ( Throwable t ) { + throw new ClassNotFoundException( name + " not found", t ); } - return defineClass( name, enhanced, 0, enhanced.length ); - } - catch ( Throwable t ) { - throw new ClassNotFoundException( name + " not found", t ); } } - }; + + return getParent().loadClass( name ); + } + } + + private static ClassLoader getEnhancerClassLoader(EnhancementContext context, String packageName) { + return buildEnhancerClassLoader( context, Collections.singletonList( new PackageSelector( packageName ) ) ); } @Override diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelector.java new file mode 100644 index 000000000000..3ab99fbf4058 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelector.java @@ -0,0 +1,25 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +@Repeatable( ClassEnhancementSelectors.class ) +public @interface ClassEnhancementSelector { + Class value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelectors.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelectors.java new file mode 100644 index 000000000000..c27b42e7e0f5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassEnhancementSelectors.java @@ -0,0 +1,23 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +public @interface ClassEnhancementSelectors { + ClassEnhancementSelector[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassSelector.java new file mode 100644 index 000000000000..7a170473a7cd --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ClassSelector.java @@ -0,0 +1,25 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +/** + * EnhancementSelector based on class name + * + * @author Steve Ebersole + */ +public class ClassSelector implements EnhancementSelector { + private final String className; + + public ClassSelector(String className) { + this.className = className; + } + + @Override + public boolean select(String name) { + return name.equals( className ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java new file mode 100644 index 000000000000..7853126bec3b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java @@ -0,0 +1,26 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target( ElementType.TYPE ) +@Inherited +public @interface EnhancementOptions { + boolean biDirectionalAssociationManagement() default false; + boolean inlineDirtyChecking() default false; + boolean lazyLoading() default false; + boolean extendedEnhancement() default false; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementSelector.java new file mode 100644 index 000000000000..2a598c49846d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementSelector.java @@ -0,0 +1,20 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +/** + * Used by {@link BytecodeEnhancerRunner} to determine which classes should + * be enhanced. + * + * @author Steve Ebersole + */ +public interface EnhancementSelector { + /** + * Determine whether the named class should be enhanced. + */ + boolean select(String name); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EverythingSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EverythingSelector.java new file mode 100644 index 000000000000..f2249fcac9c1 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EverythingSelector.java @@ -0,0 +1,22 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +/** + * @author Steve Ebersole + */ +public class EverythingSelector implements EnhancementSelector { + /** + * Singleton access + */ + public static final EverythingSelector INSTANCE = new EverythingSelector(); + + @Override + public boolean select(String name) { + return true; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelector.java new file mode 100644 index 000000000000..93bbded1f20d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelector.java @@ -0,0 +1,25 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +@Repeatable( ImplEnhancementSelectors.class ) +public @interface ImplEnhancementSelector { + Class impl(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelectors.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelectors.java new file mode 100644 index 000000000000..bf11867a7841 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/ImplEnhancementSelectors.java @@ -0,0 +1,23 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +public @interface ImplEnhancementSelectors { + ImplEnhancementSelector[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelector.java new file mode 100644 index 000000000000..b0deaf901598 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelector.java @@ -0,0 +1,27 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +@Repeatable( PackageEnhancementSelectors.class ) +public @interface PackageEnhancementSelector { + String value(); + + boolean includeSubPackages() default true; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelectors.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelectors.java new file mode 100644 index 000000000000..c4c611d4015f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageEnhancementSelectors.java @@ -0,0 +1,23 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME) +@Target( ElementType.TYPE) +@Inherited +public @interface PackageEnhancementSelectors { + PackageEnhancementSelector[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageSelector.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageSelector.java new file mode 100644 index 000000000000..1983607848df --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/PackageSelector.java @@ -0,0 +1,25 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.bytecode.enhancement; + +/** + * EnhancementSelector based on package name + * + * @author Steve Ebersole + */ +public class PackageSelector implements EnhancementSelector { + private final String packageName; + + public PackageSelector(String packageName) { + this.packageName = packageName; + } + + @Override + public boolean select(String name) { + return name.startsWith( packageName ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseNonConfigCoreFunctionalTestCase.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseNonConfigCoreFunctionalTestCase.java index 37257aeec21e..7a9640806f59 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseNonConfigCoreFunctionalTestCase.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseNonConfigCoreFunctionalTestCase.java @@ -16,10 +16,12 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; +import java.util.function.Function; import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.Session; +import org.hibernate.StatelessSession; import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.MetadataSources; @@ -555,8 +557,28 @@ public void inSession(Consumer action) { TransactionUtil2.inSession( sessionFactory(), action ); } + public void inStatelessSession(Consumer action) { + log.trace( "#inSession(action)" ); + TransactionUtil2.inStatelessSession( sessionFactory(), action ); + } + + public R fromSession(Function action) { + log.trace( "#inSession(action)" ); + return TransactionUtil2.fromSession( sessionFactory(), action ); + } + public void inTransaction(Consumer action) { log.trace( "#inTransaction(action)" ); TransactionUtil2.inTransaction( sessionFactory(), action ); } + + public void inStatelessTransaction(Consumer action) { + log.trace( "#inTransaction(action)" ); + TransactionUtil2.inStatelessTransaction( sessionFactory(), action ); + } + + public R fromTransaction(Function action) { + log.trace( "#inTransaction(action)" ); + return TransactionUtil2.fromTransaction( sessionFactory(), action ); + } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil2.java b/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil2.java index d91a3f64e842..c8c834182bf3 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil2.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil2.java @@ -7,7 +7,9 @@ package org.hibernate.testing.transaction; import java.util.function.Consumer; +import java.util.function.Function; +import org.hibernate.StatelessSession; import org.hibernate.Transaction; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -21,6 +23,10 @@ public class TransactionUtil2 { private static final Logger log = Logger.getLogger( TransactionUtil2.class ); public static final String ACTION_COMPLETED_TXN = "Execution of action caused managed transaction to be completed"; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // in/from Session + public static void inSession(SessionFactoryImplementor sfi, Consumer action) { log.trace( "#inSession(SF,action)" ); @@ -34,15 +40,33 @@ public static void inSession(SessionFactoryImplementor sfi, Consumer R fromSession(SessionFactoryImplementor sfi, Function action) { + log.trace( "#inSession(SF,action)" ); + + try (SessionImplementor session = (SessionImplementor) sfi.openSession()) { + log.trace( "Session opened, calling action" ); + return action.apply( session ); + } + finally { + log.trace( "Session closed (AutoCloseable)" ); + } + } + public static void inTransaction(SessionFactoryImplementor factory, Consumer action) { log.trace( "#inTransaction(factory, action)"); inSession( factory, - session -> { - inTransaction( session, action ); - } + session -> inTransaction( session, action ) + ); + } + public static R fromTransaction(SessionFactoryImplementor factory, Function action) { + log.trace( "#inTransaction(factory, action)"); + + return fromSession( + factory, + session -> fromTransaction( session, action ) ); } @@ -95,6 +119,138 @@ public static void inTransaction(SessionImplementor session, Consumer R fromTransaction(SessionImplementor session, Function action) { + log.trace( "inTransaction(session,action)" ); + + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + + final R result; + try { + log.trace( "Calling action in txn" ); + result = action.apply( session ); + log.trace( "Called action - in txn" ); + + if ( !txn.isActive() ) { + throw new TransactionManagementException( ACTION_COMPLETED_TXN ); + } + } + catch (Exception e) { + // an error happened in the action + if ( ! txn.isActive() ) { + log.warn( ACTION_COMPLETED_TXN, e ); + } + else { + log.trace( "Rolling back transaction due to action error" ); + try { + txn.rollback(); + log.trace( "Rolled back transaction due to action error" ); + } + catch (Exception inner) { + log.trace( "Rolling back transaction due to action error failed; throwing original error" ); + } + } + + throw e; + } + + assert result != null; + + // action completed with no errors - attempt to commit the transaction allowing + // any RollbackException to propagate. Note that when we get here we know the + // txn is active + + log.trace( "Committing transaction after successful action execution" ); + try { + txn.commit(); + log.trace( "Committing transaction after successful action execution - success" ); + } + catch (Exception e) { + log.trace( "Committing transaction after successful action execution - failure" ); + throw e; + } + + return result; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // in/from StatelessSession + + public static void inStatelessSession(SessionFactoryImplementor sfi, Consumer action) { + log.trace( "#inSession(SF,action)" ); + + try (StatelessSession session = sfi.openStatelessSession()) { + log.trace( "StatelessSession opened, calling action" ); + action.accept( session ); + log.trace( "called action" ); + } + finally { + log.trace( "Session closed (AutoCloseable)" ); + } + } + + + public static void inStatelessTransaction(SessionFactoryImplementor factory, Consumer action) { + log.trace( "#inTransaction(factory, action)"); + + inStatelessSession( + factory, + session -> inStatelessTransaction( session, action ) + ); + } + + public static void inStatelessTransaction(StatelessSession session, Consumer action) { + log.trace( "inTransaction(session,action)" ); + + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + + try { + log.trace( "Calling action in txn" ); + action.accept( session ); + log.trace( "Called action - in txn" ); + + if ( !txn.isActive() ) { + throw new TransactionManagementException( ACTION_COMPLETED_TXN ); + } + } + catch (Exception e) { + // an error happened in the action + if ( ! txn.isActive() ) { + log.warn( ACTION_COMPLETED_TXN, e ); + } + else { + log.trace( "Rolling back transaction due to action error" ); + try { + txn.rollback(); + log.trace( "Rolled back transaction due to action error" ); + } + catch (Exception inner) { + log.trace( "Rolling back transaction due to action error failed; throwing original error" ); + } + } + + throw e; + } + + // action completed with no errors - attempt to commit the transaction allowing + // any RollbackException to propagate. Note that when we get here we know the + // txn is active + + log.trace( "Committing transaction after successful action execution" ); + try { + txn.commit(); + log.trace( "Committing transaction after successful action execution - success" ); + } + catch (Exception e) { + log.trace( "Committing transaction after successful action execution - failure" ); + throw e; + } + } + + + private static class TransactionManagementException extends RuntimeException { public TransactionManagementException(String message) { super( message ); From 6d5f7dd77cd8fad0ab7d5153913419b2a1064f02 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 13 Jun 2019 13:52:15 -0500 Subject: [PATCH 300/772] HHH-11147 - Allow enhanced entities to be returned in a completely uninitialized state - checkstyle fix --- .../src/main/java/org/hibernate/internal/SessionImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index a7f997a0c2c7..f883610300ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1152,7 +1152,8 @@ public final Object internalLoad( LoadEvent event = loadEvent; loadEvent = null; event = recycleEventInstance( event, id, entityName ); - event.setShouldUnwrapProxy( unwrapProxy );fireLoad( event, type ); + event.setShouldUnwrapProxy( unwrapProxy ); + fireLoad( event, type ); Object result = event.getResult(); if ( !nullable ) { UnresolvableObjectException.throwIfNull( result, id, entityName );} From 6d2c4aad297fb62c72367dce4eb8145dbfc82e40 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 14 Jun 2019 12:08:43 +0100 Subject: [PATCH 301/772] HHH-11147 - Add failing test (cherry picked from commit 48d88cfef6f99eeb757b194f8e560d1793717f54) --- .../SetIdentifierOnAEnhancedProxyTest.java | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java new file mode 100644 index 000000000000..fd89d7f1fb06 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java @@ -0,0 +1,288 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.PersistenceException; +import javax.persistence.Table; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.hamcrest.MatcherAssert; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true) +public class SetIdentifierOnAEnhancedProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + private static final int CHILDREN_SIZE = 10; + private Long lastChildID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class, Person.class }; + } + + @Test + public void setIdTest() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + stats.clear(); + Child loadedChild = session.load( Child.class, lastChildID ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) loadedChild; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + + loadedChild.setId( lastChildID ); + + // ^ should have triggered "base fetch group" initialization which would mean a SQL select + assertEquals( 1, stats.getPrepareStatementCount() ); + + // check that the `#setName` "persisted" + assertThat( loadedChild.getId(), is( lastChildID ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + } + ); + + inTransaction( + session -> { + Child loadedChild = session.load( Child.class, lastChildID ); + assertThat( loadedChild, is( notNullValue() ) ); + } + ); + + } + + @Ignore + @Test(expected = PersistenceException.class) + public void updateIdTest() { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + Long updatedId = new Long( lastChildID + 1 ); + + inTransaction( + session -> { + stats.clear(); + Child loadedChild = session.load( Child.class, lastChildID ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) loadedChild; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + + loadedChild.setId( updatedId ); + + // ^ should have triggered "base fetch group" initialization which would mean a SQL select + assertEquals( 1, stats.getPrepareStatementCount() ); + + // check that the `#setName` "persisted" + assertThat( loadedChild.getId(), is( updatedId ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + } + ); + + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Parent parent = new Parent(); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { + Child child = new Child(); + child.setId( new Long( i ) ); + // Association management should kick in here + child.parent = parent; + + Person relative = new Person(); + relative.setName( "Luigi" ); + child.addRelative( relative ); + + s.persist( relative ); + + s.persist( child ); + lastChildID = child.id; + } + s.persist( parent ); + } ); + } + + @After + public void tearDown() { + doInHibernate( this::sessionFactory, s -> { + s.createQuery( "delete from Child" ).executeUpdate(); + s.createQuery( "delete from Parent" ).executeUpdate(); + } ); + } + + @Entity(name = "Parent") + @Table(name = "PARENT") + private static class Parent { + + String name; + + public Parent() { + } + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + List children; + + void setChildren(List children) { + this.children = children; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Person") + @Table(name = "Person") + private static class Person { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Child") + @Table(name = "CHILD") + private static class Child { + + @Id + Long id; + + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) +// @LazyToOne(LazyToOneOption.NO_PROXY) + Parent parent; + + @OneToMany + List relatives; + + String name; + + Child() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + Child(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + public List getRelatives() { + return relatives; + } + + public void setRelatives(List relatives) { + this.relatives = relatives; + } + + public void addRelative(Person person) { + if ( this.relatives == null ) { + this.relatives = new ArrayList<>(); + } + this.relatives.add( person ); + } + } +} From 0a17f5ba6d7a9408407a4a4df2719a7bb2f53d51 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 14 Jun 2019 08:22:07 -0500 Subject: [PATCH 302/772] HHH-11147 - Allow enhanced entities to be returned in a completely uninitialized state (cherry picked from commit 94c49aaaa6fce73677d9709e4a56a65ce573d350) --- ...EnhancementAsProxyLazinessInterceptor.java | 36 +++++++++++++------ .../org/hibernate/engine/spi/EntityKey.java | 4 +++ .../SetIdentifierOnAEnhancedProxyTest.java | 13 +++---- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java index d9d79ad734ef..653f4604aa67 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java @@ -6,11 +6,13 @@ */ package org.hibernate.bytecode.enhance.spi.interceptor; +import java.io.Serializable; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.hibernate.EntityMode; +import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.bytecode.BytecodeLogger; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; @@ -22,6 +24,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.type.CompositeType; +import org.hibernate.type.Type; /** * @author Steve Ebersole @@ -213,16 +216,29 @@ protected Object handleWrite(Object target, String attributeName, Object oldValu } if ( identifierAttributeNames.contains( attributeName ) ) { - EnhancementHelper.performWork( - this, - (session, isTempSession) -> session.getFactory() - .getMetamodel() - .entityPersister( getEntityName() ) - .getEntityTuplizer() - .getPropertyValue( target, attributeName ), - getEntityName(), - attributeName - ); + // it is illegal for the identifier value to be changed. Normally Hibernate + // validates this during flush. However, here it is dangerous to just allow the + // new value to be set and continue on waiting for the flush for validation + // because this interceptor manages the entity's entry in the PC itself. So + // just do the check here up-front + final boolean changed; + if ( nonAggregatedCidMapper == null ) { + changed = ! entityKey.getPersister().getIdentifierType().isEqual( oldValue, newValue ); + } + else { + final int subAttrIndex = nonAggregatedCidMapper.getPropertyIndex( attributeName ); + final Type subAttrType = nonAggregatedCidMapper.getSubtypes()[subAttrIndex]; + changed = ! subAttrType.isEqual( oldValue, newValue ); + } + + if ( changed ) { + throw new HibernateException( + "identifier of an instance of " + entityKey.getEntityName() + " was altered from " + oldValue + " to " + newValue + ); + } + + // otherwise, setId has been called but passing in the same value - just pass it through + return newValue; } if ( ! inLineDirtyChecking ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java index 8caa5b77670a..52a9cd870671 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java @@ -74,6 +74,10 @@ public String getEntityName() { return persister.getEntityName(); } + public EntityPersister getPersister() { + return persister; + } + @Override public boolean equals(Object other) { if ( this == other ) { diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java index fd89d7f1fb06..5acaa762ac22 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java @@ -95,13 +95,11 @@ public void setIdTest() { loadedChild.setId( lastChildID ); - // ^ should have triggered "base fetch group" initialization which would mean a SQL select - assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, stats.getPrepareStatementCount() ); // check that the `#setName` "persisted" assertThat( loadedChild.getId(), is( lastChildID ) ); - assertEquals( 1, stats.getPrepareStatementCount() ); - + assertEquals( 0, stats.getPrepareStatementCount() ); } ); @@ -114,7 +112,6 @@ public void setIdTest() { } - @Ignore @Test(expected = PersistenceException.class) public void updateIdTest() { final Statistics stats = sessionFactory().getStatistics(); @@ -133,13 +130,11 @@ public void updateIdTest() { loadedChild.setId( updatedId ); - // ^ should have triggered "base fetch group" initialization which would mean a SQL select - assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, stats.getPrepareStatementCount() ); // check that the `#setName` "persisted" assertThat( loadedChild.getId(), is( updatedId ) ); - assertEquals( 1, stats.getPrepareStatementCount() ); - + assertEquals( 0, stats.getPrepareStatementCount() ); } ); From f96eb35f97f6fc67efc5af389f9e21915d8a722d Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 14 Jun 2019 15:13:13 +0100 Subject: [PATCH 303/772] HHH-11147 - Allow enhanced entities to be returned in a completely uninitialized state - Add test using IdClass (cherry picked from commit 2e1d602f685ce4bd6179a121b0c4147e2aee6b37) --- .../tuple/entity/EntityMetamodel.java | 4 +- .../SetIdentifierOnAEnhancedProxyTest.java | 129 ++++++++++++++++-- 2 files changed, 121 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 9bb4d7f1b6a4..c68718d9bfcc 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -150,9 +150,9 @@ public EntityMetamodel( nonAggregatedCidMapper = (CompositeType) identifierMapperComponent.getType(); idAttributeNames = new HashSet<>( ); //noinspection unchecked - final Iterator propertyItr = identifierMapperComponent.getPropertyIterator(); + final Iterator propertyItr = identifierMapperComponent.getPropertyIterator(); while ( propertyItr.hasNext() ) { - idAttributeNames.add( propertyItr.next() ); + idAttributeNames.add( propertyItr.next().getName() ); } } else { diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java index 5acaa762ac22..875fd3d9dc89 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SetIdentifierOnAEnhancedProxyTest.java @@ -13,6 +13,7 @@ */ package org.hibernate.test.bytecode.enhancement.lazy.proxy; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.persistence.CascadeType; @@ -21,6 +22,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.IdClass; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.PersistenceException; @@ -97,7 +99,6 @@ public void setIdTest() { assertEquals( 0, stats.getPrepareStatementCount() ); - // check that the `#setName` "persisted" assertThat( loadedChild.getId(), is( lastChildID ) ); assertEquals( 0, stats.getPrepareStatementCount() ); } @@ -112,12 +113,81 @@ public void setIdTest() { } + @Test + public void setIdClassTest(){ + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + stats.clear(); + ModelId id = new ModelId(); + id.setId1( 1L ); + id.setId2( 2L ); + Parent parent = session.load( Parent.class, id ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) parent; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + + parent.getId1(); + parent.setId1( 1L ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + + assertThat( parent.getId1(), is( 1L ) ); + assertThat( parent.getId2(), is( 2L ) ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + Child loadedChild = session.load( Child.class, lastChildID ); + assertThat( loadedChild, is( notNullValue() ) ); + } + ); + } + + @Test(expected = PersistenceException.class) + public void updateIdClassTest(){ + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + stats.clear(); + ModelId id = new ModelId(); + id.setId1( 1L ); + id.setId2( 2L ); + Parent parent = session.load( Parent.class, id ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) parent; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + + // should trigger an exception + parent.setId1( 3L ); + } + ); + + inTransaction( + session -> { + Child loadedChild = session.load( Child.class, lastChildID ); + assertThat( loadedChild, is( notNullValue() ) ); + } + ); + } + @Test(expected = PersistenceException.class) public void updateIdTest() { final Statistics stats = sessionFactory().getStatistics(); stats.clear(); - Long updatedId = new Long( lastChildID + 1 ); + Long updatedId = lastChildID + 1; inTransaction( session -> { @@ -128,13 +198,8 @@ public void updateIdTest() { final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + // should trigger an exception loadedChild.setId( updatedId ); - - assertEquals( 0, stats.getPrepareStatementCount() ); - - // check that the `#setName` "persisted" - assertThat( loadedChild.getId(), is( updatedId ) ); - assertEquals( 0, stats.getPrepareStatementCount() ); } ); @@ -144,6 +209,9 @@ public void updateIdTest() { public void prepare() { doInHibernate( this::sessionFactory, s -> { Parent parent = new Parent(); + parent.setId1( 1L ); + parent.setId2( 2L ); + for ( int i = 0; i < CHILDREN_SIZE; i++ ) { Child child = new Child(); child.setId( new Long( i ) ); @@ -171,8 +239,31 @@ public void tearDown() { } ); } + private static class ModelId implements Serializable { + Long id1; + + Long id2; + + public Long getId1() { + return id1; + } + + public void setId1(Long id1) { + this.id1 = id1; + } + + public Long getId2() { + return id2; + } + + public void setId2(Long id2) { + this.id2 = id2; + } + } + @Entity(name = "Parent") @Table(name = "PARENT") + @IdClass( ModelId.class ) private static class Parent { String name; @@ -181,8 +272,10 @@ public Parent() { } @Id - @GeneratedValue(strategy = GenerationType.AUTO) - Long id; + Long id1; + + @Id + Long id2; @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY) List children; @@ -198,6 +291,22 @@ public String getName() { public void setName(String name) { this.name = name; } + + public Long getId1() { + return id1; + } + + public void setId1(Long id1) { + this.id1 = id1; + } + + public Long getId2() { + return id2; + } + + public void setId2(Long id2) { + this.id2 = id2; + } } @Entity(name = "Person") From 3d74724b8121f51e467167c09e19e3e12c263e89 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 12 Jun 2019 20:02:06 +0100 Subject: [PATCH 304/772] HHH-11147 - Add test case LazyCollectionDeletedAllowProxyTest --- .../LazyCollectionDeletedAllowProxyTest.java | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java new file mode 100644 index 000000000000..e3cbe4779e0a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java @@ -0,0 +1,287 @@ +/* + * 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.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Query; +import javax.persistence.Table; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true) +public class LazyCollectionDeletedAllowProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + private Long postId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Post.class, Tag.class, AdditionalDetails.class, Label.class }; + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + } + + @Test + public void updatingAnAttributeDoesNotDeleteLazyCollectionsTest() { + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from AdditionalDetails where id = :id" ); + query.setParameter( "id", postId ); + AdditionalDetails additionalDetails = (AdditionalDetails) query.getSingleResult(); + additionalDetails.setDetails( "New data" ); + s.persist( additionalDetails ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post where id = :id" ); + query.setParameter( "id", postId ); + Post retrievedPost = (Post) query.getSingleResult(); + + assertFalse( "No tags found", retrievedPost.getTags().isEmpty() ); + retrievedPost.getTags().forEach( tag -> assertNotNull( tag ) ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from AdditionalDetails where id = :id" ); + query.setParameter( "id", postId ); + AdditionalDetails additionalDetails = (AdditionalDetails) query.getSingleResult(); + + Post post = additionalDetails.getPost(); + assertIsEnhancedProxy( post ); + post.setMessage( "new message" ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post where id = :id" ); + query.setParameter( "id", postId ); + Post retrievedPost = (Post) query.getSingleResult(); + + assertEquals( "new message", retrievedPost.getMessage() ); + assertFalse( "No tags found", retrievedPost.getTags().isEmpty() ); + retrievedPost.getTags().forEach( tag -> { + assertNotNull( tag ); + assertFalse( "No Labels found", tag.getLabels().isEmpty() ); + } ); + + } ); + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Post post = new Post(); + + Tag tag1 = new Tag( "tag1" ); + Tag tag2 = new Tag( "tag2" ); + + Label label1 = new Label( "label1" ); + Label label2 = new Label( "label2" ); + + tag1.addLabel( label1 ); + tag2.addLabel( label2 ); + + Set tagSet = new HashSet<>(); + tagSet.add( tag1 ); + tagSet.add( tag2 ); + post.setTags( tagSet ); + + AdditionalDetails details = new AdditionalDetails(); + details.setPost( post ); + post.setAdditionalDetails( details ); + details.setDetails( "Some data" ); + + postId = (Long) s.save( post ); + } ); + } + + @After + public void cleanData() { + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post" ); + List posts = query.getResultList(); + posts.forEach( post -> { + s.delete( post ); + } ); + } ); + } + + + private void assertIsEnhancedProxy(Object entity) { + assertThat( entity, instanceOf( PersistentAttributeInterceptable.class ) ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + } + + // --- // + + @Entity(name = "Tag") + @Table(name = "TAG") + private static class Tag { + + @Id + @GeneratedValue + Long id; + + String name; + + @ManyToMany(cascade = CascadeType.ALL) + Set