Spring Boot #Transactional not working when on #Service class (instead of controller) - spring

I thought it was best practice to put the #Transactional annotation on the service layer classes and not on the controllers (see f.e. Why we shouldn't make a Spring MVC controller #Transactional?). But this not working on my Spring Boot application. Why is that?
The registerAction method in the controller (see code below) performs multiple service calls. When f.e. the mailService.sendActivationMail(...) fails, I want to rollback the inserted user from the userService.registerUser(...) call. Do I need to put the #Transactional annotation on the controller class or not?
My Spring Boot application correctly uses transactions when the #Transactional annotation is set on the controller class:
AuthController.java
#RestController
#RequestMapping("/api/auth")
#Transactional
public class AuthController {
#Autowired
private UserService userService;
#Autowired
private ProfileService profileService;
#Autowired
private MailService mailService;
#RequestMapping(path = "register", method = RequestMethod.POST)
public Profile registerAction(#Valid #RequestBody Registration registration) {
ApiUser user = userService.registerUser(registration);
Profile profile = profileService.createProfile(user, registration);
mailService.sendActivationMail(user);
return profile;
}
}
but transactions don't work when the #Transactional annotation is set on the Service classes instead (and not on the controller):
UserService.java
#Service
#Transactional
public class UserService {
#Autowired
private ApiUserRepository userRepository;
public ApiUser registerUser(Registration registration) {
...
userRepository.save(user);
...
}
}
My configuration classes:
SpringApiApplication.java
#SpringBootApplication
public class SpringApiApplication {
public static void main(String[] args) {
SpringApplication.run(SpringApiCommonApplication.class, args);
}
}
ApiConfiguration.java
#Configuration
#EnableJpaAuditing
#EnableTransactionManagement
public class ApiConfiguration {
#Autowired
private ApiProperties properties;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public UsernameCanonicalizer usernameCanonicalizer() {
return new UsernameCanonicalizer();
}
#Bean
public EmailCanonicalizer emailCanonicalizer() {
return new EmailCanonicalizer();
}
#Bean
public ApiTokenHandler activationTokenHandler() {
return new StandardApiTokenHandler(properties.getActivationTokenDuration());
}
}

#M.Deinum got me on the right track. Spring (boot) doesn't automatically wrap your controller calls in a transaction like other frameworks do. So you have to either add a #Transactional annotation to your controller, or move the code from the controller class to a service class.
Moving the code from the controller class to a service class is the better thing to do since (amongst other things) makes the code better testable. So that's what I did.
AuthenticationService.java
#Service
public class AuthenticationService {
#Autowired
private UserManager userManager;
#Autowired
private ProfileManager profileManager;
#Autowired
private MailManager mailManager;
#Transactional
public Profile registerUser(Registration registration) {
ApiUser user = userManager.registerUser(registration);
Profile profile = profileManager.createProfile(user, registration);
mailManager.sendActivationMail(user);
return profile;
}
...
}
AuthController.java
#RestController
#RequestMapping("/api/auth")
public class AuthController {
#Autowired
private AuthenticationService authenticationService;
#RequestMapping(path = "register", method = RequestMethod.POST)
#ApiOperation(value = "Registers a user")
public Profile register(#Valid #RequestBody Registration registration) {
return authenticationService.registerUser(registration);
}
...
}

You can place the #Transactional annotation before an interface definition, a method on an interface, a class definition, or a public method on a class. However, the mere presence of the #Transactional annotation is not enough to activate the transactional behavior.
The #Transactional annotation is simply metadata that can be consumed by some runtime infrastructure that is #Transactional-aware and that can use the metadata to configure the appropriate beans with transactional behavior.
In the preceding example, the <tx:annotation-driven/> element switches on the transactional behavior.
// Below is the service class that we want to make transactional
#Transactional
public class DefaultEmployeeService implements EmployeeService {
void insertEmployee(Employee Employee);
void updateEmployee(Employee Employee);
}
XML Configuration:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" ...>
<!-- this is the service object that we want to make transactional -->
<bean id="employeeService" class="service.DefaultEmployeeService"/>
<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (this dependency is defined somewhere else) -->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
Some points that you can try:
Do all above mentioned configurations.
Remove #Transactional configuration from Controller.
Reference the Service object along with Transaction manager bean.
More Details: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

