In my previous article JPA Auditing: Persisting Audit Logs Automatically using EntityListeners, I have discussed how we can use Spring Data JPA automate Auditing and automatically create audit logs or history records and update CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate properties.
So in order to save history records for our File entity, we were trying to auto-wire EntityManager inside our FileEntityListener class and we have come to know that we can not do this.
We can not inject any Spring-managed bean in the EntityListener because EntityListeners are instantiated by JPA before Spring inject anything into it. EntityListeners are not managed Spring so Spring cannot inject any Spring-managed bean e.g. EntityManager in the EntityListeners.
And this case is not just with EntityListeners, you can not auto wire any Spring-managed bean into another class (i.e. utility classes) which is not managed by Spring.
Because it is a very common problem and can also arise with other classes so I tried to come out with a common solution which will not just solve this problem but will also help us getting Spring managed beans in other places.
So I have created one utility class to fetch any bean according to our requirement.
Now to get any a bean in class we will just need call the BeanUtil.getBean(YourClass.class) and pass the class type to it and we will get the bean.
For Example in our case, we were trying to get the EntityManager bean inside FileEntityListener, we can simply do it by writing BeanUtil.getBean(EntityManager.class).
You can find complete code on this Github Repository and please feel free to provide your valuable feedback.
So in order to save history records for our File entity, we were trying to auto-wire EntityManager inside our FileEntityListener class and we have come to know that we can not do this.
We can not inject any Spring-managed bean in the EntityListener because EntityListeners are instantiated by JPA before Spring inject anything into it. EntityListeners are not managed Spring so Spring cannot inject any Spring-managed bean e.g. EntityManager in the EntityListeners.
And this case is not just with EntityListeners, you can not auto wire any Spring-managed bean into another class (i.e. utility classes) which is not managed by Spring.
Because it is a very common problem and can also arise with other classes so I tried to come out with a common solution which will not just solve this problem but will also help us getting Spring managed beans in other places.
So I have created one utility class to fetch any bean according to our requirement.
@Service
public class BeanUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
Now to get any a bean in class we will just need call the BeanUtil.getBean(YourClass.class) and pass the class type to it and we will get the bean.
For Example in our case, we were trying to get the EntityManager bean inside FileEntityListener, we can simply do it by writing BeanUtil.getBean(EntityManager.class).
public class FileEntityListener {
private void perform(File target, Action action) {
EntityManager entityManager = BeanUtil.getBean(EntityManager.class);
entityManager.persist(new FileHistory(target, action));
}
}
You can find complete code on this Github Repository and please feel free to provide your valuable feedback.
hello, I've tried this and got an NullPointerException for the ApplicationContext object (the static one).
ReplyDeleteHi Agustinus, you should not get any problems in it.
DeleteHowever you find the complete code on https://github.com/njnareshjoshi/articles/tree/master/spring-data-jpa-auditing
In class BeanUtil the context has to be set first otherwise the npe would be thrown when getBean method is invoked. I assume this is the problem that Agustinus has. In your repo I have not found the place where you provide this context to the utility class.
ReplyDelete`BeanUtil` class is implementing `ApplicationContextAware` and overriding `setApplicationContext` method which automatically get called and set the applicationContext in the `BeanUtil`. I have executed the repo's code and it working fine, please tell me if you are getting the problem while executing it.
DeleteFor the Multi module spring boot projects where beanUtil class should be place?
ReplyDeleteIt depends on the project architecture and from where you want access this code. If you want to access it from multiple modules then BeanUtil class should be in a module which is visible from other modules. Some kind of core module which other module can access.
DeleteI am getting RuntimeException
DeleteNo EntityManager with actual transaction available for current thread
do you have any idea why?