HHH-17984 initial support for Statistics with StatelessSession

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-04-21 01:07:36 +02:00
parent 34fd71c131
commit 62132594eb
4 changed files with 109 additions and 34 deletions

View File

@ -114,6 +114,7 @@ import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.CriteriaUpdate;
import org.hibernate.stat.spi.StatisticsImplementor;
import static java.lang.Boolean.TRUE;
import static org.hibernate.internal.util.ReflectHelper.isClass;
@ -311,6 +312,42 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
: sessionJdbcBatchSize;
}
void afterTransactionBeginEvents() {
getInterceptor().afterTransactionBegin( getTransactionIfAccessible() );
}
void beforeTransactionCompletionEvents() {
try {
getInterceptor().beforeTransactionCompletion( getTransactionIfAccessible() );
}
catch (Throwable t) {
log.exceptionInBeforeTransactionCompletionInterceptor( t );
}
}
void afterTransactionCompletionEvents(boolean successful) {
getEventListenerManager().transactionCompletion(successful);
final StatisticsImplementor statistics = getFactory().getStatistics();
if ( statistics.isStatisticsEnabled() ) {
statistics.endTransaction(successful);
}
try {
getInterceptor().afterTransactionCompletion( getTransactionIfAccessible() );
}
catch (Throwable t) {
log.exceptionInAfterTransactionCompletionInterceptor( t );
}
}
private Transaction getTransactionIfAccessible() {
// We do not want an exception to be thrown if the transaction
// is not accessible. If the transaction is not accessible,
// then return null.
return fastSessionServices.isJtaTransactionAccessible ? accessTransaction() : null;
}
protected void addSharedSessionTransactionObserver(TransactionCoordinator transactionCoordinator) {
}

View File

@ -1971,24 +1971,12 @@ public class SessionImpl
private transient LobHelperImpl lobHelper;
private Transaction getTransactionIfAccessible() {
// We do not want an exception to be thrown if the transaction
// is not accessible. If the transaction is not accessible,
// then return null.
return fastSessionServices.isJtaTransactionAccessible ? accessTransaction() : null;
}
@Override
public void beforeTransactionCompletion() {
log.trace( "SessionImpl#beforeTransactionCompletion()" );
flushBeforeTransactionCompletion();
actionQueue.beforeTransactionCompletion();
try {
getInterceptor().beforeTransactionCompletion( getTransactionIfAccessible() );
}
catch (Throwable t) {
log.exceptionInBeforeTransactionCompletionInterceptor( t );
}
beforeTransactionCompletionEvents();
super.beforeTransactionCompletion();
}
@ -2007,19 +1995,7 @@ public class SessionImpl
persistenceContext.afterTransactionCompletion();
actionQueue.afterTransactionCompletion( successful );
getEventListenerManager().transactionCompletion( successful );
final StatisticsImplementor statistics = getFactory().getStatistics();
if ( statistics.isStatisticsEnabled() ) {
statistics.endTransaction( successful );
}
try {
getInterceptor().afterTransactionCompletion( getTransactionIfAccessible() );
}
catch (Throwable t) {
log.exceptionInAfterTransactionCompletionInterceptor( t );
}
afterTransactionCompletionEvents( successful );
if ( !delayed ) {
if ( shouldAutoClose() && (!isClosed() || waitingForAutoClose) ) {
@ -2271,12 +2247,7 @@ public class SessionImpl
managedFlush();
}
actionQueue.beforeTransactionCompletion();
try {
getInterceptor().beforeTransactionCompletion( getTransactionIfAccessible() );
}
catch ( Throwable t ) {
log.exceptionInBeforeTransactionCompletionInterceptor( t );
}
beforeTransactionCompletionEvents();
}
@Override
@ -2317,7 +2288,7 @@ public class SessionImpl
@Override
public void afterTransactionBegin() {
checkOpenOrWaitingForAutoClose();
getInterceptor().afterTransactionBegin( getTransactionIfAccessible() );
afterTransactionBeginEvents();
}
@Override

View File

@ -59,6 +59,7 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.tuple.entity.EntityMetamodel;
import jakarta.persistence.EntityGraph;
@ -152,6 +153,10 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
forEachOwnedCollection( entity, id, persister,
(descriptor, collection) -> descriptor.recreate( collection, id, this) );
firePostInsert(entity, id, state, persister);
final StatisticsImplementor statistics = getFactory().getStatistics();
if ( statistics.isStatisticsEnabled() ) {
statistics.insertEntity( persister.getEntityName() );
}
return id;
}
@ -176,6 +181,10 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
(descriptor, collection) -> descriptor.remove(id, this) );
persister.getDeleteCoordinator().delete( entity, id, version, this );
firePostDelete(entity, id, persister);
final StatisticsImplementor statistics = getFactory().getStatistics();
if ( statistics.isStatisticsEnabled() ) {
statistics.deleteEntity( persister.getEntityName() );
}
}
}
@ -220,6 +229,10 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
forEachOwnedCollection( entity, id, persister,
(descriptor, collection) -> descriptor.recreate( collection, id, this) );
firePostUpdate(entity, id, state, persister);
final StatisticsImplementor statistics = getFactory().getStatistics();
if ( statistics.isStatisticsEnabled() ) {
statistics.updateEntity( persister.getEntityName() );
}
}
}
@ -234,7 +247,6 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
.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!
// TODO: can we do better here?
forEachOwnedCollection( entity, id, persister,
(descriptor, collection) -> descriptor.remove(id, this) );
@ -884,15 +896,18 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
@Override
public void afterTransactionBegin() {
afterTransactionBeginEvents();
}
@Override
public void beforeTransactionCompletion() {
flushBeforeTransactionCompletion();
beforeTransactionCompletionEvents();
}
@Override
public void afterTransactionCompletion(boolean successful, boolean delayed) {
afterTransactionCompletionEvents( successful );
if ( shouldAutoClose() && !isClosed() ) {
managedClose();
}

View File

@ -0,0 +1,52 @@
package org.hibernate.orm.test.stateless;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import org.hibernate.stat.spi.StatisticsImplementor;
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.Test;
import static org.hibernate.cfg.StatisticsSettings.GENERATE_STATISTICS;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SessionFactory
@DomainModel(annotatedClasses = StatelessSessionStatisticsTest.Person.class)
@ServiceRegistry(settings = @Setting(name = GENERATE_STATISTICS, value = "true"))
public class StatelessSessionStatisticsTest {
@Test
void test(SessionFactoryScope scope) {
StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
assertEquals(0, statistics.getEntityInsertCount());
assertEquals(0, statistics.getEntityUpdateCount());
assertEquals(0, statistics.getEntityDeleteCount());
assertEquals(0, statistics.getEntityLoadCount());
Person person = new Person();
person.name = "Gavin";
scope.inStatelessTransaction(s -> s.insert(person));
assertEquals(1, statistics.getEntityInsertCount());
scope.inStatelessSession(s -> s.get(Person.class, person.id));
assertEquals(1, statistics.getEntityLoadCount());
person.name = "Gavin King";
scope.inStatelessTransaction(s -> s.update(person));
assertEquals(1, statistics.getEntityUpdateCount());
scope.inStatelessSession(s -> s.get(Person.class, person.id));
assertEquals(2, statistics.getEntityLoadCount());
scope.inStatelessTransaction(s -> s.delete(person));
assertEquals(1, statistics.getEntityDeleteCount());
assertEquals(3, statistics.getTransactionCount());
}
@Entity(name="Entity")
static class Person {
@Id @GeneratedValue
long id;
@Basic(optional = false)
String name;
}
}