Related

BeanNotOfRequiredTypeException with PlatformTransactionManager

I'm new to Spring and I did a login/register applicaton following a youtube tutorial but I want to add a new functionality that allows to delete a student. I used #Transactional on my delete method and modified accordingly the xml file but I get this error:
Message Request processing failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'platformTransactionManager' is expected to be of type 'org.springframework.transaction.PlatformTransactionManager' but was actually of type 'com.infotech.service.impl.StudentServiceImpl'
my Service class
#Service("studentService")
public class StudentServiceImpl implements StudentService {
#Autowired
private StudentDAO studentDAO;
public void setStudentDAO(StudentDAO studentDAO) {
this.studentDAO = studentDAO;
}
public StudentDAO getStudentDAO() {
return studentDAO;
}
//other methods
#Override
public void delete(String email) {
getStudentDAO().delete(email);
}
}
my DAO class
#EnableTransactionManagement
#Repository("studentDAO")
public class StudentDAOImpl implements StudentDAO {
#Autowired
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
public HibernateTemplate getHibernateTemplate() {
return hibernateTemplate;
}
#Autowired
private SessionFactory sessionFactory;
protected Session getSession() {
return (Session) sessionFactory.getCurrentSession();
}
//other methods
#Transactional("platformTransactionManager")
public void delete(String email) {
Student student = (Student) ((HibernateTemplate) getSession()).get(Student.class, email);
((HibernateTemplate) getSession()).delete(student);
}
}
In the dispatcher servlet I have defined InternalResourceViewResolver, dataSource, hibernateTemplate, sessionFactory beans and then I added another bean
<tx:annotation-driven transaction-manager="platformTransactionManager"/>
<bean id= "platformTransactionManager"class="com.infotech.service.impl.StudentServiceImpl">
</bean>
Finally, this is the controller
#Controller
public class MyController {
#Autowired
private StudentService studentService;
public void setStudentService(StudentService studentService) {
this.studentService = studentService;
}
public StudentService getStudentService() {
return studentService;
}
//...RequestMappings...
#RequestMapping(value = "/delete/{email}", method = RequestMethod.GET)
public ModelAndView delete(#PathVariable("email") String email) {
studentService.delete(email);
return new ModelAndView("redirect:/view/home");
}
...
}
Now, how can I make my bean of PlatformTransactionManager type?
But most of all I think there's a simpler way to delete a field from my table, maybe without using #Transaction at all so can anyone help me understand why I get the error and explain me what is #Transactional and if I really should use it in this case?
Remember that I'm NEW to Spring, I still don't have many notions so sorry if I wrote something totally stupid :-)
Spring is looking for transaction manager - it requires a concrete implementation of the PlatformTransactionManager interface. It's being given your service implementation, which isn't a PlatformTransactionManager and not what it needs. If you're using JDBC, org.springframework.jdbc.datasource.DataSourceTransactionManager should work.
Try changing:
<bean id= "platformTransactionManager" class="com.infotech.service.impl.StudentServiceImpl">
To:
<bean id= "platformTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

How enable/disable #EnableGlobalMethodSecurity for #Service methods for testing scenario

