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

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 I get the following exception because (I think) I am not in a transaction and therefore do not have a session available to grab the ClassDescriptor so that I can add a listener to a particular entity:
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 am trying to do the EclipseLink equivalent of this: http://invariantproperties.com/2013/09/29/spring-injected-beans-in-jpa-entitylisteners/. I would prefer to annotate the entity with the listener rather than doing something like this: Injecting a Spring dependency into a JPA EntityListener.
Thoughts?

Of course I figure it out 30 minutes after I add a bounty :)
I finally got this to work by getting the entityManager from a wired in EntityManagerFactory instead of using: #PersistenceContext to inject it into the TorchEntityListenerConfigurer
Here is the working solution...and it works great!
Here is the config:
<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 is where the magic happens (in case this is useful to someone):
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 my #SpringEntityListeners({ApprovalEntityListener.class}) to any entity I want with any listener that I want and those listeners can be spring beans!
In case it helps here's the annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface SpringEntityListeners
{
Class<?>[] value();
}

In fact you can do your registrations without getting an EntityManager, because you only use it to get a client session, and you only use the client session to register listeners with the server session without any direct interaction with the database nor changing any object.
The EntityManagerFactory is actually an EntityManagerFactoryImpl that can directly expose the ServerSession with unwrap. I had to dig through classes explicitly marked as INTERNAL, to find that, and the ServerSession (also marked as INTERNAL) explicitely states in his javadoc : All changes to objects and the database must be done through a unit of work acquired from the client session, this allows the changes to occur in a transactional object space and under a exclusive database connection.
But for your use case, I think it is correct to use it like that, using the server session only to get access to the Project that 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);
}
}
}
}
}
}

Related

Spring JPA: javax.persistence.TransactionRequiredException: no transaction is in progress [duplicate]

