Spring Data JPA - 多个EnableJpaRepositories

56

我的应用程序有多个数据源,因此我根据此URL创建了两个数据源配置类。

但是在运行Spring Boot应用程序时,我遇到了错误:

描述: com.cavion.services.UserDataService中的userDataRepo字段需要一个名为“entityManagerFactory”的bean,但找不到该bean。 操作: 请考虑在您的配置中定义一个名为“entityManagerFactory”的bean。

从StackOverflow上的这个问题中得知了解决方法,需要在我的JPA repositories中指定entityManagerFactoryRef。

但是我有很多repository类,其中一些使用EntityManager“A”,一些使用“B”。我的当前Spring Boot应用程序类是这样的:

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
    DataSourceTransactionManagerAutoConfiguration.class })
@EnableTransactionManagement
@EntityScan("com.info.entity")
@ComponentScan({"com.info.services","com.info.restcontroller"})
@EnableJpaRepositories("com.info.repositories")
public class CavionApplication {

public static void main(String[] args) {
    SpringApplication.run(CavionApplication.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
    return args -> {

        System.out.println("Let's inspect the beans provided by Spring Boot:");

        String[] beanNames = ctx.getBeanDefinitionNames();
        Arrays.sort(beanNames);
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }

    };
}}

我已经在Spring Boot类上使用了EnableJpaRepositories,那么我该如何配置多个EnableJpaRepositories以便可以配置多个entityManagerFactory?

请建议设置多个数据源的最佳方法。

3个回答

95
为了让Spring知道DataSource与哪个Repository相关,您需要在@EnableJpaRepositories注释中定义它。假设我们有两个实体,Servers实体和Domains实体,每个实体都有自己的Repo,则每个Repo都有自己的JpaDataSource配置。
1. 根据它们所关联的数据源将所有存储库分组。例如: Domains实体的Repository(包: org.springdemo.multiple.datasources.repository.domains):
package org.springdemo.multiple.datasources.repository.domains;

import org.springdemo.multiple.datasources.domain.domains.Domains;
import org.springframework.data.jpa.repository.JpaRepository;

public interface DomainsRepository extends JpaRepository<Domains,Long> {
}

Servers实体的仓库(包名:org.springdemo.multiple.datasources.repository.servers

package org.springdemo.multiple.datasources.repository.servers;

import org.springdemo.multiple.datasources.domain.servers.Servers;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ServersRepository extends JpaRepository<Servers,Long> {
}

2. 对于每个JPA数据源,您需要定义一个配置。在这个示例中,我展示了如何配置两个不同的数据源。

Domains Jpa配置:数据源和仓库之间的关系在basePackages值中定义,因此必须根据每个仓库将使用的实体管理器将仓库分组到不同的包中。

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "domainsEntityManager",
        transactionManagerRef = "domainsTransactionManager",
        basePackages = {"org.springdemo.multiple.datasources.repository.domains"}
        )
public class DomainsConfig {

服务器数据源配置:如您所见,basePackages的值具有服务器存储库的包名称,而且entityManagerFactoryReftransactionManagerRef的值不同,以便让Spring分别管理每个entityManager。

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "serversEntityManager",
        transactionManagerRef = "serversTransactionManager",
        basePackages = {"org.springdemo.multiple.datasources.repository.servers"}
        )
public class ServersConfig {

3. 将一个数据源设置为主要数据源

为了避免出现错误信息:Parameter 0 of constructor in org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration required a single bean, but 2 were found: 只需将其中一个数据源设置为 @Primary,例如在这个例子中我选择将 Servers 数据源设置为主要数据源:

@Bean("serversDataSourceProperties")
@Primary
@ConfigurationProperties("app.datasource.servers")
public DataSourceProperties serversDataSourceProperties(){
    return new DataSourceProperties();
}



@Bean("serversDataSource")
@Primary
@ConfigurationProperties("app.datasource.servers")
public DataSource serversDataSource(@Qualifier("serversDataSourceProperties") DataSourceProperties serversDataSourceProperties) {
    return serversDataSourceProperties().initializeDataSourceBuilder().build();
}

如果您需要更多信息,请查看每个配置的完整示例:
Servers JPA 配置
@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "serversEntityManager",
        transactionManagerRef = "serversTransactionManager",
        basePackages = {"org.springdemo.multiple.datasources.repository.servers"}
        )
public class ServersConfig {

    @Bean(name = "serversEntityManager")
    public LocalContainerEntityManagerFactoryBean getServersEntityManager(EntityManagerFactoryBuilder builder,
                                                                          @Qualifier("serversDataSource") DataSource serversDataSource){


        return builder
                .dataSource(serversDataSource)
                .packages("org.springdemo.multiple.datasources.domain.servers")
                .persistenceUnit("servers")
                .properties(additionalJpaProperties())
                .build();

    }

    Map<String,?> additionalJpaProperties(){
        Map<String,String> map = new HashMap<>();

        map.put("hibernate.hbm2ddl.auto", "create");
        map.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        map.put("hibernate.show_sql", "true");

        return map;
    }


