HHH-17955 add Interceptor callbacks for StatelessSession

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-04-13 21:32:13 +02:00
parent 9a4d21d71d
commit dd77ef651a
3 changed files with 94 additions and 11 deletions

View File

@ -148,6 +148,8 @@ public interface Interceptor {
* @return {@code true} if the user modified the {@code currentState} in any way.
*
* @throws CallbackException Thrown if the interceptor encounters any problems handling the callback.
*
* @see Session#flush()
*/
default boolean onFlushDirty(
Object entity,
@ -163,7 +165,9 @@ public interface Interceptor {
}
/**
* Called before an object is saved. The interceptor may modify the {@code state}, which will be used for
* Called before an object is made persistent by a stateful session.
* <p>
* The interceptor may modify the {@code state}, which will be used for
* the SQL {@code INSERT} and propagated to the persistent object.
*
* @param entity The entity instance whose state is being inserted
@ -185,7 +189,9 @@ public interface Interceptor {
}
/**
* Called before an object is saved. The interceptor may modify the {@code state}, which will be used for
* Called before an object is made persistent by a stateful session.
* <p>
* The interceptor may modify the {@code state}, which will be used for
* the SQL {@code INSERT} and propagated to the persistent object.
*
* @param entity The entity instance whose state is being inserted
@ -197,6 +203,10 @@ public interface Interceptor {
* @return {@code true} if the user modified the {@code state} in any way.
*
* @throws CallbackException Thrown if the interceptor encounters any problems handling the callback.
*
* @see Session#persist(Object)
* @see Session#merge(Object)
* @see Session#save(Object)
*/
default boolean onSave(Object entity, Object id, Object[] state, String[] propertyNames, Type[] types)
throws CallbackException {
@ -206,7 +216,9 @@ public interface Interceptor {
return false;
}
/**
* Called before an object is deleted. It is not recommended that the interceptor modify the {@code state}.
* Called before an object is removed by a stateful session.
* <p>
* It is not recommended that the interceptor modify the {@code state}.
*
* @param entity The entity instance being deleted
* @param id The identifier of the entity
@ -223,7 +235,9 @@ public interface Interceptor {
throws CallbackException {}
/**
* Called before an object is deleted. It is not recommended that the interceptor modify the {@code state}.
* Called before an object is removed by a stateful session.
* <p>
* It is not recommended that the interceptor modify the {@code state}.
*
* @param entity The entity instance being deleted
* @param id The identifier of the entity
@ -232,6 +246,9 @@ public interface Interceptor {
* @param types The types of the entity properties
*
* @throws CallbackException Thrown if the interceptor encounters any problems handling the callback.
*
* @see Session#remove(Object)
* @see Session#delete(Object)
*/
default void onDelete(Object entity, Object id, Object[] state, String[] propertyNames, Type[] types)
throws CallbackException {
@ -515,4 +532,63 @@ public interface Interceptor {
* @param tx The Hibernate transaction facade object
*/
default void afterTransactionCompletion(Transaction tx) {}
/**
* Called before a record is inserted by a {@link StatelessSession}.
*
* @param entity The entity instance being deleted
* @param id The identifier of the entity
* @param state The entity state
* @param propertyNames The names of the entity properties.
* @param propertyTypes The types of the entity properties
*
* @throws CallbackException Thrown if the interceptor encounters any problems handling the callback.
*
* @see StatelessSession#insert(Object)
*/
default void onInsert(Object entity, Object id, Object[] state, String[] propertyNames, Type[] propertyTypes) {}
/**
* Called before a record is updated by a {@link StatelessSession}.
*
* @param entity The entity instance being deleted
* @param id The identifier of the entity
* @param state The entity state
* @param propertyNames The names of the entity properties.
* @param propertyTypes The types of the entity properties
*
* @throws CallbackException Thrown if the interceptor encounters any problems handling the callback.
*
* @see StatelessSession#update(Object)
*/
default void onUpdate(Object entity, Object id, Object[] state, String[] propertyNames, Type[] propertyTypes) {}
/**
* Called before a record is upserted by a {@link StatelessSession}.
*
* @param entity The entity instance being deleted
* @param id The identifier of the entity
* @param state The entity state
* @param propertyNames The names of the entity properties.
* @param propertyTypes The types of the entity properties
*
* @throws CallbackException Thrown if the interceptor encounters any problems handling the callback.
*
* @see StatelessSession#upsert(String, Object)
*/
default void onUpsert(Object entity, Object id, Object[] state, String[] propertyNames, Type[] propertyTypes) {}
/**
* Called before a record is deleted by a {@link StatelessSession}.
*
* @param entity The entity instance being deleted
* @param id The identifier of the entity
* @param propertyNames The names of the entity properties.
* @param propertyTypes The types of the entity properties
*
* @throws CallbackException Thrown if the interceptor encounters any problems handling the callback.
*
* @see StatelessSession#delete(Object)
*/
default void onDelete(Object entity, Object id, String[] propertyNames, Type[] propertyTypes) {}
}

View File

@ -30,13 +30,10 @@ import org.hibernate.graph.GraphSemantic;
* checking.
* </ul>
* <p>
* Furthermore, operations performed via a stateless session:
* <ul>
* <li>never cascade to associated instances, no matter what the
* {@link jakarta.persistence.CascadeType}, and
* <li>bypass Hibernate's {@linkplain org.hibernate.event.spi event model},
* lifecycle callbacks, and {@linkplain Interceptor interceptors}.
* </ul>
* Furthermore, the basic operations of a stateless session do not have
* corresponding {@linkplain jakarta.persistence.CascadeType cascade types},
* and so an operation performed via a stateless session never cascades to
* associated instances.
* <p>
* Stateless sessions are vulnerable to data aliasing effects, due to the
* lack of a first-level cache.

View File

@ -133,12 +133,16 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
if ( firePreInsert(entity, id, state, persister) ) {
return id;
}
getInterceptor()
.onInsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() );
persister.getInsertCoordinator().insert( entity, id, state, this );
}
else {
if ( firePreInsert(entity, null, state, persister) ) {
return null;
}
getInterceptor()
.onInsert( entity, null, state, persister.getPropertyNames(), persister.getPropertyTypes() );
final GeneratedValues generatedValues = persister.getInsertCoordinator().insert( entity, state, this );
id = castNonNull( generatedValues ).getGeneratedValue( persister.getIdentifierMapping() );
}
@ -164,6 +168,8 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
final Object id = persister.getIdentifier( entity, this );
final Object version = persister.getVersion( entity );
if ( !firePreDelete(entity, id, persister) ) {
getInterceptor()
.onDelete( entity, id, persister.getPropertyNames(), persister.getPropertyTypes() );
forEachOwnedCollection( entity, id, persister,
(descriptor, collection) -> descriptor.remove(id, this) );
persister.getDeleteCoordinator().delete( entity, id, version, this );
@ -203,6 +209,8 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
oldVersion = null;
}
if ( !firePreUpdate(entity, id, state, persister) ) {
getInterceptor()
.onUpdate( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() );
persister.getUpdateCoordinator().update( entity, id, null, state, oldVersion, null, null, false, this );
// TODO: can we do better here?
forEachOwnedCollection( entity, id, persister,
@ -220,6 +228,8 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
final Object id = idToUpsert( entity, persister );
final Object[] state = persister.getValues( entity );
if ( !firePreUpsert(entity, id, state, persister) ) {
getInterceptor()
.onUpsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() );
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!