在运行时 Spring 引导应用程序中连接到两个相同数据库中的一个
Connection to one from two Identical databases in Spring boot application at RUNTIME
已更新
当我尝试连接到 2 个相同的数据库(具有相同的表)然后选择其中一个来发出请求时,Spring 框架出现问题。我为此使用了 3 个配置文件 DbConfig、AbstractRoutingDataSource 和一个 application.properties 文件。
我目前在启动我的应用程序时没有收到任何错误,但任何时候我使用我的 restful 网络服务时我都会得到空数据,请检查下面的控制器文件。
注意:我已经点击了所有这些链接,但仍然没有结果:
- Baeldung AbstractRoutingDataSource guide
- Alexander guide
- Dynamic DataSource Routing on Spring website
我正在开发 Spring boot v2.0.5。
注意:如果您对 AbstractRoutingDataSource class 及其工作原理没有任何想法,请不要回答我。
我的application.properties文件
spring.jpa.database=mysql
spring.jpa.open-in-view=false
# Main database DEV_database1
first.datasource.url = jdbc:mysql://localhost:3306/DEV_database1
first.datasource.username = root
first.datasource.password =
first.datasource.driver-class-name = com.mysql.jdbc.Driver
# second database DEV_database2
second.datasource.url = jdbc:mysql://localhost:3306/DEV_database2
second.datasource.username = root
second.datasource.password =
second.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.jpa.database-platform = org.hibernate.dialect.H2Dialect
spring.jpa.show-sql = true
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
#...
[更新] 我的 DbConfig 文件
@Configuration
public class DbConfig {
@Bean
@Primary
@ConfigurationProperties("first.datasource")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public HikariDataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("second.datasource")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public HikariDataSource secondDataSource() {
return secondDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
DataSource dataSources() {
AbstractRoutingDataSource dataSource = new CustomerRoutingDataSource();
Map<Object, Object> resolvedDataSources = new HashMap<>();
resolvedDataSources.put(DbType.DATASOURCE1, firstDataSource());
resolvedDataSources.put(DbType.DATASOURCE2, secondDataSource());
dataSource.setDefaultTargetDataSource(firstDataSource()); // << default
dataSource.setTargetDataSources(resolvedDataSources);
return dataSource;
}
}
我的CustomerRoutingDataSource.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* AbstractRoutingDatasource can be used in place of standard DataSource implementations and enables a mechanism to
* determine which concrete DataSource to use for each operation at runtime.
*
* @author fre
*/
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
private static final Logger log = LoggerFactory.getLogger(CustomerRoutingDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
log.info(">>> determineCurrentLookupKey thread: {}", Thread.currentThread().getName() );
log.info(">>> RoutingDataSource: {}", DbContextHolder.getDbType());
return DbContextHolder.getDbType();
}
}
我的DbContextHolder.java
public class DbContextHolder {
private static final ThreadLocal<DbType> contextHolder =
new ThreadLocal<DbType>();
// set the datasource
public static void setDbType(DbType dbType) {
if(dbType == null){
throw new NullPointerException();
}
contextHolder.set(dbType);
}
// get the current datasource in use
public static DbType getDbType() {
return (DbType) contextHolder.get();
}
// clear datasource
public static void clearDbType() {
contextHolder.remove();
}
}
DbType.java
public enum DbType {
DATASOURCE1,
DATASOURCE2,
}
这就是我在 Controller 中使用它的方式:
我的控制器
@RestController
@RequestMapping("/api/user")
public class MembreController extends MainController {
@Autowired
MembreRepository membreRepository;
@GetMapping("/login")
public ResponseString login(String userName, String password) {
// setting the datasouce to DATASOURCE1
DbContextHolder.setDbType(DbType.DATASOURCE1);
// finding the list of user in DataSource1
List<Membres> list = membreRepository.findAll();
// clearing the current datasource
DbContextHolder.clearDbType();
for (Iterator<Membres> membreIter = list.iterator(); membreIter.hasNext();) {
Membres membre = membreIter.next();
if (membre.getUserName().equals(userName) && membre.getPassword().equals(password)) {
return new ResponseString("Welcome" + membre.getFirstName() + " " + membre.getLastName());
}
}
return new ResponseString("User not found");
}
}
我的Application.java文件
@ComponentScan
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
/**
* main method
*
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
好吧,我找到了解决方案!
问题出在我的 .properties 和 DbConfig 文件上。我只是从 .properties 文件中省略了数据库参数,并手动将它们添加到我的 DbConfig 文件中。它工作正常:
我的属性文件
spring.jpa.database=mysql
spring.jpa.open-in-view=false
spring.jpa.database-platform = org.hibernate.dialect.H2Dialect
spring.jpa.show-sql = true
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
#...
我的 DbConfig
@Configuration
public class DbConfig{
@Autowired
private Environment env;
@Bean(name = "dataSource1")
DataSource dataSource1() {
return DataSourceBuilder.create()
.url("jdbc:mysql://localhost:3306/DEV_database1?zeroDateTimeBehavior=convertToNull")
.driverClassName("com.mysql.jdbc.Driver").username("root").password("").build();
}
@Bean(name = "dataSource2")
DataSource dataSource2() {
return DataSourceBuilder.create()
.url("jdbc:mysql://localhost:3306/database2")
.driverClassName("com.mysql.jdbc.Driver").username("root").password("").build();
}
@Primary
@Bean(name = "mainDataSource")
DataSource dataSources() {
AbstractRoutingDataSource dataSource = new CustomerRoutingDataSource();
DataSource dataSource1= dataSource1();
DataSource dataSource2 = dataSource2();
Map<Object, Object> resolvedDataSources = new HashMap<>();
resolvedDataSources.put(DbType.DATASOURCE1, dataSource1);
resolvedDataSources.put(DbType.DATASOURCE2, dataSource2 );
dataSource.setTargetDataSources(resolvedDataSources);
dataSource.setDefaultTargetDataSource(dataSource1); // << default
return dataSource;
}
}
这还不是全部! 之后我遇到了一个新错误!它说:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration
┌─────┐
| dataSource defined in class path resource [tn/fre/gestdoc/DbConfig.class]
↑ ↓
| dataSource1 defined in class path resource [tn/fre/gestdoc/DbConfig.class]
↑ ↓
| org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
└─────┘
我关注了这个:Answer on GitHub and the error has gone, the application works fine now!! I can choose which datasource from my login controller, thanks @M. Deinum
已更新
当我尝试连接到 2 个相同的数据库(具有相同的表)然后选择其中一个来发出请求时,Spring 框架出现问题。我为此使用了 3 个配置文件 DbConfig、AbstractRoutingDataSource 和一个 application.properties 文件。 我目前在启动我的应用程序时没有收到任何错误,但任何时候我使用我的 restful 网络服务时我都会得到空数据,请检查下面的控制器文件。
注意:我已经点击了所有这些链接,但仍然没有结果:
- Baeldung AbstractRoutingDataSource guide
- Alexander guide
- Dynamic DataSource Routing on Spring website
我正在开发 Spring boot v2.0.5。
注意:如果您对 AbstractRoutingDataSource class 及其工作原理没有任何想法,请不要回答我。
我的application.properties文件
spring.jpa.database=mysql
spring.jpa.open-in-view=false
# Main database DEV_database1
first.datasource.url = jdbc:mysql://localhost:3306/DEV_database1
first.datasource.username = root
first.datasource.password =
first.datasource.driver-class-name = com.mysql.jdbc.Driver
# second database DEV_database2
second.datasource.url = jdbc:mysql://localhost:3306/DEV_database2
second.datasource.username = root
second.datasource.password =
second.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.jpa.database-platform = org.hibernate.dialect.H2Dialect
spring.jpa.show-sql = true
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
#...
[更新] 我的 DbConfig 文件
@Configuration
public class DbConfig {
@Bean
@Primary
@ConfigurationProperties("first.datasource")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public HikariDataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("second.datasource")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public HikariDataSource secondDataSource() {
return secondDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
DataSource dataSources() {
AbstractRoutingDataSource dataSource = new CustomerRoutingDataSource();
Map<Object, Object> resolvedDataSources = new HashMap<>();
resolvedDataSources.put(DbType.DATASOURCE1, firstDataSource());
resolvedDataSources.put(DbType.DATASOURCE2, secondDataSource());
dataSource.setDefaultTargetDataSource(firstDataSource()); // << default
dataSource.setTargetDataSources(resolvedDataSources);
return dataSource;
}
}
我的CustomerRoutingDataSource.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* AbstractRoutingDatasource can be used in place of standard DataSource implementations and enables a mechanism to
* determine which concrete DataSource to use for each operation at runtime.
*
* @author fre
*/
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
private static final Logger log = LoggerFactory.getLogger(CustomerRoutingDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
log.info(">>> determineCurrentLookupKey thread: {}", Thread.currentThread().getName() );
log.info(">>> RoutingDataSource: {}", DbContextHolder.getDbType());
return DbContextHolder.getDbType();
}
}
我的DbContextHolder.java
public class DbContextHolder {
private static final ThreadLocal<DbType> contextHolder =
new ThreadLocal<DbType>();
// set the datasource
public static void setDbType(DbType dbType) {
if(dbType == null){
throw new NullPointerException();
}
contextHolder.set(dbType);
}
// get the current datasource in use
public static DbType getDbType() {
return (DbType) contextHolder.get();
}
// clear datasource
public static void clearDbType() {
contextHolder.remove();
}
}
DbType.java
public enum DbType {
DATASOURCE1,
DATASOURCE2,
}
这就是我在 Controller 中使用它的方式:
我的控制器
@RestController
@RequestMapping("/api/user")
public class MembreController extends MainController {
@Autowired
MembreRepository membreRepository;
@GetMapping("/login")
public ResponseString login(String userName, String password) {
// setting the datasouce to DATASOURCE1
DbContextHolder.setDbType(DbType.DATASOURCE1);
// finding the list of user in DataSource1
List<Membres> list = membreRepository.findAll();
// clearing the current datasource
DbContextHolder.clearDbType();
for (Iterator<Membres> membreIter = list.iterator(); membreIter.hasNext();) {
Membres membre = membreIter.next();
if (membre.getUserName().equals(userName) && membre.getPassword().equals(password)) {
return new ResponseString("Welcome" + membre.getFirstName() + " " + membre.getLastName());
}
}
return new ResponseString("User not found");
}
}
我的Application.java文件
@ComponentScan
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
/**
* main method
*
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
好吧,我找到了解决方案! 问题出在我的 .properties 和 DbConfig 文件上。我只是从 .properties 文件中省略了数据库参数,并手动将它们添加到我的 DbConfig 文件中。它工作正常:
我的属性文件
spring.jpa.database=mysql
spring.jpa.open-in-view=false
spring.jpa.database-platform = org.hibernate.dialect.H2Dialect
spring.jpa.show-sql = true
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
#...
我的 DbConfig
@Configuration
public class DbConfig{
@Autowired
private Environment env;
@Bean(name = "dataSource1")
DataSource dataSource1() {
return DataSourceBuilder.create()
.url("jdbc:mysql://localhost:3306/DEV_database1?zeroDateTimeBehavior=convertToNull")
.driverClassName("com.mysql.jdbc.Driver").username("root").password("").build();
}
@Bean(name = "dataSource2")
DataSource dataSource2() {
return DataSourceBuilder.create()
.url("jdbc:mysql://localhost:3306/database2")
.driverClassName("com.mysql.jdbc.Driver").username("root").password("").build();
}
@Primary
@Bean(name = "mainDataSource")
DataSource dataSources() {
AbstractRoutingDataSource dataSource = new CustomerRoutingDataSource();
DataSource dataSource1= dataSource1();
DataSource dataSource2 = dataSource2();
Map<Object, Object> resolvedDataSources = new HashMap<>();
resolvedDataSources.put(DbType.DATASOURCE1, dataSource1);
resolvedDataSources.put(DbType.DATASOURCE2, dataSource2 );
dataSource.setTargetDataSources(resolvedDataSources);
dataSource.setDefaultTargetDataSource(dataSource1); // << default
return dataSource;
}
}
这还不是全部! 之后我遇到了一个新错误!它说:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration
┌─────┐
| dataSource defined in class path resource [tn/fre/gestdoc/DbConfig.class]
↑ ↓
| dataSource1 defined in class path resource [tn/fre/gestdoc/DbConfig.class]
↑ ↓
| org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
└─────┘
我关注了这个:Answer on GitHub and the error has gone, the application works fine now!! I can choose which datasource from my login controller, thanks @M. Deinum