HHH-13565 Review allocations for default SessionEventListener instances

This commit is contained in:
Sanne Grinovero 2019-08-22 08:47:50 +01:00
parent 4b2f056a63
commit 269d5f8358
8 changed files with 243 additions and 83 deletions

View File

@ -7,6 +7,7 @@
package org.hibernate.cfg;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.HibernateException;
@ -17,6 +18,9 @@ import org.hibernate.engine.internal.StatisticalLoggingSessionEventListener;
* @author Steve Ebersole
*/
public class BaselineSessionEventsListenerBuilder {
private static final SessionEventListener[] EMPTY = new SessionEventListener[0];
private boolean logSessionMetrics;
private Class<? extends SessionEventListener> autoListener;
@ -33,6 +37,10 @@ public class BaselineSessionEventsListenerBuilder {
}
@SuppressWarnings("UnusedDeclaration")
/**
* @deprecated this method will be removed as this builder should become immutable
*/
@Deprecated
public void setLogSessionMetrics(boolean logSessionMetrics) {
this.logSessionMetrics = logSessionMetrics;
}
@ -43,26 +51,59 @@ public class BaselineSessionEventsListenerBuilder {
}
@SuppressWarnings("UnusedDeclaration")
/**
* @deprecated this method will be removed as this builder should become immutable
*/
@Deprecated
public void setAutoListener(Class<? extends SessionEventListener> autoListener) {
this.autoListener = autoListener;
}
public List<SessionEventListener> buildBaselineList() {
List<SessionEventListener> list = new ArrayList<SessionEventListener>();
if ( logSessionMetrics && StatisticalLoggingSessionEventListener.isLoggingEnabled() ) {
list.add( new StatisticalLoggingSessionEventListener() );
}
if ( autoListener != null ) {
try {
list.add( autoListener.newInstance() );
}
catch (Exception e) {
throw new HibernateException(
"Unable to instantiate specified auto SessionEventListener : " + autoListener.getName(),
e
);
}
}
final SessionEventListener[] sessionEventListeners = buildBaseline();
//Capacity: needs to hold at least all elements from the baseline, but also expect to add a little more later.
ArrayList<SessionEventListener> list = new ArrayList<>( sessionEventListeners.length + 3 );
Collections.addAll( list, sessionEventListeners );
return list;
}
public SessionEventListener[] buildBaseline() {
final boolean addStats = logSessionMetrics && StatisticalLoggingSessionEventListener.isLoggingEnabled();
final boolean addAutoListener = autoListener != null;
final SessionEventListener[] arr;
if ( addStats && addAutoListener ) {
arr = new SessionEventListener[2];
arr[0] = buildStatsListener();
arr[1] = buildAutoListener( autoListener );
}
else if ( !addStats && !addAutoListener ) {
arr = EMPTY;
}
else if ( !addStats && addAutoListener ) {
arr = new SessionEventListener[1];
arr[0] = buildAutoListener( autoListener );
}
else { //Last case: if ( addStats && !addAutoListener )
arr = new SessionEventListener[1];
arr[0] = buildStatsListener();
}
return arr;
}
private static SessionEventListener buildAutoListener(final Class<? extends SessionEventListener> autoListener) {
try {
return autoListener.newInstance();
}
catch (Exception e) {
throw new HibernateException(
"Unable to instantiate specified auto SessionEventListener : " + autoListener.getName(),
e
);
}
}
private static SessionEventListener buildStatsListener() {
return new StatisticalLoggingSessionEventListener();
}
}

View File