I am working with Spring Framework and Spring Security
About Testing
For a set of Test classes for #Controller with security, .apply(springSecurity() and #WithUserDetails(value="something") are used
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(springSecurity())// <---
.build();
}
For other set of Test classes for #Controller without security, therefore .apply(springSecurity()) and #WithUserDetails(value="something") are not used.
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.build();
}
Until here all about for #Controller with and without security work fine.
The problem is for the #Service, when #EnableGlobalMethodSecurity is defined and the #Service methods are annotated with #PreAuthorize("hasRole('ROLE_ADMIN')"), all the other Test classes for #Service where security is not required fail now with:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException:
An Authentication object was not found in the SecurityContext
Of course it because the #Test methods do not use #WithUserDetails(value="something")
Thus, practically .apply(springSecurity()) does the job, but it for a Web environment through MockMvcBuilders.webAppContextSetup(webApplicationContext)
But for the server side, where security is not needed, I have:
#Transactional
#RunWith(Parameterized.class)
#ContextConfiguration(classes={RootApplicationContext.class})
#ActiveProfiles(resolver=TestActiveProfilesResolver.class)
#TestExecutionListeners(listeners={LoggingTestExecutionListener.class}, mergeMode=MergeMode.MERGE_WITH_DEFAULTS)
public class PersonaServiceImplTest {
private static final Logger logger = LoggerFactory.getLogger(PersonaServiceImplTest.class.getSimpleName());
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Autowired
private Environment environment;
...
Thus MockMvcBuilders.webAppContextSetup(webApplicationContext) has no sense to be used. What is the best way to resolve this?
You can use #WithUserDetails and #WithMockUser to test method security as well.
For the tests to pick up on method security, you need to include the class annotated with #EnableGlobalMethodSecurity in the component classes used for loading the ApplicationContext.
For example, if the configuration class SecurityConfig is annotated with EnableGlobalMethodSecurity
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig { }
And the Service MessageService has a method using #PreAuthorize.
#Service
public class MessageService {
public String getHelloMessage() {
return "Hello!";
}
#PreAuthorize("hasRole('ADMIN')")
public String getGoodbyeMessage() {
return "Goodbye!";
}
}
Then you need to include both of those classes in the MessageServiceTest and you can use the security testing annotations.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = {SecurityConfig.class, MessageService.class})
public class MessageServiceTest {
#Autowired
MessageService messageService;
#Test
public void helloMessageReturnsHello() {
assertThat(messageService.getHelloMessage()).isEqualTo("Hello!");
}
#Test(expected = AuthenticationCredentialsNotFoundException.class)
public void goodbyeMessageWithoutUserThrowsException() {
messageService.getGoodbyeMessage();
}
#WithMockUser(roles = "ADMIN")
#Test
public void goodbyeMessageWithAdminReturnsGoodbye() {
assertThat(messageService.getGoodbyeMessage()).isEqualTo("Goodbye!");
}
}

Autowired bean giving Null Pointer Exception

