HHH-17955 Bean Validation and @PostXxxx callbacks for StatelessSession

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-04-13 20:09:02 +02:00
parent 59603dffb3
commit 9a4d21d71d
41 changed files with 730 additions and 177 deletions

View File

@ -239,19 +239,20 @@ public class EntityInsertAction extends AbstractEntityInsertAction {
}
protected boolean preInsert() {
boolean veto = false;
final EventListenerGroup<PreInsertEventListener> listenerGroup
= getFastSessionServices().eventListenerGroup_PRE_INSERT;
if ( listenerGroup.isEmpty() ) {
return veto;
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;
}
}
@Override
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) throws HibernateException {

View File

@ -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<String,Object> settings, ClassLoaderService classLoaderService) {
public BeanValidationEventListener(
ValidatorFactory factory, Map<String,Object> 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 <T> 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 );

View File

@ -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" );

View File

@ -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<ValidationMode> 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<ValidationMode> 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<PersistentClass> persistentClasses,
Map<String,Object> settings,
Dialect dialect,
ClassLoaderAccess classLoaderAccess) {
Class<?>[] groupsArray = GroupsPerOperation.buildGroupsForOperation(
GroupsPerOperation.Operation.DDL,
settings,
classLoaderAccess
);
Set<Class<?>> groups = new HashSet<>( Arrays.asList( groupsArray ) );
final Class<?>[] groupsArray =
buildGroupsForOperation( GroupsPerOperation.Operation.DDL, settings, classLoaderAccess );
final Set<Class<?>> 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<Min> minConstraint = (ConstraintDescriptor<Min>) descriptor;
long min = minConstraint.getAnnotation().value();
final ConstraintDescriptor<Min> minConstraint = (ConstraintDescriptor<Min>) 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<Max> maxConstraint = (ConstraintDescriptor<Max>) descriptor;
long max = maxConstraint.getAnnotation().value();
final ConstraintDescriptor<Max> maxConstraint = (ConstraintDescriptor<Max>) 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<? extends Annotation> 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()
)

View File

@ -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;

View File

@ -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;
/**

View File

@ -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<FlushEntityEventListener> flushListeners = source.getFactory()
.getFastSessionServices().eventListenerGroup_FLUSH_ENTITY;
final EventListenerGroup<FlushEntityEventListener> 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

View File

@ -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() )
);
}

View File

@ -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;

View File

@ -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 );

View File

@ -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;
}

View File

@ -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,12 +191,12 @@ 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 );
@ -208,7 +206,7 @@ public class DefaultLoadEventListener implements LoadEventListener {
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() );
}

View File

@ -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;

View File

@ -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 ) ) {

View File

@ -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() ) {

View File

@ -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() )
);
}

View File

@ -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;

View File

@ -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() )
);
}

View File

@ -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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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 );
}
}

View File

@ -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 <T> EventListenerGroup<T> getEventListenerGroup(EventType<T> 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<T> 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() );

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -61,11 +61,13 @@ public final class EventType<T> {
public static final EventType<PreDeleteEventListener> PRE_DELETE = create( "pre-delete", PreDeleteEventListener.class );
public static final EventType<PreUpdateEventListener> PRE_UPDATE = create( "pre-update", PreUpdateEventListener.class );
public static final EventType<PreInsertEventListener> PRE_INSERT = create( "pre-insert", PreInsertEventListener.class );
public static final EventType<PreUpsertEventListener> PRE_UPSERT = create( "pre-upsert", PreUpsertEventListener.class );
public static final EventType<PostLoadEventListener> POST_LOAD = create( "post-load", PostLoadEventListener.class );
public static final EventType<PostDeleteEventListener> POST_DELETE = create( "post-delete", PostDeleteEventListener.class );
public static final EventType<PostUpdateEventListener> POST_UPDATE = create( "post-update", PostUpdateEventListener.class );
public static final EventType<PostInsertEventListener> POST_INSERT = create( "post-insert", PostInsertEventListener.class );
public static final EventType<PostUpsertEventListener> POST_UPSERT = create( "post-upsert", PostUpsertEventListener.class );
public static final EventType<PostDeleteEventListener> POST_COMMIT_DELETE = create( "post-commit-delete", PostDeleteEventListener.class );
public static final EventType<PostUpdateEventListener> POST_COMMIT_UPDATE = create( "post-commit-update", PostUpdateEventListener.class );

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.event.spi;
/**
* Called after updating the datastore
*
* @author Gavin King
*/
public interface PostUpsertEventListener extends PostActionEventListener {
void onPostUpsert(PostUpsertEvent event);
}

View File

@ -16,7 +16,7 @@ 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.

View File

@ -16,8 +16,8 @@ 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.

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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);
}

View File

@ -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<PostLoadEventListener> eventListenerGroup_POST_LOAD; //Frequently used by 2LC initialization:
public final EventListenerGroup<PostUpdateEventListener> eventListenerGroup_POST_COMMIT_UPDATE;
public final EventListenerGroup<PostUpdateEventListener> eventListenerGroup_POST_UPDATE;
public final EventListenerGroup<PostUpsertEventListener> eventListenerGroup_POST_UPSERT;
public final EventListenerGroup<PreCollectionRecreateEventListener> eventListenerGroup_PRE_COLLECTION_RECREATE;
public final EventListenerGroup<PreCollectionRemoveEventListener> eventListenerGroup_PRE_COLLECTION_REMOVE;
public final EventListenerGroup<PreCollectionUpdateEventListener> eventListenerGroup_PRE_COLLECTION_UPDATE;
@ -147,6 +150,7 @@ public final class FastSessionServices {
public final EventListenerGroup<PreInsertEventListener> eventListenerGroup_PRE_INSERT;
public final EventListenerGroup<PreLoadEventListener> eventListenerGroup_PRE_LOAD;
public final EventListenerGroup<PreUpdateEventListener> eventListenerGroup_PRE_UPDATE;
public final EventListenerGroup<PreUpsertEventListener> eventListenerGroup_PRE_UPSERT;
public final EventListenerGroup<RefreshEventListener> eventListenerGroup_REFRESH;
public final EventListenerGroup<ReplicateEventListener> eventListenerGroup_REPLICATE;
public final EventListenerGroup<ResolveNaturalIdEventListener> 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 );

View File

@ -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 );
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;
}
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 );
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(

View File

@ -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 );
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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"
);
}
);
}
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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) {