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 tes开发者_StackOverflow社区ts 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.
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 );
}
}
}
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.
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.)
First I want to add this related documentation here. https://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html/listeners.html
You can use xml configuration instead of annotations, It will allow more flexibility.
In some scenarios, maybe not yours, there is the posiblity to extend your implementation class and add the @ExcludeSuperclassListeners. Then test around the new implementation class.
精彩评论