From 8101e3e47d65497dd30e2d7a9338ae5375c5b279 Mon Sep 17 00:00:00 2001 From: Jaanus Hansen Date: Tue, 10 Apr 2018 00:57:47 +0300 Subject: [PATCH 1/7] HHH-9663 added failing test case for Embeddable orphanRemoval --- ...LazyOrphanRemovalInEmbeddedEntityTest.java | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/jpa/orphan/one2one/embedded/OneToOneLazyOrphanRemovalInEmbeddedEntityTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/orphan/one2one/embedded/OneToOneLazyOrphanRemovalInEmbeddedEntityTest.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/orphan/one2one/embedded/OneToOneLazyOrphanRemovalInEmbeddedEntityTest.java new file mode 100644 index 000000000000..f72f71024e5f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/orphan/one2one/embedded/OneToOneLazyOrphanRemovalInEmbeddedEntityTest.java @@ -0,0 +1,184 @@ +/* + * 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.orphan.one2one.embedded; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Assert; +import org.junit.Test; + +import javax.persistence.*; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNull; + +/** + * Similar test as ../OneToOneLazyOrphanRemovalTest, + * shows that orphanRemoval = true is not removing the orphan of OneToOne relations in Embedded objects + * for unidirectional relationship. + * + * @TestForIssue( jiraKey = "HHH-9663" ) + */ +public class OneToOneLazyOrphanRemovalInEmbeddedEntityTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { RaceDriver.class, Car.class, Engine.class}; + } + + @Test + public void testOneToOneLazyOrphanRemoval() { + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Initialize the data + doInJPA( this::entityManagerFactory, entityManager -> { + final Engine engine = new Engine( 1, 275 ); + final Car car = new Car(1, engine, "red"); + final RaceDriver raceDriver = new RaceDriver(1, car); + entityManager.persist( engine ); + entityManager.persist( raceDriver ); + } ); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //set car engine to null, orphanRemoval = true should trigger deletion for engine entity + doInJPA( this::entityManagerFactory, entityManager -> { + final RaceDriver raceDriver = entityManager.find( RaceDriver.class, 1 ); + final Car car = raceDriver.getCar(); + + //check, that at the moment the engine is orphan + Assert.assertNotNull(car.getEngine()); + + car.setEngine( null ); + entityManager.merge( raceDriver ); + + final RaceDriver raceDriver2 = entityManager.find( RaceDriver.class, 1 ); + Assert.assertNotNull(raceDriver2.car); + } ); + + //check, that the engine is deleted: + doInJPA( this::entityManagerFactory, entityManager -> { + final RaceDriver raceDriver = entityManager.find( RaceDriver.class, 1 ); + final Car car = raceDriver.getCar(); + Assert.assertNull(car.getEngine()); + + final Engine engine = entityManager.find( Engine.class, 1 ); + assertNull( engine); + } ); + } + + @Entity(name = "RaceDriver") + public static class RaceDriver { + + @Id + private Integer id; + + @Embedded + private Car car; + + RaceDriver() { + // Required by JPA + } + + RaceDriver(Integer id, Car car) { + this.id = id; + this.car = car; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Car getCar() { + return car; + } + + public void setCar(Car car) { + this.car = car; + } + } + + @Embeddable + public static class Car { + + @Id + private Integer id; + + // represents a unidirectional one-to-one + @OneToOne(orphanRemoval = true, fetch = FetchType.LAZY) + private Engine engine; + + @Column + private String color; + + Car() { + // Required by JPA + } + + Car(Integer id, Engine engine, String color) { + this.id = id; + this.engine = engine; + this.color = color; + } + + public Engine getEngine() { + return engine; + } + + public void setEngine(Engine engine) { + this.engine = engine; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + } + + @Entity(name = "Engine") + public static class Engine { + @Id + private Integer id; + private Integer horsePower; + + Engine() { + // Required by JPA + } + + Engine(Integer id, int horsePower) { + this.id = id; + this.horsePower = horsePower; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getHorsePower() { + return horsePower; + } + + public void setHorsePower(Integer horsePower) { + this.horsePower = horsePower; + } + } +} From d1445efd828b67c299981d78306bf299fa3ca142 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 11 Mar 2021 12:58:03 +0100 Subject: [PATCH 2/7] HHH-9663 Implement support for orphan removal of assocations within embeddables --- .../hibernate/engine/internal/Cascade.java | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 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 f8788c0af476..107a3eff8f87 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 @@ -7,9 +7,11 @@ package org.hibernate.engine.internal; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import org.hibernate.HibernateException; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; @@ -29,6 +31,7 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; +import org.hibernate.type.ComponentType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.ForeignKeyDirection; @@ -90,7 +93,6 @@ public static void cascade( final String[] propertyNames = persister.getPropertyNames(); final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); - final int componentPathStackDepth = 0; for ( int i = 0; i < types.length; i++) { final CascadeStyle style = cascadeStyles[ i ]; final String propertyName = propertyNames[ i ]; @@ -152,7 +154,7 @@ else if ( action.performOnLazyProperty() && types[ i ].isEntityType() ) { action, cascadePoint, eventSource, - componentPathStackDepth, + null, parent, child, types[ i ], @@ -177,7 +179,7 @@ else if ( action.performOnLazyProperty() && types[ i ].isEntityType() ) { cascadeLogicalOneToOneOrphanRemoval( action, eventSource, - componentPathStackDepth, + null, parent, persister.getPropertyValue( parent, i ), types[ i ], @@ -202,7 +204,7 @@ private static void cascadeProperty( final CascadingAction action, final CascadePoint cascadePoint, final EventSource eventSource, - final int componentPathStackDepth, + List componentPath, final Object parent, final Object child, final Type type, @@ -219,7 +221,7 @@ private static void cascadeProperty( action, cascadePoint, eventSource, - componentPathStackDepth, + componentPath, parent, child, type, @@ -230,23 +232,28 @@ private static void cascadeProperty( } } else if ( type.isComponentType() ) { + if ( componentPath == null ) { + componentPath = new ArrayList<>(); + } + componentPath.add( propertyName ); cascadeComponent( action, cascadePoint, eventSource, - componentPathStackDepth, + componentPath, parent, child, (CompositeType) type, anything ); + componentPath.remove( componentPath.size() - 1 ); } } cascadeLogicalOneToOneOrphanRemoval( action, eventSource, - componentPathStackDepth, + componentPath, parent, child, type, @@ -258,7 +265,7 @@ else if ( type.isComponentType() ) { private static void cascadeLogicalOneToOneOrphanRemoval( final CascadingAction action, final EventSource eventSource, - final int componentPathStackDepth, + final List componentPath, final Object parent, final Object child, final Type type, @@ -277,26 +284,26 @@ private static void cascadeLogicalOneToOneOrphanRemoval( final EntityEntry entry = persistenceContext.getEntry( parent ); if ( entry != null && entry.getStatus() != Status.SAVING ) { Object loadedValue; - if ( componentPathStackDepth == 0 ) { + if ( componentPath == null ) { // association defined on entity loadedValue = entry.getLoadedValue( propertyName ); } else { // association defined on component - // todo : this is currently unsupported because of the fact that - // we do not know the loaded state of this value properly - // and doing so would be very difficult given how components and - // entities are loaded (and how 'loaded state' is put into the - // EntityEntry). Solutions here are to either: - // 1) properly account for components as a 2-phase load construct - // 2) just assume the association was just now orphaned and - // issue the orphan delete. This would require a special - // set of SQL statements though since we do not know the - // orphaned value, something a delete with a subquery to - // match the owner. -// final EntityType entityType = (EntityType) type; -// final String getPropertyPath = composePropertyPath( entityType.getPropertyName() ); - loadedValue = null; + // Since the loadedState in the EntityEntry is a flat domain type array + // We first have to extract the component object and then ask the component type + // recursively to give us the value of the sub-property of that object + loadedValue = entry.getLoadedValue( componentPath.get( 0 ) ); + ComponentType componentType = (ComponentType) entry.getPersister().getPropertyType( componentPath.get( 0 ) ); + if ( componentPath.size() != 1 ) { + for ( int i = 1; i < componentPath.size(); i++ ) { + final int subPropertyIndex = componentType.getPropertyIndex( componentPath.get( i ) ); + loadedValue = componentType.getPropertyValue( loadedValue, subPropertyIndex ); + componentType = (ComponentType) componentType.getSubtypes()[subPropertyIndex]; + } + } + + loadedValue = componentType.getPropertyValue( loadedValue, componentType.getPropertyIndex( propertyName ) ); } // orphaned if the association was nulled (child == null) or receives a new value while the @@ -367,7 +374,7 @@ private static void cascadeComponent( final CascadingAction action, final CascadePoint cascadePoint, final EventSource eventSource, - final int componentPathStackDepth, + final List componentPath, final Object parent, final Object child, final CompositeType componentType, @@ -379,7 +386,7 @@ private static void cascadeComponent( for ( int i = 0; i < types.length; i++ ) { final CascadeStyle componentPropertyStyle = componentType.getCascadeStyle( i ); final String subPropertyName = propertyNames[i]; - if ( componentPropertyStyle.doCascade( action ) ) { + if ( componentPropertyStyle.doCascade( action ) || componentPropertyStyle.hasOrphanDelete() && action.deleteOrphans() ) { if (children == null) { // Get children on demand. children = componentType.getPropertyValues( child, eventSource ); @@ -388,7 +395,7 @@ private static void cascadeComponent( action, cascadePoint, eventSource, - componentPathStackDepth + 1, + componentPath, parent, children[i], types[i], @@ -405,7 +412,7 @@ private static void cascadeAssociation( final CascadingAction action, final CascadePoint cascadePoint, final EventSource eventSource, - final int componentPathStackDepth, + final List componentPath, final Object parent, final Object child, final Type type, @@ -420,7 +427,7 @@ else if ( type.isCollectionType() ) { action, cascadePoint, eventSource, - componentPathStackDepth, + componentPath, parent, child, style, @@ -437,7 +444,7 @@ private static void cascadeCollection( final CascadingAction action, final CascadePoint cascadePoint, final EventSource eventSource, - final int componentPathStackDepth, + final List componentPath, final Object parent, final Object child, final CascadeStyle style, @@ -457,7 +464,7 @@ private static void cascadeCollection( action, elementsCascadePoint, eventSource, - componentPathStackDepth, + componentPath, parent, child, type, @@ -504,7 +511,7 @@ private static void cascadeCollectionElements( final CascadingAction action, final CascadePoint cascadePoint, final EventSource eventSource, - final int componentPathStackDepth, + final List componentPath, final Object parent, final Object child, final CollectionType collectionType, @@ -526,7 +533,7 @@ private static void cascadeCollectionElements( action, cascadePoint, eventSource, - componentPathStackDepth, + componentPath, parent, itr.next(), elemType, From 52684bba265199696eca48f62cbfcd32212d1bc7 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 11 Mar 2021 16:48:15 +0100 Subject: [PATCH 3/7] Fix concurrency issue in QueryCacheTest --- .../test/java/org/hibernate/test/querycache/QueryCacheTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java index 7855edda7a70..8e23e2cd2f58 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java @@ -744,11 +744,9 @@ public boolean onLoad(Object entity, Serializable id, Object[] state, String[] p try { if (waitLatch != null) { waitLatch.countDown(); - waitLatch = null; } if (blockLatch != null) { blockLatch.await(); - blockLatch = null; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); From fe1e098ba18ab0bbc64f833e58a3a42a18ec4226 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 8 Mar 2021 14:11:32 +0000 Subject: [PATCH 4/7] HHH-14485 Upgrade integration tests to use Oracle JDBC driver version 21.1 --- databases/oracle/matrix.gradle | 2 +- gradle/libraries.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/databases/oracle/matrix.gradle b/databases/oracle/matrix.gradle index 1805b136d7bc..b72fb8807176 100644 --- a/databases/oracle/matrix.gradle +++ b/databases/oracle/matrix.gradle @@ -4,4 +4,4 @@ * 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.database.jdbc:ojdbc8:19.8.0.0' \ No newline at end of file +jdbcDependency 'com.oracle.database.jdbc:ojdbc8:21.1.0.0' \ No newline at end of file diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index f10acd124cb7..eee7e07204ca 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -118,7 +118,7 @@ ext { mariadb: 'org.mariadb.jdbc:mariadb-java-client:2.2.3', cockroachdb: 'org.postgresql:postgresql:42.2.8', - oracle: 'com.oracle.database.jdbc:ojdbc8:19.8.0.0', + oracle: 'com.oracle.database.jdbc:ojdbc8:21.1.0.0', mssql: 'com.microsoft.sqlserver:mssql-jdbc:7.2.1.jre8', db2: 'com.ibm.db2:jcc:11.5.4.0', hana: 'com.sap.cloud.db.jdbc:ngdbc:2.4.59', From 7f814107a88da0b138fdf8ad55c815d00a16275d Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 11 Mar 2020 16:42:12 +0000 Subject: [PATCH 5/7] HHH-14494 Upgrade the PostgreSQL JDBC driver used for testing to v. 42.2.19 --- databases/pgsql/matrix.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/pgsql/matrix.gradle b/databases/pgsql/matrix.gradle index 9536407774ea..b8ac50d60726 100644 --- a/databases/pgsql/matrix.gradle +++ b/databases/pgsql/matrix.gradle @@ -4,4 +4,4 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -jdbcDependency 'org.postgresql:postgresql:42.2.2' +jdbcDependency 'org.postgresql:postgresql:42.2.19' From 44d78d9b854d3f1562a806d831db5342949a9bcd Mon Sep 17 00:00:00 2001 From: randymay Date: Fri, 3 Nov 2017 16:22:22 -0400 Subject: [PATCH 6/7] HHH-12076 Fix index out of bounds exception and allow table group joins for collection joins as well --- .../hibernate/engine/internal/JoinSequence.java | 14 +++++++++++--- .../hibernate/hql/internal/ast/HqlSqlWalker.java | 4 +++- .../query/hhh12076/HbmMappingJoinClassTest.java | 1 - 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java index f9ca7acfb685..713c5b430ba7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java @@ -14,6 +14,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.ast.tree.ImpliedFromElement; import org.hibernate.internal.util.StringHelper; +import org.hibernate.persister.collection.AbstractCollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.Joinable; @@ -422,9 +423,16 @@ private boolean needsTableGroupJoin(List joins, String withClauseFragment) } private boolean isSubclassAliasDereferenced(Join join, String withClauseFragment) { - if ( join.getJoinable() instanceof AbstractEntityPersister ) { - AbstractEntityPersister persister = (AbstractEntityPersister) join.getJoinable(); - int subclassTableSpan = persister.getSubclassTableSpan(); + Object joinable = join.getJoinable(); + if ( joinable instanceof AbstractCollectionPersister ) { + final AbstractCollectionPersister collectionPersister = (AbstractCollectionPersister) joinable; + if ( collectionPersister.getElementType().isEntityType() ) { + joinable = ( collectionPersister ).getElementPersister(); + } + } + if ( joinable instanceof AbstractEntityPersister ) { + final AbstractEntityPersister persister = (AbstractEntityPersister) joinable; + final int subclassTableSpan = persister.getSubclassTableSpan(); for ( int j = 1; j < subclassTableSpan; j++ ) { String subclassAlias = AbstractEntityPersister.generateTableAlias( join.getAlias(), j ); if ( isAliasDereferenced( withClauseFragment, subclassAlias ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java index 5943b7ffb13d..73b7d6afecba 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java @@ -557,7 +557,9 @@ public void visit(AST node) { } else { referencedFromElement = fromElement; - joinAlias = extractAppliedAlias( dotNode ); + if ( fromElement != null ) { + joinAlias = extractAppliedAlias( dotNode ); + } // TODO : temporary // needed because currently persister is the one that // creates and renders the join fragments for inheritance diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/HbmMappingJoinClassTest.java b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/HbmMappingJoinClassTest.java index be9fb29624ce..57feab4e0ff4 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/hhh12076/HbmMappingJoinClassTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh12076/HbmMappingJoinClassTest.java @@ -80,7 +80,6 @@ protected void prepareTest() { } @Test - @FailureExpected( jiraKey = "HHH-12076") public void testClassExpressionInOnClause() { doInHibernate( this::sessionFactory, session -> { List results = session.createQuery( From b6ba93c786e569ad4b318244b446b68525779517 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 12 Mar 2021 12:51:38 +0100 Subject: [PATCH 7/7] Increase maximum open cursors --- docker_db.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docker_db.sh b/docker_db.sh index a496ea5ca6a3..940ac409d96b 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -148,6 +148,7 @@ alter system checkpoint; alter database drop logfile group 1; alter database drop logfile group 2; alter database drop logfile group 3; +alter system set open_cursors=1000 sid='*' scope=both; EOF\"" }