This commit is contained in:
2023-06-28 17:48:50 +08:00
parent 4a3c958e1a
commit a567befac3
21 changed files with 309 additions and 122 deletions

View File

@ -8,6 +8,10 @@ import com.alibaba.druid.pool.DruidPooledConnection;
import com.qiaoba.common.base.constants.TenantConstant;
import com.qiaoba.common.base.context.BaseContext;
import com.qiaoba.common.base.enums.BaseEnum;
import com.qiaoba.common.database.context.BackupDatasourceContext;
import com.qiaoba.common.database.context.DynamicDataSourceContext;
import com.qiaoba.common.database.context.PrimaryDatasourceContext;
import com.qiaoba.common.database.context.TenantDbTypeContext;
import com.qiaoba.common.database.entity.DynamicDataSource;
import com.qiaoba.common.database.monitor.NotOnlineDatasourceMonitor;
import com.qiaoba.common.database.properties.DataSourceProperties;
@ -24,7 +28,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 多数据源配置
@ -52,25 +55,11 @@ public class DynamicDataSourceConfig {
*/
public static Boolean COMPLETE_LOAD_DATASOURCE = false;
/**
* 主要数据源
*/
public static Map<Object, Object> PRIMARY_DATASOURCE_MAP = new ConcurrentHashMap<>();
/**
* 备用数据源-每次取0索引
*/
public static Map<String, List<DynamicDataSource>> BACKUP_DATASOURCE_MAP = new ConcurrentHashMap<>();
/**
* 租户 ids
*/
public static List<String> TENANT_IDS = ListUtil.toList(TenantConstant.DEFAULT_TENANT_ID);
/**
* 租户数据源类型
*/
public static Map<String, String> TENANT_DATASOURCE_TYPE_MAP = new ConcurrentHashMap<>();
/**
* 把DynamicDataSourceContext 纳入容器管理其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象并调用freshDataSource方法
@ -85,7 +74,7 @@ public class DynamicDataSourceConfig {
}
public static DruidDataSource getPrimaryDataSource(String tenantId) {
return (DruidDataSource) PRIMARY_DATASOURCE_MAP.get(tenantId);
return (DruidDataSource) PrimaryDatasourceContext.get(tenantId);
}
private void initDefault() {
@ -105,13 +94,13 @@ public class DynamicDataSourceConfig {
}
}
if (CollUtil.isEmpty(PRIMARY_DATASOURCE_MAP)) {
if (CollUtil.isEmpty(PrimaryDatasourceContext.getAll())) {
log.error("主系统配置数据源全部无效, 请检查 yml 中相关配置");
}
// 其他数据源备用
addBackupMap(TenantConstant.DEFAULT_TENANT_ID, dataSources);
// 刷新数据源
this.dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP);
this.dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
}
private void initTenant() {
@ -143,29 +132,25 @@ public class DynamicDataSourceConfig {
}
BaseContext.clearAllHolder();
// 刷新数据源
dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP);
dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
}
public void deleteTenantDataSource(String tenantId) {
PRIMARY_DATASOURCE_MAP.remove(tenantId);
dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP);
}
public void changePrimaryDatasource(String tenantId, Object datasource) {
PRIMARY_DATASOURCE_MAP.put(tenantId, datasource);
PrimaryDatasourceContext.set(tenantId, datasource);
// 将数据源的类型保存
DruidPooledConnection connection = null;
try {
connection = ((DruidDataSource) datasource).getConnection();
TENANT_DATASOURCE_TYPE_MAP.put(tenantId, connection.getMetaData().getDatabaseProductName());
TenantDbTypeContext.set(tenantId, connection.getMetaData().getDatabaseProductName());
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 归还 connection
IoUtil.close(connection);
}
dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP);
dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
}
public static Object buildDataSource(String tenantId, DynamicDataSource dynamicDataSource) {
@ -183,7 +168,7 @@ public class DynamicDataSourceConfig {
dataSource.setInitialSize(dynamicDataSource.getInitialSize());
dataSource.setMinIdle(dynamicDataSource.getMinIdle());
dataSource.setMaxActive(dynamicDataSource.getMaxActive());
//dataSource.setKeepAlive(false);
try {
// 初始化数据源
dataSource.init();
@ -199,7 +184,7 @@ public class DynamicDataSourceConfig {
*/
@PreDestroy
public void close() {
Set<Map.Entry<Object, Object>> entries = PRIMARY_DATASOURCE_MAP.entrySet();
Set<Map.Entry<Object, Object>> entries = PrimaryDatasourceContext.getAll().entrySet();
for (Map.Entry<Object, Object> entry : entries) {
DruidDataSource dataSource = (DruidDataSource) entry.getValue();
IoUtil.close(dataSource);
@ -209,10 +194,10 @@ public class DynamicDataSourceConfig {
private void addPrimaryMap(String tenantId, Object dataSource) {
if (Objects.nonNull(dataSource)) {
try {
PRIMARY_DATASOURCE_MAP.put(tenantId, dataSource);
PrimaryDatasourceContext.set(tenantId, dataSource);
// 将数据源的类型保存
DruidPooledConnection connection = ((DruidDataSource) dataSource).getConnection();
TENANT_DATASOURCE_TYPE_MAP.put(tenantId, connection.getMetaData().getDatabaseProductName());
TenantDbTypeContext.set(tenantId, connection.getMetaData().getDatabaseProductName());
// 归还 connection
IoUtil.close(connection);
} catch (SQLException e) {
@ -223,7 +208,7 @@ public class DynamicDataSourceConfig {
private void addBackupMap(String tenantId, List<DynamicDataSource> dataSources) {
if (CollUtil.isNotEmpty(dataSources)) {
BACKUP_DATASOURCE_MAP.put(tenantId, dataSources);
BackupDatasourceContext.set(tenantId, dataSources);
}
}
@ -235,6 +220,6 @@ public class DynamicDataSourceConfig {
public void setDefaultSetting() {
BaseContext.setDataSource(TenantConstant.DEFAULT_TENANT_ID);
BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
BaseContext.setDatabaseType(TENANT_DATASOURCE_TYPE_MAP.get(TenantConstant.DEFAULT_TENANT_ID));
BaseContext.setDatabaseType(TenantDbTypeContext.getDefault());
}
}

View File

@ -0,0 +1,44 @@
package com.qiaoba.common.database.context;
import com.qiaoba.common.database.entity.DynamicDataSource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 备用(未在使用)数据源
*
* @author ailanyin
* @version 1.0
* @since 2023/6/28 10:36
*/
public class BackupDatasourceContext {
/**
* 备用数据源
*/
private static Map<String, List<DynamicDataSource>> BACKUP_DATASOURCE_MAP = new ConcurrentHashMap<>();
/**
* 获取租户备用数据源
*
* @param tenantId 租户ID
* @return 数据源集合
*/
public static List<DynamicDataSource> get(String tenantId) {
return BACKUP_DATASOURCE_MAP.get(tenantId);
}
/**
* 设置租户备用数据源
*
* @param tenantId 租户ID
* @param list 数据源集合
*/
public static void set(String tenantId, List<DynamicDataSource> list) {
BACKUP_DATASOURCE_MAP.put(tenantId, list);
}
}

View File

@ -1,4 +1,4 @@
package com.qiaoba.common.database.config;
package com.qiaoba.common.database.context;
import com.qiaoba.common.base.constants.TenantConstant;
import com.qiaoba.common.base.context.BaseContext;

View File

@ -0,0 +1,63 @@
package com.qiaoba.common.database.context;
import com.qiaoba.common.base.constants.TenantConstant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 主要(正在使用)数据源
*
* @author ailanyin
* @version 1.0
* @since 2023/6/28 10:36
*/
public class PrimaryDatasourceContext {
/**
* 主要数据源
*/
private static Map<Object, Object> PRIMARY_DATASOURCE_MAP = new ConcurrentHashMap<>();
public static Map<Object, Object> getAll() {
return PRIMARY_DATASOURCE_MAP;
}
/**
* 获取租户主要数据源
*
* @param tenantId 租户ID
* @return 数据源
*/
public static Object get(String tenantId) {
return PRIMARY_DATASOURCE_MAP.get(tenantId);
}
/**
* 获取默认(主)主要数据源
*
* @return 数据源
*/
public static Object getDefault() {
return PRIMARY_DATASOURCE_MAP.get(TenantConstant.DEFAULT_TENANT_ID);
}
/**
* 设置租户主要数据源
*
* @param tenantId 租户ID
* @param datasource 数据源
*/
public static void set(String tenantId, Object datasource) {
PRIMARY_DATASOURCE_MAP.put(tenantId, datasource);
}
/**
* 删除
*
* @param tenantId 租户ID
*/
public static void remove(String tenantId) {
PRIMARY_DATASOURCE_MAP.remove(tenantId);
}
}

View File

@ -0,0 +1,59 @@
package com.qiaoba.common.database.context;
import com.qiaoba.common.base.constants.TenantConstant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 租户数据源类型
*
* @author ailanyin
* @version 1.0
* @since 2023/6/28 10:25
*/
public class TenantDbTypeContext {
/**
* 租户数据源类型
*/
private static Map<String, String> TENANT_DATASOURCE_TYPE_MAP = new ConcurrentHashMap<>();
/**
* 获取租户数据源类型
*
* @param tenantId 租户ID
* @return 数据源类型
*/
public static String get(String tenantId) {
return TENANT_DATASOURCE_TYPE_MAP.get(tenantId);
}
/**
* 设置租户数据源类型
*
* @param tenantId 租户ID
* @param dbType 数据源类型
*/
public static void set(String tenantId, String dbType) {
TENANT_DATASOURCE_TYPE_MAP.put(tenantId, dbType);
}
/**
* 获取默认(主)数据源类型
*
* @return 数据源类型
*/
public static String getDefault() {
return TENANT_DATASOURCE_TYPE_MAP.get(TenantConstant.DEFAULT_TENANT_ID);
}
/**
* 设置默认(主)数据源类型
*
* @param dbType 数据源类型
*/
public static void set(String dbType) {
set(TenantConstant.DEFAULT_TENANT_ID, dbType);
}
}

View File

@ -1,7 +1,7 @@
package com.qiaoba.common.database.factories;
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
import com.qiaoba.common.database.config.DynamicDataSourceContext;
import com.qiaoba.common.database.context.DynamicDataSourceContext;
import com.qiaoba.common.database.properties.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

View File

@ -1,17 +1,19 @@
package com.qiaoba.common.database.interceptors;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.sql.SqlExecutor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.qiaoba.common.base.constants.BaseConstant;
import com.qiaoba.common.base.constants.TenantConstant;
import com.qiaoba.common.base.context.BaseContext;
import com.qiaoba.common.database.utils.JdbcUtil;
import com.qiaoba.common.database.utils.SqlUtil;
import com.qiaoba.common.database.context.TenantDbTypeContext;
import com.qiaoba.common.database.utils.DbUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.postgresql.jdbc.PgConnection;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Objects;
/**
@ -21,17 +23,22 @@ import java.util.Objects;
* @version 1.0
* @since 2023-04-25 22:48:43
*/
@Slf4j
public class SchemaInterceptor implements InnerInterceptor {
@Value("${spring.application.name}")
private String schemaPrefix;
@Override
public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) {
if (Objects.nonNull(BaseContext.isSchemaMode()) && BaseContext.isSchemaMode()) {
try {
conn.unwrap(PgConnection.class).setSchema("qiaoba-boot-2");
// schemaPrefix + '-' + tenantId
// eg: schema = qiaoba-boot-2
DbUtil.setSchema(TenantDbTypeContext.getDefault(), conn, schemaPrefix + BaseConstant.HYPHEN_JOIN_STR + BaseContext.getTenantId());
} catch (SQLException e) {
e.printStackTrace();
log.info("切换SCHEMA失败, 原因: {}", e.getMessage());
}
}
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
import com.qiaoba.common.database.context.BackupDatasourceContext;
import com.qiaoba.common.database.entity.DynamicDataSource;
import com.qiaoba.common.database.utils.JdbcUtil;
import lombok.extern.slf4j.Slf4j;
@ -98,9 +99,9 @@ public class NotOnlineDatasourceMonitor {
}
private void addBackupMap(String tenantId, DynamicDataSource dataSource) {
List<DynamicDataSource> dataSourceList = DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.get(tenantId);
List<DynamicDataSource> dataSourceList = BackupDatasourceContext.get(tenantId);
if (CollUtil.isEmpty(dataSourceList)) {
DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.put(tenantId, ListUtil.toList(dataSource));
BackupDatasourceContext.set(tenantId, ListUtil.toList(dataSource));
} else {
dataSourceList.add(dataSource);
}

View File

@ -8,6 +8,8 @@ import com.baomidou.lock.LockTemplate;
import com.qiaoba.common.base.constants.TenantConstant;
import com.qiaoba.common.base.enums.DataBaseEnum;
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
import com.qiaoba.common.database.context.BackupDatasourceContext;
import com.qiaoba.common.database.context.PrimaryDatasourceContext;
import com.qiaoba.common.database.entity.DynamicDataSource;
import com.qiaoba.common.database.service.DynamicDatasourceService;
import com.qiaoba.common.database.utils.JdbcUtil;
@ -78,8 +80,7 @@ public class OnlineDatasourceMonitor {
private void tryConnect() {
for (String tenantId : DynamicDataSourceConfig.TENANT_IDS) {
Object primary = DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.get(tenantId);
DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.get(tenantId);
Object primary = PrimaryDatasourceContext.get(tenantId);
if (Objects.isNull(primary)) {
// 说明初始化主要数据源的时候出错
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
@ -110,7 +111,7 @@ public class OnlineDatasourceMonitor {
// 关闭原有异常数据源
IoUtil.close(dataSource);
// 在数据源Map中删除
DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.remove(tenantId);
PrimaryDatasourceContext.remove(tenantId);
}
// 将原有异常数据源加入到错误数据源Map, 等待重试
addErrorDatasource(tenantId, dataSource);
@ -130,7 +131,7 @@ public class OnlineDatasourceMonitor {
*/
private Boolean backToPrimary(String tenantId) {
// 备用数据源
List<DynamicDataSource> dataSources = DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.get(tenantId);
List<DynamicDataSource> dataSources = BackupDatasourceContext.get(tenantId);
if (CollUtil.isEmpty(dataSources)) {
log.error("租户:[{}]切换备用数据源失败, 原因: 没有备用数据源", tenantId);
return false;

View File

@ -0,0 +1,43 @@
package com.qiaoba.common.database.utils;
import com.qiaoba.common.base.enums.DataBaseEnum;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.postgresql.jdbc.PgConnection;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author ailanyin
* @version 1.0
* @since 2023/6/28 8:52
*/
public class DbUtil {
public static void setSchema(String dbType, Connection conn, String schema) throws SQLException {
if (DataBaseEnum.MY_SQL.getType().equals(dbType)) {
conn.setSchema(schema);
} else if (DataBaseEnum.ORACLE.getType().equals(dbType)) {
} else if (DataBaseEnum.POSTGRE_SQL.getType().equals(dbType)) {
conn.unwrap(PgConnection.class).setSchema(schema);
} else if (DataBaseEnum.SQL_SERVER.getType().equals(dbType)) {
}
}
public static void runScript(Connection conn, String filePath) throws IOException {
ClassPathResource resource = new ClassPathResource(filePath);
File file = resource.getFile();
FileReader reader = new FileReader(file);
ScriptRunner scriptRunner = new ScriptRunner(conn);
scriptRunner.setSendFullScript(true);
scriptRunner.runScript(reader);
reader.close();
}
}

View File

@ -8,10 +8,7 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.*;
/**
* JdbcUtil
@ -104,6 +101,18 @@ public class JdbcUtil {
}
public static void main(String[] args) {
System.out.println(check("oracle.jdbc.OracleDriver", "jdbc:oracle:thin:@//192.168.0.205:1521/ORCL", "system", "root"));
Connection connection = getConnection("oracle.jdbc.OracleDriver", "jdbc:oracle:thin:@//192.168.0.205:1521/ORCL", "system", "root");
try {
DbUtil.setSchema("Oracle",connection,"scott");
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
System.out.println();
}
}

View File

@ -1,28 +0,0 @@
package com.qiaoba.common.database.utils;
import cn.hutool.core.io.IoUtil;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
/**
* SqlUtil
*
* @author ailanyin
* @version 1.0
* @since 2023/6/27 17:30
*/
public class SqlUtil {
public static void runSql(Connection connection, String sql) throws SQLException {
Statement statement = null;
try {
statement = connection.createStatement();
statement.execute(sql);
} finally {
IoUtil.close(statement);
}
}
}