    @Bean("serversDataSourceProperties")
    @Primary
    @ConfigurationProperties("app.datasource.servers")
    public DataSourceProperties serversDataSourceProperties(){
        return new DataSourceProperties();
    }



    @Bean("serversDataSource")
    @Primary
    @ConfigurationProperties("app.datasource.servers")
    public DataSource serversDataSource(@Qualifier("serversDataSourceProperties") DataSourceProperties serversDataSourceProperties) {
        return serversDataSourceProperties().initializeDataSourceBuilder().build();
    }

    @Bean(name = "serversTransactionManager")
    public JpaTransactionManager transactionManager(@Qualifier("serversEntityManager") EntityManagerFactory serversEntityManager){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(serversEntityManager);

        return transactionManager;
    }
}

域名 JPA 配置

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "domainsEntityManager",
        transactionManagerRef = "domainsTransactionManager",
        basePackages = {"org.springdemo.multiple.datasources.repository.domains"}
        )
public class DomainsConfig {

    @Bean(name = "domainsEntityManager")
    public LocalContainerEntityManagerFactoryBean getdomainsEntityManager(EntityManagerFactoryBuilder builder
    ,@Qualifier("domainsDataSource") DataSource domainsDataSource){

        return builder
                .dataSource(domainsDataSource)
                .packages("org.springdemo.multiple.datasources.domain.domains")
                .persistenceUnit("domains")
                .properties(additionalJpaProperties())
                .build();

    }


    Map<String,?> additionalJpaProperties(){
        Map<String,String> map = new HashMap<>();

        map.put("hibernate.hbm2ddl.auto", "create");
        map.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        map.put("hibernate.show_sql", "true");

        return map;
    }


    @Bean("domainsDataSourceProperties")
    @ConfigurationProperties("app.datasource.domains")
    public DataSourceProperties domainsDataSourceProperties(){
        return new DataSourceProperties();
    }


    @Bean("domainsDataSource")
    public DataSource domainsDataSource(@Qualifier("domainsDataSourceProperties") DataSourceProperties domainsDataSourceProperties) {
        return domainsDataSourceProperties.initializeDataSourceBuilder().build();
    }

    @Bean(name = "domainsTransactionManager")
    public JpaTransactionManager transactionManager(@Qualifier("domainsEntityManager") EntityManagerFactory domainsEntityManager){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(domainsEntityManager);

        return transactionManager;
    }

}

为了分离每个数据源,我将配置放在application.properties文件中,像这样:
app.datasource.domains.url=jdbc:h2:mem:~/test
app.datasource.domains.driver-class-name=org.h2.Driver


app.datasource.servers.driver-class-name=com.mysql.jdbc.Driver
app.datasource.servers.url=jdbc:mysql://localhost:3306/v?autoReconnect=true&useSSL=false
app.datasource.servers.username=myuser
app.datasource.servers.password=mypass

如果需要更多信息请参考以下文档:

Spring文档:如何配置两个数据源

如何配置两个不同数据库的类似示例:Github示例


2
我尝试了这个,但是出现了错误:“在您的配置中考虑定义类型为'org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder'的bean。”最初我遇到了一个错误,说找不到服务类型的bean,我希望这是因为新的Config类没有加载。所以我将这两个类导入到Main类中。之后我能够执行Config类,但是出现了上述错误。 - Ansar Samad
2
我能够通过从主类中删除HibernateJpaAutoConfiguration的排除来解决上述问题。但是在初始化entitymanagers时,我遇到了新的错误。描述: org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration中的requestMappingHandlerMapping方法需要一个单一的bean,但是找到了2个:
  • ibDemoEntityManager:由IBDemoConfig中的“getIBDemoEntityManager”方法定义
  • mwDemoEntityManager:由MWDemoConfig中的“getServersEntityManager”方法定义
- Ansar Samad
3
我通过将一个类的entitymanager设置为主要,成功解决了该问题。 - Ansar Samad
好的,我也添加了这个观察来将其中一个数据源设为主要。 - Daniel C.
3
这是你的文章,还是有人只是从StackOverflow复制粘贴答案到他们的网站上: https://newbedev.com/spring-data-jpa-multiple-enablejparepositories - ADJenks
显示剩余4条评论

3
@Daniel C.提供的答案是正确的。我在这里做出一些小的更正和观察。
  • @Primary 如果您不想将任何数据源标记为默认值,则不需要,否则必需。
  • 如果您使用@Bean名称定义任何一个EntityManagerFactoryBean 作为 entityManagerFactory,那么最好将其标记为< strong > @Primary 以避免冲突。
  • 可以将@ConfigurationProperties("app.datasource.servers")标记在类级别而不是在方法级别进行定义。
  • 如果您正在使用Spring Boot 2.x或更高版本,则最好返回HikariDataSource作为数据源,因为它已经被改变了。
  • 请确保为 HikariDataSource 定义精确的属性,用于引用JDBC连接URL。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接