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

@ -1,13 +1,13 @@
UPDATE sys_dept set tenant_id = '1'; UPDATE sys_dept set tenant_id = '2';
UPDATE sys_menu set tenant_id = '1'; UPDATE sys_menu set tenant_id = '2';
UPDATE sys_post set tenant_id = '1'; UPDATE sys_post set tenant_id = '2';
UPDATE sys_role set tenant_id = '1'; UPDATE sys_role set tenant_id = '2';
UPDATE sys_user set tenant_id = '1'; UPDATE sys_user set tenant_id = '2';
UPDATE sys_config set tenant_id = '1'; UPDATE sys_config set tenant_id = '2';
UPDATE sys_login_log set tenant_id = '1'; UPDATE sys_login_log set tenant_id = '2';
UPDATE sys_role_dept set tenant_id = '1'; UPDATE sys_role_dept set tenant_id = '2';
UPDATE sys_role_menu set tenant_id = '1'; UPDATE sys_role_menu set tenant_id = '2';
UPDATE sys_user_post set tenant_id = '1'; UPDATE sys_user_post set tenant_id = '2';
UPDATE sys_user_role set tenant_id = '1'; UPDATE sys_user_role set tenant_id = '2';
UPDATE sys_dict_data set tenant_id = '1'; UPDATE sys_dict_data set tenant_id = '2';
UPDATE sys_dict_type set tenant_id = '1'; UPDATE sys_dict_type set tenant_id = '2';

View File

@ -2,7 +2,7 @@ qiaoba:
file-upload-path: C:/${spring.application.name}/uploadPath/ file-upload-path: C:/${spring.application.name}/uploadPath/
dataSources: dataSources:
- driver: com.mysql.cj.jdbc.Driver - driver: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.0.202:3306/${spring.application.name}?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true url: jdbc:mysql://192.168.0.202:3306/${spring.application.name}?databaseTerm=SCHEMA&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
username: root username: root
password: Root123456789. password: Root123456789.
#连接池初始化大小 #连接池初始化大小
@ -12,7 +12,7 @@ qiaoba:
#最大连接池数量 #最大连接池数量
max-active: 20 max-active: 20
- driver: com.mysql.cj.jdbc.Driver - driver: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.0.203:3306/${spring.application.name}?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true url: jdbc:mysql://192.168.0.203:3306/${spring.application.name}?databaseTerm=SCHEMA&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
username: root username: root
password: Root123456789. password: Root123456789.
#连接池初始化大小 #连接池初始化大小
@ -22,7 +22,16 @@ qiaoba:
#最大连接池数量 #最大连接池数量
max-active: 20 max-active: 20
# - driver: org.postgresql.Driver
# url: jdbc:postgresql://192.168.0.202:5432/mydb?currentSchema=qiaoba-boot&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
# username: postgres
# password: postgres
# #连接池初始化大小
# initial-size: 5
# #最小空闲线程数
# min-idle: 10
# #最大连接池数量
# max-active: 20
mybatis-plus: mybatis-plus:
configuration: configuration:
# 自动驼峰命名规则camel case映射 # 自动驼峰命名规则camel case映射

View File

@ -5,6 +5,7 @@ spring:
#新创建的bean覆盖旧的bean #新创建的bean覆盖旧的bean
allow-bean-definition-overriding: true allow-bean-definition-overriding: true
application: application:
# DbName / FileDirName / SchemaPrefix
name: qiaoba-boot name: qiaoba-boot
profiles: profiles:
active: dev active: dev

View File

