fix: 优化主从数据库配置

This commit is contained in:
tangwei 2026-07-20 17:10:54 +08:00
parent bbac60655d
commit 49466f3990
12 changed files with 269 additions and 111 deletions

View File

@ -4,6 +4,8 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.platform.datasource.DataSourceKeys;
import com.yfd.platform.datasource.TargetDataSource;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collections;
@ -30,6 +32,11 @@ public interface MicroservicDynamicSQLMapper<T> {
Map<String, Object> getOneBySql(@Param("sql") String sql, @Param("map") Map<String, Object> map);
@Select({"<script>", "${sql}", "</script>"})
@TargetDataSource(DataSourceKeys.ORACLE_MASTER)
Map<String, Object> getOneOracleBySql(@Param("sql") String sql, @Param("map") Map<String, Object> map);
default <R> R getOneBySqlWithResultType(String sql,
Map<String, Object> map,
Class<R> resultType) {

View File

@ -0,0 +1,41 @@
package com.yfd.platform.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.yfd.platform.datasource.DynamicDataSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.SQLException;
/**
* 动态分页拦截器根据当前数据源自动切换 Oracle/DM 分页方言
*/
public class DynamicPaginationInterceptor extends PaginationInnerInterceptor {
public DynamicPaginationInterceptor() {
super(DbType.DM);
}
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.setDbType(resolveDbType());
super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
/**
* 根据当前线程绑定的数据源 key 解析数据库类型
* oracle-* ORACLE其他dm-*null DM
*/
private DbType resolveDbType() {
String dsKey = DynamicDataSource.getDataSource();
if (dsKey != null && dsKey.startsWith("oracle")) {
return DbType.ORACLE;
}
return DbType.DM;
}
}

View File

@ -1,37 +1,19 @@
package com.yfd.platform.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/******************************
* 用途说明:
* 作者姓名: pcj
* 创建时间: 2022/10/24 10:50
******************************/
/**
* MyBatis-Plus 配置使用动态分页拦截器支持 Oracle/达梦 自动切换
*/
@Configuration
public class MybitsPlusConfig {
// @Bean
// public MybatisPlusInterceptor mybatisPlusInterceptor() {
// MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// return mybatisPlusInterceptor;
// }
/**
* 分页插件配置Oracle 兼容
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页拦截器指定数据库类型为 Oracle
// interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.ORACLE));
// 添加分页拦截器指定数据库类型为 Oracle
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.DM));
interceptor.addInnerInterceptor(new DynamicPaginationInterceptor());
return interceptor;
}
}

View File

@ -1,55 +1,41 @@
package com.yfd.platform.datasource;
import cn.hutool.core.util.StrUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/******************************
* 用途说明:
* 作者姓名: wxy
* 创建时间: 2022/9/23 17:50
******************************/
/**
* 数据源切换切面处理 @TargetDataSource 注解
*/
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.yfd.platform.datasource.DataSource)")
@Pointcut("@annotation(com.yfd.platform.datasource.TargetDataSource)")
public void dataSourcePointCut() {
}
private String DataBaseName;
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
if (StrUtil.isNotBlank(DataBaseName)){
DynamicDataSource.setDataSource(DataBaseName);
}else {
DynamicDataSource.setDataSource("master");
TargetDataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), TargetDataSource.class);
if (annotation == null) {
// 类级别注解
annotation = AnnotationUtils.findAnnotation(point.getTarget().getClass(), TargetDataSource.class);
}
String dsKey = (annotation != null && !annotation.value().isEmpty())
? annotation.value()
: DataSourceKeys.DEFAULT;
DynamicDataSource.setDataSource(dsKey);
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
}
}
public String getDataBase(Integer type){
if (type == 1){
DataBaseName="master";
}else {
DataBaseName="slave";
}
return DataBaseName;
}
}

View File

@ -0,0 +1,22 @@
package com.yfd.platform.datasource;
/**
* 数据源 key 常量
*/
public interface DataSourceKeys {
/** 达梦主库 */
String DM_MASTER = "dm-master";
/** 达梦从库 */
String DM_SLAVE = "dm-slave";
/** Oracle 主库 */
String ORACLE_MASTER = "oracle-master";
/** Oracle 从库 */
String ORACLE_SLAVE = "oracle-slave";
/** 默认数据源 */
String DEFAULT = DM_MASTER;
}

