HHH-13198 Introduce a global configuration flag to disable JPA callbacks

This commit is contained in:
Sanne Grinovero 2018-09-04 12:34:01 +01:00
parent 29e135c015
commit a78c56c01f
12 changed files with 287 additions and 21 deletions

View File

@ -100,6 +100,7 @@ import static org.hibernate.cfg.AvailableSettings.MAX_FETCH_DEPTH;
import static org.hibernate.cfg.AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER;
import static org.hibernate.cfg.AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE;
import static org.hibernate.cfg.AvailableSettings.ORDER_INSERTS;
import static org.hibernate.cfg.AvailableSettings.JPA_CALLBACKS_ENABLED;
import static org.hibernate.cfg.AvailableSettings.ORDER_UPDATES;
import static org.hibernate.cfg.AvailableSettings.PREFER_USER_TRANSACTION;
import static org.hibernate.cfg.AvailableSettings.PROCEDURE_NULL_PARAM_PASSING;
@ -196,6 +197,9 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private boolean orderInsertsEnabled;
private boolean postInsertIdentifierDelayed;
// JPA callbacks
private boolean callbacksEnabled;
// multi-tenancy
private MultiTenancyStrategy multiTenancyStrategy;
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
@ -346,6 +350,8 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
DISABLE_DELAYED_IDENTIFIER_POST_INSERTS, configurationSettings, false
);
this.callbacksEnabled = ConfigurationHelper.getBoolean( JPA_CALLBACKS_ENABLED, configurationSettings, true );
this.jtaTrackByThread = cfgService.getSetting( JTA_TRACK_BY_THREAD, BOOLEAN, true );
this.querySubstitutions = ConfigurationHelper.toMap( QUERY_SUBSTITUTIONS, " ,=;:\n\t\r\f", configurationSettings );
@ -1053,6 +1059,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return postInsertIdentifierDelayed;
}
@Override
public boolean areJPACallbacksEnabled() {
return callbacksEnabled;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// In-flight mutation access

View File

@ -437,4 +437,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
public int getQueryStatisticsMaxSize() {
return delegate.getQueryStatisticsMaxSize();
}
@Override
public boolean areJPACallbacksEnabled() {
return delegate.areJPACallbacksEnabled();
}
}

View File

@ -299,4 +299,9 @@ public interface SessionFactoryOptions {
default boolean isPostInsertIdentifierDelayableEnabled() {
return true;
}
default boolean areJPACallbacksEnabled() {
return true;
}
}

View File

