diff --git a/databases/oracle/matrix.gradle b/databases/oracle/matrix.gradle index 1805b136d7..b72fb88071 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/databases/pgsql/matrix.gradle b/databases/pgsql/matrix.gradle index 9536407774..b8ac50d607 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' diff --git a/docker_db.sh b/docker_db.sh index 36873cebc0..e09a87ec37 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -153,6 +153,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\"" } diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index e33b277d0b..799fc9c142 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -131,7 +131,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', 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 f56f1e4785..116fb324b3 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 @@ -6,9 +6,11 @@ */ package org.hibernate.engine.internal; +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; @@ -28,6 +30,7 @@ import org.hibernate.pretty.MessageHelper; 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; @@ -89,7 +92,6 @@ public final class 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 @@ public final class Cascade { action, cascadePoint, eventSource, - componentPathStackDepth, + null, parent, child, type, @@ -177,7 +179,7 @@ public final class Cascade { cascadeLogicalOneToOneOrphanRemoval( action, eventSource, - componentPathStackDepth, + null, parent, persister.getPropertyValue( parent, i ), type, @@ -202,7 +204,7 @@ public final class Cascade { 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 @@ public final class Cascade { action, cascadePoint, eventSource, - componentPathStackDepth, + componentPath, parent, child, type, @@ -230,23 +232,28 @@ public final class Cascade { } } 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 @@ public final class Cascade { 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 @@ public final class Cascade { 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 @@ public final class Cascade { 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 @@ public final class Cascade { 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 @@ public final class Cascade { action, cascadePoint, eventSource, - componentPathStackDepth + 1, + componentPath, parent, children[i], types[i], @@ -405,7 +412,7 @@ public final class Cascade { 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 @@ public final class Cascade { action, cascadePoint, eventSource, - componentPathStackDepth, + componentPath, parent, child, style, @@ -437,7 +444,7 @@ public final class Cascade { 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 @@ public final class Cascade { action, elementsCascadePoint, eventSource, - componentPathStackDepth, + componentPath, parent, child, type, @@ -504,7 +511,7 @@ public final class Cascade { 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 @@ public final class Cascade { action, cascadePoint, eventSource, - componentPathStackDepth, + componentPath, parent, itr.next(), elemType, 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 be9fb29624..57feab4e0f 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 @@ public class HbmMappingJoinClassTest extends BaseCoreFunctionalTestCase { } @Test - @FailureExpected( jiraKey = "HHH-12076") public void testClassExpressionInOnClause() { doInHibernate( this::sessionFactory, session -> { List results = session.createQuery( 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 0000000000..f72f71024e --- /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; + } + } +} 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 36e5956ba3..e87a93ef3a 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 @@ -760,11 +760,9 @@ public class QueryCacheTest extends BaseNonConfigCoreFunctionalTestCase { try { if (waitLatch != null) { waitLatch.countDown(); - waitLatch = null; } if (blockLatch != null) { blockLatch.await(); - blockLatch = null; } } catch (InterruptedException e) { Thread.currentThread().interrupt();