JPA错误记录 No qualifying bean of type ‘javax.persistence.EntityManagerFactory‘ available

最近在公司在一个已经很臃肿的项目上添加新功能,要求使用的是新数据库,并且使用Spring Data JPA。这让我很懵逼,因为此项目臃肿的程度已经无法想象了,里面有N+个数据源,并且ORM使用到了JDBC、mybatis、Spring Data JPA。如果当时约定使用相同的技术就不会出现问题了(前人种坑,后人填坑....)。

已经有的数据源配置:

1.使用mybatis配置的数据源且标记了主数据源:(Mybatis数据源配置有多个,我只列标记为@Primary的数据源)


@Configuration
@MapperScan(value = "com.xichuan.flow.mapper.flow", sqlSessionFactoryRef = "sqlSessionFactory")
public class FlowDataSourceConfig {

    @Primary
    @Bean(name="dataSource")
    @ConfigurationProperties(prefix="spring.flow_datasource")
    public DataSource flowDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(flowDataSource());
        return sessionFactory.getObject();
    }
}

2.已经有的JPA数据源


@SuppressWarnings(value = "unused")
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = {
                "com.xichuan.imap.repository"
        },
        entityManagerFactoryRef = "imapEntityManagerFactory",
        transactionManagerRef = "imaptransactionManager")
public class ImapDataSourceConfig {
    @Autowired
    private Environment env;

    public ImapDataSourceConfig() {
        super();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean imapEntityManagerFactory() {
        final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(imapDataSource());
        em.setPackagesToScan(
                new String[]{
                        "com.xichuan.imap.entity"
                }
        );

        final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        return em;
    }

    @ConfigurationProperties(prefix = "spring.imap_datasource")
    public DataSource imapDataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("spring.imap_datasource.driverClassName")));
        dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("spring.imap_datasource.url")));
        dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("spring.imap_datasource.username")));
        dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("spring.imap_datasource.password")));

        final Properties propertiesJpa = new Properties();
        dataSource.setConnectionProperties(propertiesJpa);
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager imapTransactionManager(EntityManagerFactory emf) {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

}

3.代码中使用到的EntityManager:

@Service
public class ImapServiceImpl{

    @Autowired
    private EntityManager mEntityManager;

    @PostConstruct
    public void initFactory() {
        logger.info("开始实例化JPAQueryFactory");
        mQueryFactory = new JPAQueryFactory(mEntityManager);
    }
...
...

}

乍一看并没有什么问题,对,我感觉没有问题,后来发现问题贼多..................

4.配置新代码的JPA数据源

@Configuration
@EnableJpaRepositories
        (
                entityManagerFactoryRef="classroom_EntityManagerFactoryBean",
                transactionManagerRef = "classroom_transactionManager",
                basePackages = {
                        "com.xichuan.classroom.dao"
                }
        )
@EnableTransactionManagement
public class ClassroomDataSourceConfig {


    @Autowired
    private JpaProperties jpaProperties;


    @Bean(name = "classroom_databaseSource")
    //@Qualifier("classroom_databaseSource")
    @ConfigurationProperties(prefix="spring.cr_datasource")
    public DataSource classroomDataSource() {
        return  DataSourceBuilder.create().build();
    }


    /**
     *实体管理
     * @param builder
     * @return
     */
    @Bean(name = "classroom_EntityManagerFactoryBean")
    public LocalContainerEntityManagerFactoryBean classroomEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(classroomDataSource())
                .properties(jpaProperties.getHibernateProperties(classroomDataSource()))
                .packages("com.xichuan.classroom.domain") //设置实体类所在位置
                .persistenceUnit("bPersistenceUnit")
                .build();
    }

    /**
     * EntityManagerFactory类似于Hibernate的SessionFactory,mybatis的SqlSessionFactory
     * 总之,在执行操作之前,我们总要获取一个EntityManager,这就类似于Hibernate的Session,
     * mybatis的sqlSession.
     * @param builder
     * @return
     */
    @Bean(name = "classroom_entityManagerFactory")
    public EntityManagerFactory classroomEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return this.classroomEntityManagerFactoryBean(builder).getObject();
    }

    /**
     * 配置imap的jpa事务
     * @param builder
     * @return
     */
    @Bean(name = "classroom_transactionManager")
    public PlatformTransactionManager writeTransactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(classroomEntityManagerFactory(builder));
    }
}

-------------------------------------------运行过程,且解决之路---------------------------------------------------------------------

嗯....,感觉完全正确,运行吧..

2018-12-13 11:01:44.395 ERROR  LoggingFailureAnalysisReporter.report 

***************************
APPLICATION FAILED TO START
***************************

Description:

Field mEntityManager in com.viroyal.cloud.imap.service.impl.EduAdminServiceImpl required a single bean, but 3 were found:
	- org.springframework.orm.jpa.SharedEntityManagerCreator#0: defined by method 'createSharedEntityManager' in null
	- org.springframework.orm.jpa.SharedEntityManagerCreator#1: defined by method 'createSharedEntityManager' in null
	- org.springframework.orm.jpa.SharedEntityManagerCreator#2: defined by method 'createSharedEntityManager' in null


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

[WARNING] 
java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:507)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'eduAdminController': Unsatisfied dependency expressed through field 'mEduAdminService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'eduAdminServiceImpl': Unsatisfied dependency expressed through field 'mEntityManager'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 3: org.springframework.orm.jpa.SharedEntityManagerCreator#0,org.springframework.orm.jpa.SharedEntityManagerCreator#1,org.springframework.orm.jpa.SharedEntityManagerCreator#2
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
	at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:372)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1187)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1176)
	at com.viroyal.cloud.SmartSchoolApplication.main(SmartSchoolApplication.java:36)
	... 6 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'eduAdminServiceImpl': Unsatisfied dependency expressed through field 'mEntityManager'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 3: org.springframework.orm.jpa.SharedEntityManagerCreator#0,org.springframework.orm.jpa.SharedEntityManagerCreator#1,org.springframework.orm.jpa.SharedEntityManagerCreator#2
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
	... 25 more
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 3: org.springframework.orm.jpa.SharedEntityManagerCreator#0,org.springframework.orm.jpa.SharedEntityManagerCreator#1,org.springframework.orm.jpa.SharedEntityManagerCreator#2
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:173)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1116)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
	... 38 more

刚看到错误,一脸懵逼,这是啥...........。明显,这是因为多数据源导致的错误。隐隐约约感觉到,数据源与EntityManeger原因导致的错误。日志中有个提示像是在暗示什么:

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

我发现mybatis数据源设置@Primary并没又特别大的作用,将器@Primary移植到imapDataSource数据源上。

修改内容在配置文件中,我会详细标记的。

1.已有的MyBatis去掉@Primary注解


@Configuration
@MapperScan(value = "com.xichuan.flow.mapper.flow", sqlSessionFactoryRef = "sqlSessionFactory")
public class FlowDataSourceConfig {

    //@Primary                        /**-------------将此注解取消到------*/
    @Bean(name="dataSource")
    @ConfigurationProperties(prefix="spring.flow_datasource")
    public DataSource flowDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(flowDataSource());
        return sessionFactory.getObject();
    }
}

2.imapDatasource数据源添加上@Primary。在实例化imapEntityManagerFactory的时候,加上UniteName。并将Bean命名进行规范化。


@SuppressWarnings(value = "unused")
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = {
                "com.xichuan.imap.repository"
        },
        entityManagerFactoryRef = "imap_EntityManagerFactory",   /**--相应的修改---*/
        transactionManagerRef = "imap_transactionManager")       /**--相应的修改---*/
public class ImapDataSourceConfig {
    @Autowired
    private Environment env;

    public ImapDataSourceConfig() {
        super();
    }

    @Bean(name = "imap_EntityManagerFactory")        /**----------重命名Bean-------------------*/
    @Primary                                        /**-----------添加@Primary-------------------*/
    public LocalContainerEntityManagerFactoryBean imapEntityManagerFactory() {
        final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(imapDataSource());    
        em.setPersistenceUnitName("aPersistenceUnit");    /**----------设置了UniteName-------------*/
        em.setPackagesToScan(
                new String[]{
                        "com.xichuan.imap.entity"
                }
        );

        final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);

        return em;
    }
   
    @Bean(name = "imap_datasource")                     /**----------重命名Bean-------------------*/
    @ConfigurationProperties(prefix = "spring.imap_datasource")
    @Primary                                           /**-----------添加@Primary-------------------*/
    public DataSource imapDataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("spring.imap_datasource.driverClassName")));
        dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("spring.imap_datasource.url")));
        dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("spring.imap_datasource.username")));
        dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("spring.imap_datasource.password")));

        final Properties propertiesJpa = new Properties();
        dataSource.setConnectionProperties(propertiesJpa);
        return dataSource;
    }

    @Bean(name = "imap_transactionManager")                /**----------重命名Bean-------------------*/
    @Primary                                               /**-----------添加@Primary-------------------*/
    public PlatformTransactionManager imapTransactionManager(@Qualifier("imap_EntityManagerFactory")EntityManagerFactory emf) {  /**----------使用@Qualifier--------------*/
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }
}

3.修改EntityManager的引用,通过UniteName寻找Jpa中的哪一个数据源中的EntityManager

@Service
public class ImapServiceImpl{

    //@Autowired
    @PersistenceContext(unitName = "aPersistenceUnit")
    private EntityManager mEntityManager;

    @PostConstruct
    public void initFactory() {
        logger.info("开始实例化JPAQueryFactory");
        mQueryFactory = new JPAQueryFactory(mEntityManager);
    }
...
...

}

好了,此时运行就没有错误了.....,要是当时都用mybatis-plus多好啊,就不会有这样那样的问题。最近写的项目都约定好使用mybatis-plus。心累,心累...

第三个参考链接的一句话关于@PersistenceContext。

@PersistenceContext allows you to specify which persistence unit you want to use. Your project might have multiple data sources connected to different DBs and @PersistenceContext allows you to say which one you want to operate on

参考链接:

java - No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 2 - Stack Overflow

@Qualifier与@Resource与@Autowired的区别_z_k_h的博客-CSDN博客

spring - @Autowired vs @PersistenceContext for EntityManager bean - Stack Overflow

 github上有我更多的笔记:Raray-chuan (兮川) · GitHub,欢迎stars与following,如果有问题可以在issue中向我咨询

关注我的公众号,获取更多关于后端、大数据的知识

你可能感兴趣的:(后端,bug,JPA错误记录)