diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java index 9e224b3286..4a7d17d6d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java @@ -239,18 +239,19 @@ public class EntityInsertAction extends AbstractEntityInsertAction { } protected boolean preInsert() { - boolean veto = false; - final EventListenerGroup listenerGroup = getFastSessionServices().eventListenerGroup_PRE_INSERT; if ( listenerGroup.isEmpty() ) { + return false; + } + else { + boolean veto = false; + final PreInsertEvent event = new PreInsertEvent( getInstance(), getId(), getState(), getPersister(), eventSource() ); + for ( PreInsertEventListener listener : listenerGroup.listeners() ) { + veto |= listener.onPreInsert( event ); + } return veto; } - final PreInsertEvent event = new PreInsertEvent( getInstance(), getId(), getState(), getPersister(), eventSource() ); - for ( PreInsertEventListener listener : listenerGroup.listeners() ) { - veto |= listener.onPreInsert( event ); - } - return veto; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java index afbca6d7d4..b579c91b16 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java @@ -20,6 +20,8 @@ import org.hibernate.event.spi.PreInsertEvent; import org.hibernate.event.spi.PreInsertEventListener; import org.hibernate.event.spi.PreUpdateEvent; import org.hibernate.event.spi.PreUpdateEventListener; +import org.hibernate.event.spi.PreUpsertEvent; +import org.hibernate.event.spi.PreUpsertEventListener; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.RepresentationMode; @@ -42,7 +44,7 @@ import jakarta.validation.ValidatorFactory; */ //FIXME review exception model public class BeanValidationEventListener - implements PreInsertEventListener, PreUpdateEventListener, PreDeleteEventListener { + implements PreInsertEventListener, PreUpdateEventListener, PreDeleteEventListener, PreUpsertEventListener { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, @@ -60,7 +62,8 @@ public class BeanValidationEventListener * @param factory The {@code ValidatorFactory} to use to create {@code Validator} instance(s) * @param settings Configured properties */ - public BeanValidationEventListener(ValidatorFactory factory, Map settings, ClassLoaderService classLoaderService) { + public BeanValidationEventListener( + ValidatorFactory factory, Map settings, ClassLoaderService classLoaderService) { init( factory, settings, classLoaderService ); } @@ -80,9 +83,8 @@ public class BeanValidationEventListener public boolean onPreInsert(PreInsertEvent event) { validate( event.getEntity(), - event.getPersister().getRepresentationStrategy().getMode(), event.getPersister(), - event.getSession().getFactory(), + event.getFactory(), GroupsPerOperation.Operation.INSERT ); return false; @@ -91,9 +93,8 @@ public class BeanValidationEventListener public boolean onPreUpdate(PreUpdateEvent event) { validate( event.getEntity(), - event.getPersister().getRepresentationStrategy().getMode(), event.getPersister(), - event.getSession().getFactory(), + event.getFactory(), GroupsPerOperation.Operation.UPDATE ); return false; @@ -102,21 +103,30 @@ public class BeanValidationEventListener public boolean onPreDelete(PreDeleteEvent event) { validate( event.getEntity(), - event.getPersister().getRepresentationStrategy().getMode(), event.getPersister(), - event.getSession().getFactory(), + event.getFactory(), GroupsPerOperation.Operation.DELETE ); return false; } + @Override + public boolean onPreUpsert(PreUpsertEvent event) { + validate( + event.getEntity(), + event.getPersister(), + event.getFactory(), + GroupsPerOperation.Operation.UPSERT + ); + return false; + } + private void validate( T object, - RepresentationMode mode, EntityPersister persister, SessionFactoryImplementor sessionFactory, GroupsPerOperation.Operation operation) { - if ( object == null || mode != RepresentationMode.POJO ) { + if ( object == null || persister.getRepresentationStrategy().getMode() != RepresentationMode.POJO ) { return; } TraversableResolver tr = new HibernateTraversableResolver( persister, associationsPerEntityPersister, sessionFactory ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/GroupsPerOperation.java b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/GroupsPerOperation.java index d0ede8c3b4..82d754d0f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/GroupsPerOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/GroupsPerOperation.java @@ -39,6 +39,7 @@ public class GroupsPerOperation { applyOperationGrouping( groupsPerOperation, Operation.INSERT, settings, classLoaderAccess ); applyOperationGrouping( groupsPerOperation, Operation.UPDATE, settings, classLoaderAccess ); applyOperationGrouping( groupsPerOperation, Operation.DELETE, settings, classLoaderAccess ); + applyOperationGrouping( groupsPerOperation, Operation.UPSERT, settings, classLoaderAccess ); applyOperationGrouping( groupsPerOperation, Operation.DDL, settings, classLoaderAccess ); return groupsPerOperation; @@ -99,10 +100,11 @@ public class GroupsPerOperation { return groupsPerOperation.get( operation ); } - public static enum Operation { + public enum Operation { INSERT( "persist", JPA_GROUP_PREFIX + "pre-persist", JAKARTA_JPA_GROUP_PREFIX + "pre-persist" ), UPDATE( "update", JPA_GROUP_PREFIX + "pre-update", JAKARTA_JPA_GROUP_PREFIX + "pre-update" ), DELETE( "remove", JPA_GROUP_PREFIX + "pre-remove", JAKARTA_JPA_GROUP_PREFIX + "pre-remove" ), + UPSERT( "upsert", JPA_GROUP_PREFIX + "pre-upsert", JAKARTA_JPA_GROUP_PREFIX + "pre-upsert" ), DDL( "ddl", HIBERNATE_GROUP_PREFIX + "ddl", HIBERNATE_GROUP_PREFIX + "ddl" ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java index 181e6158d7..b66b4d623a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java @@ -6,6 +6,7 @@ */ package org.hibernate.boot.beanvalidation; +import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -23,8 +24,6 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.spi.ClassLoaderAccess; import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; @@ -40,6 +39,7 @@ import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.SingleTableSubclass; +import org.hibernate.service.spi.SessionFactoryServiceRegistry; import org.jboss.logging.Logger; import jakarta.validation.Validation; @@ -48,6 +48,11 @@ import jakarta.validation.metadata.BeanDescriptor; import jakarta.validation.metadata.ConstraintDescriptor; import jakarta.validation.metadata.PropertyDescriptor; +import static org.hibernate.boot.beanvalidation.GroupsPerOperation.buildGroupsForOperation; +import static org.hibernate.cfg.ValidationSettings.CHECK_NULLABILITY; +import static org.hibernate.cfg.ValidationSettings.JAKARTA_VALIDATION_FACTORY; +import static org.hibernate.cfg.ValidationSettings.JPA_VALIDATION_FACTORY; + /** * @author Emmanuel Bernard * @author Hardy Ferentschik @@ -62,7 +67,7 @@ class TypeSafeActivator { * * @param object The supplied ValidatorFactory instance. */ - @SuppressWarnings( {"UnusedDeclaration"}) + @SuppressWarnings("UnusedDeclaration") public static void validateSuppliedFactory(Object object) { if ( !(object instanceof ValidatorFactory) ) { throw new IntegrationException( @@ -95,49 +100,46 @@ class TypeSafeActivator { applyCallbackListeners( factory, activationContext ); } - @SuppressWarnings( {"UnusedDeclaration"}) public static void applyCallbackListeners(ValidatorFactory validatorFactory, ActivationContext activationContext) { final Set modes = activationContext.getValidationModes(); - if ( ! ( modes.contains( ValidationMode.CALLBACK ) || modes.contains( ValidationMode.AUTO ) ) ) { + if ( !modes.contains( ValidationMode.CALLBACK ) && !modes.contains( ValidationMode.AUTO ) ) { return; } - final ConfigurationService cfgService = activationContext.getServiceRegistry().requireService( ConfigurationService.class ); - final ClassLoaderService classLoaderService = activationContext.getServiceRegistry().requireService( ClassLoaderService.class ); + final SessionFactoryServiceRegistry serviceRegistry = activationContext.getServiceRegistry(); + final ConfigurationService cfgService = serviceRegistry.requireService( ConfigurationService.class ); + final ClassLoaderService classLoaderService = serviceRegistry.requireService( ClassLoaderService.class ); + final EventListenerRegistry listenerRegistry = serviceRegistry.requireService( EventListenerRegistry.class ); // de-activate not-null tracking at the core level when Bean Validation is present unless the user explicitly // asks for it - if ( cfgService.getSettings().get( Environment.CHECK_NULLABILITY ) == null ) { + if ( cfgService.getSettings().get( CHECK_NULLABILITY ) == null ) { activationContext.getSessionFactory().getSessionFactoryOptions().setCheckNullability( false ); } - final BeanValidationEventListener listener = new BeanValidationEventListener( - validatorFactory, - cfgService.getSettings(), - classLoaderService - ); - - final EventListenerRegistry listenerRegistry = activationContext.getServiceRegistry() - .requireService( EventListenerRegistry.class ); + final BeanValidationEventListener listener = + new BeanValidationEventListener( validatorFactory, cfgService.getSettings(), classLoaderService ); listenerRegistry.addDuplicationStrategy( DuplicationStrategyImpl.INSTANCE ); listenerRegistry.appendListeners( EventType.PRE_INSERT, listener ); listenerRegistry.appendListeners( EventType.PRE_UPDATE, listener ); listenerRegistry.appendListeners( EventType.PRE_DELETE, listener ); + listenerRegistry.appendListeners( EventType.PRE_UPSERT, listener ); listener.initialize( cfgService.getSettings(), classLoaderService ); } private static void applyRelationalConstraints(ValidatorFactory factory, ActivationContext activationContext) { - final ConfigurationService cfgService = activationContext.getServiceRegistry().requireService( ConfigurationService.class ); + final SessionFactoryServiceRegistry serviceRegistry = activationContext.getServiceRegistry(); + final ConfigurationService cfgService = serviceRegistry.requireService( ConfigurationService.class ); if ( !cfgService.getSetting( BeanValidationIntegrator.APPLY_CONSTRAINTS, StandardConverters.BOOLEAN, true ) ) { LOG.debug( "Skipping application of relational constraints from legacy Hibernate Validator" ); return; } final Set modes = activationContext.getValidationModes(); - if ( ! ( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO ) ) ) { + if ( !modes.contains( ValidationMode.DDL ) && !modes.contains( ValidationMode.AUTO ) ) { return; } @@ -145,32 +147,25 @@ class TypeSafeActivator { factory, activationContext.getMetadata().getEntityBindings(), cfgService.getSettings(), - activationContext.getServiceRegistry().requireService( JdbcServices.class ).getDialect(), - new ClassLoaderAccessImpl( - null, - activationContext.getServiceRegistry().getService( ClassLoaderService.class ) - ) + serviceRegistry.requireService( JdbcServices.class ).getDialect(), + new ClassLoaderAccessImpl( null, + serviceRegistry.getService( ClassLoaderService.class ) ) ); } - @SuppressWarnings( {"UnusedDeclaration"}) public static void applyRelationalConstraints( ValidatorFactory factory, Collection persistentClasses, Map settings, Dialect dialect, ClassLoaderAccess classLoaderAccess) { - Class[] groupsArray = GroupsPerOperation.buildGroupsForOperation( - GroupsPerOperation.Operation.DDL, - settings, - classLoaderAccess - ); - Set> groups = new HashSet<>( Arrays.asList( groupsArray ) ); + final Class[] groupsArray = + buildGroupsForOperation( GroupsPerOperation.Operation.DDL, settings, classLoaderAccess ); + final Set> groups = new HashSet<>( Arrays.asList( groupsArray ) ); for ( PersistentClass persistentClass : persistentClasses ) { final String className = persistentClass.getClassName(); - - if ( className == null || className.length() == 0 ) { + if ( className == null || className.isEmpty() ) { continue; } Class clazz; @@ -202,17 +197,21 @@ class TypeSafeActivator { //no bean level constraints can be applied, go to the properties for ( PropertyDescriptor propertyDesc : descriptor.getConstrainedProperties() ) { - Property property = findPropertyByName( persistentClass, prefix + propertyDesc.getPropertyName() ); - boolean hasNotNull; + final Property property = + findPropertyByName( persistentClass, prefix + propertyDesc.getPropertyName() ); if ( property != null ) { - hasNotNull = applyConstraints( - propertyDesc.getConstraintDescriptors(), property, propertyDesc, groups, activateNotNull, dialect + final boolean hasNotNull = applyConstraints( + propertyDesc.getConstraintDescriptors(), + property, + propertyDesc, + groups, + activateNotNull, + dialect ); if ( property.isComposite() && propertyDesc.isCascaded() ) { - Class componentClass = ( (Component) property.getValue() ).getComponentClass(); - + final Component component = (Component) property.getValue(); /* - * we can apply not null if the upper component let's us activate not null + * we can apply not null if the upper component lets us activate not null * and if the property is not null. * Otherwise, all sub columns should be left nullable */ @@ -220,7 +219,7 @@ class TypeSafeActivator { applyDDL( prefix + propertyDesc.getPropertyName() + ".", persistentClass, - componentClass, + component.getComponentClass(), factory, groups, canSetNotNullOnColumns, @@ -275,8 +274,8 @@ class TypeSafeActivator { private static void applyMin(Property property, ConstraintDescriptor descriptor, Dialect dialect) { if ( Min.class.equals( descriptor.getAnnotation().annotationType() ) ) { @SuppressWarnings("unchecked") - ConstraintDescriptor minConstraint = (ConstraintDescriptor) descriptor; - long min = minConstraint.getAnnotation().value(); + final ConstraintDescriptor minConstraint = (ConstraintDescriptor) descriptor; + final long min = minConstraint.getAnnotation().value(); for ( Selectable selectable : property.getSelectables() ) { if ( selectable instanceof Column ) { @@ -291,8 +290,8 @@ class TypeSafeActivator { private static void applyMax(Property property, ConstraintDescriptor descriptor, Dialect dialect) { if ( Max.class.equals( descriptor.getAnnotation().annotationType() ) ) { @SuppressWarnings("unchecked") - ConstraintDescriptor maxConstraint = (ConstraintDescriptor) descriptor; - long max = maxConstraint.getAnnotation().value(); + final ConstraintDescriptor maxConstraint = (ConstraintDescriptor) descriptor; + final long max = maxConstraint.getAnnotation().value(); for ( Selectable selectable : property.getSelectables() ) { if ( selectable instanceof Column ) { @@ -318,9 +317,10 @@ class TypeSafeActivator { private static boolean applyNotNull(Property property, ConstraintDescriptor descriptor) { boolean hasNotNull = false; // NotNull, NotEmpty, and NotBlank annotation add not-null on column - if ( NotNull.class.equals( descriptor.getAnnotation().annotationType()) - || NotEmpty.class.equals( descriptor.getAnnotation().annotationType()) - || NotBlank.class.equals( descriptor.getAnnotation().annotationType())) { + final Class annotationType = descriptor.getAnnotation().annotationType(); + if ( NotNull.class.equals(annotationType) + || NotEmpty.class.equals(annotationType) + || NotBlank.class.equals(annotationType)) { // single table inheritance should not be forced to null due to shared state if ( !( property.getPersistentClass() instanceof SingleTableSubclass ) ) { //composite should not add not-null on all columns @@ -406,7 +406,7 @@ class TypeSafeActivator { String idName = idProperty != null ? idProperty.getName() : null; try { if ( propertyName == null - || propertyName.length() == 0 + || propertyName.isEmpty() || propertyName.equals( idName ) ) { //default to id property = idProperty; @@ -488,11 +488,9 @@ class TypeSafeActivator { private static ValidatorFactory resolveProvidedFactory(SessionFactoryOptions options) { final Object validatorFactoryReference = options.getValidatorFactoryReference(); - if ( validatorFactoryReference == null ) { return null; } - try { return (ValidatorFactory) validatorFactoryReference; } @@ -511,7 +509,7 @@ class TypeSafeActivator { private static ValidatorFactory resolveProvidedFactory(ConfigurationService cfgService) { return cfgService.getSetting( - AvailableSettings.JPA_VALIDATION_FACTORY, + JPA_VALIDATION_FACTORY, value -> { try { return (ValidatorFactory) value; @@ -521,7 +519,7 @@ class TypeSafeActivator { String.format( Locale.ENGLISH, "ValidatorFactory reference (provided via `%s` setting) was not castable to %s : %s", - AvailableSettings.JPA_VALIDATION_FACTORY, + JPA_VALIDATION_FACTORY, ValidatorFactory.class.getName(), value.getClass().getName() ) @@ -529,7 +527,7 @@ class TypeSafeActivator { } }, cfgService.getSetting( - AvailableSettings.JAKARTA_VALIDATION_FACTORY, + JAKARTA_VALIDATION_FACTORY, value -> { try { return (ValidatorFactory) value; @@ -539,7 +537,7 @@ class TypeSafeActivator { String.format( Locale.ENGLISH, "ValidatorFactory reference (provided via `%s` setting) was not castable to %s : %s", - AvailableSettings.JAKARTA_VALIDATION_FACTORY, + JAKARTA_VALIDATION_FACTORY, ValidatorFactory.class.getName(), value.getClass().getName() ) diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java index c41d4c5378..a349e54511 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java @@ -10,14 +10,12 @@ import java.util.Set; import org.hibernate.HibernateException; import org.hibernate.action.internal.CollectionAction; -import org.hibernate.action.spi.AfterTransactionCompletionProcess; import org.hibernate.boot.Metadata; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventType; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java index a627c523ca..1659383e56 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java @@ -22,7 +22,6 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Collection; -import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.Nullable; /** diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index d79b0d9772..ed332e027c 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -115,18 +115,19 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi } final EventSource session = event.getSession(); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final ActionQueue actionQueue = session.getActionQueue(); LOG.debugf( "Flushed: %s insertions, %s updates, %s deletions to %s objects", - session.getActionQueue().numberOfInsertions(), - session.getActionQueue().numberOfUpdates(), - session.getActionQueue().numberOfDeletions(), + actionQueue.numberOfInsertions(), + actionQueue.numberOfUpdates(), + actionQueue.numberOfDeletions(), persistenceContext.getNumberOfManagedEntities() ); LOG.debugf( "Flushed: %s (re)creations, %s updates, %s removals to %s collections", - session.getActionQueue().numberOfCollectionCreations(), - session.getActionQueue().numberOfCollectionUpdates(), - session.getActionQueue().numberOfCollectionRemovals(), + actionQueue.numberOfCollectionCreations(), + actionQueue.numberOfCollectionUpdates(), + actionQueue.numberOfCollectionRemovals(), persistenceContext.getCollectionEntriesSize() ); new EntityPrinter( session.getFactory() ).toString( @@ -205,8 +206,8 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi LOG.trace( "Flushing entities and processing referenced collections" ); final EventSource source = event.getSession(); - final EventListenerGroup flushListeners = source.getFactory() - .getFastSessionServices().eventListenerGroup_FLUSH_ENTITY; + final EventListenerGroup flushListeners = + event.getFactory().getFastSessionServices().eventListenerGroup_FLUSH_ENTITY; // Among other things, updateReachables() will recursively load all // collections that are moving roles. This might cause entities to diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractReassociateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractReassociateEventListener.java index 3271aec3f0..f1f8b9625c 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractReassociateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractReassociateEventListener.java @@ -47,7 +47,7 @@ public abstract class AbstractReassociateEventListener { if ( log.isTraceEnabled() ) { log.tracev( "Reassociating transient instance: {0}", - MessageHelper.infoString( persister, id, event.getSession().getFactory() ) + MessageHelper.infoString( persister, id, event.getFactory() ) ); } 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 2b256b47e3..840fd26e90 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 @@ -20,7 +20,6 @@ import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryExtraState; import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionImplementor; 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 ac07226a41..27e0ac503b 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 @@ -104,7 +104,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback if ( lazyInitializer != null ) { if ( lazyInitializer.isUninitialized() ) { final EventSource source = event.getSession(); - final EntityPersister persister = source.getFactory().getMappingMetamodel() + final EntityPersister persister = event.getFactory().getMappingMetamodel() .findEntityDescriptor( lazyInitializer.getEntityName() ); final Object id = lazyInitializer.getIdentifier(); final EntityKey key = source.generateEntityKey( id, persister ); 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 edc8646654..59d2739ef5 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 @@ -223,10 +223,9 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener final Object entity = event.getEntity(); processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); processIfManagedEntity( entity, DefaultFlushEntityEventListener::useTracker ); - final EventSource source = event.getSession(); - source.getFactory() + event.getFactory() .getCustomEntityDirtinessStrategy() - .resetDirty( entity, entry.getPersister(), source ); + .resetDirty( entity, entry.getPersister(), event.getSession() ); return false; } } @@ -247,7 +246,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener final EntityPersister persister = entry.getPersister(); final Object[] values = event.getPropertyValues(); - logScheduleUpdate( entry, session, status, persister ); + logScheduleUpdate( entry, event.getFactory(), status, persister ); final boolean intercepted = !entry.isBeingReplicated() && handleInterception( event ); @@ -299,32 +298,32 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener } } - private static void logScheduleUpdate(EntityEntry entry, EventSource session, Status status, EntityPersister persister) { + private static void logScheduleUpdate(EntityEntry entry, SessionFactoryImplementor factory, Status status, EntityPersister persister) { if ( LOG.isTraceEnabled() ) { if ( status == Status.DELETED ) { if ( !persister.isMutable() ) { LOG.tracev( "Updating immutable, deleted entity: {0}", - MessageHelper.infoString(persister, entry.getId(), session.getFactory() ) + MessageHelper.infoString(persister, entry.getId(), factory) ); } else if ( !entry.isModifiableEntity() ) { LOG.tracev( "Updating non-modifiable, deleted entity: {0}", - MessageHelper.infoString(persister, entry.getId(), session.getFactory() ) + MessageHelper.infoString(persister, entry.getId(), factory) ); } else { LOG.tracev( "Updating deleted entity: {0}", - MessageHelper.infoString(persister, entry.getId(), session.getFactory() ) + MessageHelper.infoString(persister, entry.getId(), factory) ); } } else { LOG.tracev( "Updating entity: {0}", - MessageHelper.infoString(persister, entry.getId(), session.getFactory() ) + MessageHelper.infoString(persister, entry.getId(), factory) ); } } @@ -352,7 +351,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener if ( entry.getStatus() != Status.DELETED ) { if ( callbackRegistry.preUpdate( entity ) ) { - isDirty = copyState( entity, persister.getPropertyTypes(), values, session.getFactory() ); + isDirty = copyState( entity, persister.getPropertyTypes(), values, event.getFactory() ); } } @@ -593,10 +592,9 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener } } } - final EventSource session = event.getSession(); final DirtyCheckContextImpl context = new DirtyCheckContextImpl(); - session.getFactory().getCustomEntityDirtinessStrategy() - .findDirty( event.getEntity(), event.getEntityEntry().getPersister(), session, context ); + event.getFactory().getCustomEntityDirtinessStrategy() + .findDirty( event.getEntity(), event.getEntityEntry().getPersister(), event.getSession(), context ); return context.found; } 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 8b9733d854..a4038c9b97 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 @@ -85,21 +85,19 @@ public class DefaultLoadEventListener implements LoadEventListener { protected EntityPersister getPersister(final LoadEvent event) { final Object instanceToLoad = event.getInstanceToLoad(); - final EventSource source = event.getSession(); if ( instanceToLoad != null ) { //the load() which takes an entity does not pass an entityName event.setEntityClassName( instanceToLoad.getClass().getName() ); - return source.getEntityPersister( null, instanceToLoad ); + return event.getSession().getEntityPersister( null, instanceToLoad ); } else { - return source.getFactory().getMappingMetamodel().getEntityDescriptor( event.getEntityClassName() ); + return event.getFactory().getMappingMetamodel().getEntityDescriptor( event.getEntityClassName() ); } } private void doOnLoad(EntityPersister persister, LoadEvent event, LoadType loadType) { try { - final EventSource session = event.getSession(); - final EntityKey keyToLoad = session.generateEntityKey( event.getEntityId(), persister ); + final EntityKey keyToLoad = event.getSession().generateEntityKey( event.getEntityId(), persister ); if ( loadType.isNakedEntityReturned() ) { //do not return a proxy! //(this option indicates we are initializing a proxy) @@ -193,22 +191,22 @@ public class DefaultLoadEventListener implements LoadEventListener { * @return The loaded entity. */ private Object load(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) { - final EventSource session = event.getSession(); if ( event.getInstanceToLoad() != null ) { + final EventSource session = event.getSession(); if ( session.getPersistenceContextInternal().getEntry( event.getInstanceToLoad() ) != null ) { throw new PersistentObjectException( "attempted to load into an instance that was already associated with the session: " - + infoString( persister, event.getEntityId(), session.getFactory() ) + + infoString( persister, event.getEntityId(), event.getFactory() ) ); } - persister.setIdentifier( event.getInstanceToLoad(), event.getEntityId(), session); + persister.setIdentifier( event.getInstanceToLoad(), event.getEntityId(), session ); } final Object entity = doLoad( event, persister, keyToLoad, options ); boolean isOptionalInstance = event.getInstanceToLoad() != null; if ( entity == null && ( !options.isAllowNulls() || isOptionalInstance ) ) { - session.getFactory().getEntityNotFoundDelegate() + event.getFactory().getEntityNotFoundDelegate() .handleEntityNotFound( event.getEntityClassName(), event.getEntityId() ); } else if ( isOptionalInstance && entity != event.getInstanceToLoad() ) { @@ -397,7 +395,7 @@ public class DefaultLoadEventListener implements LoadEventListener { else { if ( options != LoadEventListener.INTERNAL_LOAD_NULLABLE ) { // throw an appropriate exception - event.getSession().getFactory().getEntityNotFoundDelegate() + event.getFactory().getEntityNotFoundDelegate() .handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier() ); } // Otherwise, if it's INTERNAL_LOAD_NULLABLE, the proxy is @@ -448,8 +446,9 @@ public class DefaultLoadEventListener implements LoadEventListener { private static Object createProxy(LoadEvent event, EntityPersister persister, EntityKey keyToLoad) { // return new uninitialized proxy - final Object proxy = persister.createProxy( event.getEntityId(), event.getSession() ); - PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal(); + final EventSource session = event.getSession(); + final Object proxy = persister.createProxy( event.getEntityId(), session ); + PersistenceContext persistenceContext = session.getPersistenceContextInternal(); persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad ); persistenceContext.addProxy( keyToLoad, proxy ); return proxy; @@ -477,7 +476,7 @@ public class DefaultLoadEventListener implements LoadEventListener { ck = cache.generateCacheKey( event.getEntityId(), persister, - source.getFactory(), + event.getFactory(), source.getTenantIdentifier() ); lock = cache.lockItem( source, ck, null ); @@ -520,7 +519,7 @@ public class DefaultLoadEventListener implements LoadEventListener { if ( LOG.isTraceEnabled() ) { LOG.tracev( "Attempting to resolve: {0}", - infoString( persister, event.getEntityId(), session.getFactory() ) + infoString( persister, event.getEntityId(), event.getFactory() ) ); } @@ -575,7 +574,7 @@ public class DefaultLoadEventListener implements LoadEventListener { if ( LOG.isTraceEnabled() ) { LOG.tracev( "Resolved object in second-level cache: {0}", - infoString( persister, event.getEntityId(), event.getSession().getFactory() ) + infoString( persister, event.getEntityId(), event.getFactory() ) ); } return entity; @@ -584,7 +583,7 @@ public class DefaultLoadEventListener implements LoadEventListener { if ( LOG.isTraceEnabled() ) { LOG.tracev( "Object not resolved in any cache: {0}", - infoString( persister, event.getEntityId(), event.getSession().getFactory() ) + infoString( persister, event.getEntityId(), event.getFactory() ) ); } return loadFromDatasource( event, persister ); @@ -620,7 +619,7 @@ public class DefaultLoadEventListener implements LoadEventListener { entity = lazyInitializer.getImplementation(); } - final StatisticsImplementor statistics = event.getSession().getFactory().getStatistics(); + final StatisticsImplementor statistics = event.getFactory().getStatistics(); if ( event.isAssociationFetch() && statistics.isStatisticsEnabled() ) { statistics.fetchEntity( event.getEntityClassName() ); } 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 2abf27aef5..ff655326c6 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 @@ -216,16 +216,15 @@ public class DefaultMergeEventListener entityIsPersistent( event, copiedAlready ); break; default: //DELETED - if ( event.getSession().getPersistenceContext().getEntry( entity ) == null ) { - assert event.getSession().getPersistenceContext().containsDeletedUnloadedEntityKey( - event.getSession().generateEntityKey( - event.getSession() - .getEntityPersister( event.getEntityName(), entity ) + if ( persistenceContext.getEntry( entity ) == null ) { + assert persistenceContext.containsDeletedUnloadedEntityKey( + source.generateEntityKey( + source.getEntityPersister( event.getEntityName(), entity ) .getIdentifier( entity, event.getSession() ), - event.getSession().getEntityPersister( event.getEntityName(), entity ) + source.getEntityPersister( event.getEntityName(), entity ) ) ); - event.getSession().getActionQueue().unScheduleUnloadedDeletion( entity ); + source.getActionQueue().unScheduleUnloadedDeletion( entity ); entityIsDetached(event, copiedId, originalId, copiedAlready); break; } @@ -395,7 +394,7 @@ public class DefaultMergeEventListener } final Object clonedIdentifier; if ( copiedId == null ) { - clonedIdentifier = persister.getIdentifierType().deepCopy( originalId, source.getFactory() ); + clonedIdentifier = persister.getIdentifierType().deepCopy( originalId, event.getFactory() ); } else { clonedIdentifier = copiedId; diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java index b2134d3886..d051f57bf7 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java @@ -120,7 +120,7 @@ public class DefaultPersistEventListener // entity state again. // NOTE: entityEntry must be null to get here, so we cannot use any of its values - final EntityPersister persister = source.getFactory().getMappingMetamodel() + final EntityPersister persister = event.getFactory().getMappingMetamodel() .getEntityDescriptor( entityName ); if ( persister.getGenerator() instanceof ForeignGenerator ) { if ( LOG.isDebugEnabled() && persister.getIdentifier( entity, source ) != null ) { @@ -183,7 +183,7 @@ public class DefaultPersistEventListener if ( LOG.isTraceEnabled() ) { LOG.tracef( "un-scheduling entity deletion [%s]", - MessageHelper.infoString( persister, persister.getIdentifier( entity, source ), source.getFactory() ) + MessageHelper.infoString( persister, persister.getIdentifier( entity, source ), event.getFactory() ) ); } if ( createCache.add( entity ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java index 9aa230a4c0..ced2a98b38 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java @@ -134,7 +134,7 @@ public class DefaultRefreshEventListener implements RefreshEventListener { if ( LOG.isTraceEnabled() ) { LOG.tracev( "Refreshing transient {0}", - infoString( persister, id, source.getFactory() ) + infoString( persister, id, event.getFactory() ) ); } if ( persistenceContext.getEntry( source.generateEntityKey( id, persister ) ) != null ) { @@ -145,7 +145,7 @@ public class DefaultRefreshEventListener implements RefreshEventListener { if ( LOG.isTraceEnabled() ) { LOG.tracev( "Refreshing ", - infoString( entry.getPersister(), entry.getId(), source.getFactory() ) + infoString( entry.getPersister(), entry.getId(), event.getFactory() ) ); } if ( !entry.isExistsInDatabase() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultReplicateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultReplicateEventListener.java index 0a2b4a6ccc..83b0abea5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultReplicateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultReplicateEventListener.java @@ -85,11 +85,8 @@ public class DefaultReplicateEventListener if ( oldVersion != null ) { if ( LOG.isTraceEnabled() ) { LOG.tracev( - "Found existing row for {0}", MessageHelper.infoString( - persister, - id, - source.getFactory() - ) + "Found existing row for {0}", + MessageHelper.infoString( persister, id, event.getFactory() ) ); } @@ -119,7 +116,7 @@ public class DefaultReplicateEventListener if ( LOG.isTraceEnabled() ) { LOG.tracev( "No existing row, replicating new instance {0}", - MessageHelper.infoString( persister, id, source.getFactory() ) + MessageHelper.infoString( persister, id, event.getFactory() ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java index 3643610dbf..fa0bef1746 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java @@ -14,7 +14,6 @@ import org.hibernate.event.spi.ResolveNaturalIdEventListener; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.pretty.MessageHelper; import org.hibernate.stat.spi.StatisticsImplementor; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -106,7 +105,7 @@ public class DefaultResolveNaturalIdEventListener protected Object loadFromDatasource(ResolveNaturalIdEvent event) { final EventSource session = event.getSession(); final EntityPersister entityPersister = event.getEntityPersister(); - final StatisticsImplementor statistics = session.getFactory().getStatistics(); + final StatisticsImplementor statistics = event.getFactory().getStatistics(); final boolean statisticsEnabled = statistics.isStatisticsEnabled(); final long startTime = statisticsEnabled ? System.nanoTime() : 0; diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultSaveOrUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultSaveOrUpdateEventListener.java index c553af0fbb..7cb0a5dc84 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultSaveOrUpdateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultSaveOrUpdateEventListener.java @@ -120,7 +120,7 @@ public class DefaultSaveOrUpdateEventListener throw new AssertionFailure( "entity was deleted" ); } - final SessionFactoryImplementor factory = event.getSession().getFactory(); + final SessionFactoryImplementor factory = event.getFactory(); final Object requestedId = event.getRequestedId(); final Object savedId; @@ -250,7 +250,7 @@ public class DefaultSaveOrUpdateEventListener } LOG.tracev( "Updating {0}", - MessageHelper.infoString( persister, event.getRequestedId(), source.getFactory() ) + MessageHelper.infoString( persister, event.getRequestedId(), event.getFactory() ) ); } @@ -293,7 +293,7 @@ public class DefaultSaveOrUpdateEventListener if ( LOG.isTraceEnabled() ) { LOG.tracev( "Updating {0}", - MessageHelper.infoString( persister, event.getRequestedId(), source.getFactory() ) + MessageHelper.infoString( persister, event.getRequestedId(), event.getFactory() ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpdateEventListenerStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpdateEventListenerStandardImpl.java index c85fabe4f1..09c71e7b5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpdateEventListenerStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpdateEventListenerStandardImpl.java @@ -6,7 +6,6 @@ */ package org.hibernate.event.internal; -import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.PostUpdateEvent; @@ -33,9 +32,9 @@ public class PostUpdateEventListenerStandardImpl implements PostUpdateEventListe } private void handlePostUpdate(Object entity, EventSource source) { - EntityEntry entry = source.getPersistenceContextInternal().getEntry( entity ); // mimic the preUpdate filter - if ( Status.DELETED != entry.getStatus() ) { + if ( source == null // it must be a StatelessSession + || source.getPersistenceContextInternal().getEntry(entity).getStatus() != Status.DELETED ) { callbackRegistry.postUpdate(entity); } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpsertEventListenerStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpsertEventListenerStandardImpl.java new file mode 100644 index 0000000000..664ef602e4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/PostUpsertEventListenerStandardImpl.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.event.internal; + +import org.hibernate.event.spi.EventSource; +import org.hibernate.event.spi.PostUpsertEvent; +import org.hibernate.event.spi.PostUpsertEventListener; +import org.hibernate.jpa.event.spi.CallbackRegistry; +import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; +import org.hibernate.persister.entity.EntityPersister; + +/** + * This is just a stub, since we don't yet have a {@code @PostUpsert} callback + * + * @author Gavin King + */ +public class PostUpsertEventListenerStandardImpl implements PostUpsertEventListener, CallbackRegistryConsumer { + private CallbackRegistry callbackRegistry; + + @Override + public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { + this.callbackRegistry = callbackRegistry; + } + + @Override + public void onPostUpsert(PostUpsertEvent event) { + handlePostUpsert( event.getEntity(), event.getSession() ); + } + + private void handlePostUpsert(Object entity, EventSource source) { +// // mimic the preUpdate filter +// if ( source == null // it must be a StatelessSession +// || source.getPersistenceContextInternal().getEntry(entity).getStatus() != Status.DELETED ) { +// callbackRegistry.postUpdate(entity); +// } + } + + @Override + public boolean requiresPostCommitHandling(EntityPersister persister) { + return false; //callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_UPDATE ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java index e44b094ffd..b86843781f 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java @@ -38,6 +38,7 @@ import org.hibernate.event.internal.DefaultUpdateEventListener; import org.hibernate.event.internal.PostDeleteEventListenerStandardImpl; import org.hibernate.event.internal.PostInsertEventListenerStandardImpl; import org.hibernate.event.internal.PostUpdateEventListenerStandardImpl; +import org.hibernate.event.internal.PostUpsertEventListenerStandardImpl; import org.hibernate.event.service.spi.DuplicationStrategy; import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistrationException; @@ -68,6 +69,7 @@ import static org.hibernate.event.spi.EventType.POST_DELETE; import static org.hibernate.event.spi.EventType.POST_INSERT; import static org.hibernate.event.spi.EventType.POST_LOAD; import static org.hibernate.event.spi.EventType.POST_UPDATE; +import static org.hibernate.event.spi.EventType.POST_UPSERT; import static org.hibernate.event.spi.EventType.PRE_COLLECTION_RECREATE; import static org.hibernate.event.spi.EventType.PRE_COLLECTION_REMOVE; import static org.hibernate.event.spi.EventType.PRE_COLLECTION_UPDATE; @@ -75,6 +77,7 @@ import static org.hibernate.event.spi.EventType.PRE_DELETE; import static org.hibernate.event.spi.EventType.PRE_INSERT; import static org.hibernate.event.spi.EventType.PRE_LOAD; import static org.hibernate.event.spi.EventType.PRE_UPDATE; +import static org.hibernate.event.spi.EventType.PRE_UPSERT; import static org.hibernate.event.spi.EventType.REFRESH; import static org.hibernate.event.spi.EventType.REPLICATE; import static org.hibernate.event.spi.EventType.RESOLVE_NATURAL_ID; @@ -97,14 +100,14 @@ public class EventListenerRegistryImpl implements EventListenerRegistry { this.eventListeners = eventListeners; } - @SuppressWarnings("unchecked") public EventListenerGroup getEventListenerGroup(EventType eventType) { if ( eventListeners.length < eventType.ordinal() + 1 ) { - // eventTpe is a custom EventType that has not been registered. + // eventType is a custom EventType that has not been registered. // registeredEventListeners array was not allocated enough space to // accommodate it. throw new HibernateException( "Unable to find listeners for type [" + eventType.eventName() + "]" ); } + @SuppressWarnings("unchecked") final EventListenerGroup listeners = eventListeners[ eventType.ordinal() ]; if ( listeners == null ) { throw new HibernateException( "Unable to find listeners for type [" + eventType.eventName() + "]" ); @@ -278,6 +281,9 @@ public class EventListenerRegistryImpl implements EventListenerRegistry { // pre-update listeners prepareListeners( PRE_UPDATE ); + // pre-update listeners + prepareListeners( PRE_UPSERT ); + // post-collection-recreate listeners prepareListeners( POST_COLLECTION_RECREATE ); @@ -308,6 +314,9 @@ public class EventListenerRegistryImpl implements EventListenerRegistry { // post-update listeners prepareListeners( POST_UPDATE, new PostUpdateEventListenerStandardImpl() ); + // post-upsert listeners + prepareListeners( POST_UPSERT, new PostUpsertEventListenerStandardImpl() ); + // update listeners prepareListeners( UPDATE, new DefaultUpdateEventListener() ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractEvent.java index 2272e5adb2..a070b10711 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractEvent.java @@ -6,6 +6,8 @@ */ package org.hibernate.event.spi; +import org.hibernate.engine.spi.SessionFactoryImplementor; + import java.io.Serializable; /** @@ -36,4 +38,7 @@ public abstract class AbstractEvent implements Serializable { return session; } + public SessionFactoryImplementor getFactory() { + return session.getFactory(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java index 9faf21bea9..6d4f931d3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java @@ -6,6 +6,7 @@ */ package org.hibernate.event.spi; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.persister.entity.EntityPersister; /** @@ -64,5 +65,15 @@ public abstract class AbstractPreDatabaseOperationEvent extends AbstractEvent { public EntityPersister getPersister() { return persister; } + + /** + * The factory which owns the persister for the entity. + * + * @return The factory + */ + @Override + public SessionFactoryImplementor getFactory() { + return persister.getFactory(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/EventType.java b/hibernate-core/src/main/java/org/hibernate/event/spi/EventType.java index d847ec835c..32f6871b52 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/EventType.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/EventType.java @@ -61,11 +61,13 @@ public final class EventType { public static final EventType PRE_DELETE = create( "pre-delete", PreDeleteEventListener.class ); public static final EventType PRE_UPDATE = create( "pre-update", PreUpdateEventListener.class ); public static final EventType PRE_INSERT = create( "pre-insert", PreInsertEventListener.class ); + public static final EventType PRE_UPSERT = create( "pre-upsert", PreUpsertEventListener.class ); public static final EventType POST_LOAD = create( "post-load", PostLoadEventListener.class ); public static final EventType POST_DELETE = create( "post-delete", PostDeleteEventListener.class ); public static final EventType POST_UPDATE = create( "post-update", PostUpdateEventListener.class ); public static final EventType POST_INSERT = create( "post-insert", PostInsertEventListener.class ); + public static final EventType POST_UPSERT = create( "post-upsert", PostUpsertEventListener.class ); public static final EventType POST_COMMIT_DELETE = create( "post-commit-delete", PostDeleteEventListener.class ); public static final EventType POST_COMMIT_UPDATE = create( "post-commit-update", PostUpdateEventListener.class ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEvent.java index e6b459a1df..3d458d1756 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostDeleteEvent.java @@ -6,6 +6,7 @@ */ package org.hibernate.event.spi; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.persister.entity.EntityPersister; /** @@ -40,6 +41,11 @@ public class PostDeleteEvent extends AbstractEvent { return persister; } + @Override + public SessionFactoryImplementor getFactory() { + return persister.getFactory(); + } + public Object getEntity() { return entity; } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEvent.java index 38c95d6880..35d8f0a8a4 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostInsertEvent.java @@ -6,6 +6,7 @@ */ package org.hibernate.event.spi; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.persister.entity.EntityPersister; /** @@ -43,6 +44,12 @@ public class PostInsertEvent extends AbstractEvent { public EntityPersister getPersister() { return persister; } + + @Override + public SessionFactoryImplementor getFactory() { + return persister.getFactory(); + } + public Object[] getState() { return state; } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEvent.java index 75511f714b..361afa2068 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpdateEvent.java @@ -6,6 +6,7 @@ */ package org.hibernate.event.spi; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.persister.entity.EntityPersister; /** @@ -56,6 +57,11 @@ public class PostUpdateEvent extends AbstractEvent { return persister; } + @Override + public SessionFactoryImplementor getFactory() { + return persister.getFactory(); + } + public Object[] getState() { return state; } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpsertEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpsertEvent.java new file mode 100644 index 0000000000..9600bc2e54 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpsertEvent.java @@ -0,0 +1,65 @@ +/* + * 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.event.spi; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.entity.EntityPersister; + +/** + * Occurs after the datastore is updated + * + * @author Gavin King + */ +public class PostUpsertEvent extends AbstractEvent { + private Object entity; + private EntityPersister persister; + private Object[] state; + private Object id; + //list of dirty properties as computed by Hibernate during a FlushEntityEvent + private final int[] dirtyProperties; + + public PostUpsertEvent( + Object entity, + Object id, + Object[] state, + int[] dirtyProperties, + EntityPersister persister, + EventSource source + ) { + super(source); + this.entity = entity; + this.id = id; + this.state = state; + this.dirtyProperties = dirtyProperties; + this.persister = persister; + } + + public Object getEntity() { + return entity; + } + + public Object getId() { + return id; + } + + public EntityPersister getPersister() { + return persister; + } + + @Override + public SessionFactoryImplementor getFactory() { + return persister.getFactory(); + } + + public Object[] getState() { + return state; + } + + public int[] getDirtyProperties() { + return dirtyProperties; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpsertEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpsertEventListener.java new file mode 100644 index 0000000000..6b1cf82a82 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PostUpsertEventListener.java @@ -0,0 +1,16 @@ +/* + * 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.event.spi; + +/** + * Called after updating the datastore + * + * @author Gavin King + */ +public interface PostUpsertEventListener extends PostActionEventListener { + void onPostUpsert(PostUpsertEvent event); +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreDeleteEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreDeleteEvent.java index 7eabf3fbd8..53094b780e 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreDeleteEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreDeleteEvent.java @@ -22,7 +22,7 @@ public class PreDeleteEvent extends AbstractPreDatabaseOperationEvent { /** * Constructs an event containing the pertinent information. - * @param entity The entity to be deleted. + * @param entity The entity to be deleted. * @param id The id to use in the deletion. * @param deletedState The entity's state at deletion time. * @param persister The entity's persister. diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreInsertEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreInsertEvent.java index ebe3f8a94e..e67b1b88b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreInsertEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreInsertEvent.java @@ -16,11 +16,11 @@ import org.hibernate.persister.entity.EntityPersister; * @author Steve Ebersole */ public class PreInsertEvent extends AbstractPreDatabaseOperationEvent { - private Object[] state; + private final Object[] state; /** * Constructs an event containing the pertinent information. - * @param entity The entity to be inserted. + * @param entity The entity to be inserted. * @param id The id to use in the insertion. * @param state The state to be inserted. * @param persister The entity's persister. diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpdateEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpdateEvent.java index b01ccde722..940fd90bdc 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpdateEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpdateEvent.java @@ -16,16 +16,16 @@ import org.hibernate.persister.entity.EntityPersister; * @author Steve Ebersole */ public class PreUpdateEvent extends AbstractPreDatabaseOperationEvent { - private Object[] state; - private Object[] oldState; + private final Object[] state; + private final Object[] oldState; /** * Constructs an event containing the pertinent information. - * @param entity The entity to be updated. + * @param entity The entity to be updated. * @param id The id of the entity to use for updating. * @param state The state to be updated. * @param oldState The state of the entity at the time it was loaded from -* the database. + * the database. * @param persister The entity's persister. * @param source The session from which the event originated. */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpsertEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpsertEvent.java new file mode 100644 index 0000000000..5229304653 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpsertEvent.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.event.spi; + +import org.hibernate.persister.entity.EntityPersister; + +/** + * Represents a pre-upsert event, which occurs just prior to + * performing the upsert of an entity in the database. + * + * @author Gavin King + */ +public class PreUpsertEvent extends AbstractPreDatabaseOperationEvent { + private final Object[] state; + + /** + * Constructs an event containing the pertinent information. + * @param entity The entity to be updated. + * @param id The id of the entity to use for updating. + * @param state The state to be updated. + * @param persister The entity's persister. + * @param source The session from which the event originated. + */ + public PreUpsertEvent( + Object entity, + Object id, + Object[] state, + EntityPersister persister, + EventSource source) { + super( source, entity, id, persister ); + this.state = state; + } + + /** + * Retrieves the state to be used in the upsert. + * + * @return The current state. + */ + public Object[] getState() { + return state; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpsertEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpsertEventListener.java new file mode 100644 index 0000000000..e8871fbb28 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/PreUpsertEventListener.java @@ -0,0 +1,19 @@ +/* + * 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.event.spi; + +/** + * Called before updating the datastore + * + * @author Gavin King + */ +public interface PreUpsertEventListener { + /** + * Return true if the operation should be vetoed + */ + boolean onPreUpsert(PreUpsertEvent event); +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index 1edbf53a8b..fb60ca74f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -26,7 +26,6 @@ import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.internal.EmptyEventManager; -import org.hibernate.event.spi.EventManager; import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.AutoFlushEventListener; @@ -34,6 +33,7 @@ import org.hibernate.event.spi.ClearEventListener; import org.hibernate.event.spi.DeleteEventListener; import org.hibernate.event.spi.DirtyCheckEventListener; import org.hibernate.event.spi.EntityCopyObserverFactory; +import org.hibernate.event.spi.EventManager; import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.EvictEventListener; import org.hibernate.event.spi.FlushEntityEventListener; @@ -51,6 +51,7 @@ import org.hibernate.event.spi.PostInsertEventListener; import org.hibernate.event.spi.PostLoadEvent; import org.hibernate.event.spi.PostLoadEventListener; import org.hibernate.event.spi.PostUpdateEventListener; +import org.hibernate.event.spi.PostUpsertEventListener; import org.hibernate.event.spi.PreCollectionRecreateEventListener; import org.hibernate.event.spi.PreCollectionRemoveEventListener; import org.hibernate.event.spi.PreCollectionUpdateEventListener; @@ -58,6 +59,7 @@ import org.hibernate.event.spi.PreDeleteEventListener; import org.hibernate.event.spi.PreInsertEventListener; import org.hibernate.event.spi.PreLoadEventListener; import org.hibernate.event.spi.PreUpdateEventListener; +import org.hibernate.event.spi.PreUpsertEventListener; import org.hibernate.event.spi.RefreshEventListener; import org.hibernate.event.spi.ReplicateEventListener; import org.hibernate.event.spi.ResolveNaturalIdEventListener; @@ -140,6 +142,7 @@ public final class FastSessionServices { public final EventListenerGroup eventListenerGroup_POST_LOAD; //Frequently used by 2LC initialization: public final EventListenerGroup eventListenerGroup_POST_COMMIT_UPDATE; public final EventListenerGroup eventListenerGroup_POST_UPDATE; + public final EventListenerGroup eventListenerGroup_POST_UPSERT; public final EventListenerGroup eventListenerGroup_PRE_COLLECTION_RECREATE; public final EventListenerGroup eventListenerGroup_PRE_COLLECTION_REMOVE; public final EventListenerGroup eventListenerGroup_PRE_COLLECTION_UPDATE; @@ -147,6 +150,7 @@ public final class FastSessionServices { public final EventListenerGroup eventListenerGroup_PRE_INSERT; public final EventListenerGroup eventListenerGroup_PRE_LOAD; public final EventListenerGroup eventListenerGroup_PRE_UPDATE; + public final EventListenerGroup eventListenerGroup_PRE_UPSERT; public final EventListenerGroup eventListenerGroup_REFRESH; public final EventListenerGroup eventListenerGroup_REPLICATE; public final EventListenerGroup eventListenerGroup_RESOLVE_NATURAL_ID; @@ -220,6 +224,7 @@ public final class FastSessionServices { this.eventListenerGroup_POST_INSERT = listeners( eventListenerRegistry, EventType.POST_INSERT ); this.eventListenerGroup_POST_LOAD = listeners( eventListenerRegistry, EventType.POST_LOAD ); this.eventListenerGroup_POST_UPDATE = listeners( eventListenerRegistry, EventType.POST_UPDATE ); + this.eventListenerGroup_POST_UPSERT = listeners( eventListenerRegistry, EventType.POST_UPSERT ); this.eventListenerGroup_PRE_COLLECTION_RECREATE = listeners( eventListenerRegistry, EventType.PRE_COLLECTION_RECREATE ); this.eventListenerGroup_PRE_COLLECTION_REMOVE = listeners( eventListenerRegistry, EventType.PRE_COLLECTION_REMOVE ); this.eventListenerGroup_PRE_COLLECTION_UPDATE = listeners( eventListenerRegistry, EventType.PRE_COLLECTION_UPDATE ); @@ -227,6 +232,7 @@ public final class FastSessionServices { this.eventListenerGroup_PRE_INSERT = listeners( eventListenerRegistry, EventType.PRE_INSERT ); this.eventListenerGroup_PRE_LOAD = listeners( eventListenerRegistry, EventType.PRE_LOAD ); this.eventListenerGroup_PRE_UPDATE = listeners( eventListenerRegistry, EventType.PRE_UPDATE ); + this.eventListenerGroup_PRE_UPSERT = listeners( eventListenerRegistry, EventType.PRE_UPSERT ); this.eventListenerGroup_REFRESH = listeners( eventListenerRegistry, EventType.REFRESH ); this.eventListenerGroup_REPLICATE = listeners( eventListenerRegistry, EventType.REPLICATE ); this.eventListenerGroup_RESOLVE_NATURAL_ID = listeners( eventListenerRegistry, EventType.RESOLVE_NATURAL_ID ); 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 1ba31744be..7118572b3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -32,6 +32,22 @@ import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; +import org.hibernate.event.spi.PostDeleteEvent; +import org.hibernate.event.spi.PostDeleteEventListener; +import org.hibernate.event.spi.PostInsertEvent; +import org.hibernate.event.spi.PostInsertEventListener; +import org.hibernate.event.spi.PostUpdateEvent; +import org.hibernate.event.spi.PostUpdateEventListener; +import org.hibernate.event.spi.PostUpsertEvent; +import org.hibernate.event.spi.PostUpsertEventListener; +import org.hibernate.event.spi.PreDeleteEvent; +import org.hibernate.event.spi.PreDeleteEventListener; +import org.hibernate.event.spi.PreInsertEvent; +import org.hibernate.event.spi.PreInsertEventListener; +import org.hibernate.event.spi.PreUpdateEvent; +import org.hibernate.event.spi.PreUpdateEventListener; +import org.hibernate.event.spi.PreUpsertEvent; +import org.hibernate.event.spi.PreUpsertEventListener; import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.Generator; import org.hibernate.generator.values.GeneratedValues; @@ -114,15 +130,22 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen persister.setValues( entity, state ); } } + if ( firePreInsert(entity, id, state, persister) ) { + return id; + } persister.getInsertCoordinator().insert( entity, id, state, this ); } else { + if ( firePreInsert(entity, null, state, persister) ) { + return null; + } final GeneratedValues generatedValues = persister.getInsertCoordinator().insert( entity, state, this ); id = castNonNull( generatedValues ).getGeneratedValue( persister.getIdentifierMapping() ); } persister.setIdentifier( entity, id, this ); forEachOwnedCollection( entity, id, persister, (descriptor, collection) -> descriptor.recreate( collection, id, this) ); + firePostInsert(entity, id, state, persister); return id; } @@ -140,9 +163,12 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen final EntityPersister persister = getEntityPersister( entityName, entity ); final Object id = persister.getIdentifier( entity, this ); final Object version = persister.getVersion( entity ); - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> descriptor.remove(id, this) ); - persister.getDeleteCoordinator().delete( entity, id, version, this ); + if ( !firePreDelete(entity, id, persister) ) { + forEachOwnedCollection( entity, id, persister, + (descriptor, collection) -> descriptor.remove(id, this) ); + persister.getDeleteCoordinator().delete( entity, id, version, this ); + firePostDelete(entity, id, persister); + } } @@ -176,12 +202,15 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen else { oldVersion = null; } - persister.getUpdateCoordinator().update( entity, id, null, state, oldVersion, null, null, false, this ); - // TODO: can we do better here? - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> descriptor.remove(id, this) ); - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> descriptor.recreate( collection, id, this) ); + if ( !firePreUpdate(entity, id, state, persister) ) { + persister.getUpdateCoordinator().update( entity, id, null, state, oldVersion, null, null, false, this ); + // TODO: can we do better here? + forEachOwnedCollection( entity, id, persister, + (descriptor, collection) -> descriptor.remove(id, this) ); + forEachOwnedCollection( entity, id, persister, + (descriptor, collection) -> descriptor.recreate( collection, id, this) ); + firePostUpdate(entity, id, state, persister); + } } @Override @@ -190,13 +219,17 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen final EntityPersister persister = getEntityPersister( entityName, entity ); final Object id = idToUpsert( entity, persister ); final Object[] state = persister.getValues( entity ); - final Object oldVersion = versionToUpsert( entity, persister, state ); - persister.getMergeCoordinator().update( entity, id, null, state, oldVersion, null, null, false, this ); - // TODO: can we do better here? - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> descriptor.remove(id, this) ); - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> descriptor.recreate( collection, id, this) ); + if ( !firePreUpsert(entity, id, state, persister) ) { + final Object oldVersion = versionToUpsert( entity, persister, state ); + persister.getMergeCoordinator().update( entity, id, null, state, oldVersion, null, null, false, this ); + // TODO: need PreUpsert and PostUpsert events! + // TODO: can we do better here? + forEachOwnedCollection( entity, id, persister, + (descriptor, collection) -> descriptor.remove(id, this) ); + forEachOwnedCollection( entity, id, persister, + (descriptor, collection) -> descriptor.recreate( collection, id, this) ); + firePostUpsert(entity, id, state, persister); + } } private Object versionToUpsert(Object entity, EntityPersister persister, Object[] state) { @@ -239,6 +272,100 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen return id; } + // event processing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private boolean firePreInsert(Object entity, Object id, Object[] state, EntityPersister persister) { + if ( fastSessionServices.eventListenerGroup_PRE_INSERT.isEmpty() ) { + return false; + } + else { + boolean veto = false; + final PreInsertEvent event = new PreInsertEvent( entity, id, state, persister, null ); + for ( PreInsertEventListener listener : fastSessionServices.eventListenerGroup_PRE_INSERT.listeners() ) { + veto |= listener.onPreInsert( event ); + } + return veto; + } + } + + private boolean firePreUpdate(Object entity, Object id, Object[] state, EntityPersister persister) { + if ( fastSessionServices.eventListenerGroup_PRE_UPDATE.isEmpty() ) { + return false; + } + else { + boolean veto = false; + final PreUpdateEvent event = new PreUpdateEvent( entity, id, state, null, persister, null ); + for ( PreUpdateEventListener listener : fastSessionServices.eventListenerGroup_PRE_UPDATE.listeners() ) { + veto |= listener.onPreUpdate( event ); + } + return veto; + } + } + + private boolean firePreUpsert(Object entity, Object id, Object[] state, EntityPersister persister) { + if ( fastSessionServices.eventListenerGroup_PRE_UPSERT.isEmpty() ) { + return false; + } + else { + boolean veto = false; + final PreUpsertEvent event = new PreUpsertEvent( entity, id, state, persister, null ); + for ( PreUpsertEventListener listener : fastSessionServices.eventListenerGroup_PRE_UPSERT.listeners() ) { + veto |= listener.onPreUpsert( event ); + } + return veto; + } + } + + private boolean firePreDelete(Object entity, Object id, EntityPersister persister) { + if ( fastSessionServices.eventListenerGroup_PRE_DELETE.isEmpty() ) { + return false; + } + else { + boolean veto = false; + final PreDeleteEvent event = new PreDeleteEvent( entity, id, null, persister, null ); + for ( PreDeleteEventListener listener : fastSessionServices.eventListenerGroup_PRE_DELETE.listeners() ) { + veto |= listener.onPreDelete( event ); + } + return veto; + } + } + + private void firePostInsert(Object entity, Object id, Object[] state, EntityPersister persister) { + if ( !fastSessionServices.eventListenerGroup_POST_INSERT.isEmpty() ) { + final PostInsertEvent event = new PostInsertEvent( entity, id, state, persister, null ); + for ( PostInsertEventListener listener : fastSessionServices.eventListenerGroup_POST_INSERT.listeners() ) { + listener.onPostInsert( event ); + } + } + } + + private void firePostUpdate(Object entity, Object id, Object[] state, EntityPersister persister) { + if ( !fastSessionServices.eventListenerGroup_POST_UPDATE.isEmpty() ) { + final PostUpdateEvent event = new PostUpdateEvent( entity, id, state, null, null, persister, null ); + for ( PostUpdateEventListener listener : fastSessionServices.eventListenerGroup_POST_UPDATE.listeners() ) { + listener.onPostUpdate( event ); + } + } + } + + private void firePostUpsert(Object entity, Object id, Object[] state, EntityPersister persister) { + if ( !fastSessionServices.eventListenerGroup_POST_UPSERT.isEmpty() ) { + final PostUpsertEvent event = new PostUpsertEvent( entity, id, state, null, persister, null ); + for ( PostUpsertEventListener listener : fastSessionServices.eventListenerGroup_POST_UPSERT.listeners() ) { + listener.onPostUpsert( event ); + } + } + } + + private void firePostDelete(Object entity, Object id, EntityPersister persister) { + if (!fastSessionServices.eventListenerGroup_POST_DELETE.isEmpty()) { + final PostDeleteEvent event = new PostDeleteEvent( entity, id, null, persister, null ); + for ( PostDeleteEventListener listener : fastSessionServices.eventListenerGroup_POST_DELETE.listeners() ) { + listener.onPostDelete( event ); + } + } + } + // collections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private void forEachOwnedCollection( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CacheEntityLoaderHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CacheEntityLoaderHelper.java index e21cdc09d6..d88e90dfb9 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CacheEntityLoaderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CacheEntityLoaderHelper.java @@ -169,8 +169,7 @@ public class CacheEntityLoaderHelper { } } if ( options.isAllowNulls() ) { - final EntityPersister persister = event.getSession() - .getFactory() + final EntityPersister persister = event.getFactory() .getRuntimeMetamodels() .getMappingMetamodel() .getEntityDescriptor( keyToLoad.getEntityName() ); @@ -215,7 +214,7 @@ public class CacheEntityLoaderHelper { .setId( event.getEntityId() ) .setPersister( persister ); - event.getSession().getSessionFactory() + event.getFactory() .getFastSessionServices() .firePostLoadEvent( postLoadEvent ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/beanvalidation/StatelessBeanValidationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/beanvalidation/StatelessBeanValidationTest.java new file mode 100644 index 0000000000..cf4c3cf195 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/beanvalidation/StatelessBeanValidationTest.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.orm.test.jpa.beanvalidation; + +import jakarta.validation.ConstraintViolationException; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Emmanuel Bernard + */ +@SessionFactory +@DomainModel(annotatedClasses = CupHolder.class) +@ServiceRegistry(settings = @Setting(name = AvailableSettings.JAKARTA_VALIDATION_MODE, value = "auto")) +public class StatelessBeanValidationTest { + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> session.createQuery( "delete from CupHolder" ).executeUpdate() + ); + } + + @Test + public void testStatelessBeanValidationIntegrationOnInsert(SessionFactoryScope scope) { + scope.inStatelessTransaction( + entityManager -> { + CupHolder ch = new CupHolder(); + ch.setRadius( new BigDecimal( "12" ) ); + ch.setTitle( "foo" ); + try { + entityManager.insert(ch); + fail( "invalid object should not be persisted" ); + } + catch (ConstraintViolationException e) { + assertEquals( 1, e.getConstraintViolations().size() ); + } + assertFalse( + entityManager.getTransaction().getRollbackOnly(), + "Stateless session errors don't need to mark the transaction for rollback" + ); + } + ); + } + + @Test + public void testStatelessBeanValidationIntegrationOnUpdate(SessionFactoryScope scope) { + scope.inStatelessTransaction( + entityManager -> { + CupHolder ch = new CupHolder(); + ch.setId(123); + ch.setRadius( new BigDecimal( "12" ) ); + ch.setTitle( "foo" ); + try { + entityManager.update(ch); + fail( "invalid object should not be persisted" ); + } + catch (ConstraintViolationException e) { + assertEquals( 1, e.getConstraintViolations().size() ); + } + assertFalse( + entityManager.getTransaction().getRollbackOnly(), + "Stateless session errors don't need to mark the transaction for rollback" + ); + } + ); + } + + @Test + public void testStatelessBeanValidationIntegrationOnUpsert(SessionFactoryScope scope) { + scope.inStatelessTransaction( + entityManager -> { + CupHolder ch = new CupHolder(); + ch.setId(123); + ch.setRadius( new BigDecimal( "12" ) ); + ch.setTitle( "foo" ); + try { + entityManager.upsert(ch); + fail( "invalid object should not be persisted" ); + } + catch (ConstraintViolationException e) { + assertEquals( 1, e.getConstraintViolations().size() ); + } + assertFalse( + entityManager.getTransaction().getRollbackOnly(), + "Stateless session errors don't need to mark the transaction for rollback" + ); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/StatelessCallbacksTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/StatelessCallbacksTest.java new file mode 100644 index 0000000000..5e12bda082 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/StatelessCallbacksTest.java @@ -0,0 +1,80 @@ +package org.hibernate.orm.test.stateless; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.PostPersist; +import jakarta.persistence.PostRemove; +import jakarta.persistence.PostUpdate; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreRemove; +import jakarta.persistence.PreUpdate; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SessionFactory +@DomainModel(annotatedClasses = StatelessCallbacksTest.WithCallbacks.class) +public class StatelessCallbacksTest { + @Test void test(SessionFactoryScope scope) { + scope.inStatelessSession(s -> { + WithCallbacks instance = new WithCallbacks(); + instance.name = "gavin"; + s.insert(instance); + // because the semantics of @PrePersist and @PreRemove + // are inappropriate for a StatelessSession, the @Pre + // don't get called. However, the @Post events do make + // sense, since they correspond to database operations + assertFalse(instance.prePersist); + assertTrue(instance.postPersist); + s.update(instance); + assertFalse(instance.preUpdate); + assertTrue(instance.postUpdate); + s.delete(instance); + assertFalse(instance.preRemove); + assertTrue(instance.postRemove); + }); + } + + @Entity + static class WithCallbacks { + boolean prePersist = false; + boolean preUpdate = false; + boolean preRemove = false; + boolean postPersist = false; + boolean postUpdate = false; + boolean postRemove = false; + + @GeneratedValue @Id + Long id; + String name; + @PrePersist + void prePersist() { + prePersist = true; + } + @PostPersist + void postPersist() { + postPersist = true; + } + @PreUpdate + void preUpdate() { + preUpdate = true; + } + @PostUpdate + void postUpdate() { + postUpdate = true; + } + @PreRemove + void preRemove() { + preRemove = true; + } + @PostRemove + void postRemove() { + postRemove = true; + } + } +} diff --git a/hibernate-jfr/src/main/java/org/hibernate/event/jfr/internal/JfrEventManager.java b/hibernate-jfr/src/main/java/org/hibernate/event/jfr/internal/JfrEventManager.java index 5965e856af..f01fc93977 100644 --- a/hibernate-jfr/src/main/java/org/hibernate/event/jfr/internal/JfrEventManager.java +++ b/hibernate-jfr/src/main/java/org/hibernate/event/jfr/internal/JfrEventManager.java @@ -12,7 +12,6 @@ import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.EventManager; -import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.HibernateMonitoringEvent; import org.hibernate.internal.build.AllowNonPortable; import org.hibernate.persister.collection.CollectionPersister; @@ -453,8 +452,7 @@ public class JfrEventManager implements EventManager { flushEvent.end(); if ( flushEvent.shouldCommit() ) { flushEvent.executionTime = getExecutionTime( flushEvent.startedAt ); - EventSource session = event.getSession(); - flushEvent.sessionIdentifier = getSessionIdentifier( session ); + flushEvent.sessionIdentifier = getSessionIdentifier( event.getSession() ); flushEvent.numberOfEntitiesProcessed = event.getNumberOfEntitiesProcessed(); flushEvent.numberOfCollectionsProcessed = event.getNumberOfCollectionsProcessed(); flushEvent.isAutoFlush = autoFlush; @@ -485,8 +483,7 @@ public class JfrEventManager implements EventManager { flushEvent.end(); if ( flushEvent.shouldCommit() ) { flushEvent.executionTime = getExecutionTime( flushEvent.startedAt ); - EventSource session = event.getSession(); - flushEvent.sessionIdentifier = getSessionIdentifier( session ); + flushEvent.sessionIdentifier = getSessionIdentifier( event.getSession() ); flushEvent.numberOfEntitiesProcessed = event.getNumberOfEntitiesProcessed(); flushEvent.numberOfCollectionsProcessed = event.getNumberOfCollectionsProcessed(); flushEvent.isAutoFlush = true; diff --git a/hibernate-micrometer/src/main/java/org/hibernate/stat/HibernateQueryMetrics.java b/hibernate-micrometer/src/main/java/org/hibernate/stat/HibernateQueryMetrics.java index 1480b75904..73447127a0 100644 --- a/hibernate-micrometer/src/main/java/org/hibernate/stat/HibernateQueryMetrics.java +++ b/hibernate-micrometer/src/main/java/org/hibernate/stat/HibernateQueryMetrics.java @@ -101,7 +101,7 @@ public class HibernateQueryMetrics implements MeterBinder { @Override public void onPostLoad(PostLoadEvent event) { - registerQueryMetric( event.getSession().getFactory().getStatistics() ); + registerQueryMetric( event.getFactory().getStatistics() ); } void registerQueryMetric(Statistics statistics) {