Java 如何配置Spring引导以使用两个数据库?

Java 如何配置Spring引导以使用两个数据库?,java,mysql,spring,hibernate,spring-boot,Java,Mysql,Spring,Hibernate,Spring Boot,我正在使用SpringBoot2.X和Hibernate5连接不同服务器上的两个不同的MySQL数据库(Bar和Foo)。我试图从REST控制器中的方法列出实体的所有信息(自身属性和@OneToMany和@ManyToOne关系) 为此,我遵循了一些教程,因此,我能够获得我的@Primary数据库(Foo)的所有信息,但是,在检索@OneToMany集时,我的辅助数据库(Bar)总是会出现异常。如果我将@Primary注释交换到Bar数据库,我可以从Bar数据库获取数据,但不能从Foo数据库获取

我正在使用SpringBoot2.X和Hibernate5连接不同服务器上的两个不同的MySQL数据库(Bar和Foo)。我试图从REST控制器中的方法列出实体的所有信息(自身属性和
@OneToMany
@ManyToOne
关系)

为此,我遵循了一些教程,因此,我能够获得我的
@Primary
数据库(Foo)的所有信息,但是,在检索
@OneToMany
集时,我的辅助数据库(Bar)总是会出现异常。如果我将
@Primary
注释交换到Bar数据库,我可以从Bar数据库获取数据,但不能从Foo数据库获取数据。有办法解决这个问题吗

这是我得到的一个例外:

...w.s.m.s.DefaultHandlerExceptionResolver :
Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: 
    Could not write JSON document: failed to lazily initialize a collection of role: 
        com.foobar.bar.domain.Bar.manyBars, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]-com.foobar.bar.domain.Bar["manyBars"]); 
    nested exception is com.fasterxml.jackson.databind.JsonMappingException:
        failed to lazily initialize a collection of role: 
        com.foobar.bar.domain.Bar.manyBars, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.foobar.bar.domain.Bar["manyBars"])
My application.properties:

# MySQL DB - "foo"
spring.datasource.url=jdbc:mysql://XXX:3306/foo?currentSchema=public
spring.datasource.username=XXX
spring.datasource.password=XXX
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# MySQL DB - "bar"
bar.datasource.url=jdbc:mysql://YYYY:3306/bar?currentSchema=public
bar.datasource.username=YYYY
bar.datasource.password=YYYY
bar.datasource.driver-class-name=com.mysql.jdbc.Driver
# JPA
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
我的
@主数据源配置:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager",
        basePackages = {"com.foobar.foo.repo"})
public class FooDbConfig {

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

    @Primary
    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("dataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.foobar.foo.domain")
                .persistenceUnit("foo")
                .build();
    }

    @Primary
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "barEntityManagerFactory",
        transactionManagerRef = "barTransactionManager", basePackages = {"com.foobar.bar.repo"})
public class BarDbConfig {

    @Bean(name = "barDataSource")
    @ConfigurationProperties(prefix = "bar.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "barEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean barEntityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("barDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.foobar.bar.domain")
                .persistenceUnit("bar")
                .build();
    }

    @Bean(name = "barTransactionManager")
    public PlatformTransactionManager barTransactionManager(
            @Qualifier("barEntityManagerFactory") EntityManagerFactory barEntityManagerFactory) {
        return new JpaTransactionManager(barEntityManagerFactory);
    }
}
我的辅助数据源配置:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager",
        basePackages = {"com.foobar.foo.repo"})
public class FooDbConfig {

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

    @Primary
    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("dataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.foobar.foo.domain")
                .persistenceUnit("foo")
                .build();
    }

    @Primary
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "barEntityManagerFactory",
        transactionManagerRef = "barTransactionManager", basePackages = {"com.foobar.bar.repo"})
public class BarDbConfig {

    @Bean(name = "barDataSource")
    @ConfigurationProperties(prefix = "bar.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "barEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean barEntityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("barDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.foobar.bar.domain")
                .persistenceUnit("bar")
                .build();
    }

    @Bean(name = "barTransactionManager")
    public PlatformTransactionManager barTransactionManager(
            @Qualifier("barEntityManagerFactory") EntityManagerFactory barEntityManagerFactory) {
        return new JpaTransactionManager(barEntityManagerFactory);
    }
}
REST控制器类:

@RestController
public class FooBarController {

    private final FooRepository fooRepo;
    private final BarRepository barRepo;

    @Autowired
    FooBarController(FooRepository fooRepo, BarRepository barRepo) {
        this.fooRepo = fooRepo;
        this.barRepo = barRepo;
    }

