Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to disable JPA 2 callback methods and entity listeners

I have some classes (entities) with methods annotated by @PostLoad, @PrePersist etc. and classes has @EntityListeners annotation. I want to disable processing of callback methods and listeners in my test environment.

Removing part of code is impossible because tests are run on CI server and I don't have any possibility to change code just for tests.

Is there any possibility to turn off callback methods and entity listeners in JPA 2 configuration? I use Hibernate.

like image 531
Koziołek Avatar asked Mar 23 '11 14:03

Koziołek


3 Answers

If you want to remove all JPA listeners from Hibernate 4.3.5 (the only one I have tested) it can be done. I will not show how to get hold of the EntityMangerFactory (emf in below code) but after that the below code should be added/run.


Explanation: it seems there is a very central class called org.hibernate.jpa.event.internal.jpa.CallbackRegistryImpl that contains all registered listeners and callbacks on the entities. By replacing the registry with an empty one no callbacks will be performed.

SessionFactoryImpl sessionFactory = (SessionFactoryImpl) ((EntityManagerFactoryImpl) emf).getSessionFactory();
EventListenerRegistry eventListenerRegistry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);

CallbackRegistryImpl emptyRegistry= new CallbackRegistryImpl();

for ( EventType eventType : EventType.values() ) {
  final EventListenerGroup eventListenerGroup = eventListenerRegistry.getEventListenerGroup( eventType );
  for ( Object listener : eventListenerGroup.listeners() ) {
    if ( CallbackRegistryConsumer.class.isInstance( listener ) ) {
      ( (CallbackRegistryConsumer) listener ).injectCallbackRegistry( emptyRegistry );
    }
  }
}
like image 54
Konstantin Avatar answered Nov 06 '22 02:11

Konstantin


I'm pretty sure this isn't possible. The annotated-entity mechanism is very static, which is why I stopped using it.

What I did instead in similar situations was to define interfaces for the entities to implement, something like this:

interface UpdateValidation{
    void preUpdate();
}

interface PersistValidation{
    void prePersist();
}

// etc.

Now define a single EntityListener that checks the entities for the above interfaces. In the @PreUpdate method, check for UpdateValidation, in the @PrePersist method, check for PersistValidation. Then delegate to the entity methods.

That way you have a single Listener that controls the entire functionality, and you can turn that listener on or off with XML configuration.


Update here's a complete implementation:

public class DelegatingEntityListener{

    public interface PrePersistSupport{
        void prePersist();
    }

    public interface PostPersistSupport{
        void postPersist();
    }

    public interface PreRemoveSupport{
        void preRemove();
    }

    public interface PostRemoveSupport{
        void postRemove();
    }

    public interface PreUpdateSupport{
        void preUpdate();
    }

    public interface PostUpdateSupport{
        void postUpdate();
    }

    public interface PostLoadSupport{
        void postLoad();
    }

    @PrePersist
    public void prePersist(final Object entity){
        if(entity instanceof PrePersistSupport){
            ((PrePersistSupport) entity).prePersist();
        }
    }

    @PostPersist
    public void postPersist(final Object entity){
        if(entity instanceof PostPersistSupport){
            ((PostPersistSupport) entity).postPersist();
        }
    }

    @PreRemove
    public void preRemove(final Object entity){
        if(entity instanceof PreRemoveSupport){
            ((PreRemoveSupport) entity).preRemove();
        }
    }

    @PostRemove
    public void postRemove(final Object entity){
        if(entity instanceof PostRemoveSupport){
            ((PostRemoveSupport) entity).postRemove();
        }
    }

    @PreUpdate
    public void preUpdate(final Object entity){
        if(entity instanceof PreUpdateSupport){
            ((PreUpdateSupport) entity).preUpdate();
        }
    }

    @PostUpdate
    public void postUpdate(final Object entity){
        if(entity instanceof PostUpdateSupport){
            ((PostUpdateSupport) entity).postUpdate();
        }
    }

    @PostLoad
    public void postLoad(final Object entity){
        if(entity instanceof PostLoadSupport){
            ((PostLoadSupport) entity).postLoad();
        }
    }

}

And in case you wondered: no, I didn't write the code by hand. Here's the code that wrote that code :-) You can easily adjust it to your own needs.

public static void main(final String[] args){

    final StringBuilder ib = new StringBuilder(); // interface builder
    final StringBuilder sb = new StringBuilder(); // method builder
    for(final Class<? extends Annotation> annotationType : Arrays
        .asList(
            // all lifecycle annotations:
            PrePersist.class, PostPersist.class, PreRemove.class,
            PostRemove.class, PreUpdate.class, PostUpdate.class,
            PostLoad.class)){

        final String annotationName = annotationType.getSimpleName();
        final String lower =
            annotationName
                .substring(0, 1)
                .toLowerCase()
                .concat(annotationName.substring(1));

        ib.append("public interface ")
            .append(annotationName)
            .append("Support{\n\tvoid ")
            .append(lower)
            .append("();\n}\n\n");

        sb.append('@')
            .append(annotationName)
            .append(" public void ")
            .append(lower)
            .append("(Object entity){\nif(entity instanceof ")
            .append(annotationName)
            .append("Support){((")
            .append(annotationName)
            .append("Support)entity).")
            .append(lower)
            .append("();}}\n\n");

    }
    System.out.println(ib.toString());
    System.out.println(sb.toString());

}

The drawback is of course that the JPA provider can't cache the lifecycle methods that are actually used, but I'd say this is the only way to get what you want / need.

like image 27
Sean Patrick Floyd Avatar answered Nov 06 '22 02:11

Sean Patrick Floyd


I recently had the same problem with entities annotated with @EntityListeners using EclipseLink. Here is what I have done to remove the listeners:

private fun disableEntityListeners(entityManager: EntityManager) {
    entityManager.metamodel.entities
            .map { it as EntityTypeImpl<*> }
            .forEach {
                it.descriptor.eventManager.entityListenerEventListeners.clear()
            }
}

(It’s Kotlin code but you get the gist.)

like image 32
Bombe Avatar answered Nov 06 '22 03:11

Bombe