View File

@ -1,28 +1,28 @@
package com.yfd.platform.datasource;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.alibaba.druid.pool.DruidDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/******************************
* 用途说明:
* 作者姓名: wxy
* 创建时间: 2022/9/23 17:45
******************************/
/**
* 动态数据源配置支持 达梦(DM) Oracle 两种数据库每种各有主从(master/slave)
*
* @author yfd
*/
@Configuration
@Component
public class DynamicDataSourceConfig {
// ==================== 达梦(DM) 数据源 ====================
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource wglMasterDataSource(){
@ConfigurationProperties("spring.datasource.druid.dm-master")
public DataSource dmMasterDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setBreakAfterAcquireFailure(true);
dataSource.setConnectionErrorRetryAttempts(0);
@ -30,22 +30,48 @@ public class DynamicDataSourceConfig {
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource wglSlaveDataSource(){
@ConfigurationProperties("spring.datasource.druid.dm-slave")
public DataSource dmSlaveDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setBreakAfterAcquireFailure(true);
dataSource.setConnectionErrorRetryAttempts(0);
return dataSource;
}
// ==================== Oracle 数据源 ====================
@Bean
@ConfigurationProperties("spring.datasource.druid.oracle-master")
public DataSource oracleMasterDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setBreakAfterAcquireFailure(true);
dataSource.setConnectionErrorRetryAttempts(0);
return dataSource;
}
@Bean
@ConfigurationProperties("spring.datasource.druid.oracle-slave")
public DataSource oracleSlaveDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setBreakAfterAcquireFailure(true);
dataSource.setConnectionErrorRetryAttempts(0);
return dataSource;
}
// ==================== 路由数据源 ====================
@Bean
@Primary
public DynamicDataSource dataSource(DataSource wglMasterDataSource, DataSource wglSlaveDataSource) {
public DynamicDataSource dataSource(DataSource dmMasterDataSource,
DataSource dmSlaveDataSource,
DataSource oracleMasterDataSource,
DataSource oracleSlaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master",wglMasterDataSource);
targetDataSources.put("slave",wglSlaveDataSource);
return new DynamicDataSource(wglMasterDataSource, targetDataSources);
targetDataSources.put(DataSourceKeys.DM_MASTER, dmMasterDataSource);
targetDataSources.put(DataSourceKeys.DM_SLAVE, dmSlaveDataSource);
targetDataSources.put(DataSourceKeys.ORACLE_MASTER, oracleMasterDataSource);
targetDataSources.put(DataSourceKeys.ORACLE_SLAVE, oracleSlaveDataSource);
// 默认使用达梦主库
return new DynamicDataSource(dmMasterDataSource, targetDataSources);
}
}

View File

@ -0,0 +1,23 @@
package com.yfd.platform.datasource;
import java.lang.annotation.*;
/**
* 动态数据源注解用于方法级别切换数据源
* <p>
* 支持的取值
* <ul>
* <li>"dm-master" / "dm-slave" 达梦数据库</li>
* <li>"oracle-master" / "oracle-slave" Oracle 数据库</li>
* </ul>
* 默认值为 "dm-master"
*
* @author yfd
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value() default DataSourceKeys.DM_MASTER;
}

View File

@ -33,10 +33,10 @@ public class MapLegendServiceImpl extends ServiceImpl<MsMaplegendBMapper, MsMapl
@Resource
private MsMapmoduleBMapper msMapmoduleBMapper;
@Value("${spring.datasource.druid.master.driverClassName}")
@Value("${spring.datasource.druid.dm-master.driverClassName}")
private String jdbcDriver;
@Value("${spring.datasource.druid.slave.driverClassName}")
@Value("${spring.datasource.druid.dm-slave.driverClassName}")
private String slaveJdbcDriver;
@Override

View File

@ -1,42 +1,43 @@
package com.yfd.platform.system.controller;
import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.datasource.DataSource;
import com.yfd.platform.datasource.DataSourceAspect;
import com.yfd.platform.datasource.DataSourceKeys;
import com.yfd.platform.datasource.DynamicDataSource;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
/**
* @author zhengsl
* @since 2022-09-20
* 数据源切换控制器
*/
@RestController
@RequestMapping("/system")
@Tag(name = "切换数据库")
public class DataSourceController {
@Resource
DataSourceAspect dataSourceAspect;
/**
* 切换数据库
*
* @DataSource(name="master") 可以通过注解方式切换数据库
* 手动切换数据源当前线程有效
* <p>
* 支持的 dataSourceKey
* <ul>
* <li>"dm-master" / "dm-slave" 达梦数据库</li>
* <li>"oracle-master" / "oracle-slave" Oracle 数据库</li>
* </ul>
* 注意此方法仅对当前请求线程生效且必须在被 @TargetDataSource 注解的方法中使用才会真正路由
* 建议使用 @TargetDataSource 注解方式切换数据源
*/
@GetMapping("/changeDataSource")
@Operation(summary = "切换数据库")
public ResponseResult changeDataSource(Integer type) {
@Deprecated
public ResponseResult changeDataSource(@RequestParam(required = false) Integer type) {
if (type == null) {
return ResponseResult.error("参数为空");
}
String dataBase = dataSourceAspect.getDataBase(type);
String mess = "已切换为" + dataBase + "数据库";
return ResponseResult.success(mess);
String dataSourceKey = (type == 1) ? DataSourceKeys.DM_MASTER : DataSourceKeys.DM_SLAVE;
DynamicDataSource.setDataSource(dataSourceKey);
return ResponseResult.success("已切换为" + dataSourceKey + "数据源");
}
}

View File

@ -4,6 +4,8 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yfd.platform.datasource.DataSource;
import com.yfd.platform.datasource.DataSourceKeys;
import com.yfd.platform.datasource.TargetDataSource;
import com.yfd.platform.system.domain.LoginUser;
import com.yfd.platform.system.domain.SysUser;
import com.yfd.platform.system.mapper.SysMenuMapper;
@ -38,7 +40,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
private SysMenuMapper sysMenuMapper;
@Override
@DataSource(name = "master")
@TargetDataSource()
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 获取当前请求
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

View File

@ -8,16 +8,30 @@ spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
# ==================== 达梦(DM) 数据源 ====================
dm-master:
driverClassName: dm.jdbc.driver.DmDriver
url: "${DB_MASTER_URL:jdbc:dm://localhost:5236/WPPDB}"
username: "${DB_MASTER_USERNAME:WPPDB}"
password: "${DB_MASTER_PASSWORD:}"
slave:
url: "${DB_DM_MASTER_URL:jdbc:dm://localhost:5236/WPPDB}"
username: "${DB_DM_MASTER_USERNAME:WPPDB}"
password: "${DB_DM_MASTER_PASSWORD:}"
dm-slave:
driverClassName: dm.jdbc.driver.DmDriver
url: "${DB_SLAVE_URL:jdbc:dm://localhost:5236/WPPDB}"
username: "${DB_SLAVE_USERNAME:WPPDB}"
password: "${DB_SLAVE_PASSWORD:}"
url: "${DB_DM_SLAVE_URL:jdbc:dm://localhost:5236/WPPDB}"
username: "${DB_DM_SLAVE_USERNAME:WPPDB}"
password: "${DB_DM_SLAVE_PASSWORD:}"
# ==================== Oracle 数据源 ====================
oracle-master:
driverClassName: oracle.jdbc.OracleDriver
url: "${DB_ORACLE_MASTER_URL:jdbc:oracle:thin:@localhost:1521/ORCL}"
username: "${DB_ORACLE_MASTER_USERNAME:}"
password: "${DB_ORACLE_MASTER_PASSWORD:}"
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
oracle-slave:
driverClassName: oracle.jdbc.OracleDriver
url: "${DB_ORACLE_SLAVE_URL:jdbc:oracle:thin:@localhost:1521/ORCL}"
username: "${DB_ORACLE_SLAVE_USERNAME:}"
password: "${DB_ORACLE_SLAVE_PASSWORD:}"
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
mvc:
pathmatch:

View File

@ -10,13 +10,12 @@ spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
# driverClassName: oracle.jdbc.OracleDriver
# url: "${DB_MASTER_URL:jdbc:oracle:thin:@172.16.31.190:1521/SDLYZ}"
# ==================== 达梦(DM) 数据源 ====================
dm-master:
driverClassName: dm.jdbc.driver.DmDriver
url: "${DB_MASTER_URL:jdbc:dm://172.16.21.143:5236/QGC_REFA}"
username: "${DB_MASTER_USERNAME:QGC_REFA}"
password: "${DB_MASTER_PASSWORD:Y4M4K1oCkL8U}"
url: "${DB_DM_MASTER_URL:jdbc:dm://172.16.21.143:5236/QGC_REFA}"
username: "${DB_DM_MASTER_USERNAME:QGC_REFA}"
password: "${DB_DM_MASTER_PASSWORD:Y4M4K1oCkL8U}"
initial-size: 5
min-idle: 5
max-active: 20
@ -41,15 +40,11 @@ spring:
pool-prepared-statements: true
max-open-prepared-statements: 100
max-pool-prepared-statement-per-connection-size: 100
connection-properties: connectTimeout=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
# connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
slave:
dm-slave:
driverClassName: dm.jdbc.driver.DmDriver
url: "${DB_MASTER_URL:jdbc:dm://172.16.21.143:5236/QGC_REFA}"
# driverClassName: oracle.jdbc.OracleDriver
# url: "${DB_SLAVE_URL:jdbc:oracle:thin:@172.16.31.190:1521/SDLYZ}"
username: "${DB_SLAVE_USERNAME:QGC_REFA}"
password: "${DB_SLAVE_PASSWORD:Y4M4K1oCkL8U}"
url: "${DB_DM_SLAVE_URL:jdbc:dm://172.16.21.143:5236/QGC_REFA}"
username: "${DB_DM_SLAVE_USERNAME:QGC_REFA}"
password: "${DB_DM_SLAVE_PASSWORD:Y4M4K1oCkL8U}"
initial-size: 5
min-idle: 5
max-active: 20
@ -74,8 +69,67 @@ spring:
pool-prepared-statements: true
max-open-prepared-statements: 100
max-pool-prepared-statement-per-connection-size: 100
connection-properties: connectTimeout=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
# connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
# ==================== Oracle 数据源 ====================
oracle-master:
driverClassName: oracle.jdbc.OracleDriver
url: "${DB_ORACLE_MASTER_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}"
username: "${DB_ORACLE_MASTER_USERNAME:SDLY_QX}"
password: "${DB_ORACLE_MASTER_PASSWORD:jNHnqv3hH7}"
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 30000
async-init: true
keep-alive-between-time-millis: 120000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 180000
max-evictable-idle-time-millis: 300000
phy-timeout-millis: 25200000
validation-query: SELECT 1 FROM DUAL
validation-query-timeout: 3
test-while-idle: true
test-on-borrow: false
test-on-return: false
keep-alive: true
remove-abandoned: true
remove-abandoned-timeout: 1800
log-abandoned: true
break-after-acquire-failure: true
time-between-connect-error-millis: 30000
pool-prepared-statements: true
max-open-prepared-statements: 100
max-pool-prepared-statement-per-connection-size: 100
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
oracle-slave:
driverClassName: oracle.jdbc.OracleDriver
url: "${DB_ORACLE_SLAVE_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}"
username: "${DB_ORACLE_SLAVE_USERNAME:SDLY_QX}"
password: "${DB_ORACLE_SLAVE_PASSWORD:jNHnqv3hH7}"
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 30000
async-init: true
keep-alive-between-time-millis: 120000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 180000
max-evictable-idle-time-millis: 300000
phy-timeout-millis: 25200000
validation-query: SELECT 1 FROM DUAL
validation-query-timeout: 3
test-while-idle: true
test-on-borrow: false
test-on-return: false
keep-alive: true
remove-abandoned: true
remove-abandoned-timeout: 1800
log-abandoned: true
break-after-acquire-failure: true
time-between-connect-error-millis: 30000
pool-prepared-statements: true
max-open-prepared-statements: 100
max-pool-prepared-statement-per-connection-size: 100
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
filter:
stat:
enabled: true