diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 9712744e77..23acf0cbac 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 480d4b1ffc..0f2f4c14bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -437,4 +437,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp public int getQueryStatisticsMaxSize() { return delegate.getQueryStatisticsMaxSize(); } + + @Override + public boolean areJPACallbacksEnabled() { + return delegate.areJPACallbacksEnabled(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index dd65549056..99f8667886 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -299,4 +299,9 @@ public interface SessionFactoryOptions { default boolean isPostInsertIdentifierDelayableEnabled() { return true; } + + default boolean areJPACallbacksEnabled() { + return true; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 7e95845d9f..f8a2379d5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -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}. diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java index a3b9751f04..3a847044a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java @@ -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 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() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java index 1a6851ba3c..d8bb075281 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackBuilderLegacyImpl.java @@ -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; } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImpl.java index 0a50d33de1..90328f13bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImpl.java @@ -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 preCreates = new HashMap(); private HashMap postCreates = new HashMap(); private HashMap preRemoves = new HashMap(); diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImplementor.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImplementor.java new file mode 100644 index 0000000000..6805b73b7e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackRegistryImplementor.java @@ -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 . + */ +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(); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbacksFactory.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbacksFactory.java new file mode 100644 index 0000000000..3365c5a44e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbacksFactory.java @@ -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(); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmptyCallbackBuilder.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmptyCallbackBuilder.java new file mode 100644 index 0000000000..dbc557e0d8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmptyCallbackBuilder.java @@ -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 + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmptyCallbackRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmptyCallbackRegistryImpl.java new file mode 100644 index 0000000000..fc23e51666 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmptyCallbackRegistryImpl.java @@ -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 + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/CallbacksDisabledTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/CallbacksDisabledTest.java new file mode 100644 index 0000000000..86b508bcfa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/CallbacksDisabledTest.java @@ -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 . + */ +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" ); + } + +}