@ -7,8 +7,8 @@
package org.hibernate.engine.internal;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.Objects;
import org.hibernate.SessionEventListener;
import org.hibernate.engine.spi.SessionEventListenerManager;
@ -17,255 +17,269 @@ import org.hibernate.engine.spi.SessionEventListenerManager;
* @author Steve Ebersole
*/
public class SessionEventListenerManagerImpl implements SessionEventListenerManager, Serializable {
private List<SessionEventListener> listenerList;
private SessionEventListener[] listeners;
public SessionEventListenerManagerImpl(SessionEventListener... initialListener) {
//no need for defensive copies until the array is mutated:
this.listeners = initialListener;
}
@Override
public void addListener(SessionEventListener... listeners) {
if ( listenerList == null ) {
listenerList = new ArrayList<>();
public void addListener(final SessionEventListener... additionalListeners) {
Objects.requireNonNull( additionalListeners );
final SessionEventListener[] existing = this.listeners;
if ( existing == null ) {
//Make a defensive copy as this array can be tracked back to API (user code)
this.listeners = Arrays.copyOf( additionalListeners, additionalListeners.length );
}
else {
// Resize our existing array and add the new listeners
final SessionEventListener[] newlist = new SessionEventListener[ existing.length + additionalListeners.length ];
System.arraycopy( existing, 0, newlist, 0, existing.length );
System.arraycopy( additionalListeners, 0, newlist, existing.length, additionalListeners.length );
this.listeners = newlist;
}
java.util.Collections.addAll( listenerList, listeners );
}
@Override
public void transactionCompletion(boolean successful) {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.transactionCompletion( successful );
}
}
@Override
public void jdbcConnectionAcquisitionStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcConnectionAcquisitionStart();
}
}
@Override
public void jdbcConnectionAcquisitionEnd() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcConnectionAcquisitionEnd();
}
}
@Override
public void jdbcConnectionReleaseStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcConnectionReleaseStart();
}
}
@Override
public void jdbcConnectionReleaseEnd() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcConnectionReleaseEnd();
}
}
@Override
public void jdbcPrepareStatementStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcPrepareStatementStart();
}
}
@Override
public void jdbcPrepareStatementEnd() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcPrepareStatementEnd();
}
}
@Override
public void jdbcExecuteStatementStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcExecuteStatementStart();
}
}
@Override
public void jdbcExecuteStatementEnd() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcExecuteStatementEnd();
}
}
@Override
public void jdbcExecuteBatchStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcExecuteBatchStart();
}
}
@Override
public void jdbcExecuteBatchEnd() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.jdbcExecuteBatchEnd();
}
}
@Override
public void cachePutStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.cachePutStart();
}
}
@Override
public void cachePutEnd() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.cachePutEnd();
}
}
@Override
public void cacheGetStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.cacheGetStart();
}
}
@Override
public void cacheGetEnd(boolean hit) {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.cacheGetEnd( hit );
}
}
@Override
public void flushStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.flushStart();
}
}
@Override
public void flushEnd(int numberOfEntities, int numberOfCollections) {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.flushEnd( numberOfEntities, numberOfCollections );
}
}
@Override
public void partialFlushStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.partialFlushStart();
}
}
@Override
public void partialFlushEnd(int numberOfEntities, int numberOfCollections) {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.partialFlushEnd( numberOfEntities, numberOfCollections );
}
}
@Override
public void dirtyCalculationStart() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.dirtyCalculationStart();
}
}
@Override
public void dirtyCalculationEnd(boolean dirty) {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.dirtyCalculationEnd( dirty );
}
}
@Override
public void end() {
if ( listenerList == null ) {
if ( listeners == null ) {
return;
}
for ( SessionEventListener listener : listenerList ) {
for ( SessionEventListener listener : listeners ) {
listener.end();
}
}

View File

@ -32,6 +32,7 @@ import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.LockMode;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.SessionEventListener;
import org.hibernate.SessionException;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
@ -136,7 +137,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
protected boolean waitingForAutoClose;
// transient & non-final for Serialization purposes - ugh
private transient SessionEventListenerManagerImpl sessionEventsManager = new SessionEventListenerManagerImpl();
private transient SessionEventListenerManagerImpl sessionEventsManager;
private transient EntityNameResolver entityNameResolver;
private Integer jdbcBatchSize;
@ -168,6 +169,13 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
this.interceptor = interpret( options.getInterceptor() );
this.jdbcTimeZone = options.getJdbcTimeZone();
final List<SessionEventListener> customSessionEventListener = options.getCustomSessionEventListener();
if ( customSessionEventListener == null ) {
sessionEventsManager = new SessionEventListenerManagerImpl( fastSessionServices.defaultSessionEventListeners.buildBaseline() );
}
else {
sessionEventsManager = new SessionEventListenerManagerImpl( customSessionEventListener.toArray( new SessionEventListener[0] ) );
}
final StatementInspector statementInspector = interpret( options.getStatementInspector() );
this.jdbcSessionContext = new JdbcSessionContextImpl( this, statementInspector, fastSessionServices );
@ -1190,7 +1198,6 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Step 1 :: read back non-transient state...
ois.defaultReadObject();
sessionEventsManager = new SessionEventListenerManagerImpl();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Step 2 :: read back transient state...
@ -1198,6 +1205,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
factory = SessionFactoryImpl.deserialize( ois );
fastSessionServices = factory.getFastSessionServices();
sessionEventsManager = new SessionEventListenerManagerImpl( fastSessionServices.defaultSessionEventListeners.buildBaseline() );
jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject(), fastSessionServices );
jdbcCoordinator = JdbcCoordinatorImpl.deserialize( ois, this );

View File

@ -10,6 +10,8 @@ import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.LockOptions;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cfg.BaselineSessionEventsListenerBuilder;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
@ -115,6 +117,7 @@ final class FastSessionServices {
final boolean isJtaTransactionAccessible;
final CacheMode initialSessionCacheMode;
final boolean discardOnClose;
final BaselineSessionEventsListenerBuilder defaultSessionEventListeners;
//Private fields:
private final Dialect dialect;
@ -126,6 +129,7 @@ final class FastSessionServices {
Objects.requireNonNull( sf );
final ServiceRegistryImplementor sr = sf.getServiceRegistry();
final JdbcServices jdbcServices = sf.getJdbcServices();
final SessionFactoryOptions sessionFactoryOptions = sf.getSessionFactoryOptions();
// Pre-compute all iterators on Event listeners:
final EventListenerRegistry eventListenerRegistry = sr.getService( EventListenerRegistry.class );
@ -150,7 +154,7 @@ final class FastSessionServices {
//Other highly useful constants:
this.dialect = jdbcServices.getJdbcEnvironment().getDialect();
this.disallowOutOfTransactionUpdateOperations = !sf.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations();
this.disallowOutOfTransactionUpdateOperations = !sessionFactoryOptions.isAllowOutOfTransactionUpdateOperations();
this.useStreamForLobBinding = Environment.useStreamsForBinary() || dialect.useInputStreamToInsertBlob();
this.requiresMultiTenantConnectionProvider = sf.getSettings().getMultiTenancyStrategy().requiresMultiTenantConnectionProvider();
@ -167,8 +171,9 @@ final class FastSessionServices {
this.defaultCacheStoreMode = determineCacheStoreMode( defaultSessionProperties );
this.defaultCacheRetrieveMode = determineCacheRetrieveMode( defaultSessionProperties );
this.initialSessionCacheMode = CacheModeHelper.interpretCacheMode( defaultCacheStoreMode, defaultCacheRetrieveMode );
this.discardOnClose = sf.getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled();
this.discardOnClose = sessionFactoryOptions.isReleaseResourcesOnCloseEnabled();
this.defaultJdbcObservers = Collections.singletonList( new ConnectionObserverStatsBridge( sf ) );
this.defaultSessionEventListeners = sessionFactoryOptions.getBaselineSessionEventsListenerBuilder();
}
Iterable<ClearEventListener> getClearEventListeners() {

View File

@ -9,6 +9,7 @@ package org.hibernate.internal;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Objects;
import org.hibernate.SessionEventListener;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
@ -24,6 +25,8 @@ public class NonContextualJdbcConnectionAccess implements JdbcConnectionAccess,
public NonContextualJdbcConnectionAccess(
SessionEventListener listener,
ConnectionProvider connectionProvider) {
Objects.requireNonNull( listener );
Objects.requireNonNull( connectionProvider );
this.listener = listener;
this.connectionProvider = connectionProvider;
}

View File

@ -7,10 +7,12 @@
package org.hibernate.internal;
import java.sql.Connection;
import java.util.List;
import java.util.TimeZone;
import org.hibernate.FlushMode;
import org.hibernate.Interceptor;
import org.hibernate.SessionEventListener;
import org.hibernate.engine.spi.SessionOwner;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector;
@ -45,6 +47,12 @@ public interface SessionCreationOptions {
TimeZone getJdbcTimeZone();
/**
* @return the full list of SessionEventListener if this was customized,
* or null if this Session is being created with the default list.
*/
List<SessionEventListener> getCustomSessionEventListener();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// deprecations

View File

@ -1156,6 +1156,9 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
private TimeZone jdbcTimeZone;
private boolean queryParametersValidationEnabled;
// Lazy: defaults can be built by invoking the builder in fastSessionServices.defaultSessionEventListeners
// (Need a fresh build for each Session as the listener instances can't be reused across sessions)
// Only initialize of the builder is overriding the default.
private List<SessionEventListener> listeners;
//todo : expose setting
@ -1178,9 +1181,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
tenantIdentifier = currentTenantIdentifierResolver.resolveCurrentTenantIdentifier();
}
this.jdbcTimeZone = sessionFactoryOptions.getJdbcTimeZone();
listeners = sessionFactoryOptions.getBaselineSessionEventsListenerBuilder().buildBaselineList();
queryParametersValidationEnabled = sessionFactoryOptions.isQueryParametersValidationEnabled();
this.queryParametersValidationEnabled = sessionFactoryOptions.isQueryParametersValidationEnabled();
}
@ -1268,20 +1269,18 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
return jdbcTimeZone;
}
@Override
public List<SessionEventListener> getCustomSessionEventListener() {
return listeners;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SessionBuilder
@Override
public Session openSession() {
log.tracef( "Opening Hibernate Session. tenant=%s", tenantIdentifier );
final SessionImpl session = new SessionImpl( sessionFactory, this );
final SessionEventListenerManager eventListenerManager = session.getEventListenerManager();
for ( SessionEventListener listener : listeners ) {
eventListenerManager.addListener( listener );
}
return session;
return new SessionImpl( sessionFactory, this );
}
@Override
@ -1377,6 +1376,11 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
@Override
@SuppressWarnings("unchecked")
public T eventListeners(SessionEventListener... listeners) {
if ( this.listeners == null ) {
this.listeners = sessionFactory.getSessionFactoryOptions()
.getBaselineSessionEventsListenerBuilder()
.buildBaselineList();
}
Collections.addAll( this.listeners, listeners );
return (T) this;
}
@ -1384,7 +1388,13 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
@Override
@SuppressWarnings("unchecked")
public T clearEventListeners() {
listeners.clear();
if ( listeners == null ) {
//Needs to initialize explicitly to an empty list as otherwise "null" immplies the default listeners will be applied
this.listeners = new ArrayList<SessionEventListener>( 3 );
}
else {
listeners.clear();
}
return (T) this;
}
@ -1484,6 +1494,11 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
return sessionFactory.getSessionFactoryOptions().getJdbcTimeZone();
}
@Override
public List<SessionEventListener> getCustomSessionEventListener() {
return null;
}
@Override
public SessionOwner getSessionOwner() {
return null;

View File

@ -0,0 +1,66 @@
/*
* 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;
import org.hibernate.BaseSessionEventListener;
import org.hibernate.SessionEventListener;
import org.hibernate.engine.internal.SessionEventListenerManagerImpl;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Assert;
import org.junit.Test;
public class SessionEventListenersManagerTest extends BaseUnitTestCase {
@Test
public void testListenerAppending() {
StringBuilder sb = new StringBuilder();
SessionEventListener a = new TestSessionEventListener( sb , 'a' );
SessionEventListener b = new TestSessionEventListener( sb , 'b' );
SessionEventListener c = new TestSessionEventListener( sb , 'c' );
SessionEventListener d = new TestSessionEventListener( sb , 'd' );
SessionEventListenerManagerImpl l = new SessionEventListenerManagerImpl( a, b );
l.addListener( c, d );
l.dirtyCalculationEnd( true );
Assert.assertEquals( "abcd", sb.toString() );
l.dirtyCalculationEnd( true );
Assert.assertEquals( "abcdabcd", sb.toString() );
l.addListener( new TestSessionEventListener( sb , 'e' ) );
l.dirtyCalculationEnd( true );
Assert.assertEquals( "abcdabcdabcde", sb.toString() );
}
@Test
public void testEmptyListenerAppending() {
StringBuilder sb = new StringBuilder();
SessionEventListenerManagerImpl l = new SessionEventListenerManagerImpl();
l.dirtyCalculationEnd( true );
Assert.assertEquals( "", sb.toString() );
l.addListener( new TestSessionEventListener( sb , 'e' ) );
l.dirtyCalculationEnd( true );
Assert.assertEquals( "e", sb.toString() );
}
private static class TestSessionEventListener extends BaseSessionEventListener {
private final StringBuilder sb;
private final char theChar;
public TestSessionEventListener(StringBuilder sb, char theChar) {
this.sb = sb;
this.theChar = theChar;
}
//Just picking any method. This one is funny..
@Override
public void dirtyCalculationEnd(boolean dirty) {
sb.append( theChar );
}
}
}