Skip to content

Commit

Permalink
[#1782] Upgrade Hibernate ORM to 6.4.0.Final
Browse files Browse the repository at this point in the history
  • Loading branch information
DavideD committed Nov 24, 2023
1 parent f517fc0 commit f341f8a
Show file tree
Hide file tree
Showing 29 changed files with 409 additions and 332 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Hibernate Reactive has been tested with:
- CockroachDB 22.1
- MS SQL Server 2019
- Oracle 21.3
- [Hibernate ORM][] 6.3.2.Final
- [Hibernate ORM][] 6.4.0.Final
- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.0
- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.0
- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.0
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ version = projectVersion
// ./gradlew clean build -PhibernateOrmVersion=5.6.15-SNAPSHOT
ext {
if ( !project.hasProperty('hibernateOrmVersion') ) {
hibernateOrmVersion = '6.3.2.Final'
hibernateOrmVersion = '6.4.0.Final'
}
if ( !project.hasProperty( 'hibernateOrmGradlePluginVersion' ) ) {
// Same as ORM as default
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ org.gradle.java.installations.auto-download=false
#enableMavenLocalRepo = true

# Override default Hibernate ORM version
#hibernateOrmVersion = 6.2.3.Final
#hibernateOrmVersion = 6.4.0.Final

# Override default Hibernate ORM Gradle plugin version
# Using the stable version because I don't know how to configure the build to download the snapshot version from
# a remote repository
#hibernateOrmGradlePluginVersion = 6.2.3.Final
#hibernateOrmGradlePluginVersion = 6.4.0.Final

# If set to true, skip Hibernate ORM version parsing (default is true, if set to null)
# this is required when using intervals or weird versions or the build will fail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.reactive.logging.impl.Log;
import org.hibernate.reactive.logging.impl.LoggerFactory;
import org.hibernate.reactive.session.ReactiveSession;
Expand All @@ -40,6 +39,7 @@
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;

import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy;
import static org.hibernate.pretty.MessageHelper.infoString;
import static org.hibernate.reactive.util.impl.CompletionStages.loop;
import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;
Expand Down Expand Up @@ -111,9 +111,9 @@ public static CompletionStage<?> fetchLazyAssociationsBeforeCascade(
* Cascade an action from the parent entity instance to all its children.
*/
public CompletionStage<Void> cascade() throws HibernateException {
return voidFuture().thenCompose(v -> {
return voidFuture().thenCompose( v -> {
CacheMode cacheMode = eventSource.getCacheMode();
if ( action==CascadingActions.DELETE ) {
if ( action == CascadingActions.DELETE ) {
eventSource.setCacheMode( CacheMode.GET );
}
eventSource.getPersistenceContextInternal().incrementCascadeLevel();
Expand All @@ -125,18 +125,23 @@ public CompletionStage<Void> cascade() throws HibernateException {
}

private CompletionStage<Void> cascadeInternal() throws HibernateException {

if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) { // performance opt
final boolean traceEnabled = LOG.isTraceEnabled();
if ( traceEnabled ) {
LOG.tracev( "Processing cascade {0} for: {1}", action, persister.getEntityName() );
}
final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal();
final EntityEntry entry = persistenceContext.getEntry( parent );
if ( entry != null && entry.getLoadedState() == null && entry.getStatus() == Status.MANAGED && persister.getBytecodeEnhancementMetadata()
.isEnhancedForLazyLoading() ) {
return voidFuture();
}

final Type[] types = persister.getPropertyTypes();
final String[] propertyNames = persister.getPropertyNames();
final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles();
final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent );

for ( int i = 0; i < types.length; i++) {
final CascadeStyle style = cascadeStyles[ i ];
final String propertyName = propertyNames[ i ];
Expand All @@ -154,7 +159,7 @@ private CompletionStage<Void> cascadeInternal() throws HibernateException {
// If parent is a detached entity being merged,
// then parent will not be in the PersistenceContext
// (so lazy attributes must not be initialized).
if ( persistenceContext.getEntry( parent ) == null ) {
if ( entry == null ) {
// parent was not in the PersistenceContext
continue;
}
Expand Down Expand Up @@ -295,91 +300,88 @@ private void cascadeLogicalOneToOneOrphanRemoval(
final String propertyName,
final boolean isCascadeDeleteEnabled) throws HibernateException {

// potentially we need to handle orphan deletes for one-to-ones here...
if ( isLogicalOneToOne( type ) ) {
// We have a physical or logical one-to-one. See if the attribute cascade settings and action-type require
// orphan checking
if ( style.hasOrphanDelete() && action.deleteOrphans() ) {
// value is orphaned if loaded state for this property shows not null
// because it is currently null.
final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal();
final EntityEntry entry = persistenceContext.getEntry( parent );
if ( entry != null && entry.getStatus() != Status.SAVING ) {
Object loadedValue;
if ( componentPath == null ) {
// association defined on entity
loadedValue = entry.getLoadedValue( propertyName );
// We have a physical or logical one-to-one. See if the attribute cascade settings and action-type require
// orphan checking
if ( style.hasOrphanDelete() && action.deleteOrphans() ) {
// value is orphaned if loaded state for this property shows not null
// because it is currently null.
final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal();
final EntityEntry entry = persistenceContext.getEntry( parent );
if ( entry != null && entry.getStatus() != Status.SAVING ) {
Object loadedValue;
if ( componentPath == null ) {
// association defined on entity
loadedValue = entry.getLoadedValue( propertyName );
}
else {
// association defined on component
// 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
final AttributeMapping propertyType = entry.getPersister().findAttributeMapping( componentPath.get( 0) );
if ( propertyType instanceof ComponentType) {
loadedValue = entry.getLoadedValue( componentPath.get( 0 ) );
ComponentType componentType = (ComponentType) propertyType;
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 ) );
}
else {
// association defined on component
// 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
final AttributeMapping propertyType = entry.getPersister().findAttributeMapping( componentPath.get( 0) );
if ( propertyType instanceof ComponentType) {
loadedValue = entry.getLoadedValue( componentPath.get( 0 ) );
ComponentType componentType = (ComponentType) propertyType;
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];
}
}
// Association is probably defined in an element collection, so we can't do orphan removals
loadedValue = null;
}
}

loadedValue = componentType.getPropertyValue( loadedValue, componentType.getPropertyIndex( propertyName ) );
}
else {
// Association is probably defined in an element collection, so we can't do orphan removals
loadedValue = null;
// orphaned if the association was nulled (child == null) or receives a new value while the
// entity is managed (without first nulling and manually flushing).
if ( child == null || loadedValue != null && child != loadedValue ) {
EntityEntry valueEntry = persistenceContext.getEntry( loadedValue );

if ( valueEntry == null && isHibernateProxy( loadedValue ) ) {
// un-proxy and re-associate for cascade operation
// useful for @OneToOne defined as FetchType.LAZY
loadedValue = persistenceContext.unproxyAndReassociate( loadedValue );
valueEntry = persistenceContext.getEntry( loadedValue );

// HHH-11965
// Should the unwrapped proxy value be equal via reference to the entity's property value
// provided by the 'child' variable, we should not trigger the orphan removal of the
// associated one-to-one.
if ( child == loadedValue ) {
// do nothing
return;
}
}

// orphaned if the association was nulled (child == null) or receives a new value while the
// entity is managed (without first nulling and manually flushing).
if ( child == null || loadedValue != null && child != loadedValue ) {
EntityEntry valueEntry = persistenceContext.getEntry( loadedValue );

if ( valueEntry == null && loadedValue instanceof HibernateProxy ) {
// un-proxy and re-associate for cascade operation
// useful for @OneToOne defined as FetchType.LAZY
loadedValue = persistenceContext.unproxyAndReassociate( loadedValue );
valueEntry = persistenceContext.getEntry( loadedValue );

// HHH-11965
// Should the unwrapped proxy value be equal via reference to the entity's property value
// provided by the 'child' variable, we should not trigger the orphan removal of the
// associated one-to-one.
if ( child == loadedValue ) {
// do nothing
return;
}
if ( valueEntry != null ) {
final EntityPersister persister = valueEntry.getPersister();
final String entityName = persister.getEntityName();
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Deleting orphaned entity instance: {0}",
infoString( entityName, persister.getIdentifier( loadedValue, eventSource ) )
);
}

if ( valueEntry != null ) {
final EntityPersister persister = valueEntry.getPersister();
final String entityName = persister.getEntityName();
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Deleting orphaned entity instance: {0}",
infoString( entityName, persister.getIdentifier( loadedValue, eventSource ) )
);
}

final Object loaded = loadedValue;
if ( type.isAssociationType()
&& ( (AssociationType) type ).getForeignKeyDirection().equals(TO_PARENT) ) {
// If FK direction is to-parent, we must remove the orphan *before* the queued update(s)
// occur. Otherwise, replacing the association on a managed entity, without manually
// nulling and flushing, causes FK constraint violations.
stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource )
.reactiveRemoveOrphanBeforeUpdates( entityName, loaded ) );
}
else {
// Else, we must delete after the updates.
stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource )
.reactiveRemove( entityName, loaded, isCascadeDeleteEnabled, DeleteContext.create() ) );
}
final Object loaded = loadedValue;
if ( type.isAssociationType()
&& ( (AssociationType) type ).getForeignKeyDirection().equals(TO_PARENT) ) {
// If FK direction is to-parent, we must remove the orphan *before* the queued update(s)
// occur. Otherwise, replacing the association on a managed entity, without manually
// nulling and flushing, causes FK constraint violations.
stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource )
.reactiveRemoveOrphanBeforeUpdates( entityName, loaded ) );
}
else {
// Else, we must delete after the updates.
stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource )
.reactiveRemove( entityName, loaded, isCascadeDeleteEnabled, DeleteContext.create() ) );
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ protected void logFlushResults(FlushEvent event) {
persistenceContext.getCollectionEntriesSize()
);
new EntityPrinter( session.getFactory() ).toString(
persistenceContext.getEntitiesByKey().entrySet()
persistenceContext.getEntityHoldersByKey().entrySet()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
*/
package org.hibernate.reactive.event.impl;

import static org.hibernate.pretty.MessageHelper.collectionInfoString;
import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;

import java.lang.invoke.MethodHandles;
import java.util.concurrent.CompletionStage;

Expand All @@ -29,6 +26,9 @@
import org.hibernate.sql.results.internal.ResultsHelper;
import org.hibernate.stat.spi.StatisticsImplementor;

import static org.hibernate.pretty.MessageHelper.collectionInfoString;
import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;

public class DefaultReactiveInitializeCollectionEventListener implements InitializeCollectionEventListener {

private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() );
Expand Down Expand Up @@ -144,7 +144,7 @@ private boolean initializeCollectionFromCache(
final SessionFactoryImplementor factory = source.getFactory();
final CollectionDataAccess cacheAccessStrategy = persister.getCacheAccessStrategy();
final Object ck = cacheAccessStrategy.generateCacheKey( id, persister, factory, source.getTenantIdentifier() );
final Object ce = CacheHelper.fromSharedCache( source, ck, cacheAccessStrategy );
final Object ce = CacheHelper.fromSharedCache( source, ck, persister, cacheAccessStrategy );

final StatisticsImplementor statistics = factory.getStatistics();
if ( statistics.isStatisticsEnabled() ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ public CompletionStage<Void> reactiveOnRefresh(RefreshEvent event) throws Hibern

@Override
public void onRefresh(RefreshEvent event) throws HibernateException {
throw new UnsupportedOperationException();
throw LOG.nonReactiveMethodCall( "reactiveOnRefresh" );
}

@Override
public void onRefresh(RefreshEvent event, RefreshContext refreshedAlready) throws HibernateException {
throw new UnsupportedOperationException();
throw LOG.nonReactiveMethodCall( "reactiveOnRefresh" );
}

/**
Expand All @@ -83,16 +83,25 @@ public CompletionStage<Void> reactiveOnRefresh(RefreshEvent event, RefreshContex

if ( detached ) {
// Hibernate Reactive doesn't support detached instances in refresh()
throw new IllegalArgumentException("unmanaged instance passed to refresh()");
throw new IllegalArgumentException( "Unmanaged instance passed to refresh()" );
}
return ( (ReactiveSession) source ).reactiveFetch( event.getObject(), true )
return ( (ReactiveSession) source )
.reactiveFetch( event.getObject(), true )
.thenCompose( entity -> reactiveOnRefresh( event, refreshedAlready, entity ) );
}

private CompletionStage<Void> reactiveOnRefresh(RefreshEvent event, RefreshContext refreshedAlready, Object entity) {
private CompletionStage<Void> reactiveOnRefresh(RefreshEvent event, RefreshContext refreshedAlready, Object object) {
final EventSource source = event.getSession();
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();

if ( persistenceContext.reassociateIfUninitializedProxy( object ) ) {
if ( isTransient( event, source, object ) ) {
source.setReadOnly( object, source.isDefaultReadOnly() );
}
return voidFuture();
}

Object entity = persistenceContext.unproxyAndReassociate( object );
if ( !refreshedAlready.add( entity) ) {
LOG.trace( "Already refreshed" );
return voidFuture();
Expand Down Expand Up @@ -169,6 +178,11 @@ private CompletionStage<Void> reactiveOnRefresh(RefreshEvent event, RefreshConte
} );
}

private static boolean isTransient(RefreshEvent event, EventSource source, Object object) {
final String entityName = event.getEntityName();
return entityName != null ? !source.contains( entityName, object) : !source.contains(object);
}

private static void evictEntity(Object entity, EntityPersister persister, Object id, EventSource source) {
if ( persister.canWriteToCache() ) {
Object previousVersion = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
*/
package org.hibernate.reactive.loader.ast.internal;

import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.EntityHolder;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.sql.exec.internal.BaseExecutionContext;
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;

/**
* Copy and paste of {@link org.hibernate.loader.ast.internal.ExecutionContextWithSubselectFetchHandler}
Expand All @@ -25,9 +24,9 @@ public ExecutionContextWithSubselectFetchHandler(SharedSessionContractImplemento
}

@Override
public void registerLoadingEntityEntry(EntityKey entityKey, LoadingEntityEntry entry) {
public void registerLoadingEntityHolder(EntityHolder holder) {
if ( subSelectFetchableKeysHandler != null ) {
subSelectFetchableKeysHandler.addKey( entityKey, entry );
subSelectFetchableKeysHandler.addKey( holder );
}
}

Expand Down
Loading

0 comments on commit f341f8a

Please sign in to comment.