如何配置 Spring Boot 以使用两个数据库?
Posted
技术标签:
【中文标题】如何配置 Spring Boot 以使用两个数据库?【英文标题】:How to configure Spring boot for work with two databases? 【发布时间】:2018-08-07 13:42:03 【问题描述】:我正在使用 Spring Boot 2.X 和 Hibernate 5 来连接不同服务器上的两个不同的 mysql 数据库(Bar 和 Foo)。我正在尝试从 REST 控制器中的方法列出实体的所有信息(自己的属性和 @OneToMany
和 @ManyToOne
关系)。
我遵循了几个教程来执行此操作,因此,我能够获取我的 @Primary
数据库 (Foo) 的所有信息,但是,在检索 @ 时,我的辅助数据库 (Bar) 总是出现异常987654324@ 套。如果我将 @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"])
我的 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
我的@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);
我的辅助数据源配置:
@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() + "!";
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);
@Primary
数据源的实体。第二个数据源的实体是一样的(只是改了类名):
@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
最后,我的应用程序 main:
@SpringBootApplication
public class Application
public static void main(String[] args)
SpringApplication.run(Application.class, args);
值得注意的是,解决方案应为两个数据库保留 Lazy 属性以保持最佳性能。
编辑 1:如果两个目录(MySQL 术语中的“数据库”)在同一个数据库(“服务器”)中,Rick James 解决方案有效!!
问题仍然存在,当目录(MySQL 数据库)位于不同的数据库(服务器)中并试图保持 Lazy 属性时
非常感谢。
【问题讨论】:
哪个控制器方法抛出这个异常? "/foobar/id" ? @hovanessyan 从第二个数据库(Bar)中检索 OneToMany 对象的所有方法,例如“/bar”。根据您的第二个问题,方法“/foobar/id”不会抛出任何异常,因为它只返回对象 bar 的名称而不是 ManyToOne Foo foo。 您能否也发布您的存储库? 我编辑了问题以包含两个存储库。 【参考方案1】:*ToMany 集合在 Hibernate 和 JPA 中默认是惰性的。该错误是因为 Jackson 试图在实体管理器(即休眠会话中的会话)关闭时序列化 OneToMany。因此,无法检索惰性集合。
默认情况下,带有 JPA 的 Spring Boot 为主 EM 提供 OpenEntityManagerInViewFilter
。这允许只读数据库访问,但默认情况下仅适用于主 EM。
您有 3 个选项:
1) 您可以添加连接提取,例如How does the FetchMode work in Spring Data JPA
2) 您可以为非主要实体管理器添加一个 OpenEntityManagerInViewFilter 并将其添加到您的上下文中。
请注意,这意味着一个挑战,对于每个 Bar 和 Foo 实例,您的应用程序将返回数据库以检索 OneToMany。这部分不适用于 Bar,但适用于 Foo。这意味着一个可扩展性问题(某些人称为 N + 1 问题),因为对于每个 foo 和 bar,您运行一个额外的查询,这对于非平凡数量的 Foos 和 Bars 会变慢。
3) 另一种方法是让您在 Bar 和 Foo 上的收藏变得急切(参见 https://docs.oracle.com/javaee/7/api/javax/persistence/OneToMany.html#fetch--),但如果您完全关心可扩展性,则需要仔细分析。
我推荐选项 #1。
【讨论】:
open session in view 和 Eager fetch 是反模式。应该使用 JoinFetch。 不确定视图中的打开会话是一种反模式,它是默认的 Spring Boot 行为,尽管它为 N + 1 设置了一个。无论如何,添加了 join fetch,感谢您的输入。 可能感兴趣vladmihalcea.com/the-open-session-in-view-anti-pattern 非常感谢!!我知道渴望它是一种可能的灵魂,但仅适用于小型数据库。如果您有一个对象与一千个连接对象,这不是一个好主意。我尝试为两个数据库维护惰性属性。我试图找到一个类似于将两个数据库都作为主数据库的解决方案。我将编辑问题以添加此内容。 实际上,在大批量时,懒惰带来了不同的,有些人会说更糟糕的挑战。对于每个 Foo 或 Bar,您将运行一个对 db 的查询,一个较大的查询通常比许多较小的查询要好。【参考方案2】:两个数据库(又名“目录”)在同一台服务器上?只使用一个连接。然后参考:
Foo.table1
Bar.table2
只要有一个简单的表名,就使用该语法。
不同的服务器
如果数据不在同一台机器上,它会变得混乱。几个想法:
从每个目录中获取数据,然后在应用程序代码中进行操作。该框架可能没有用于同时对两台服务器执行任何操作的挂钩。 使用 MariaDB 及其FEDERATEDX
引擎。
【讨论】:
谢谢!如果两个数据库在同一台服务器上,这是一个很好的解决方案,但如果它们在不同的服务器上,则不起作用。 @Martin - 如果您的问题涉及 2 个不同的服务器(或单个服务器上的不同 MySQL 实例),请在您的问题中明确说明。您的代码听起来好像都在 localhost:3306 上连接,这必然是 same 服务器上的 same 实例。 我复制它以给出一个复制它的例子(我没有看到两个数据库在同一个网络中)。我编辑问题以明确这一点。但是对于将阅读您的问题的下一个用户,重要的是要说如果 @Table(name="Foo" ,catalog="FooDB") 和 @Table(name="Bar" ,catalog="BarDB") 您的示例有效它被添加。仅当它们位于相同的数据库中时。非常感谢! @Martin - 您的更改不明确,因此我进一步编辑了您的问题。 @rick-james,对于同一服务器中的两个不同目录,它可以工作,但有没有机会我可以避免 schema.table 名称而只写表名。以上是关于如何配置 Spring Boot 以使用两个数据库?的主要内容,如果未能解决你的问题,请参考以下文章
如何配置两个实例mongodb使用spring boot和spring data
如何配置 IntelliJ HQL 控制台以使用 Spring Boot
启动时如何配置 Spring Boot 应用程序以在特定数据库上运行