springboot+dynamic-datasource は、複数のデータソースの動的切り替えを実装します(アノテーションなし)
I.はじめに
最近、 SaaS プラットフォームの機能を分析しています多租户
が、これにはデータベース部分の機能が関与する必要があります。マルチテナント設計スキームでは、テナント分離データとテナント共有データを考慮する必要があります。共有データは実装が簡単ですが、分離データは比較的実装が困難です。一般に、分離性とスケーラビリティ、テナントのコスト、運用と保守の複雑さを考慮する必要があります。通常、SaaS マルチテナント データ ストレージには 3 つの主要なソリューションがあります。
- 独立したデータベース: テナントごとに 1 つのデータベース。
- 共有データベース、分離されたデータ アーキテクチャ: 共有されています
database
が、複数またはすべてのテナントtenant
によって異なりますschema
。 - 共有データベース、共有データ アーキテクチャ: テナントは、テーブル内のテナントのデータを区別することで、1 つ
database
、1 つを共有します。schema
tenantID
注: マルチデータ関数は日常業務に関与しているため、今日は最初のソリューションを共有します。これはマルチテナント アプリケーション シナリオにのみ適用できるものではありません。
2. 企画のアイデア
詳細:
1) テナント管理データベースからテナントデータベースの接続情報を照会する;
2) テナントの構成に応じて、該当するテナントデータベースの情報を動的に追加および削除する; 3)
テナントのフィールドに応じてclientId
使用されるデータベース接続を判断するリクエストヘッダー非@DS注解方式
。
3. コードの実装
pom.xml
<!--mybatis-plus的springboot支持-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
注:dynamic-datasource
マルチデータ ソース機能は、dynamic-datasource
使用例でより頻繁に実装されています@DS注解
。今日の例では、データ ソースを動的に切り替えるための aop の使用を共有しています。
コントローラー.java
@RestController
@AllArgsConstructor
public class TestController {
private final UserService userService;
@GetMapping("/test/list")
public List<User> getUserList(){
return userService.queryAll();
}
}
テスト機能インターフェイス、Bean、Dao は例ではなくなりました
アプリケーション.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志
map-underscore-to-camel-case: true
# 该配置就是将带有下划线的表字段映射为驼峰格式的实体类属性
spring:
# 配置时区
jackson:
time-zone: GMT+8
# 数据源相关配置
datasource:
dynamic:
# 主数据源
primary: db1
datasource:
# 数据源1
db1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
username: root
password: admin_123
# druid 全局配置
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: true
test-on-return: false
server:
port: 8089
SourceConstants.java
package com.sk.common;
public class SourceConstants {
/** 数据源查询sql */
public static final String SELECT_SOURCE = "select s.slave, s.username, s.password, s.url, s.driver_class_name from te_source s where s.status = 0 and s.del_flag = 0";
/** 数据源字段 */
public enum Details {
SLAVE("slave", "数据源编码"),
USERNAME("username", "用户名"),
PASSWORD("password", "密码"),
URL_PREPEND("url_prepend", "连接地址"),
URL("url", "连接地址"),
URL_APPEND("url_append", "连接参数"),
DRIVER_CLASS_NAME("driver_class_name", "驱动");
private final String code;
private final String info;
Details(String code, String info) {
this.code = code;
this.info = info;
}
public String getCode() {
return code;
}
public String getInfo() {
return info;
}
}
}
一定の構成
SourceProperties.java
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.db1")
public class SourceProperties {
/** 数据源驱动 */
private String driverClassName;
/** 数据源路径 */
private String url;
/** 数据源账号 */
private String username;
/** 数据源密码 */
private String password;
}
データソース情報テーブル
DROP TABLE IF EXISTS `te_source`;
CREATE TABLE `te_source` (
`slave` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`driver_class_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` int(255) NULL DEFAULT NULL,
`del_flag` int(255) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of te_source
-- ----------------------------
INSERT INTO `te_source` VALUES ('db2', 'root', 'admin_123', 'jdbc:mysql://localhost:3306/test02?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8', 'com.mysql.cj.jdbc.Driver', 0, 0);
INSERT INTO `te_source` VALUES ('db3', 'root', 'admin_123', 'jdbc:mysql://localhost:3306/test03?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8', 'com.mysql.cj.jdbc.Driver', 0, 0);
DynamicDataSourceLoading.java
/**
* 子数据源加载
*
* @author xueyi
*/
@Configuration
@AllArgsConstructor
public class DynamicDataSourceLoading {
private final SourceProperties sourceProperties;
@Bean
public DynamicDataSourceProvider jdbcDynamicDataSourceProvider() {
return new AbstractJdbcDataSourceProvider(sourceProperties.getDriverClassName(), sourceProperties.getUrl(), sourceProperties.getUsername(), sourceProperties.getPassword()) {
@Override
protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
ResultSet rs = statement.executeQuery(SourceConstants.SELECT_SOURCE);
Map<String, DataSourceProperty> map = new HashMap<>();
while (rs.next()) {
String name = rs.getString(Details.SLAVE.getCode());
String username = rs.getString(Details.USERNAME.getCode());
String password = rs.getString(Details.PASSWORD.getCode());
//String url = rs.getString(Details.URL_PREPEND.getCode()).concat(rs.getString(Details.URL_APPEND.getCode()));
String url = rs.getString(Details.URL.getCode());
String driver = rs.getString(Details.DRIVER_CLASS_NAME.getCode());
DataSourceProperty property = new DataSourceProperty();
property.setUsername(username);
property.setPassword(password);
property.setUrl(url);
property.setDriverClassName(driver);
map.put(name, property);
}
return map;
}
};
}
}
すべてのデータ ソース情報をクエリし、各データベース接続をロードします
TransformDataSource.java
@Log4j2
@Aspect // FOR AOP
@Configuration // 配置类
public class TransformDataSource {
@Pointcut("execution( * com.sk.controller..*.*(..))")
/**
* 这个方法的方法名要和下面注解方法名一致
*/
public void doPointcut() {
}
@Before("doPointcut()")
public void doBefore(JoinPoint joinPoint) {
// 请求开始时间
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String headValue = sra.getRequest().getHeader("clientId");
log.info("--------------clientId:{}", headValue);
DynamicDataSourceContextHolder.poll();
DynamicDataSourceContextHolder.push(headValue);
}
@After("doPointcut()")
public void doAfter() {
System.out.println("==doAfter==");
}
}
aop を使用して、リクエスト ヘッダーの clientId データに基づいて異なるデータベース接続を使用します。
リクエスト1
リクエスト2