I am using Autowired annotation in a Service class to inject Dao dependency. The code I have written is:
public interface Service {
public List<String> getAllCountries() ;
}
public class ServiceImpl implements Service {
#Autowired
private TestDao testDao;
public TestDao getTestDao() {
return testDao;
}
public void setTestDao(TestDao testDao) {
this.testDao = testDao;
}
public List<String> getAllCountries() {
// TODO Auto-generated method stub
System.out.println("coming in here:" + testDao);
return testDao.getAllCountries();
}
}
public interface TestDao {
List<String> getAllCountries() ;
}
public class TestDaoImpl implements TestDao{
#Override
public List<String> getAllCountries() {
// TODO Auto-generated method stub
List<String> ls = new ArrayList<>();
ls.add("test1");
return ls;
}
}
And a controller
public class TestController {
public void doSth() {
Service service = new ServiceImpl();
try {
System.out.println("service obj:" + service);
List<String> list = service.getAllCountries();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Beans.xml:
<context:annotation-config/>
<bean id="controller" class="org.test.TestController"/>
<bean id="testDao" class="org.test.TestDaoImpl"/>
And a main class:
ApplicationContext context = new
ClassPathXmlApplicationContext("beans4.xml");
TestController obj= (TestController) context.getBean("controller");
obj.doSth();
But it is throwing NullPointerException in ServiceImpl class. These all classes are in the same package.
Can somebody help me understand what exactly is the issue?
Solution:
public class TestController {
#Autowired
Service service;
//remaining code as it is
}
Your Service class is not managed by Spring . Hence, the dependency is getting injected.
To make your service class managed,
You can use #Service stereo type annotation and do a component scan.
I mean,
package com;
#Service
public class ServiceImpl implement XXXX{
//Your changes
}
and in spring config file:
<context:component-scan base-package="com"/>
I did not see any bean declaration or annotation for the ServiceImpl.
//You canot do it like the following
#controller
class A{
ServiceImpl simp=new ServiceImpl();
}
class ServiceImpl{
#Autowired
Adao adoa;//throws NPE
}
//it should be like this
#Controller
class A{
#autowired
ServiceImpl simp;
}
#Service
class ServiceImpl{
#Autowired
Adao adoa;
}
#Repository
class Adao{
}
Your testDao dependency bean has not been injected and you need the Spring container that you are using annotations and also specify which packages need to be scanned for Autowiring by adding the following:
<context:annotation-config />
<context:component-scan base-package="org.test" />
You can look here on how you can auto wire spring beans.

How to understand #Configuration

I'm new to Java config. I have a code like this. SomeDao has its own dependency, shouldn't we set the dependencies since we are doing new?
Can someone please help me understand this code?
#Configuration
public class DAOConfiguration {
#Bean(name = "someDao")
public SomeDao someDao() {
return new SomeDao();
}
Are you familiar with how this is done in xml? It is extremely similar to that.
Here is an example of SomeDao being configured with Dep1 (via constructor injection) and Dep2 (via setter injection) in xml:
<bean id="someDao" class="com.example.SomeDao">
<constructor-arg ref="dep1"/>
<property name="dep2" ref="dep2"/>
</bean>
<bean id="dep1" class="com.example.Dep1" />
<bean id="dep2" class="com.example.Dep2" />
This same example in JavaConfig would be configured as such:
#Configuration
public class DAOConfiguration {
#Bean(name = "someDao")
public SomeDao someDao() {
final SomeDao someDao = new SomeDao(dep1());
someDao.setDep2(dep2());
return someDao;
}
#Bean(name="dep1")
public Dep1 dep1() {
return new Dep1();
}
#Bean(name-"dep2")
public Dep2 dep2() {
return new Dep2();
}
}
All three beans are still registered with the ApplicationContext too, so you can have all three of these beans autowired into another class, like so:
#Controller
public class MyController {
#Autowired
private SomeDao someDao;
#Autowired
private Dep1 dep1;
//...some methods
}

Injecting a Spring dependency into a JPA EntityListener

I am trying to inject a Spring dependency into an JPA EntityListener. Here is my listener class:
#Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class PliListener {
#Autowired
private EvenementPliRepository evenementPliRepository;
#PostPersist
void onPostPersist(Pli pli) {
EvenementPli ev = new EvenementPli();
ev.setPli(pli);
ev.setDateCreation(new Date());
ev.setType(TypeEvenement.creation);
ev.setMessage("Création d'un pli");
System.out.println("evenementPliRepository: " + evenementPliRepository);
evenementPliRepository.save(ev);
}
}
Here is my Entity class:
#RooJavaBean
#RooToString
#RooJpaActiveRecord
#EntityListeners(PliListener.class)
public class Pli implements Serializable{
...
However, my dependency (i.e. evenementPliRepository) is always null.
Can anyone please help?
A hack to inject dependencies on stateless beans, is to define the dependency as "static", create a setter method so that Spring can inject the dependency (assigning it to the static dependency).
Declare the dependency as static.
static private EvenementPliRepository evenementPliRepository;
Create a method so that Spring can inject it.
#Autowired
public void init(EvenementPliRepository evenementPliRepository)
{
MyListenerClass.evenementPliRepository = evenementPliRepository;
logger.info("Initializing with dependency ["+ evenementPliRepository +"]");
}
More details at: http://blog-en.lineofsightnet.com/2012/08/dependency-injection-on-stateless-beans.html
This is actually an old question but I found an alternative solution :
public class MyEntityListener {
#Autowired
private ApplicationEventPublisher publisher;
#PostPersist
public void postPersist(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnCreatedEvent<>(this, target));
}
#PostUpdate
public void postUpdate(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnUpdatedEvent<>(this, target));
}
#PostRemove
public void postDelete(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnDeletedEvent<>(this, target));
}
}
Probably not the best one but better than static variables w/o AOP + weaving.
I annotated the listener with #Component annotation, then created a non static setter to assign the injected Spring bean, it works well
My code looks like :
#Component
public class EntityListener {
private static MyService service;
#Autowired
public void setMyService (MyService service) {
this.service=service;
}
#PreUpdate
public void onPreUpdate() {
service.doThings()
}
#PrePersist
public void onPersist() {
...
}
}
Since Spring V5.1 (and Hibernate V5.3) it should work out of the box as Spring registers as the provider of those classes.
see documentation of SpringBeanContainer
And what about this solution?
#MappedSuperclass
#EntityListeners(AbstractEntityListener.class)
public abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "creation_date")
private Date creationDate;
#Column(name = "modification_date")
private Date modificationDate;
}
Then the Listener...
#Component
public class AbstractEntityListener {
#Autowired
private DateTimeService dateTimeService;
#PreUpdate
public void preUpdate(AbstractEntity abstractEntity) {
AutowireHelper.autowire(this, this.dateTimeService);
abstractEntity.setModificationDate(this.dateTimeService.getCurrentDate());
}
#PrePersist
public void prePersist(AbstractEntity abstractEntity) {
AutowireHelper.autowire(this, this.dateTimeService);
Date currentDate = this.dateTimeService.getCurrentDate();
abstractEntity.setCreationDate(currentDate);
abstractEntity.setModificationDate(currentDate);
}
}
And the helper...
/**
* Helper class which is able to autowire a specified class. It holds a static reference to the {#link org
* .springframework.context.ApplicationContext}.
*/
public final class AutowireHelper implements ApplicationContextAware {
private static final AutowireHelper INSTANCE = new AutowireHelper();
private static ApplicationContext applicationContext;
private AutowireHelper() {
}
/**
* Tries to autowire the specified instance of the class if one of the specified beans which need to be autowired
* are null.
*
* #param classToAutowire the instance of the class which holds #Autowire annotations
* #param beansToAutowireInClass the beans which have the #Autowire annotation in the specified {#classToAutowire}
*/
public static void autowire(Object classToAutowire, Object... beansToAutowireInClass) {
for (Object bean : beansToAutowireInClass) {
if (bean == null) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
}
}
}
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
/**
* #return the singleton instance.
*/
public static AutowireHelper getInstance() {
return INSTANCE;
}
}
Works for me.
Source:
http://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/
I started to go down the path of using AOP to inject a spring bean into an Entity listener. After a day and a half of research and trying different things I came across this link which stated:
It is not possible to inject spring managed beans into a JPA EntityListener class. This is because the JPA listener mechanism should be based on a stateless class, so the methods are effectively static, and non-context aware. ... No amount of AOP will save you, nothing gets injected to the ‘object’ representing the listener, because the implementations don’t actually create instances, but uses the class method.
At this point I regrouped and stumbled across the EclipseLink DescriptorEventAdapter. Using this information I created a listener class that extended the Descriptor Adapter.
public class EntityListener extends DescriptorEventAdapter {
private String injectedValue;
public void setInjectedValue(String value){
this.injectedValue = value;
}
#Override
public void aboutToInsert(DescriptorEvent event) {
// Do what you need here
}
}
In order to use the class I could have used the #EntityListeners annotation on my entity class. Unfortunately, this method would not allow Spring to control the creation of my listener and as a result would not allow for dependency injection. Instead I added the following 'init' function to my class:
public void init() {
JpaEntityManager entityManager = null;
try {
// Create an entity manager for use in this function
entityManager = (JpaEntityManager) entityManagerFactory.createEntityManager();
// Use the entity manager to get a ClassDescriptor for the Entity class
ClassDescriptor desc =
entityManager.getSession().getClassDescriptor(<EntityClass>.class);
// Add this class as a listener to the class descriptor
desc.getEventManager().addListener(this);
} finally {
if (entityManager != null) {
// Cleanup the entity manager
entityManager.close();
}
}
}
Add a little Spring XML configuration
<!-- Define listener object -->
<bean id="entityListener" class="EntityListener " init-method="init">
<property name="injectedValue" value="Hello World"/>
<property name="entityManagerFactory" ref="emf"/>
</bean>
Now we have a situation where Spring creates a entity listener, injects it with whatever dependencies are needed, and the listener object registers itself with the entity class to which it intends to listen.
I hope this helps.
try use ObjectFactory like this
#Configurable
public class YourEntityListener {
#Autowired
private ObjectFactory<YourBean> yourBeanProvider;
#PrePersist
public void beforePersist(Object target) {
YourBean yourBean = yourBeanProvider.getObject();
// do somthing with yourBean here
}
}
I found this solution in org.springframework.data.jpa.domain.support.AuditingEntityListener from spring-data-jpa.
demo: https://github.com/eclipseAce/inject-into-entity-listener
I tested out the approach suggested in https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/ and worked. Not very clean but does the job. Slightly modified AutowireHelper class for me looked like this:
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class AutowireHelper implements ApplicationContextAware {
private static ApplicationContext applicationContext;
private AutowireHelper() {
}
public static void autowire(Object classToAutowire) {
AutowireHelper.applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
}
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
}
Then called this from entity listener like this:
public class MyEntityAccessListener {
#Autowired
private MyService myService;
#PostLoad
public void postLoad(Object target) {
AutowireHelper.autowire(this);
myService.doThings();
...
}
public void setMyService(MyService myService) {
this.myService = myService;
}
}
The problem with JPA Listeners is that:
they are not managed by Spring (so no injections)
they are (or might be) created before Spring's Application Context is ready (so we can't inject beans on a constructor call)
My workaround to deal with the issue:
1) Create Listener class with public static LISTENERS field:
public abstract class Listener {
// for encapsulation purposes we have private modifiable and public non-modifiable lists
private static final List<Listener> PRIVATE_LISTENERS = new ArrayList<>();
public static final List<Listener> LISTENERS = Collections.unmodifiableList(PRIVATE_LISTENERS);
protected Listener() {
PRIVATE_LISTENERS.add(this);
}
}
2) All JPA listeners that we want to be added to Listener.LISTENERS has to extend this class:
public class MyListener extends Listener {
#PrePersist
public void onPersist() {
...
}
...
}
3) Now we can get all listeners and inject beans just after Spring's Application Context is ready
#Component
public class ListenerInjector {
#Autowired
private ApplicationContext context;
#EventListener(ContextRefreshedEvent.class)
public void contextRefreshed() {
Listener.LISTENERS.forEach(listener -> context.getAutowireCapableBeanFactory().autowireBean(listener));
}
}
I believe it is because this listener bean is not under control of Spring. Spring is not instantiating it, how can Spring know how to find that bean and do the injection?
I haven't tried on that, but seems that you can make use of AspectJ Weaver with Spring's Configurable annotation to have Spring control non-Spring-instantiated beans.
http://static.springsource.org/spring/docs/3.1.2.RELEASE/spring-framework-reference/html/aop.html#aop-using-aspectj
Since version 5.3 of Hibernate and version 5.1 of Spring (that's version 2.1 of Spring Boot), there's an easy solution.
No hack, no need to use AOP, no helper classes, no explicit autowiring, no init block to force injection.
You just need to:
Make the listener a #Component and declare the autowired bean, as usual.
Configure JPA in your Spring application to use Spring as the bean provider.
Here's how (in Kotlin)...
1) Entity listener
#Component
class EntityXyzListener(val mySpringBean: MySpringBean) {
#PostLoad
fun afterLoad(entityXyz: EntityXyz) {
// Injected bean is available here. (In my case the bean is a
// domain service that I make available to the entity.)
entityXyz.mySpringBean= mySpringBean
}
}
2) JPA datasource config
Get access to LocalContainerEntityManagerFactoryBean in your application. Then add to jpaPropertyMap the following key-value pair: AvailableSettings.BEAN_CONTAINER => the application context's bean factory.
In my Spring Boot application I already had the code below to configure a datasource (boilerplate code found here for example). I only had to add the line of code that puts the BEAN_CONTAINER property in the jpaPropertyMap.
#Resource
lateinit var context: AbstractApplicationContext
#Primary
#Bean
#Qualifier("appDatasource")
#ConfigurationProperties(prefix = "spring.datasource")
fun myAppDatasource(): DataSource {
return DataSourceBuilder.create().build()
}
#Primary
#Bean(name = ["myAppEntityManagerFactory"])
fun entityManagerFactoryBean(builder: EntityManagerFactoryBuilder): LocalContainerEntityManagerFactoryBean {
val localContainerEntityManagerFactoryBean =
builder
.dataSource(myAppDatasource())
.packages("com.mydomain.myapp")
.persistenceUnit("myAppPersistenceUnit")
.build()
// the line below does the trick
localContainerEntityManagerFactoryBean.jpaPropertyMap.put(
AvailableSettings.BEAN_CONTAINER, SpringBeanContainer(context.beanFactory))
return localContainerEntityManagerFactoryBean
}
Another option:
Create a service to make AplicationContext accessible:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import lombok.Setter;
#Service
class ContextWrapper {
#Setter
private static ApplicationContext context;
#Autowired
public ContextWrapper(ApplicationContext ac) {
setContext(ac);
}
}
Use it:
...
public class AuditListener {
private static final String AUDIT_REPOSITORY = "AuditRepository";
#PrePersist
public void beforePersist(Object object){
//TODO:
}
#PreUpdate
public void beforeUpdate(Object object){
//TODO:
}
#PreRemove
public void beforeDelete(Object object) {
getRepo().save(getAuditElement("DEL",object));
}
private Audit getAuditElement(String Operation,Object object){
Audit audit = new Audit();
audit.setActor("test");
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
audit.setDate(timestamp);
return audit;
}
private AuditRepository getRepo(){
return ContextWrapper.getContext().getBean(AUDIT_REPOSITORY, AuditRepository.class);
}
}
This class is created as a listener from jpa:
...
#Entity
#EntityListeners(AuditListener.class)
#NamedQuery(name="Customer.findAll", query="SELECT c FROM Customer c")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
...
Since the listener is not under Spring's control, it can not access the context bean. I have tried multiple options (#Configurable (...)) and none has worked except to create a class that static access to the context. Already in that dilemma I think that this is an elegant option.
Building on the answer of Paulo Merson, here is a variation of how to set the SpringBeanContainer by utilizing JpaBaseConfiguration. Here are both steps:
Step 1: Define the listener as a Spring component. Note that autowiring works through constructor injection.
#Component
public class PliListener {
private EvenementPliRepository evenementPliRepository;
public PliListener(EvenementPliRepository repo) {
this.evenementPliRepository = repo;
}
#PrePersist
public void touchForCreate(Object target) {
// ...
}
#PostPersist
void onPostPersist(Object target) {
// ...
}
}
Step 2: Set the SpringBeanContainer, which enables autowiring in the listener. SpringBeanContainer JavaDoc might be worth a look.
#Configuration
public class JpaConfig extends JpaBaseConfiguration {
#Autowired
private ConfigurableListableBeanFactory beanFactory;
protected JpaConfig(DataSource dataSource, JpaProperties properties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
super(dataSource, properties, jtaTransactionManager);
}
#Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Override
protected Map<String, Object> getVendorProperties() {
Map<String, Object> props = new HashMap<>();
// configure use of SpringBeanContainer
props.put(org.hibernate.cfg.AvailableSettings.BEAN_CONTAINER,
new SpringBeanContainer(beanFactory));
return props;
}
}
The most natural way is, in my opinion, to intervene into the process of instantiating of EntityListener.
This way significantly differs in Hibernate pre-5.3 versions and post-5.3 ones.
1) In Hibernate versions earlier than 5.3 org.hibernate.jpa.event.spi.jpa.ListenerFactory is responsible for EntityListener instantiation. The instantiation of this factory can be intercepted if you provide your own CDI-based javax.enterprise.inject.spi.BeanManager. The CDI interfaces are (unnecessary for Spring DI world) verbose, but it's not difficult to implement Spring BeanFactory-backed CDI Bean manager.
#Component
public class SpringCdiBeanManager implements BeanManager {
#Autowired
private BeanFactory beanFactory;
#Override
public <T> AnnotatedType<T> createAnnotatedType(Class<T> type) {
return new SpringBeanType<T>(beanFactory, type);
}
#Override
public <T> InjectionTarget<T> createInjectionTarget(AnnotatedType<T> type) {
return (InjectionTarget<T>) type;
}
...
// have empty implementation for other methods
}
and the implementation of type-dependent SpringBeanType<T> will look like this:
public class SpringBeanType <T> implements AnnotatedType<T>, InjectionTarget<T>{
private BeanFactory beanFactory;
private Class<T> clazz;
public SpringBeanType(BeanFactory beanFactory, Class<T> clazz) {
this.beanFactory = beanFactory;
this.clazz = clazz;
}
#Override
public T produce(CreationalContext<T> ctx) {
return beanFactory.getBean(clazz);
}
...
// have empty implementation for other methods
}
Now, the only thing left is to inject into Hibernate Configuration Settings our implementation of BeanManager under a property name javax.persistence.bean.manager. There are, probably, many ways to do so, let me bring just one of them:
#Configuration
public class HibernateConfig {
#Autowired
private SpringCdiBeanManager beanManager;
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter(){
#Override
public Map<String, Object> getJpaPropertyMap(){
Map<String, Object> jpaPropertyMap = super.getJpaPropertyMap();
jpaPropertyMap.put("javax.persistence.bean.manager", beanManager);
return jpaPropertyMap;
}
};
// ...
return jpaVendorAdapter;
}
}
Just remember that two things have to be Spring beans:
a) SpringCdiBeanManager, so that BeanFactory could be injected/autowired to it;
b) your EntityListener class, so that line return beanFactory.getBean(clazz); will be successful.
2) In Hibernate versions 5.3 and later things are much easier for Spring beans, as #AdrianShum very correctly pointed out. Since 5.3 Hibernate uses org.hibernate.resource.beans.container.spi.BeanContainer concept and there is its ready-to-use implementation for Spring Beans, org.springframework.orm.hibernate5.SpringBeanContainer. In this case, just follow its javadoc.
As others have pointed out, it appears SpringBeanContainer is the way to wire up Spring to Hibernate's ManagedBeanRegistryImpl, which is responsible for creating instances of EntityListeners when Hibernate is creating it's callback objects. Calls to create beans are delegated to SpringBeanContainer which can create Spring beans with both constructor injection and autowiring. For example a EntityListener would look like
public class MyEntityListener {
#Autowired
private AnotherBean anotherBean;
private MyBean myBean;
public InquiryEntityListener(MyBean myBean) {
this.myBean = myBean;
}
public MyEntityListener() {
}
}
Note that the EntityListener does NOT require #Component annotation as this only creates an extra instance which is not used by Hibernate.
However when using SpringBeanContainer there are some important limitations and caveats that must be kept in mind. In our use case, instances of our EntityListener were created during the creation of Hibernate EntityManager. As this happened fairly early during the Spring lifecycle, many beans did not exist at this time. This led to the following discovery:
The SpringBeanContainer will only autowire/constructor bean dependencies that exist at the time when the EntityListener is created. Constructor dependencies that don't exist will cause the default constructor to be called. Essentially there is a race condition when using SpringBeanContainer.
The work around for this is to inject a DefaultListableBeanFactory instance into the EntityListener. Later when the EntityListeners lifecycle methods are called (i.e. #PostLoad, #PostPersist, etc.) instances of the desired bean can be pulled out of the BeanFactory as the beans would've been created by Spring at this point.

Resources