@ -30,7 +30,12 @@ public enum DatasourceErrorCode {
/** /**
* 创建表错误 * 创建表错误
*/ */
CREATE_TABLE_ERROR(50104, "创建表错误"); CREATE_TABLE_ERROR(50104, "创建表错误"),
/**
* 创建模式/库错误
*/
CREATE_SCHEMA_ERROR(50105, "创建模式/库错误");
private final Integer code; private final Integer code;
private final String msg; private final String msg;

View File

@ -22,7 +22,7 @@ public enum DataBaseEnum {
* MySQL * MySQL
*/ */
MY_SQL("MySQL", MY_SQL("MySQL",
"jdbc:mysql://{}:{}/{}?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true", "jdbc:mysql://{}:{}/{}?databaseTerm=SCHEMA&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true",
"com.mysql.cj.jdbc.Driver", "com.mysql.cj.jdbc.Driver",
"SELECT 1"), "SELECT 1"),

View File

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

View File

@ -1,17 +1,19 @@
package com.qiaoba.common.database.interceptors; 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.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.base.context.BaseContext;
import com.qiaoba.common.database.utils.JdbcUtil; import com.qiaoba.common.database.context.TenantDbTypeContext;
import com.qiaoba.common.database.utils.SqlUtil; import com.qiaoba.common.database.utils.DbUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler; 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.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement;
import java.util.Objects; import java.util.Objects;
/** /**
@ -21,17 +23,22 @@ import java.util.Objects;
* @version 1.0 * @version 1.0
* @since 2023-04-25 22:48:43 * @since 2023-04-25 22:48:43
*/ */
@Slf4j
public class SchemaInterceptor implements InnerInterceptor { public class SchemaInterceptor implements InnerInterceptor {
@Value("${spring.application.name}")
private String schemaPrefix;
@Override @Override
public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) { public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) {
if (Objects.nonNull(BaseContext.isSchemaMode()) && BaseContext.isSchemaMode()) { if (Objects.nonNull(BaseContext.isSchemaMode()) && BaseContext.isSchemaMode()) {
try { 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) { } 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.LockInfo;
import com.baomidou.lock.LockTemplate; import com.baomidou.lock.LockTemplate;
import com.qiaoba.common.database.config.DynamicDataSourceConfig; 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.entity.DynamicDataSource;
import com.qiaoba.common.database.utils.JdbcUtil; import com.qiaoba.common.database.utils.JdbcUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -98,9 +99,9 @@ public class NotOnlineDatasourceMonitor {
} }
private void addBackupMap(String tenantId, DynamicDataSource dataSource) { 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)) { if (CollUtil.isEmpty(dataSourceList)) {
DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.put(tenantId, ListUtil.toList(dataSource)); BackupDatasourceContext.set(tenantId, ListUtil.toList(dataSource));
} else { } else {
dataSourceList.add(dataSource); 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.constants.TenantConstant;
import com.qiaoba.common.base.enums.DataBaseEnum; import com.qiaoba.common.base.enums.DataBaseEnum;
import com.qiaoba.common.database.config.DynamicDataSourceConfig; 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.entity.DynamicDataSource;
import com.qiaoba.common.database.service.DynamicDatasourceService; import com.qiaoba.common.database.service.DynamicDatasourceService;
import com.qiaoba.common.database.utils.JdbcUtil; import com.qiaoba.common.database.utils.JdbcUtil;
@ -78,8 +80,7 @@ public class OnlineDatasourceMonitor {
private void tryConnect() { private void tryConnect() {
for (String tenantId : DynamicDataSourceConfig.TENANT_IDS) { for (String tenantId : DynamicDataSourceConfig.TENANT_IDS) {
Object primary = DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.get(tenantId); Object primary = PrimaryDatasourceContext.get(tenantId);
DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.get(tenantId);
if (Objects.isNull(primary)) { if (Objects.isNull(primary)) {
// 说明初始化主要数据源的时候出错 // 说明初始化主要数据源的时候出错
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId); log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
@ -110,7 +111,7 @@ public class OnlineDatasourceMonitor {
// 关闭原有异常数据源 // 关闭原有异常数据源
IoUtil.close(dataSource); IoUtil.close(dataSource);
// 在数据源Map中删除 // 在数据源Map中删除
DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.remove(tenantId); PrimaryDatasourceContext.remove(tenantId);
} }
// 将原有异常数据源加入到错误数据源Map, 等待重试 // 将原有异常数据源加入到错误数据源Map, 等待重试
addErrorDatasource(tenantId, dataSource); addErrorDatasource(tenantId, dataSource);
@ -130,7 +131,7 @@ public class OnlineDatasourceMonitor {
*/ */
private Boolean backToPrimary(String tenantId) { private Boolean backToPrimary(String tenantId) {
// 备用数据源 // 备用数据源
List<DynamicDataSource> dataSources = DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.get(tenantId); List<DynamicDataSource> dataSources = BackupDatasourceContext.get(tenantId);
if (CollUtil.isEmpty(dataSources)) { if (CollUtil.isEmpty(dataSources)) {
log.error("租户:[{}]切换备用数据源失败, 原因: 没有备用数据源", tenantId); log.error("租户:[{}]切换备用数据源失败, 原因: 没有备用数据源", tenantId);
return false; return false;

View File

@ -1,23 +1,36 @@
package com.qiaoba.module.tenant.utils; package com.qiaoba.common.database.utils;
import com.qiaoba.common.base.enums.DataBaseEnum;
import org.apache.ibatis.jdbc.ScriptRunner; import org.apache.ibatis.jdbc.ScriptRunner;
import org.postgresql.jdbc.PgConnection;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException;
/** /**
* 数据库工具类
*
* @author ailanyin * @author ailanyin
* @version 1.0 * @version 1.0
* @since 2023/6/27 16:11 * @since 2023/6/28 8:52
*/ */
public class DbUtil { 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 { public static void runScript(Connection conn, String filePath) throws IOException {
ClassPathResource resource = new ClassPathResource(filePath); ClassPathResource resource = new ClassPathResource(filePath);
File file = resource.getFile(); File file = resource.getFile();

View File

@ -8,10 +8,7 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.sql.Connection; import java.sql.*;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/** /**
* JdbcUtil * JdbcUtil
@ -104,6 +101,18 @@ public class JdbcUtil {
} }
public static void main(String[] args) { 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);
}
}
}

View File

@ -11,6 +11,7 @@ import com.qiaoba.api.system.entity.dto.DataScopeDto;
import com.qiaoba.api.system.entity.dto.SysRoleDto; import com.qiaoba.api.system.entity.dto.SysRoleDto;
import com.qiaoba.api.system.entity.param.SysRoleParam; import com.qiaoba.api.system.entity.param.SysRoleParam;
import com.qiaoba.auth.utils.SecurityUtil; import com.qiaoba.auth.utils.SecurityUtil;
import com.qiaoba.common.base.context.BaseContext;
import com.qiaoba.common.base.exceptions.ServiceException; import com.qiaoba.common.base.exceptions.ServiceException;
import com.qiaoba.common.database.entity.PageQuery; import com.qiaoba.common.database.entity.PageQuery;
import com.qiaoba.common.database.entity.TableDataInfo; import com.qiaoba.common.database.entity.TableDataInfo;

View File

@ -7,6 +7,7 @@ import com.qiaoba.common.base.constants.BaseConstant;
import com.qiaoba.common.base.constants.TenantConstant; import com.qiaoba.common.base.constants.TenantConstant;
import com.qiaoba.common.base.context.BaseContext; import com.qiaoba.common.base.context.BaseContext;
import com.qiaoba.common.database.config.DynamicDataSourceConfig; import com.qiaoba.common.database.config.DynamicDataSourceConfig;
import com.qiaoba.common.database.context.TenantDbTypeContext;
import com.qiaoba.common.web.utils.ResponseUtil; import com.qiaoba.common.web.utils.ResponseUtil;
import com.qiaoba.common.web.utils.UriUtil; import com.qiaoba.common.web.utils.UriUtil;
import com.qiaoba.module.tenant.entity.SysTenant; import com.qiaoba.module.tenant.entity.SysTenant;
@ -91,12 +92,12 @@ public class DynamicDataSourceFilter extends OncePerRequestFilter {
if (sysTenant.getMode().equals(SysTenant.DATASOURCE_MODE)) { if (sysTenant.getMode().equals(SysTenant.DATASOURCE_MODE)) {
//设置当前租户对应的数据源 //设置当前租户对应的数据源
BaseContext.setDataSource(sysTenant.getTenantId()); BaseContext.setDataSource(sysTenant.getTenantId());
BaseContext.setDatabaseType(DynamicDataSourceConfig.TENANT_DATASOURCE_TYPE_MAP.get(sysTenant.getTenantId())); BaseContext.setDatabaseType(TenantDbTypeContext.get(sysTenant.getTenantId()));
} }
// 字段模式 or Schema模式-数据源选择默认数据源 // 字段模式 or Schema模式-数据源选择默认数据源
else { else {
BaseContext.setDataSource(TenantConstant.DEFAULT_TENANT_ID); BaseContext.setDataSource(TenantConstant.DEFAULT_TENANT_ID);
BaseContext.setDatabaseType(DynamicDataSourceConfig.TENANT_DATASOURCE_TYPE_MAP.get(TenantConstant.DEFAULT_TENANT_ID)); BaseContext.setDatabaseType(TenantDbTypeContext.getDefault());
} }

View File

@ -1,13 +1,14 @@
package com.qiaoba.module.tenant.init.impl; package com.qiaoba.module.tenant.init.impl;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.db.sql.SqlExecutor; import cn.hutool.db.sql.SqlExecutor;
import cn.hutool.http.HttpStatus; import cn.hutool.http.HttpStatus;
import com.qiaoba.common.base.code.DatasourceErrorCode; import com.qiaoba.common.base.code.DatasourceErrorCode;
import com.qiaoba.common.database.utils.SqlUtil; import com.qiaoba.common.database.context.TenantDbTypeContext;
import com.qiaoba.common.database.utils.DbUtil;
import com.qiaoba.module.tenant.entity.vo.TenantInitVo; import com.qiaoba.module.tenant.entity.vo.TenantInitVo;
import com.qiaoba.module.tenant.init.InitTablesStrategy; import com.qiaoba.module.tenant.init.InitTablesStrategy;
import com.qiaoba.module.tenant.utils.DbUtil;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException; import java.io.IOException;
@ -23,21 +24,32 @@ import java.sql.SQLException;
*/ */
@Service @Service
public class MysqlInitTablesStrategy implements InitTablesStrategy { public class MysqlInitTablesStrategy implements InitTablesStrategy {
public static final String CREATE_MYSQL_DB_SQL = "DROP DATABASE IF EXISTS `{}`;CREATE DATABASE `{}` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
@Override @Override
public TenantInitVo create(Connection conn, String schema) { public TenantInitVo create(Connection conn, String schema) {
// 切换 Schema
try { try {
SqlUtil.runSql(conn, StrUtil.format("use `{}`;", schema)); // 创建库
DbUtil.runScript(conn, "MySQL/table/base_tables"); SqlExecutor.execute(conn, StrUtil.format(CREATE_MYSQL_DB_SQL, schema, schema));
conn.commit();
return new TenantInitVo(HttpStatus.HTTP_OK, null);
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); IoUtil.close(conn);
} catch (IOException e) { return new TenantInitVo(DatasourceErrorCode.CREATE_SCHEMA_ERROR.getCode(), e.getMessage());
e.printStackTrace(); }
try {
// 切换 Schema
DbUtil.setSchema(TenantDbTypeContext.getDefault(), conn, schema);
DbUtil.runScript(conn, "MySQL/table/base_tables");
return new TenantInitVo(HttpStatus.HTTP_OK, null);
} catch (SQLException e) {
return new TenantInitVo(DatasourceErrorCode.SWITCH_SCHEMA_ERROR.getCode(), e.getMessage());
} catch (IOException e) {
return new TenantInitVo(DatasourceErrorCode.CREATE_TABLE_ERROR.getCode(), e.getMessage());
} finally {
IoUtil.close(conn);
} }
return new TenantInitVo(DatasourceErrorCode.CREATE_TABLE_ERROR.getCode(), null);
} }
} }

View File

@ -4,9 +4,10 @@ import cn.hutool.core.io.IoUtil;
import cn.hutool.http.HttpStatus; import cn.hutool.http.HttpStatus;
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSource;
import com.qiaoba.common.base.code.DatasourceErrorCode; import com.qiaoba.common.base.code.DatasourceErrorCode;
import com.qiaoba.common.base.constants.TenantConstant; import com.qiaoba.common.base.constants.BaseConstant;
import com.qiaoba.common.base.enums.DataBaseEnum; import com.qiaoba.common.base.enums.DataBaseEnum;
import com.qiaoba.common.database.config.DynamicDataSourceConfig; import com.qiaoba.common.database.context.PrimaryDatasourceContext;
import com.qiaoba.common.database.context.TenantDbTypeContext;
import com.qiaoba.common.database.utils.JdbcUtil; import com.qiaoba.common.database.utils.JdbcUtil;
import com.qiaoba.module.tenant.entity.SysTenant; import com.qiaoba.module.tenant.entity.SysTenant;
import com.qiaoba.module.tenant.entity.SysTenantDatasource; import com.qiaoba.module.tenant.entity.SysTenantDatasource;
@ -17,6 +18,7 @@ import com.qiaoba.module.tenant.service.SysTenantDatasourceService;
import com.qiaoba.module.tenant.service.SysTenantInitService; import com.qiaoba.module.tenant.service.SysTenantInitService;
import com.qiaoba.module.tenant.service.SysTenantService; import com.qiaoba.module.tenant.service.SysTenantService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.sql.Connection; import java.sql.Connection;
@ -36,6 +38,8 @@ public class SysTenantInitServiceImpl implements SysTenantInitService {
private final SysTenantService sysTenantService; private final SysTenantService sysTenantService;
private final SysTenantDatasourceService sysTenantDatasourceService; private final SysTenantDatasourceService sysTenantDatasourceService;
@Value("${spring.application.name}")
private String schemaPrefix;
@Override @Override
public TenantInitCheckVo check(String tenantId) { public TenantInitCheckVo check(String tenantId) {
@ -77,11 +81,11 @@ public class SysTenantInitServiceImpl implements SysTenantInitService {
if (SysTenant.SCHEMA_MODE.equals(sysTenant.getMode())) { if (SysTenant.SCHEMA_MODE.equals(sysTenant.getMode())) {
Connection connection = null; Connection connection = null;
try { try {
String schema = "qiaoba-boot-2"; String schema = schemaPrefix + BaseConstant.HYPHEN_JOIN_STR + tenantId;
// 获取主库Connection 和 Schema // 获取主库Connection 和 Schema
DruidDataSource dataSource = (DruidDataSource) DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.get(TenantConstant.DEFAULT_TENANT_ID); DruidDataSource dataSource = (DruidDataSource) PrimaryDatasourceContext.getDefault();
connection = dataSource.getConnection(); connection = dataSource.getConnection();
return InitTablesStrategyFactory.getStrategy(DynamicDataSourceConfig.TENANT_DATASOURCE_TYPE_MAP.get(TenantConstant.DEFAULT_TENANT_ID)).create(connection, schema); return InitTablesStrategyFactory.getStrategy(TenantDbTypeContext.getDefault()).create(connection, schema);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {