diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/CascadeType.java b/hibernate-core/src/main/java/org/hibernate/annotations/CascadeType.java index 215318430d..497fec3930 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/CascadeType.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/CascadeType.java @@ -14,34 +14,42 @@ public enum CascadeType { * Includes all types listed here. */ ALL, + /** * Corresponds to {@link javax.persistence.CascadeType#PERSIST}. */ PERSIST, + /** * Corresponds to {@link javax.persistence.CascadeType#MERGE}. */ MERGE, + /** * Corresponds to {@link javax.persistence.CascadeType#REMOVE}. */ REMOVE, + /** * Corresponds to {@link javax.persistence.CascadeType#REFRESH}. */ REFRESH, + /** * Corresponds to the Hibernate native DELETE action. */ DELETE, + /** * Corresponds to the Hibernate native SAVE_UPDATE (direct reattachment) action. */ SAVE_UPDATE, + /** * Corresponds to the Hibernate native REPLICATE action. */ REPLICATE, + /** * Hibernate originally handled orphan removal as a specialized cascade. * @@ -49,10 +57,12 @@ public enum CascadeType { */ @Deprecated DELETE_ORPHAN, + /** * Corresponds to the Hibernate native LOCK action. */ LOCK, + /** * JPA originally planned on calling DETACH EVICT. * @@ -60,6 +70,7 @@ public enum CascadeType { */ @Deprecated EVICT, + /** * Corresponds to {@link javax.persistence.CascadeType#DETACH}. */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java index e33a082af5..ae49005e7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java @@ -34,7 +34,6 @@ import org.hibernate.sql.exec.spi.JdbcParameterBinding; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.RowTransformerPassThruImpl; - import org.jboss.logging.Logger; /** @@ -63,11 +62,7 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup final int numberOfIds = ArrayHelper.countNonNull( batchIds ); if ( numberOfIds <= 1 ) { - if ( singleIdLoader == null ) { - singleIdLoader = new SingleIdEntityLoaderStandardImpl<>( getLoadable(), session.getFactory() ); - singleIdLoader.prepare(); - } - + initializeSingleIdLoaderIfNeeded( session ); final T result = singleIdLoader.load( pkValue, lockOptions, session ); if ( result == null ) { @@ -187,6 +182,14 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup Object entityInstance, LockOptions lockOptions, SharedSessionContractImplementor session) { + initializeSingleIdLoaderIfNeeded( session ); return singleIdLoader.load( pkValue, entityInstance, lockOptions, session ); } + + private void initializeSingleIdLoaderIfNeeded(SharedSessionContractImplementor session) { + if ( singleIdLoader == null ) { + singleIdLoader = new SingleIdEntityLoaderStandardImpl<>( getLoadable(), session.getFactory() ); + singleIdLoader.prepare(); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java index d3bf9f7360..09b5e9d133 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java @@ -136,6 +136,11 @@ public class SingleIdLoadPlan implements SingleEntityLoadPlan { return entityInstance; } + @Override + public Object getEntityId() { + return restrictedValue; + } + @Override public QueryOptions getQueryOptions() { return QueryOptions.NONE; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleIdEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleIdEntityLoader.java index f120d4248c..c907aa1b35 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleIdEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleIdEntityLoader.java @@ -23,7 +23,8 @@ public interface SingleIdEntityLoader extends SingleEntityLoader { /** * Load by primary key value, populating the passed entity instance. Used to initialize an uninitialized - * bytecode-proxy. The passed instance is the enhanced proxy + * bytecode-proxy or {@link org.hibernate.event.spi.LoadEvent} handling. + * The passed instance is the enhanced proxy or the entity to be loaded. */ T load(Object pkValue, Object entityInstance, LockOptions lockOptions, SharedSessionContractImplementor session); 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 b689c01105..720fad7e46 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 @@ -4470,7 +4470,12 @@ public abstract class AbstractEntityPersister LOG.tracev( "Fetching entity: {0}", MessageHelper.infoString( this, id, getFactory() ) ); } - return singleIdEntityLoader.load( id, lockOptions, session ); + if ( optionalObject == null ) { + return singleIdEntityLoader.load( id, lockOptions, session ); + } + else { + return singleIdEntityLoader.load( id, optionalObject, lockOptions, session ); + } } public SingleIdEntityLoader getSingleIdEntityLoader() { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java index ed1aac4e45..4f106e518c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java @@ -158,7 +158,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor { @Override public Object getEffectiveOptionalId() { - return null; + return executionContext.getEntityId(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/ExecutionContext.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/ExecutionContext.java index 14593c88c2..2662b0c28d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/ExecutionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/ExecutionContext.java @@ -45,6 +45,10 @@ public interface ExecutionContext { return null; } + default Object getEntityId() { + return null; + } + default void registerLoadingEntityEntry(EntityKey entityKey, LoadingEntityEntry entry) { // by default do nothing } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java index 336193b6d4..f45ac4d8bf 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java @@ -398,8 +398,9 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces // this isEntityReturn bit is just for entity loaders, not hql/criteria if ( isEntityReturn() ) { final Object requestedEntityId = rowProcessingState.getJdbcValuesSourceProcessingState().getProcessingOptions().getEffectiveOptionalId(); - if ( requestedEntityId != null && requestedEntityId.equals( entityKey.getIdentifier() ) ) { - entityInstance = rowProcessingState.getJdbcValuesSourceProcessingState().getProcessingOptions().getEffectiveOptionalObject(); + final Object optionalEntityInstance = rowProcessingState.getJdbcValuesSourceProcessingState().getProcessingOptions().getEffectiveOptionalObject(); + if ( requestedEntityId != null && optionalEntityInstance != null && requestedEntityId.equals( entityKey.getIdentifier() ) ) { + entityInstance = optionalEntityInstance; } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cascade/RefreshTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cascade/RefreshTest.java index 822e578c7a..3fc25a2ea4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cascade/RefreshTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cascade/RefreshTest.java @@ -6,20 +6,30 @@ */ package org.hibernate.orm.test.cascade; -import java.sql.PreparedStatement; +import java.sql.Statement; import java.util.Date; -import java.util.Iterator; - +import java.util.HashSet; +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.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; import org.hibernate.engine.spi.SessionImplementor; - import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.FailureExpected; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - +import static org.junit.Assert.assertEquals; /** * Implementation of RefreshTest. @@ -27,61 +37,113 @@ import static org.junit.jupiter.api.Assertions.assertEquals; * @author Steve Ebersole */ @DomainModel( - xmlMappings = { - "org/hibernate/orm/test/cascade/Job.hbm.xml", - "org/hibernate/orm/test/cascade/JobBatch.hbm.xml" - } + annotatedClasses = { + RefreshTest.Job.class, + RefreshTest.JobBatch.class + } ) @SessionFactory -@FailureExpected( reason = "This should be fixed by Nathan PR") public class RefreshTest { - @Test - public void testRefreshCascade(SessionFactoryScope scope) { + private JobBatch batch; + + @BeforeEach + void setUp(SessionFactoryScope scope) { scope.inTransaction( - session -> { - JobBatch batch = new JobBatch( new Date() ); - batch.createJob().setProcessingInstructions( "Just do it!" ); - batch.createJob().setProcessingInstructions( "I know you can do it!" ); + session -> { + batch = new JobBatch( new Date() ); + batch.createJob().processingInstructions = "Just do it!"; + batch.createJob().processingInstructions = "I know you can do it!"; - // write the stuff to the database; at this stage all job.status values are zero - session.persist( batch ); - session.flush(); + // write the stuff to the database; at this stage all job.status values are zero + session.save( batch ); + } + ); - // behind the session's back, let's modify the statuses - updateStatuses( session ); + // behind the session's back, let's modify the statuses to one + scope.inSession( this::updateStatuses ); + } - // Now lets refresh the persistent batch, and see if the refresh cascaded to the jobs collection elements - session.refresh( batch ); - - Iterator itr = batch.getJobs().iterator(); - while ( itr.hasNext() ) { - Job job = (Job) itr.next(); - assertEquals( 1, job.getStatus(), "Jobs not refreshed!" ); - } - } + @Test + void testRefreshCascade(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.refresh( batch ); + batch.jobs.forEach( job -> assertEquals( "Jobs not refreshed!", 1, job.status ) ); + } ); } private void updateStatuses(final SessionImplementor session) { session.doWork( - connection -> { - PreparedStatement stmnt = null; - try { - stmnt = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( - "UPDATE T_JOB SET JOB_STATUS = 1" ); - session.getJdbcCoordinator().getResultSetReturn().executeUpdate( stmnt ); - } - finally { - if ( stmnt != null ) { - try { - session.getJdbcCoordinator().getResourceRegistry().release( stmnt ); - } - catch (Throwable ignore) { - } + connection -> { + Statement stmnt = null; + try { + stmnt = session.getJdbcCoordinator().getStatementPreparer().createStatement(); + stmnt.execute( "UPDATE T_JOB SET JOB_STATUS = 1" ); + } + finally { + if ( stmnt != null ) { + try { + session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( stmnt ); + } + catch( Throwable ignore ) { } } } + } ); } + + @Entity( name = "Job" ) + @Table( name = "T_JOB" ) + static class Job { + + @Id @GeneratedValue + Long id; + + @ManyToOne( fetch = FetchType.LAZY ) + JobBatch batch; + + @Column( name = "PI", nullable = false ) + String processingInstructions; + + @Column( name = "JOB_STATUS", nullable = false ) + int status; + + Job() {} + + Job(JobBatch batch) { + this.batch = batch; + } + + } + + @Entity( name = "JobBatch" ) + @Table( name = "T_JOB_BATCH" ) + static class JobBatch { + + @Id @GeneratedValue + Long id; + + @Column( nullable = false ) + @Temporal( TemporalType.TIMESTAMP ) + Date batchDate; + + @OneToMany( mappedBy = "batch", fetch = FetchType.LAZY, cascade = CascadeType.ALL ) + @Fetch( FetchMode.SELECT ) + Set jobs = new HashSet<>(); + + JobBatch() {} + + JobBatch(Date batchDate) { + this.batchDate = batchDate; + } + + Job createJob() { + Job job = new Job( this ); + jobs.add( job ); + return job; + } + } }