    @RequestMapping("/foo")
    public List<Foo> listFoo() {
        return fooRepo.findAll();
    }

    @RequestMapping("/bar")
    public List<Bar> listBar() {
        return barRepo.findAll();
    }

    @RequestMapping("/foobar/{id}")
    public String fooBar(@PathVariable("id") Integer id) {
        Foo foo = fooRepo.findById(id);
        Bar bar = barRepo.findById(id);

        return foo.getName() + " " + bar.getName() + "!";
    }

}
最后,我的应用程序主要包括:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
需要注意的是,解决方案应保留两个数据库的Lazy属性,以保持最佳性能。

编辑1:如果两个目录(MySQL术语中的“数据库”)位于同一个数据库(“服务器”)中,Rick James解决方案可以工作

当目录(MySQL数据库)位于不同的数据库(服务器)中时,问题仍然存在,并且试图保留该属性


非常感谢。

*在Hibernate&JPA中,默认情况下,许多集合都是惰性的。错误是因为Jackson试图在实体管理器(hibernate speak中的aka会话)关闭时序列化OneToMany。因此,无法检索惰性集合

默认情况下,带JPA的Spring Boot为主EM提供了一个
OpenEntityManagerViewFilter
。这允许只读DB访问,但默认情况下仅适用于主EM

您有3种选择:

1) 您可以添加连接获取,例如

2) 您可以为非主要实体管理器添加OpenEntityManagerViewFilter,并将其添加到您的上下文中

请注意,这意味着一个挑战,对于每个Bar和Foo实例,您的应用程序将返回到数据库以检索OneToMany。这一部分不适用于Bar,但适用于Foo。这意味着一个可伸缩性问题(有些人称之为N+1问题),对于每个foo和bar,您运行一个额外的查询,对于数量不多的foo和bar,这将变得很慢

3) 另一种方法是让您的Bar和Foo上的收藏更具吸引力(见下图),但如果您担心可伸缩性,则需要仔细分析这一点

我建议选项#1。

在同一台服务器上有两个数据库(也称为“目录”),只使用一个连接。因此,请参考:

Foo.table1
Bar.table2
只要有一个简单的表名,就可以使用该语法

不同的服务器

如果数据不在同一台机器上,就会变得混乱。有几个想法:

  • 从每个目录中获取数据,然后在应用程序代码中进行操作。框架可能没有钩子可以同时对两台服务器执行任何操作
  • 使用MariaDB及其
    FEDERATEDX
    引擎

哪个控制器方法引发此异常?“/foobar/{id}”@hovanesys从第二个数据库(Bar)检索一个对象的所有方法,例如“/Bar”。根据您的第二个问题,方法“/foobar/{id}”不会引发任何异常,因为它只返回对象栏的名称,而不是manytone Foo Foo。您是否也可以发布您的存储库?我对问题进行了编辑,将这两个存储库都包括在内。视图中的open session和急切获取是反模式。应该改用JoinFetch。不确定视图中的open session是否是反模式,它是默认的spring引导行为,尽管它为N+1设置了一个。无论如何,添加了join fetch,感谢您的输入。可能会感兴趣,非常感谢!!我知道这是一个可行的解决方案,但只适用于小型数据库。如果有一个对象包含一千个联接对象,这不是一个好主意。我试图为这两个达巴人保留懒惰的财产。我试图找到一种类似于将两个数据库都作为主数据库的解决方案。我将对问题进行编辑以添加这一点。实际上,在大容量的情况下,懒惰提出了一个不同的挑战,有些人会说是更糟糕的挑战。对于每个Foo或Bar,您将运行一个对db的查询,一个较大的查询通常比许多较小的查询要好。谢谢!如果两个数据库都在同一台服务器上,这是一个很好的解决方案,但如果它们在不同的服务器上,则不起作用。@Martin-如果您的问题涉及两台不同的服务器(或一台服务器上的不同MySQL实例),请在问题中说明这一点。您的代码听起来像是在localhost:3306上连接的,它必须是同一服务器上的同一个实例。我复制它是为了给出一个复制它的示例(我看不到两个数据库在同一个网络中)。我对问题进行了编辑,以澄清这一点。但对于下一个将阅读您的问题的用户来说,重要的是,如果添加了@Table(name=“Foo”,catalog=“FooDB”)和@Table(name=“Bar”,catalog=“BarDB”),那么您的示例是有效的。仅当它们位于相同的数据库中时。非常感谢@Martin-您的更改不明确,所以我进一步编辑了您的问题。@rick james,对于同一台服务器中的两个不同目录,它可以正常工作,但我是否有可能避免使用schema.table名称而只写表名。