@ -923,6 +923,14 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
*/
String ORDER_INSERTS = "hibernate.order_inserts";
/**
* JPA Callbacks are enabled by default. Set this to {@code false} to disable them.
* Mostly useful to save a bit of memory when they are not used.
* Experimental and will likely be removed as soon as the memory overhead is resolved.
* @since 5.4
*/
String JPA_CALLBACKS_ENABLED = "hibernate.jpa_callbacks.enabled";
/**
* Default precedence of null values in {@code ORDER BY} clause. Supported options: {@code none} (default),
* {@code first}, {@code last}.

View File

@ -43,12 +43,12 @@ import org.hibernate.event.service.spi.DuplicationStrategy;
import org.hibernate.event.service.spi.EventListenerRegistrationException;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.jpa.event.internal.CallbackBuilderLegacyImpl;
import org.hibernate.jpa.event.internal.CallbackRegistryImpl;
import org.hibernate.jpa.event.internal.CallbackRegistryImplementor;
import org.hibernate.jpa.event.internal.CallbacksFactory;
import org.hibernate.jpa.event.spi.CallbackBuilder;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.service.spi.Stoppable;
@ -96,7 +96,7 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
private Map<Class,Object> listenerClassToInstanceMap = new HashMap<>();
private final SessionFactoryImplementor sessionFactory;
private final CallbackRegistryImpl callbackRegistry;
private final CallbackRegistryImplementor callbackRegistry;
private final EventListenerGroupImpl[] registeredEventListeners;
private CallbackBuilder callbackBuilder;
@ -110,20 +110,16 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
ServiceRegistryImplementor registry) {
this.sessionFactory = sessionFactory;
this.callbackRegistry = new CallbackRegistryImpl();
this.callbackRegistry = CallbacksFactory.buildCallbackRegistry( sessionFactory );
this.registeredEventListeners = buildListenerGroups();
}
EventListenerRegistryImpl(BootstrapContext bootstrapContext, SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory;
this.callbackRegistry = new CallbackRegistryImpl();
this.callbackBuilder = new CallbackBuilderLegacyImpl(
bootstrapContext.getServiceRegistry().getService( ManagedBeanRegistry.class ),
bootstrapContext.getReflectionManager()
);
this.callbackRegistry = CallbacksFactory.buildCallbackRegistry( sessionFactory );
this.callbackBuilder = CallbacksFactory.buildCallbackBuilder(
sessionFactory, bootstrapContext.getReflectionManager() );
this.registeredEventListeners = buildListenerGroups();
}
@ -131,7 +127,7 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
return sessionFactory;
}
CallbackRegistryImpl getCallbackRegistry() {
CallbackRegistry getCallbackRegistry() {
return callbackRegistry;
}
@ -139,9 +135,7 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
public void prepare(MetadataImplementor metadata) {
if ( callbackBuilder == null ) {
// TODO : not needed anymore when the deprecate constructor will be removed
this.callbackBuilder = new CallbackBuilderLegacyImpl(
sessionFactory.getServiceRegistry().getService( ManagedBeanRegistry.class ),
metadata.getMetadataBuildingOptions().getReflectionManager()
this.callbackBuilder = CallbacksFactory.buildCallbackBuilder( sessionFactory, metadata.getMetadataBuildingOptions().getReflectionManager()
);
}
for ( PersistentClass persistentClass : metadata.getEntityBindings() ) {

View File

@ -40,13 +40,13 @@ import org.jboss.logging.Logger;
*
* @author Steve Ebersole
*/
public class CallbackBuilderLegacyImpl implements CallbackBuilder {
final class CallbackBuilderLegacyImpl implements CallbackBuilder {
private static final Logger log = Logger.getLogger( CallbackBuilderLegacyImpl.class );
private final ManagedBeanRegistry managedBeanRegistry;
private final ReflectionManager reflectionManager;
public CallbackBuilderLegacyImpl(ManagedBeanRegistry managedBeanRegistry, ReflectionManager reflectionManager) {
CallbackBuilderLegacyImpl(ManagedBeanRegistry managedBeanRegistry, ReflectionManager reflectionManager) {
this.managedBeanRegistry = managedBeanRegistry;
this.reflectionManager = reflectionManager;
}

View File

@ -11,9 +11,7 @@ import javax.persistence.PersistenceException;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jpa.event.spi.Callback;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.jpa.event.spi.CallbackType;
import org.hibernate.jpa.event.spi.CallbackBuilder;
/**
* Keep track of all lifecycle callbacks and listeners for a given persistence unit
@ -22,7 +20,7 @@ import org.hibernate.jpa.event.spi.CallbackBuilder;
* @author Steve Ebersole
*/
@SuppressWarnings({"unchecked", "serial"})
public class CallbackRegistryImpl implements CallbackRegistry, CallbackBuilder.CallbackRegistrar {
final class CallbackRegistryImpl implements CallbackRegistryImplementor {
private HashMap<Class, Callback[]> preCreates = new HashMap<Class, Callback[]>();
private HashMap<Class, Callback[]> postCreates = new HashMap<Class, Callback[]>();
private HashMap<Class, Callback[]> preRemoves = new HashMap<Class, Callback[]>();

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.jpa.event.internal;
import org.hibernate.jpa.event.spi.CallbackBuilder;
import org.hibernate.jpa.event.spi.CallbackRegistry;
public interface CallbackRegistryImplementor extends CallbackRegistry, CallbackBuilder.CallbackRegistrar {
void release();
}

View File

@ -0,0 +1,51 @@
/*
* 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.jpa.event.internal;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jpa.event.spi.CallbackBuilder;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
/**
* The intent of this class is to use a lighter implementation
* when JPA callbacks are disabled via
* {@link org.hibernate.boot.spi.SessionFactoryOptions#areJPACallbacksEnabled()}
*/
public final class CallbacksFactory {
public static CallbackRegistryImplementor buildCallbackRegistry(SessionFactoryImplementor sessionFactory) {
if ( jpaCallBacksEnabled( sessionFactory ) ) {
return new CallbackRegistryImpl();
}
else {
return new EmptyCallbackRegistryImpl();
}
}
public static CallbackBuilder buildCallbackBuilder(
SessionFactoryImplementor sessionFactory,
ReflectionManager reflectionManager) {
if ( jpaCallBacksEnabled( sessionFactory ) ) {
final ManagedBeanRegistry managedBeanRegistry = sessionFactory.getServiceRegistry().getService( ManagedBeanRegistry.class );
return new CallbackBuilderLegacyImpl(
managedBeanRegistry,
reflectionManager
);
}
else {
return new EmptyCallbackBuilder();
}
}
private static boolean jpaCallBacksEnabled(SessionFactoryImplementor sessionFactory) {
SessionFactoryOptions options = sessionFactory.getSessionFactoryOptions();
return options.areJPACallbacksEnabled();
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.jpa.event.internal;
import org.hibernate.jpa.event.spi.CallbackBuilder;
import org.hibernate.mapping.Property;
final class EmptyCallbackBuilder implements CallbackBuilder {
@Override
public void buildCallbacksForEntity(String entityClassName, CallbackRegistrar callbackRegistrar) {
//no-op
}
@Override
public void buildCallbacksForEmbeddable(Property embeddableProperty, String entityClassName, CallbackRegistrar callbackRegistrar) {
//no-op
}
@Override
public void release() {
//no-op
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.jpa.event.internal;
import org.hibernate.jpa.event.spi.Callback;
import org.hibernate.jpa.event.spi.CallbackType;
final class EmptyCallbackRegistryImpl implements CallbackRegistryImplementor {
@Override
public boolean hasRegisteredCallbacks(final Class entityClass, final CallbackType callbackType) {
return false;
}
@Override
public void preCreate(final Object entity) {
//no-op
}
@Override
public void postCreate(final Object entity) {
//no-op
}
@Override
public boolean preUpdate(final Object entity) {
return false;
}
@Override
public void postUpdate(final Object entity) {
//no-op
}
@Override
public void preRemove(final Object entity) {
//no-op
}
@Override
public void postRemove(final Object entity) {
//no-op
}
@Override
public boolean postLoad(final Object entity) {
return false;
}
@Override
public boolean hasPostCreateCallbacks(final Class entityClass) {
return false;
}
@Override
public boolean hasPostUpdateCallbacks(final Class entityClass) {
return false;
}
@Override
public boolean hasPostRemoveCallbacks(final Class entityClass) {
return false;
}
@Override
public boolean hasRegisteredCallbacks(final Class entityClass, final Class annotationClass) {
return false;
}
@Override
public void release() {
//no-op
}
@Override
public void registerCallbacks(Class entityClass, Callback[] callbacks) {
//no-op
}
}

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.jpa.test.callbacks;
import java.util.Date;
import java.util.Map;
import javax.persistence.EntityManager;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.jpa.test.Cat;
import org.hibernate.jpa.test.Kitten;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Sanne Grinovero
*/
@SuppressWarnings("unchecked")
public class CallbacksDisabledTest extends BaseEntityManagerFunctionalTestCase {
@Test
public void testCallbacksAreDisabled() throws Exception {
EntityManager em = getOrCreateEntityManager();
Cat c = new Cat();
c.setName( "Kitty" );
c.setDateOfBirth( new Date( 90, 11, 15 ) );
em.getTransaction().begin();
em.persist( c );
em.getTransaction().commit();
em.clear();
em.getTransaction().begin();
c = em.find( Cat.class, c.getId() );
assertTrue( c.getAge() == 0 ); // With listeners enabled this would be false. Proven by org.hibernate.jpa.test.callbacks.CallbacksTest.testCallbackMethod
em.getTransaction().commit();
em.close();
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[]{
Cat.class,
Translation.class,
Television.class,
RemoteControl.class,
Rythm.class,
Plant.class,
Kitten.class
};
}
@Override
protected void addConfigOptions(Map options) {
options.put( AvailableSettings.JPA_CALLBACKS_ENABLED, "false" );
}
}