I am new to Spring Transaction. Something that I found really odd, probably I did understand this properly.
I wanted to have a transactional around method level and I have a caller method within the same class and it seems like it does not like that, it has to be called from the separate class. I don't understand how is that possible.
If anyone has an idea how to resolve this issue, I would greatly appreciate. I would like to use the same class to call the annotated transactional method.
Here is the code:
public class UserService {
#Transactional
public boolean addUser(String userName, String password) {
try {
// call DAO layer and adds to database.
} catch (Throwable e) {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
public boolean addUsers(List<User> users) {
for (User user : users) {
addUser(user.getUserName, user.getPassword);
}
}
}
It's a limitation of Spring AOP (dynamic objects and cglib).
If you configure Spring to use AspectJ to handle the transactions, your code will work.
The simple and probably best alternative is to refactor your code. For example one class that handles users and one that process each user. Then default transaction handling with Spring AOP will work.
Configuration tips for handling transactions with AspectJ
To enable Spring to use AspectJ for transactions, you must set the mode to AspectJ:
<tx:annotation-driven mode="aspectj"/>
If you're using Spring with an older version than 3.0, you must also add this to your Spring configuration:
<bean class="org.springframework.transaction.aspectj
.AnnotationTransactionAspect" factory-method="aspectOf">
<property name="transactionManager" ref="transactionManager" />
</bean>
In Java 8+ there's another possibility, which I prefer for the reasons given below:
#Service
public class UserService {
#Autowired
private TransactionHandler transactionHandler;
public boolean addUsers(List<User> users) {
for (User user : users) {
transactionHandler.runInTransaction(() -> addUser(user.getUsername, user.getPassword));
}
}
private boolean addUser(String username, String password) {
// TODO call userRepository
}
}
#Service
public class TransactionHandler {
#Transactional(propagation = Propagation.REQUIRED)
public <T> T runInTransaction(Supplier<T> supplier) {
return supplier.get();
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public <T> T runInNewTransaction(Supplier<T> supplier) {
return supplier.get();
}
}
This approach has the following advantages:
It may be applied to private methods. So you don't have to break encapsulation by making a method public just to satisfy Spring limitations.
Same method may be called within different transaction propagations and it is up to the caller to choose the suitable one. Compare these 2 lines:
transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
It is explicit, thus more readable.
The problem here is, that Spring's AOP proxies don't extend but rather wrap your service instance to intercept calls. This has the effect, that any call to "this" from within your service instance is directly invoked on that instance and cannot be intercepted by the wrapping proxy (the proxy is not even aware of any such call). One solutions is already mentioned. Another nifty one would be to simply have Spring inject an instance of the service into the service itself, and call your method on the injected instance, which will be the proxy that handles your transactions. But be aware, that this may have bad side effects too, if your service bean is not a singleton:
<bean id="userService" class="your.package.UserService">
<property name="self" ref="userService" />
...
</bean>
public class UserService {
private UserService self;
public void setSelf(UserService self) {
this.self = self;
}
#Transactional
public boolean addUser(String userName, String password) {
try {
// call DAO layer and adds to database.
} catch (Throwable e) {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
public boolean addUsers(List<User> users) {
for (User user : users) {
self.addUser(user.getUserName, user.getPassword);
}
}
}
With Spring 4 it's possible to Self autowired
#Service
#Transactional
public class UserServiceImpl implements UserService{
#Autowired
private UserRepository repository;
#Autowired
private UserService userService;
#Override
public void update(int id){
repository.findOne(id).setName("ddd");
}
#Override
public void save(Users user) {
repository.save(user);
userService.update(1);
}
}
This is my solution for self invocation:
public class SBMWSBL {
private SBMWSBL self;
#Autowired
private ApplicationContext applicationContext;
#PostConstruct
public void postContruct(){
self = applicationContext.getBean(SBMWSBL.class);
}
// ...
}
You can autowired BeanFactory inside the same class and do a
getBean(YourClazz.class)
It will automatically proxify your class and take into account your #Transactional or other aop annotation.
Here is what I do for small projects with only marginal usage of method calls within the same class. In-code documentation is strongly advised, as it may look strange to colleagues. But it works with singletons, is easy to test, simple, quick to achieve and spares me the full blown AspectJ instrumentation. However, for more heavy usage I'd advice the AspectJ solution as described in Espens answer.
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class PersonDao {
private final PersonDao _personDao;
#Autowired
public PersonDao(PersonDao personDao) {
_personDao = personDao;
}
#Transactional
public void addUser(String username, String password) {
// call database layer
}
public void addUsers(List<User> users) {
for (User user : users) {
_personDao.addUser(user.getUserName, user.getPassword);
}
}
}
The issue is related to how spring load classes and proxies. It will not work , untill you write your inner method / transaction in another class or go to other class and then again come to your class and then write the inner nested transcation method.
To summarize, spring proxies does not allow the scenarios which you are facing. you have to write the 2nd transaction method in other class
There is no point to use AspectJ or Other ways. Just using AOP is sufficient. So, we can add #Transactional to addUsers(List<User> users) to solve current issue.
public class UserService {
private boolean addUser(String userName, String password) {
try {
// call DAO layer and adds to database.
} catch (Throwable e) {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
#Transactional
public boolean addUsers(List<User> users) {
for (User user : users) {
addUser(user.getUserName, user.getPassword);
}
}
}

Dynamic context - autowiring

I am dynamically adding contexts to the application context with the following code:
#Component
#Scope("singleton")
public class DynamicContextLoader implements ApplicationContextAware {
private static ApplicationContext context;
private Map<String, InterfacePropertyDto> contextMap;
#Autowired
IJpaDao jpaDao;
#PostConstruct
public void init() {
contextMap = (Map<String, InterfacePropertyDto>) context.getBean("contextMap");
contextMap.forEach((contextName, property) -> {
String p = jpaDao.getProperty(property.getPropertyName(), property.getPropertyType());
if (p != null) {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[]{"/META-INF/spring/integration/" + contextName},
false, context);
ctx.refresh();
}
});
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
}
This works well and all the beans defined in the new context are created. However the #Autowired does not work for any of these new beans.
For example a bean defined in the new context as:
<bean id="outboundContractJdbcFileMapper" class="com.......integration.model.contract.ContractMapper"/>
has the following autowiring:
public class ContractMapper implements RowMapper<ContractFile> {
#Autowired
IIntegrationDao integrationDao;
#Override
public ContractFile mapRow(ResultSet rs, int rowNum) throws SQLException {
......
}
}
At runtime the outboundContractJdbcFileMapper property integrationDao is null.
Is there a way to force the autowiring to occur when the beans are created? I was hoping that ctx.refresh() would do this.
That doesn't work automatically for the ClassPathXmlApplicationContext. You have to add <context:annotation-config/> to that child context as well:
<xsd:element name="annotation-config">
<xsd:annotation>
<xsd:documentation><![CDATA[
Activates various annotations to be detected in bean classes: Spring's #Required and
#Autowired, as well as JSR 250's #PostConstruct, #PreDestroy and #Resource (if available),
JAX-WS's #WebServiceRef (if available), EJB 3's #EJB (if available), and JPA's
#PersistenceContext and #PersistenceUnit (if available). Alternatively, you may
choose to activate the individual BeanPostProcessors for those annotations.
Note: This tag does not activate processing of Spring's #Transactional or EJB 3's
#TransactionAttribute annotation. Consider the use of the <tx:annotation-driven>
tag for that purpose.
See javadoc for org.springframework.context.annotation.AnnotationConfigApplicationContext
for information on code-based alternatives to bootstrapping annotation-driven support.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>

EJB with spring : transaction issue in JPA flush

I have an issue with an injected EntityManager in my MessageDriven Bean that use spring bean as services (the bootstrap is done with the SpringBeanAutowiringInterceptor.
Here is the code :
#MessageDriven(name = "ProcessMDB")
#Interceptors(SpringBeanAutowiringInterceptor.class)
public class ProcessMDB implements MessageListener {
#Autowired
private ProcessService processService;
#Override
public void onMessage(Message message) {
try {
id = message.getLongProperty("ID");
processService.process(id);
} catch (Exception e) {
// Handle error.
}
}
The process service has a DAO where the EntityManager is injected with the annotation #PersistentContext...
The problem is that if a JPA error occurs in the processService, it may occur during the entityManager.flush() call... so the try catch block is gone and the //Handle error stuff is not done.
So I tried to add manually the flush.
#MessageDriven(name = "ProcessMDB")
#Interceptors(SpringBeanAutowiringInterceptor.class)
public class ProcessMDB implements MessageListener {
#Autowired
private ProcessService processService;
#PersistenceContext
private EntityManager em;
#Override
public void onMessage(Message message) {
try {
id = message.getLongProperty("ID");
processService.process(id);
em.flush();
} catch (Exception e) {
// Handle error.
}
}
But it seems that the flush has no effect.
I've try to add the em.flush in the underlying DAO (just after the persist for instance) and it works! The exception is well raised and the catch block is executed. But it doesn't work if I put the em.flush() at the MessageDrivenBean level.
I think that it's transaction manager problem... The entitymanager in spring beans is not in the same tx than the injected entity manager in my ejb.
If I make em.find in the onMessage() method, the fetched object holds old values (the one in the database), not values that are changed in the service method.
I've configured my database as followed :
<jee:jndi-lookup id="emf" jndi-name="persistence/PUnit" />
and my tx manager as followed :
<tx:annotation-driven/>
<tx:jta-transaction-manager />
What do I wrong?
Can someonee help me?
Thanks
Stéphane
You have injected EntityManager in ProcessMDB, but the object is being persisted in ProcessService.
Now, how can any operation in ProcessMDB can affect ProcessService, both will have probably their own individual EntityManager.
Scope of PersistenceContext is upto associated EntityManager. The object will be in the context of ProcessService & not in ProcessMDB, therefore calling flush on later will have no effect & this is expected behaviour.

Accessing spring bean from logging appender class

I have log4j DailyRollingFileAppender class in which setFile() method I need to check database value to decide which file to used for logging.
DailyRollingFileAppender class
public void setFileName()
{
isLoginEnabled = authenticationManager.checkLoginLogging();
}
Here 'authenticationManager' is object of class used to make database call using spring dependency injection feature.
spring-beans.xml
<bean id="dailyRollingFileAppender" class="com.common.util.DailyRollingFileAppender">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
</bean>
<bean id="authenticationManager" class="com.security.impl.AuthenticationManagerImpl">
<property name="userService">
<ref bean="userService"/>
</property>
</bean>
Now when I start my application log4j gets initiated first and since spring-beans is yet to invoked it throws NullPointerException in method setFileName().
So is there a way I can make call to 'authenticationManager.checkLoginLogging();' from DailyFileAppender class so that when log4j loads it should able to get database value?
A few years late, but I hope this is of help to someone.
I was after similar functionality - I have a custom appender, and i wanted to use an autowired bean to perform some logging using a service we'd built. By making the appender implement the ApplicationContextAware interface, and making the field that i'd normally autowire static, i'm able to inject the spring-controlled bean into the instance of the appender that log4j has instantiated.
#Component
public class SslErrorSecurityAppender extends AppenderSkeleton implements ApplicationContextAware {
private static SecurityLogger securityLogger;
#Override
protected void append(LoggingEvent event) {
securityLogger.log(new SslExceptionSecurityEvent(SecurityEventType.AUTHENTICATION_FAILED, event.getThrowableInformation().getThrowable(), "Unexpected SSL error"));
}
#Override
public boolean requiresLayout() {
return false;
}
#Override
public synchronized void close() {
this.closed = true;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
if (applicationContext.getAutowireCapableBeanFactory().getBean("securityLogger") != null) {
securityLogger = (SecurityLogger) applicationContext.getAutowireCapableBeanFactory().getBean("securityLogger");
}
}
}

HibernateTemplate is null

I am making a web application on STS. I am using jars of Spring 3.1.0 and HIbernate 4.0.1. I am including jars in project build path.
In DAO layer when I am trying to make a HibernateTemplate object , it is not getting instantiate, It is null there. I don't understand why it is null.
Earlier I was getting an error like NoClassDefinitionFound: org.springframework.orm.hibernate3.HibernateTemplate....Then I included these jars in WEB-INF->lib folder and then this error was removed but still hibernateTemplate object is null. Can there be any issue regarding position of beans.xml in project folders. ? Can anyone help me.
Below is code for my beans.xml and Userinfo.java.
[b]Beans.xml[/b]
Only relevant part of bean.xml
<bean id="hibTemplateBean" class="org.springframework.orm.hibernate3.HibernateTemplate" >
<property name="sessionFactory" ref="sfBean" />
</bean>
[b]UserinfoDao.java[/b]
package com.home.dao;
import org.springframework.orm.hibernate3.HibernateTemplate;
import com.home.pojo.User;
public class UserinfoDao {
public UserinfoDao() {
super();
}
private static HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
UserinfoDao.hibernateTemplate = hibernateTemplate;
}
public static void fetchUserInfo(){
try{
User user = (User)hibernateTemplate.get(User.class, 111);
}catch(NullPointerException npe){
npe.printStackTrace();
}
}
}
You are using Hibernate 4 and you are using org.springframework.orm.hibernate3.HibernateTemplate from Hibernate 3 package.
This template is not supported anymore. You can do declarative transaction management.
Your version makes me believe that you are simply accessing your bean with static method:
UserinfoDao.fetchUserInfo();
Looks like you are missing few key points of Spring. First of all you should not use static methods and fields, try with the following class:
public class UserinfoDao {
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
public void fetchUserInfo(){
try{
User user = (User)hibernateTemplate.get(User.class, 111);
}catch(NullPointerException npe){
npe.printStackTrace();
}
}
}
Now you need to obtain an instance of Spring bean somehow. You cannot simply use new UserinfoDao() operator. You must either declare your bean in XML or via annotations (and declare dependency on HibernateTemplate.
Finally you shouldn't normally catch NullPointerException, but I understand this if for ddebugging purposes.

Resources