本页将在 Spring Boot 中使用 AbstractRoutingDataSource
和 Spring Data JPA 完成动态数据源路由。 AbstractRoutingDataSource
是 DataSource
的抽象实现,它根据查找键将调用路由到各种目标数据源之一。
AbstractRoutingDataSource
在 Spring 的 2.0.1 版本 中引入,提供了一种根据当前上下文动态确定实际数据源的方法。它维护通过更改 context 切换的多个数据源的 map。
类似帖子:使用 Spring JPA 的 Spring Boot 多数据源示例
有时我们需要根据地域或语言动态切换数据库,根据请求进行操作。
假设,我们有两个分支机构,即 Bangkok 和 Hongkong,每个分支机构有一个数据库。如果请求来自 Bangkok 分支,我们需要从 Bangkok 数据库获取详细信息,如果请求来自 Hongkong 分支,则来自 Hongkong 数据库。
在本教程中,我们将公开一个连接 hongkongdb 或 bangkokdb 的 REST 端点,并根据请求从表中获取员工信息并返回 JSON 对象。
端点:http://localhost:8080/employee
1.1 如果 branch =
hongkong 那么它将连接 hongkongdb 并获取员工信息并返回:
[
{
"id": 5,
"name": "Jackie Chan",
"branch": "hongkong"
},
{
"id": 8,
"name": "Maggie Cheung",
"branch": "hongkong"
}
]
1.2 同理 if branch =
bangkok 然后连接bangkokdb,获取曼谷分行的员工信息并返回:
[
{
"id": 1,
"name": "Tony Jaa",
"branch": "bangkok"
}
]
注意: 在上面的请求中,我们添加了一个名为 “branch” 的请求标头,第一个请求的值为 Bangkok,第二个请求为 Hongkong。
将以下依赖项添加到 pom.xml 文件中。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>org.websparrow</groupId>
<artifactId>spring-boot-datasource-routing</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
我们在 STS 4 IDE 中的应用程序的最终项目结构如下所示:
在 application.properties 文件中为两个数据源(即 hongkongdb 和 bangkokdb)配置数据库(数据源)连接字符串。
application.properties
#database details for bangkok branch
bangkok.datasource.url=jdbc:mysql://localhost:3306/bangkokdb?createDatabaseIfNotExist=true
bangkok.datasource.username=root
bangkok.datasource.password=root
#database details for hongkong branch
hongkong.datasource.url=jdbc:mysql://localhost:3306/hongkongdb?createDatabaseIfNotExist=true
hongkong.datasource.username=root
hongkong.datasource.password=root
# JPA property settings
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
首先——让我们创建一个简单的实体,它将存在于两个数据库中。
Employee.java
package org.websparrow.entity;
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String branch;
// Generate Getters and Setters...
}
我们创建了一个 BranchEnum
来保存两个分支的名称。 AbstractRoutingDataSource
需要一条信息到哪个数据库路由。这里这个枚举将作为 AbstractRoutingDataSource
类的上下文。
BranchEnum.java
package org.websparrow.constant;
public enum BranchEnum {
BANGKOK, HONGKONG
}
BranchContextHolder
将保存分支的当前上下文作为 ThreadLocal
引用。这个类将提供对BranchEnum
的线程绑定访问。
BranchContextHolder.java
package org.websparrow.config;
import org.websparrow.constant.BranchEnum;
public class BranchContextHolder {
private static ThreadLocal<BranchEnum> threadLocal = new ThreadLocal<>();
public static void setBranchContext(BranchEnum branchEnum) {
threadLocal.set(branchEnum);
}
public static BranchEnum getBranchContext() {
return threadLocal.get();
}
public static void clearBranchContext() {
threadLocal.remove();
}
}
DataSourceRouting
扩展了 AbstractRoutingDatasource
类,该类将包含真实数据源的映射。覆盖其 determineCurrentLookupKey()
方法以确定当前的查找键。这通常用于检查线程绑定事务上下文。
DataSourceRouting.java
package org.websparrow.config;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.websparrow.constant.BranchEnum;
public class DataSourceRouting extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return BranchContextHolder.getBranchContext();
}
public void initDatasource(DataSource bangkokDataSource,
DataSource hongkongDataSource) {
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(BranchEnum.BANGKOK, bangkokDataSource);
dataSourceMap.put(BranchEnum.HONGKONG, hongkongDataSource);
this.setTargetDataSources(dataSourceMap);
this.setDefaultTargetDataSource(bangkokDataSource);
}
}
数据源映射设置为targetDataSources,并选择一个数据源作为默认数据源。
我们将创建两个类来保存两个数据库的数据库连接属性。
HongkongDetails.java
package org.websparrow.model;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "hongkong.datasource")
public class HongkongDetails {
private String url;
private String password;
private String username;
// Generates Getters and Setters...
}
曼谷详情.java
package org.websparrow.model;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "bangkok.datasource")
public class BangkokDetails {
private String url;
private String password;
private String username;
// Generates Getters and Setters...
}
现在我们将为我们的两个数据库创建数据源,将它们放入一个映射中并将其提供给 DataSourceRouting
。
DataSourceConfig.java
package org.websparrow.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.websparrow.entity.Employee;
import org.websparrow.model.BangkokDetails;
import org.websparrow.model.HongkongDetails;
@Configuration
@EnableJpaRepositories(
basePackages = "org.websparrow.repo",
transactionManagerRef = "transcationManager",
entityManagerFactoryRef = "entityManager")
@EnableTransactionManagement
public class DataSourceConfig {
@Autowired
private BangkokDetails bangkokDetails;
@Autowired
private HongkongDetails hongkongDetails;
@Bean
@Primary
@Autowired
public DataSource dataSource() {
DataSourceRouting routingDataSource = new DataSourceRouting();
routingDataSource.initDatasource(bangkokDataSource(),
hongkongDataSource());
return routingDataSource;
}
public DataSource hongkongDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(hongkongDetails.getUrl());
dataSource.setUsername(hongkongDetails.getUsername());
dataSource.setPassword(hongkongDetails.getPassword());
return dataSource;
}
public DataSource bangkokDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(bangkokDetails.getUrl());
dataSource.setUsername(bangkokDetails.getUsername());
dataSource.setPassword(bangkokDetails.getPassword());
return dataSource;
}
@Bean(name = "entityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
EntityManagerFactoryBuilder builder) {
return builder.dataSource(dataSource()).packages(Employee.class)
.build();
}
@Bean(name = "transcationManager")
public JpaTransactionManager transactionManager(
@Autowired @Qualifier("entityManager") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
return new JpaTransactionManager(entityManagerFactoryBean.getObject());
}
}
DataSourceInterceptor
拦截每个请求并从请求头中获取分支信息并将其放入我们已经创建的上下文持有者中,我们将通过 BranchContextHolder
切换数据源。
DataSourceInterceptor.java
package org.websparrow.config;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.websparrow.constant.BranchEnum;
@Component
public class DataSourceInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String branch = request.getHeader("branch");
if (BranchEnum.BANGKOK.toString().equalsIgnoreCase(branch))
BranchContextHolder.setBranchContext(BranchEnum.BANGKOK);
else
BranchContextHolder.setBranchContext(BranchEnum.HONGKONG);
return super.preHandle(request, response, handler);
}
}
将此拦截器注册到 WebMvcConfigurer
。
WebConfig.java
package org.websparrow.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private DataSourceInterceptor dataSourceInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
EmployeeRepository.java
package org.websparrow.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.websparrow.entity.Employee;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {}
EmployeeService.java
package org.websparrow.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.websparrow.entity.Employee;
import org.websparrow.repo.EmployeeRepository;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public List<Employee> getEmployees() {
return employeeRepository.findAll();
}
}
EmployeeController
类为应用程序用户公开 REST 端点。在这个控制器类中,我们创建了一个 REST 端点,如下所示:
http://localhost:8080/employee:将从选定的数据源中检索员工的记录。
EmployeeController.java
package org.websparrow.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.websparrow.entity.Employee;
import org.websparrow.service.EmployeeService;
@RestController
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping(value = "employee")
public ResponseEntity<List<Employee>> getEmployees() {
return ResponseEntity.status(HttpStatus.ACCEPTED)
.body(employeeService.getEmployees());
}
}
DataSourceRoutingApp
类包含主要方法并负责启动应用程序。
DataSourceRoutingApp.java
package org.websparrow;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DataSourceRoutingApp {
public static void main(String[] args) {
SpringApplication.run(DataSourceRoutingApp.class, args);
}
}
要测试应用程序,请通过执行上述类来启动 Spring Boot 应用程序,并使用标头参数(即分支及其值)点击以下 API:
API:http://localhost:8080/employee
16.1 如果 branch =
hongkong 那么它将连接到 hongkongdb
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
内容来源于网络,如有侵权,请联系作者删除!