How can I add an entity listener to a JPA (EclipseLink) object without an active session?

I am trying to do the following inside a spring bean:

@PostConstruct
public void registerTorchEntityListeners()
{
    Session session = entityManager.unwrap(Session.class);
    for (EntityType<?> entity : entityManager.getMetamodel().getEntities())
    {
        if (entity.getJavaType().isAnnotationPresent(TorchEntityListeners.class))
        {
             TorchEntityListeners annotation = (TorchEntityListeners) entity.getJavaType().getAnnotation(TorchEntityListeners.class);
             for (Class listenerClass : annotation.value())
             {
                 Map<String, DescriptorEventListener> map = applicationContext.getBeansOfType(listenerClass);
                 for (DescriptorEventListener listenerBean : map.values())
                 {
                     session.getClassDescriptor(entity.getClass()).getEventManager().addListener(listenerBean);
                 }
             }
        }
    }

}

      

The problem is that I am getting the following exception because (I think) I am not a transaction and therefore do not have a session to capture the ClassDescriptor class to add a listener to a specific object:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'torchEntityListenerConfigurer': Invocation of init method failed; nested exception is java.lang.IllegalStateException: No transactional EntityManager available
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:396)

      

Basically I'm trying to make the EclipseLink equivalent of this: http://invariantproperties.com/2013/09/29/spring-injected-beans-in-jpa-entitylisteners/ . I would rather annotate the entity with a listener rather than doing something like this: Inject spring dependency into JPA EntityListener .

Thoughts?

+3


source to share


2 answers


Of course I'll figure it out 30 minutes after I add the bounty :)

I finally got this to work by getting the entityManager from wired to EntityManagerFactory instead of using: @PersistenceContext to inject it into TorchEntityListenerConfigurer

Here is a working solution ... and it works great!

Here is the configuration:

<bean id="approvalEntityListener" class="com.prometheus.torchlms.core.activity.approval.ApprovalEntityListener">
    <property name="activityRepository" ref="activityRepository" />
    <property name="notificationFactory" ref="notificationFactory" />
    <property name="notificationService" ref="notificationService" />
</bean>
<bean id="springEntityListenerConfigurer" class="com.prometheus.torchlms.core.SpringEntityListenerConfigurer">
    <constructor-arg ref="entityManagerFactory" />
</bean>

      



Here's where the magic happens (in case it's helpful to anyone):

public class SpringEntityListenerConfigurer implements ApplicationContextAware
{
    private ApplicationContext applicationContext;
    private EntityManagerFactory entityManagerFactory;

    public SpringEntityListenerConfigurer(EntityManagerFactory entityManagerFactory)
    {
        this.entityManagerFactory = entityManagerFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException
    {
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void registerTorchEntityListeners()
    {
        //entityManager.
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        Session session = entityManager.unwrap(Session.class);
        for (EntityType<?> entity : entityManagerFactory.getMetamodel().getEntities())
        {
            if (entity.getJavaType().isAnnotationPresent(SpringEntityListeners.class))
            {
                 SpringEntityListeners annotation = (SpringEntityListeners) entity.getJavaType().getAnnotation(SpringEntityListeners.class);
                 for (Class listenerClass : annotation.value())
                 {
                     Map<String, DescriptorEventListener> map = applicationContext.getBeansOfType(listenerClass);
                     for (DescriptorEventListener listenerBean : map.values())
                     {
                         ClassDescriptor classDescriptor = session.getClassDescriptor(entity.getJavaType());
                         if (null != classDescriptor)
                         {
                             classDescriptor.getEventManager().addListener(listenerBean);
                         }
                     }
                 }
            }
        }

    }
}

      

So now I can add mine @SpringEntityListeners({ApprovalEntityListener.class})

to any object I want, with any listener I want, and those listeners can be spring beans!

In case it helps here, annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SpringEntityListeners
{
    Class<?>[] value();
}

      

+3


source


You can actually do your registration without getting the EntityManager because you only use it to get the client session and only use the client session to register listeners with the server session without directly interacting with the database and without modifying any object.

EntityManagerFactory

in fact is EntityManagerFactoryImpl

, which can directly show ServerSession

with unwrap

. I had to dig through the classes explicitly marked as INTERNAL to find this, and ServerSession

(also marked as INTERNAL) explicitly states in its javadoc: All changes to objects and database should be done with a unit of work obtained from the client session, this allows make changes to the space of transactional objects and under an exclusive connection to the database.



But for your use case, I find it correct to use it like this, using the access-only server session Project

, which actually contains the class descriptors, and is a public class in EclipseLink:

public void registerTorchEntityListeners()
{
    // no entityManager here and Session is only used to get access to Project
    Project proj = entityManagerFactory.unwrap(Session.class).getProject();
    for (EntityType<?> entity : entityManagerFactory.getMetamodel().getEntities())
    {
        if (entity.getJavaType().isAnnotationPresent(SpringEntityListeners.class))
        {
             SpringEntityListeners annotation = (SpringEntityListeners) entity.getJavaType().getAnnotation(SpringEntityListeners.class);
             for (Class listenerClass : annotation.value())
             {
                 Map<String, DescriptorEventListener> map = applicationContext.getBeansOfType(listenerClass);
                 for (DescriptorEventListener listenerBean : map.values())
                 {
                     ClassDescriptor classDescriptor = proj.getClassDescriptor(entity.getJavaType());
                     if (null != classDescriptor)
                     {
                         classDescriptor.getEventManager().addListener(listenerBean);
                     }
                 }
             }
        }
    }
}

      

+1


source







All Articles