简体   繁体   English

如何配置Spring启动以使用两个数据库?

[英]How to configure Spring boot for work with two databases?

I am using Spring Boot 2.X with Hibernate 5 to connect two different MySQL databases (Bar and Foo) on different servers . 我使用Spring Boot 2.XHibernate 5 在不同的服务器上连接两个不同的MySQL数据库(Bar和Foo)。 I am trying to list all the information of an entity (own attributes and @OneToMany and @ManyToOne relations) from a method in a REST Controller. 我试图从REST控制器中的方法列出实体的所有信息(自己的属性和@OneToMany@ManyToOne关系)。

I have followed several tutorials to do this, thus, I am able to get all the information for my @Primary database (Foo), however, I always get an exception for my secondary database (Bar) when retrieving the @OneToMany sets. 我已经按照几个教程来完成这个,因此,我能够获取@Primary数据库(Foo)的所有信息,但是,在检索@OneToMany集时,我总是得到我的辅助数据库(Bar)的@OneToMany If I swap the @Primary annotation to the Bar database, I able to get the data from the Bar database but not for the Foo database . 如果我将@Primary注释交换到Bar数据库,我可以从Bar数据库获取数据,但不能从Foo数据库获取数据。 Is there a way to resolve this? 有办法解决这个问题吗?

This is the exception I am getting: 这是我得到的例外:

...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: 我的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

My @Primary DataSource configuration: 我的@Primary DataSource配置:

@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);
    }
}

My secondary DataSource configuration: 我的辅助DataSource配置:

@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);
    }
}

The REST Controller class: 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() + "!";
    }

}

The Foo/Bar repositories: Foo / Bar存储库:

@Repository
public interface FooRepository extends JpaRepository<Foo, Long> {
  Foo findById(Integer id);
}

@Repository
public interface BarRepository extends JpaRepository<Bar, Long> {
  Bar findById(Integer id);
}

The entities for the @Primary datasource. @Primary数据源的实体。 The entities of the second datasource are the same (only changing the class names): 第二个数据源的实体是相同的(只更改类名):

@Entity
@Table(name = "foo")
public class Foo {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;

    @Column(name = "name")
    private String name;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "foo")
    @JsonIgnoreProperties({"foo"})
    private Set<ManyFoo> manyFoos = new HashSet<>(0);

    // Constructors, Getters, Setters
}

@Entity
@Table(name = "many_foo")
public class ManyFoo {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;

    @Column(name = "name")
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnoreProperties({"manyFoos"})
    private Foo foo;

    // Constructors, Getters, Setters
}  

Finally, my application main: 最后,我的应用程序主要:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

It is important to remark that the solution should keep the Lazy property for both databases in order to maintain an optimal performance. 重要的是要注意解决方案应保留两个数据库的Lazy属性,以保持最佳性能。

Edit 1: If both catalogs ("databases" in MySQL terminology) are in same database ("server") the Rick James solution works!! 编辑1:如果两个目录(MySQL术语中的“数据库”)都在同一个数据库(“服务器”)中,那么Rick James解决方案就可以了!

The problem remains when catalogs (MySQL databases) are in different databases (servers) and it is tried to keep Lazy the property 当目录(MySQL数据库)位于不同的数据库(服务器)中并且尝试将Lazy保留为属性时,问题仍然存在

Many thanks. 非常感谢。

*ToMany Collections are lazy by default in Hibernate & JPA. *默认情况下,ToMany Collections在Hibernate和JPA中是惰性的。 The error is because Jackson is trying to serialize the OneToMany when the entity manager (aka session in hibernate-speak) is closed. 错误是因为当实体管理器(也就是hibernate-speak中的会话)关闭时,Jackson正在尝试序列化OneToMany。 Hence, lazy collections cannot be retrieved. 因此,无法检索惰性集合。

Spring Boot with JPA by default provides an OpenEntityManagerInViewFilter for the primary EM. 默认情况下,带JPA的Spring Boot为主EM提供OpenEntityManagerInViewFilter This allows for read-only DB access, but, by default only works for the primary EM. 这允许只读数据库访问,但默认情况下仅适用于主EM。

You have 3 options: 你有3个选择:

1) You can add a join fetch, eg How does the FetchMode work in Spring Data JPA 1)您可以添加连接提取,例如FetchMode如何在Spring Data JPA中工作

2) You can add a OpenEntityManagerInViewFilter for the non primary entity manager and add it to your context. 2)您可以为非主要实体管理器添加OpenEntityManagerInViewFilter并将其添加到您的上下文中。

Please note that this implies a challenge, for each Bar and Foo instance, your app will go back to the database to retrieve the OneToMany. 请注意,这意味着挑战,对于每个Bar和Foo实例,您的应用程序将返回数据库以检索OneToMany。 This is the part that isn't working for Bar, but is for Foo. 这是不适用于Bar的部分,但适用于Foo。 This implies a scalability problem (called the N + 1 problem by some) as for each foo and bar, you run an additional query, which will get slow for non-trivial amounts of Foos and Bars. 这意味着一个可伸缩性问题(一些人称之为N + 1问题),对于每个foo和bar,你运行一个额外的查询,这对于非平凡数量的Foos和Bars来说会变慢。

3) An alternative is to make your collection on Bar and Foo eager (see this https://docs.oracle.com/javaee/7/api/javax/persistence/OneToMany.html#fetch-- ) but this needs to be analyzed carefully if scalability is a concern at all for you. 3)另一种方法是让你的收集在Bar和Foo上急切(请参阅https://docs.oracle.com/javaee/7/api/javax/persistence/OneToMany.html#fetch-- )但这需要是仔细分析可扩展性是否适合您。

I'd recommend option #1. 我建议选项#1。

Two databases (aka "catalogs") on the same server? 同一台服务器上有两个数据库(又名“目录”)? Use only one connection. 仅使用一个连接。 Then reference thus: 然后参考:

Foo.table1
Bar.table2

Use that syntax wherever you would have a simple table name. 只要有简单的表名,就可以使用该语法。

Different servers 不同的服务器

It gets messy if the data is not in the same machine. 如果数据不在同一台机器上,它会变得混乱。 A couple of ideas: 一些想法:

  • Fetch the data from each catalog, then manipulate in application code. 从每个目录中获取数据,然后在应用程序代码中进行操作。 The framework probably has no hooks for doing anything to both servers at the same time. 该框架可能没有钩子同时对两个服务器做任何事情。
  • Use MariaDB and its FEDERATEDX Engine. 使用MariaDB及其FEDERATEDX引擎。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM