1

I have a case where I need two instances of EntityManager, one for read/writes and one for Audit tracking.

We created two instances of Entity Manager by doing the following:

// in App.java we added the following annotation:

@Configuration
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })

Then created two Entity Managers like so: 1:

package com.vw.asa.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.PersistenceContext;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@EnableTransactionManagement
@Configuration
@EnableJpaRepositories(basePackages = "com.vw.asa.repositories", entityManagerFactoryRef = "entityManager", transactionManagerRef = "transactionManager")
public class PrimaryDatasource {
    
    /**
     * Primary Datasource Configuration
     * @return
     */
    @Bean(name = "dataSource")      // 3
    @Primary
    @ConfigurationProperties(prefix = "primary.datasource.mysql")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @PersistenceContext(unitName = "primary")   // 4
    @Primary
    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean mySqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(mysqlDataSource())
                .persistenceUnit("primary")
                .properties(jpaProperties())
                .packages("com.vw.asa").build();
    }
    
    private Map<String, Object> jpaProperties() {
        Map<String, Object> props = new HashMap<>();
        props.put("hibernate.ejb.naming_strategy", new SpringPhysicalNamingStrategy());
        return props;
    }
    
}

2:

package com.vw.asa.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.PersistenceContext;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@EnableTransactionManagement
@Configuration
@EnableJpaRepositories(basePackages = "com.vw.asa.repositories", entityManagerFactoryRef = "secondaryMySqlEntityManager", transactionManagerRef = "secondaryTransactionManager")
public class SecondaryDatasource {
    
    /**
     * Secondary Datasource Configuration
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "secondary.datasource.mysql")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @PersistenceContext(unitName = "secondary")
    @Bean(name = "secondaryMySqlEntityManager")
    public LocalContainerEntityManagerFactoryBean mySqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return  builder.dataSource(mysqlDataSource()).properties(jpaProperties()).persistenceUnit("secondary").packages("au.com.myblog.domain").build();
    }
    
    @Bean(name = "secondaryTransactionManager")
    public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
        JpaTransactionManager tm = new JpaTransactionManager();
        tm.setEntityManagerFactory(mySqlEntityManagerFactory(builder).getObject());
        tm.setDataSource(mysqlDataSource());
        return tm;
    }
    
    private Map<String, Object> jpaProperties() {
        Map<String, Object> props = new HashMap<>();
        // naming strategy to put underscores instead of camel case
        // as per auto JPA Configuration
        props.put("hibernate.ejb.naming_strategy", new SpringPhysicalNamingStrategy());
        props.put("hibernate.hbm2ddl.auto", "none");
        return props;
    }
    
}

However, upon running the application, this exception is encountered:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [com/vw/asa/config/PrimaryDatasource.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: primary] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.InstantiationException: Could not instantiate managed bean directly : com.vw.asa.listeners.AttachmentListener
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1796)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)
    ... 121 more
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: primary] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.InstantiationException: Could not instantiate managed bean directly : com.vw.asa.listeners.AttachmentListener
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:403)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:378)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1855)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1792)
    ... 128 more
Caused by: org.hibernate.InstantiationException: Could not instantiate managed bean directly : com.vw.asa.listeners.AttachmentListener
    at org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer.produceBeanInstance(FallbackBeanInstanceProducer.java:45)
    at org.hibernate.resource.beans.container.spi.FallbackContainedBean.<init>(FallbackContainedBean.java:23)
    at org.hibernate.resource.beans.internal.ManagedBeanRegistryImpl.getBean(ManagedBeanRegistryImpl.java:58)
    at org.hibernate.jpa.event.internal.CallbackBuilderLegacyImpl.resolveEntityCallbacks(CallbackBuilderLegacyImpl.java:200)
    at org.hibernate.jpa.event.internal.CallbackBuilderLegacyImpl.buildCallbacksForEntity(CallbackBuilderLegacyImpl.java:74)
    at org.hibernate.event.service.internal.EventListenerRegistryImpl.prepare(EventListenerRegistryImpl.java:146)
    at org.hibernate.boot.internal.MetadataImpl.initSessionFactory(MetadataImpl.java:376)
    at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:211)
    at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:468)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1237)
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:391)
    ... 132 more
Caused by: java.lang.NoSuchMethodException: com.vw.asa.listeners.AttachmentListener.<init>()
[2021-02-21 08:43:48,090] Artifact vw-asa:war exploded: Error during artifact deployment. See server log for details.
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer.produceBeanInstance(FallbackBeanInstanceProducer.java:40)
    ... 144 more

The idea was that we could identify which EM to use with a given transaction. What is causing this? It throws different errors if I rename it from EntityManagerFactory

Barry Chapman
  • 6,690
  • 3
  • 36
  • 64
  • 1
    The cause of the error is in the stacktrace and is probably not related to the multiple datasource: spring `Could not instantiate managed bean directly : com.vw.asa.listeners.AttachmentListener` because the init method does not exist. – AllirionX Feb 22 '21 at 02:37
  • @AllirionX What is calling init? Why does it need an init method? – Barry Chapman Feb 22 '21 at 03:14
  • 1
    Beans need default constructors if you want Spring to init them. You can read this very similar SO question: https://stackoverflow.com/questions/18023870/java-lang-nosuchmethodexception-userauth-user-init/46295874 – AllirionX Feb 22 '21 at 03:54

1 Answers1

4

The problem is that your EntityListener does not provide a default (no-args) constructor, which Hibernate requires to instantiate the listener.

If you are using Hibernate 5.3 or greater you can get Spring to provide those beans to Hibernate by setting the BeanContainer into your EntityManagerFactory like this:

public LocalContainerEntityManagerFactoryBean mySqlEntityManagerFactory(EntityManagerFactoryBuilder builder, ConfigurableListableBeanFactory beanFactory) {
    LocalContainerEntityManagerFactoryBean emfb = builder.dataSource(mysqlDataSource())
            .persistenceUnit("primary")
            .properties(jpaProperties())
            .packages("com.vw.asa").build();
    emfb.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
    return emfb;
}
Rafael Ide
  • 61
  • 5