Reputation: 196
I need to have a Spring dependency injected into a JPA entity listener. I know I can solve this using @Configurable and Spring's AspectJ weaver as javaagent, but this seems like a hacky solution. Is there any other way to accomplish what I'm trying to do?
Upvotes: 5
Views: 11403
Reputation: 61
Extending a bit the above responses: Since Hibernate 5.3 org.hibernate.resource.beans.container.spi.BeanContainer and Spring 5.1. You can use this to post process loaded domain entities for instance. Instead of using the aspect. See: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/hibernate5/SpringBeanContainer.html
In your config:
@Bean
LocalContainerEntityManagerFactoryBean customCartEntityManagerFactory(DataSource customCartDataSource, EntityManagerFactoryBuilder builder, ConfigurableListableBeanFactory beanFactory) {
var mf = builder
.dataSource(customCartDataSource)
.packages("com.my.domain")
.build();
mf.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
return mf;
}
In your entity bean:
@EntityListeners(MyEntityListener.class)
The listener, notice no @Component decoration.
@Slf4j
public class MyEntityListener implements BeanFactoryAware, InitializingBean {
private final BeanConfigurerSupport beanConfigurerSupport = new BeanConfigurerSupport();
public CustomCartEntityListener() {
log.info("MyEntityListener created");
}
@PostLoad
public void postLoad(MyEntity entity) {
beanConfigurerSupport.configureBean(entity);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanConfigurerSupport.setBeanWiringInfoResolver(new AnnotationBeanWiringInfoResolver());
this.beanConfigurerSupport.setBeanFactory(beanFactory);
}
@Override
public void afterPropertiesSet() {
this.beanConfigurerSupport.afterPropertiesSet();
log.info("MyEntityListener initialized");
}
}
Upvotes: 0
Reputation: 14457
Here's a solution in Kotlin (Spring Boot 2.3.9, Hibernate 5.4.29.Final). First part is similar to Matthias' answer. However, the second part was needed even though it's a Spring Boot application.
@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
}
}
I already had this datasource @Configuration
in my spring boot app. 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 was the long-sought solution :^)
localContainerEntityManagerFactoryBean.jpaPropertyMap.put(
AvailableSettings.BEAN_CONTAINER, SpringBeanContainer(context.beanFactory))
return localContainerEntityManagerFactoryBean
}
Upvotes: 2
Reputation: 1599
Since Hibernate 5.3 org.hibernate.resource.beans.container.spi.BeanContainer and Spring 5.1 org.springframework.orm.hibernate5.SpringBeanContainer you do not need to extra autowiring effort any more. See details of this feature in https://github.com/spring-projects/spring-framework/issues/20852
Simply annotate your EntityListener class with @Component, and do any autowiring like so:
@Component
public class MyEntityListener{
private MySpringBean bean;
@Autowired
public MyEntityListener(MySpringBean bean){
this.bean = bean;
}
@PrePersist
public void prePersist(final Object entity) {
...
}
}
In Spring Boot the configuration of LocalContainerEntityManagerFactoryBean is done automatically in org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration.
Outside of Spring Boot, you have to register SpringBeanContainer to Hibernate:
LocalContainerEntityManagerFactoryBean emfb = ...
emfb.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
Upvotes: 15
Reputation: 921
You can try this solution
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
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);
return;
}
}
}
/**
* @return the singleton instance.
*/
public static AutowireHelper getInstance() {
return INSTANCE;
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
}
and then
@Autowired
SomeService thatToAutowire;
AutowireHelper.autowire(this, this.thatToAutowire);//this in the method
Upvotes: 0
Reputation: 30279
Another trick is to implement an utility class with static method that helps you to use Spring beans everywhere, not only in managed classes:
@Component
public final class BeanUtil {
private static ApplicationContext context;
private BeanUtil(ApplicationContext context) {
BeanUtil.context = context;
}
public static <T> T getBean(Class<T> clazz) throws BeansException {
Assert.state(context != null, "Spring context in the BeanUtil is not been initialized yet!");
return context.getBean(clazz);
}
}
Upvotes: 5