签入版本
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.xiaomayi</groupId>
|
||||
<artifactId>xiaomayi-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>xiaomayi-datasource</artifactId>
|
||||
|
||||
<!-- 依赖声明 -->
|
||||
<dependencies>
|
||||
<!-- 核心依赖模块 -->
|
||||
<dependency>
|
||||
<groupId>com.xiaomayi</groupId>
|
||||
<artifactId>xiaomayi-core</artifactId>
|
||||
</dependency>
|
||||
<!-- mybatis配置模块 -->
|
||||
<dependency>
|
||||
<groupId>com.xiaomayi</groupId>
|
||||
<artifactId>xiaomayi-mybatis</artifactId>
|
||||
</dependency>
|
||||
<!-- Druid数据库连接池 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- 动态数据源 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,77 @@
|
||||
```
|
||||
方法调用
|
||||
↓
|
||||
@Before (前置通知) - 数据源验证和准备
|
||||
↓
|
||||
@Around (环绕通知开始) - 设置数据源、记录开始时间
|
||||
↓
|
||||
目标方法执行
|
||||
↓
|
||||
├─ 成功 → @AfterReturning (返回通知) - 记录成功信息
|
||||
↓
|
||||
└─ 失败 → @AfterThrowing (异常通知) - 异常处理
|
||||
↓
|
||||
@Around (环绕通知结束) - 恢复数据源、记录执行时间
|
||||
↓
|
||||
@After (后置通知) - 最终清理和安全检查
|
||||
↓
|
||||
方法返回
|
||||
```
|
||||
|
||||
```angular2html
|
||||
/**
|
||||
* 数据源切面使用示例和最佳实践
|
||||
*
|
||||
* 示例1: 方法级别注解(推荐)
|
||||
* {@code
|
||||
* @Service
|
||||
* public class UserService {
|
||||
*
|
||||
* // 写操作使用主库
|
||||
* @DataSource(DataSourceType.MASTER)
|
||||
* public void createUser(User user) {
|
||||
* userMapper.insert(user);
|
||||
* }
|
||||
*
|
||||
* // 读操作使用从库
|
||||
* @DataSource(DataSourceType.SLAVE1)
|
||||
* public User getUserById(Long id) {
|
||||
* return userMapper.selectById(id);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 示例2: 类级别注解
|
||||
* {@code
|
||||
* // 该服务类所有方法默认使用从库
|
||||
* @Service
|
||||
* @DataSource(DataSourceType.SLAVE1)
|
||||
* public class QueryService {
|
||||
*
|
||||
* // 自动使用SLAVE1数据源
|
||||
* public List<User> getAllUsers() {
|
||||
* return userMapper.selectList(null);
|
||||
* }
|
||||
*
|
||||
* // 方法级别注解覆盖类级别注解
|
||||
* @DataSource(DataSourceType.MASTER)
|
||||
* public void updateUser(User user) {
|
||||
* userMapper.updateById(user);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 最佳实践:
|
||||
* 1. 读写分离:写操作用MASTER,读操作用SLAVE
|
||||
* 2. 事务一致性:在@Transactional方法中谨慎切换数据源
|
||||
* 3. 性能考虑:避免在循环中频繁切换数据源
|
||||
* 4. 异常处理:确保业务异常不会影响数据源恢复
|
||||
* 5. 测试验证:多数据源环境下充分测试事务一致性
|
||||
*
|
||||
* 注意事项:
|
||||
* 1. 确保@DataSource注解的方法在Spring代理范围内(同类调用不生效)
|
||||
* 2. 事务注解@Transactional应该在数据源切换之后执行
|
||||
* 3. 避免在异步方法中使用数据源切换(线程切换会导致ThreadLocal失效)
|
||||
* 4. 微服务环境下考虑分布式事务的一致性
|
||||
*/
|
||||
```
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// +======================================================================
|
||||
// | 小蚂蚁云·企业数字化开发平台 [ 加速数字化转型,构建智能未来 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 核心价值:赋能开发者 · 服务企业 · 共建生态
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2020-2026 小蚂蚁云团队 版权所有
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方平台: https://www.xiaomayicloud.com
|
||||
// +======================================================================
|
||||
// | 【知识产权声明】
|
||||
// | 1. 权属声明:本软件是受《中华人民共和国著作权法》
|
||||
// | 保护的专有财产,完整版权归小蚂蚁云团队所有
|
||||
// | 2. 权利范围:所有知识产权(商标、专利、著作权、
|
||||
// | 商业秘密等)均归小蚂蚁云团队所有,受法律保护
|
||||
// | 3. 授权性质:用户仅可获得有限的、非独占的使用许可
|
||||
// | 4. 禁止行为:未经书面授权,禁止对本软件本身
|
||||
// | 申请任何知识产权
|
||||
// | 5. 权利保留:未在本协议中明示授予的一切权利
|
||||
// | 均由小蚂蚁云团队保留
|
||||
// +======================================================================
|
||||
// | 【授权状态说明】
|
||||
// | 1. 未授权状态:用户未购买有效的商业许可证
|
||||
// | 2. 商业授权:已购买正式商业许可证的用户
|
||||
// +======================================================================
|
||||
// | 【绝对禁止条款】
|
||||
// | 无论授权状态如何,任何用户均严格禁止以下行为:
|
||||
// | 1. 逆向工程:对软件进行反编译、反汇编、技术破解或任何形式的逆向分析
|
||||
// | 2. 版权破坏:移除、修改、遮挡软件中的版权标识、许可控制或技术保护措施
|
||||
// | 3. 非法传播:复制、分发、出租、出借、转售、再许可本软件或其许可权利
|
||||
// | 4. 竞争性使用:利用本软件的代码、架构、设计开发直接竞争的产品或服务
|
||||
// | 5. 侵权使用:将软件用于侵犯第三方知识产权或其他合法权益的用途
|
||||
// +======================================================================
|
||||
// | 【商业授权许可范围】
|
||||
// | 购买有效商业许可证的用户,在遵守上述禁止条款的前提下,获得:
|
||||
// | 1. 使用权利:在许可证约定的项目数量、开发者数量范围内使用本软件
|
||||
// | 2. 技术支持:商业授权用户可以获取本地开发部署的技术支持服务
|
||||
// | 3. 版本更新:在许可证有效期内获取软件更新、安全补丁和功能增强
|
||||
// | 4. 商业部署:将基于本软件开发的应用程序用于商业运营和生产环境
|
||||
// +======================================================================
|
||||
// | 【许可证获取】
|
||||
// | 1. 获取途径:仅通过小蚂蚁云官方渠道购买(官网、授权代理商),购买记录凭证
|
||||
// | 2. 转让限制:许可证与购买主体绑定,未经书面同意不得转让
|
||||
// | 3. 终止情形:许可证到期、违反协议、破产清算等情况下自动终止
|
||||
// +======================================================================
|
||||
// | 【合法使用要求】
|
||||
// | 1. 仅限用于合法合规的软件研发
|
||||
// | 2. 严禁用于任何违法、侵权或恶意行为
|
||||
// | 3. 严禁违反我国法律法规和政策
|
||||
// +======================================================================
|
||||
// | 【免责声明】
|
||||
// | 因使用本框架导致的以下情况,我们概不负责:
|
||||
// | 1. 项目研发中的意外、疏忽、违约等风险
|
||||
// | 2. 侵犯第三方知识产权或其他合法权益
|
||||
// | 3. 任何直接、间接、附带或衍生一切损失
|
||||
// | 4. 用户违规违法行为的一切法律后果
|
||||
// +======================================================================
|
||||
// | 【协议重要提示】
|
||||
// | 1. 本声明是《小蚂蚁云最终用户许可协议》的摘要版本
|
||||
// | 2. 下载、安装或使用本软件即表示您接受全部协议条款
|
||||
// | 3. 我们保留单方面修改本声明的权利,修改后的版本在官网公布即生效
|
||||
// | 4. 用户有义务定期查看最新版本,继续使用视为接受修改
|
||||
// +======================================================================
|
||||
// | 【侵权举报与合规】
|
||||
// | 1. 知识产权侵权举报:请联系官方客服
|
||||
// | 2. 许可证合规咨询:xiaomayicloud@163.com
|
||||
// | 3. 违规使用将终止授权并追究责任
|
||||
// | 4. 无条件配合司法机关调查取证
|
||||
// | 5. 保留一切法律追诉权利
|
||||
// | 6. 对有效举报和合规建议,我们将给予感谢和奖励
|
||||
// +======================================================================
|
||||
|
||||
package com.xiaomayi.datasource.annotation;
|
||||
|
||||
import com.xiaomayi.datasource.enums.DataSourceType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 自定义数据源注解
|
||||
* </p>
|
||||
*
|
||||
* @author 小蚂蚁云团队
|
||||
* @since 2024-05-25
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface DataSource {
|
||||
|
||||
/**
|
||||
* 切换数据源名称
|
||||
*
|
||||
* @return 返回结果
|
||||
*/
|
||||
DataSourceType value() default DataSourceType.MASTER;
|
||||
|
||||
}
|
||||
+548
@@ -0,0 +1,548 @@
|
||||
// +======================================================================
|
||||
// | 小蚂蚁云·企业数字化开发平台 [ 加速数字化转型,构建智能未来 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 核心价值:赋能开发者 · 服务企业 · 共建生态
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2020-2026 小蚂蚁云团队 版权所有
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方平台: https://www.xiaomayicloud.com
|
||||
// +======================================================================
|
||||
// | 【知识产权声明】
|
||||
// | 1. 权属声明:本软件是受《中华人民共和国著作权法》
|
||||
// | 保护的专有财产,完整版权归小蚂蚁云团队所有
|
||||
// | 2. 权利范围:所有知识产权(商标、专利、著作权、
|
||||
// | 商业秘密等)均归小蚂蚁云团队所有,受法律保护
|
||||
// | 3. 授权性质:用户仅可获得有限的、非独占的使用许可
|
||||
// | 4. 禁止行为:未经书面授权,禁止对本软件本身
|
||||
// | 申请任何知识产权
|
||||
// | 5. 权利保留:未在本协议中明示授予的一切权利
|
||||
// | 均由小蚂蚁云团队保留
|
||||
// +======================================================================
|
||||
// | 【授权状态说明】
|
||||
// | 1. 未授权状态:用户未购买有效的商业许可证
|
||||
// | 2. 商业授权:已购买正式商业许可证的用户
|
||||
// +======================================================================
|
||||
// | 【绝对禁止条款】
|
||||
// | 无论授权状态如何,任何用户均严格禁止以下行为:
|
||||
// | 1. 逆向工程:对软件进行反编译、反汇编、技术破解或任何形式的逆向分析
|
||||
// | 2. 版权破坏:移除、修改、遮挡软件中的版权标识、许可控制或技术保护措施
|
||||
// | 3. 非法传播:复制、分发、出租、出借、转售、再许可本软件或其许可权利
|
||||
// | 4. 竞争性使用:利用本软件的代码、架构、设计开发直接竞争的产品或服务
|
||||
// | 5. 侵权使用:将软件用于侵犯第三方知识产权或其他合法权益的用途
|
||||
// +======================================================================
|
||||
// | 【商业授权许可范围】
|
||||
// | 购买有效商业许可证的用户,在遵守上述禁止条款的前提下,获得:
|
||||
// | 1. 使用权利:在许可证约定的项目数量、开发者数量范围内使用本软件
|
||||
// | 2. 技术支持:商业授权用户可以获取本地开发部署的技术支持服务
|
||||
// | 3. 版本更新:在许可证有效期内获取软件更新、安全补丁和功能增强
|
||||
// | 4. 商业部署:将基于本软件开发的应用程序用于商业运营和生产环境
|
||||
// +======================================================================
|
||||
// | 【许可证获取】
|
||||
// | 1. 获取途径:仅通过小蚂蚁云官方渠道购买(官网、授权代理商),购买记录凭证
|
||||
// | 2. 转让限制:许可证与购买主体绑定,未经书面同意不得转让
|
||||
// | 3. 终止情形:许可证到期、违反协议、破产清算等情况下自动终止
|
||||
// +======================================================================
|
||||
// | 【合法使用要求】
|
||||
// | 1. 仅限用于合法合规的软件研发
|
||||
// | 2. 严禁用于任何违法、侵权或恶意行为
|
||||
// | 3. 严禁违反我国法律法规和政策
|
||||
// +======================================================================
|
||||
// | 【免责声明】
|
||||
// | 因使用本框架导致的以下情况,我们概不负责:
|
||||
// | 1. 项目研发中的意外、疏忽、违约等风险
|
||||
// | 2. 侵犯第三方知识产权或其他合法权益
|
||||
// | 3. 任何直接、间接、附带或衍生一切损失
|
||||
// | 4. 用户违规违法行为的一切法律后果
|
||||
// +======================================================================
|
||||
// | 【协议重要提示】
|
||||
// | 1. 本声明是《小蚂蚁云最终用户许可协议》的摘要版本
|
||||
// | 2. 下载、安装或使用本软件即表示您接受全部协议条款
|
||||
// | 3. 我们保留单方面修改本声明的权利,修改后的版本在官网公布即生效
|
||||
// | 4. 用户有义务定期查看最新版本,继续使用视为接受修改
|
||||
// +======================================================================
|
||||
// | 【侵权举报与合规】
|
||||
// | 1. 知识产权侵权举报:请联系官方客服
|
||||
// | 2. 许可证合规咨询:xiaomayicloud@163.com
|
||||
// | 3. 违规使用将终止授权并追究责任
|
||||
// | 4. 无条件配合司法机关调查取证
|
||||
// | 5. 保留一切法律追诉权利
|
||||
// | 6. 对有效举报和合规建议,我们将给予感谢和奖励
|
||||
// +======================================================================
|
||||
|
||||
package com.xiaomayi.datasource.aspect;
|
||||
|
||||
import com.xiaomayi.datasource.annotation.DataSource;
|
||||
import com.xiaomayi.datasource.context.DataSourceContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.*;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 数据源切面处理类 - 完整版
|
||||
* 使用Spring AOP实现基于注解的数据源动态切换
|
||||
* 提供完整的切面生命周期方法:前置通知、后置通知、返回通知、异常通知、环绕通知
|
||||
* <p>
|
||||
* 核心功能:
|
||||
* 1. 拦截带有@DataSource注解的方法或类
|
||||
* 2. 在方法执行前根据注解值切换数据源
|
||||
* 3. 在方法执行后恢复原始数据源设置
|
||||
* 4. 提供完整的切面生命周期监控
|
||||
* 5. 保证数据源切换的线程安全和资源清理
|
||||
* <p>
|
||||
* 切面执行顺序:
|
||||
* 1. doBefore() - 前置通知
|
||||
* 2. around() - 环绕通知(包含实际的方法执行)
|
||||
* 3. doAfterReturning()- 返回通知(方法正常返回)
|
||||
* 或 doAfterThrowing()- 异常通知(方法抛出异常)
|
||||
* 4. doAfter() - 后置通知(最终通知)
|
||||
*
|
||||
* @author 小蚂蚁云团队
|
||||
* @since 2024-05-25
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Order(1) // 设置切面执行顺序,确保在事务切面之前执行
|
||||
@Slf4j
|
||||
public class DataSourceAspect {
|
||||
|
||||
/**
|
||||
* 数据源切点定义
|
||||
* 使用AspectJ切点表达式定义拦截规则
|
||||
* <p>
|
||||
* 切点表达式说明:
|
||||
* - @annotation: 拦截带有指定注解的方法
|
||||
* - @within: 拦截带有指定注解的类中的所有方法
|
||||
* <p>
|
||||
* 拦截范围:
|
||||
* 1. 任何带有@DataSource注解的方法
|
||||
* 2. 任何在带有@DataSource注解的类中的方法
|
||||
*/
|
||||
@Pointcut("@annotation(com.xiaomayi.datasource.annotation.DataSource) || " +
|
||||
"@within(com.xiaomayi.datasource.annotation.DataSource)")
|
||||
public void dataSourcePointCut() {
|
||||
// 切点方法体为空,仅用于定义切点表达式
|
||||
}
|
||||
|
||||
/**
|
||||
* 前置通知 - 在目标方法执行前执行
|
||||
* 主要用于数据源的初始设置和验证
|
||||
*
|
||||
* @param point 连接点对象
|
||||
* <p>
|
||||
* 执行时机:
|
||||
* - 在目标方法执行之前
|
||||
* - 在环绕通知的proceed()调用之前
|
||||
* <p>
|
||||
* 主要功能:
|
||||
* 1. 验证数据源注解配置
|
||||
* 2. 记录方法开始执行的日志
|
||||
* 3. 准备数据源切换的环境
|
||||
*/
|
||||
@Before("dataSourcePointCut()")
|
||||
public void doBefore(JoinPoint point) {
|
||||
// 获取当前线程ID,用于日志追踪
|
||||
long threadId = Thread.currentThread().getId();
|
||||
|
||||
// 解析数据源注解配置
|
||||
DataSource dataSourceAnnotation = getDataSource(point);
|
||||
|
||||
if (dataSourceAnnotation != null) {
|
||||
String targetDataSourceKey = dataSourceAnnotation.value().name();
|
||||
String originalDataSource = DataSourceContextHolder.getDataSource();
|
||||
|
||||
log.debug("线程[{}] 前置通知 - 准备数据源切换: {} -> {}, 目标方法: {}.{}",
|
||||
threadId,
|
||||
originalDataSource != null ? originalDataSource : "默认(主库)",
|
||||
targetDataSourceKey,
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName());
|
||||
|
||||
// 验证数据源是否存在(可选,也可以在切换时验证)
|
||||
validateDataSource(targetDataSourceKey, point);
|
||||
|
||||
} else {
|
||||
log.info("线程[{}] 前置通知 - 未找到数据源注解,使用默认数据源执行方法: {}.{}",
|
||||
threadId,
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后置通知 - 在目标方法执行后执行(无论是否抛出异常)
|
||||
* 主要用于资源清理和状态恢复的最终保证
|
||||
*
|
||||
* @param point 连接点对象
|
||||
* <p>
|
||||
* 执行时机:
|
||||
* - 在目标方法执行完成后(无论正常返回还是抛出异常)
|
||||
* - 在返回通知或异常通知之后
|
||||
* - 在环绕通知的finally块之后
|
||||
* <p>
|
||||
* 主要功能:
|
||||
* 1. 确保数据源状态最终被清理
|
||||
* 2. 记录方法执行的最终状态
|
||||
* 3. 提供额外的安全保障,防止数据源泄漏
|
||||
*/
|
||||
@After("dataSourcePointCut()")
|
||||
public void doAfter(JoinPoint point) {
|
||||
long threadId = Thread.currentThread().getId();
|
||||
|
||||
log.debug("线程[{}] 后置通知 - 方法执行完成,当前数据源状态: {}, 方法: {}.{}",
|
||||
threadId,
|
||||
DataSourceContextHolder.getDataSourceStatus(),
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName());
|
||||
|
||||
// 额外的安全检查:如果发现异常的数据源状态,进行强制清理
|
||||
performSafetyCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回通知 - 在目标方法正常返回后执行
|
||||
* 主要用于记录成功的操作和性能监控
|
||||
*
|
||||
* @param point 连接点对象
|
||||
* @param result 目标方法的返回值
|
||||
* <p>
|
||||
* 执行时机:
|
||||
* - 仅在目标方法正常返回时执行
|
||||
* - 在目标方法执行完成后,后置通知之前
|
||||
* <p>
|
||||
* 主要功能:
|
||||
* 1. 记录方法成功执行的日志
|
||||
* 2. 监控方法执行性能
|
||||
* 3. 处理返回值相关的逻辑(如记录操作结果)
|
||||
*/
|
||||
@AfterReturning(pointcut = "dataSourcePointCut()", returning = "result")
|
||||
public void doAfterReturning(JoinPoint point, Object result) {
|
||||
long threadId = Thread.currentThread().getId();
|
||||
|
||||
// 记录方法成功执行的信息
|
||||
String returnInfo = getReturnValueInfo(result);
|
||||
|
||||
log.debug("线程[{}] 返回通知 - 方法执行成功, 返回值: {}, 方法: {}.{}, 当前数据源: {}",
|
||||
threadId,
|
||||
returnInfo,
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName(),
|
||||
DataSourceContextHolder.getDataSourceStatus());
|
||||
|
||||
// 可以在这里添加成功执行的业务逻辑,如:
|
||||
// - 记录操作日志
|
||||
// - 更新监控指标
|
||||
// - 发送成功通知
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常通知 - 在目标方法抛出异常后执行
|
||||
* 主要用于异常处理和数据源状态的异常恢复
|
||||
*
|
||||
* @param point 连接点对象
|
||||
* @param ex 目标方法抛出的异常
|
||||
* <p>
|
||||
* 执行时机:
|
||||
* - 仅在目标方法抛出异常时执行
|
||||
* - 在目标方法执行完成后,后置通知之前
|
||||
* <p>
|
||||
* 主要功能:
|
||||
* 1. 记录方法执行失败的日志
|
||||
* 2. 处理异常情况下的数据源状态
|
||||
* 3. 提供异常恢复机制
|
||||
* 4. 发送异常告警
|
||||
*/
|
||||
@AfterThrowing(pointcut = "dataSourcePointCut()", throwing = "ex")
|
||||
public void doAfterThrowing(JoinPoint point, Throwable ex) {
|
||||
long threadId = Thread.currentThread().getId();
|
||||
|
||||
log.error("线程[{}] 异常通知 - 方法执行失败, 异常: {}, 方法: {}.{}, 当前数据源: {}",
|
||||
threadId,
|
||||
ex.getMessage(),
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName(),
|
||||
DataSourceContextHolder.getDataSourceStatus(),
|
||||
ex);
|
||||
|
||||
// 异常处理逻辑
|
||||
handleDataSourceException(ex, point);
|
||||
|
||||
// 可以在这里添加异常处理业务逻辑,如:
|
||||
// - 发送异常告警
|
||||
// - 记录错误日志到数据库
|
||||
// - 执行异常恢复操作
|
||||
}
|
||||
|
||||
/**
|
||||
* 环绕通知 - 在目标方法执行前后执行
|
||||
* 这是最强大的通知类型,可以控制目标方法的执行
|
||||
*
|
||||
* @param point 连接点对象
|
||||
* @return Object 目标方法的执行结果
|
||||
* @throws Throwable 目标方法抛出的任何异常
|
||||
* <p>
|
||||
* 执行时机:
|
||||
* - 包含目标方法的整个执行过程
|
||||
* - 可以控制是否执行目标方法,以及何时执行
|
||||
* <p>
|
||||
* 主要功能:
|
||||
* 1. 实际的数据源切换逻辑
|
||||
* 2. 方法执行时间的监控
|
||||
* 3. 异常处理和资源清理
|
||||
*/
|
||||
@Around("dataSourcePointCut()")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||
long threadId = Thread.currentThread().getId();
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 解析数据源注解配置
|
||||
DataSource dataSourceAnnotation = getDataSource(point);
|
||||
|
||||
if (dataSourceAnnotation == null) {
|
||||
// 没有找到注解,直接执行原方法
|
||||
log.info("线程[{}] 环绕通知 - 未找到数据源注解,直接执行方法: {}.{}",
|
||||
threadId,
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName());
|
||||
return point.proceed();
|
||||
}
|
||||
|
||||
String targetDataSourceKey = dataSourceAnnotation.value().name();
|
||||
String originalDataSource = DataSourceContextHolder.getDataSource();
|
||||
Object result = null;
|
||||
boolean success = false;
|
||||
|
||||
log.info("线程[{}] 环绕通知 - 开始数据源切换: {} -> {}, 方法: {}.{}",
|
||||
threadId,
|
||||
originalDataSource != null ? originalDataSource : "默认(主库)",
|
||||
targetDataSourceKey,
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName());
|
||||
|
||||
try {
|
||||
// 设置新的数据源
|
||||
DataSourceContextHolder.setDataSource(targetDataSourceKey);
|
||||
|
||||
// 执行目标方法
|
||||
result = point.proceed();
|
||||
success = true;
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Throwable ex) {
|
||||
// 记录异常信息,但不需要在这里处理,异常通知会处理
|
||||
success = false;
|
||||
throw ex;
|
||||
|
||||
} finally {
|
||||
// 计算执行时间
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
|
||||
// 恢复数据源设置
|
||||
DataSourceContextHolder.clearDataSource();
|
||||
|
||||
// 记录执行结果
|
||||
log.info("线程[{}] 环绕通知 - 方法执行{},耗时: {}ms, 方法: {}.{}, 数据源状态: {}",
|
||||
threadId,
|
||||
success ? "成功" : "失败",
|
||||
executionTime,
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName(),
|
||||
DataSourceContextHolder.getDataSourceStatus());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据源是否存在
|
||||
*
|
||||
* @param dataSourceKey 数据源标识
|
||||
* @param point 连接点对象
|
||||
*/
|
||||
private void validateDataSource(String dataSourceKey, JoinPoint point) {
|
||||
try {
|
||||
// 这里可以添加数据源验证逻辑
|
||||
// 例如:检查数据源是否在配置中启用
|
||||
// 由于数据源验证主要在DynamicDataSource中完成,这里主要做日志记录
|
||||
|
||||
log.info("数据源验证 - 数据源: {} 准备用于方法: {}.{}",
|
||||
dataSourceKey,
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("数据源验证失败 - 数据源: {}, 方法: {}.{}, 错误: {}",
|
||||
dataSourceKey,
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName(),
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行安全检查
|
||||
* 防止数据源状态异常导致的内存泄漏或数据污染
|
||||
*/
|
||||
private void performSafetyCheck() {
|
||||
try {
|
||||
// 检查当前线程的数据源状态
|
||||
String currentDataSource = DataSourceContextHolder.getDataSource();
|
||||
|
||||
// 如果发现异常的数据源状态,进行强制清理
|
||||
if (currentDataSource != null && currentDataSource.contains("ERROR")) {
|
||||
log.warn("安全检查 - 检测到异常数据源状态: {}, 进行强制清理", currentDataSource);
|
||||
DataSourceContextHolder.clearAll();
|
||||
}
|
||||
|
||||
// 可以添加更多的安全检查逻辑
|
||||
// 例如:检查线程是否长时间持有数据源
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("安全检查执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据源相关异常
|
||||
*
|
||||
* @param ex 异常对象
|
||||
* @param point 连接点对象
|
||||
*/
|
||||
private void handleDataSourceException(Throwable ex, JoinPoint point) {
|
||||
try {
|
||||
// 记录异常相关的数据源信息
|
||||
String dataSourceStatus = DataSourceContextHolder.getDataSourceStatus();
|
||||
|
||||
// 如果是数据源连接异常,可以尝试恢复或告警
|
||||
if (isDataSourceConnectionException(ex)) {
|
||||
log.error("数据源连接异常 - 方法: {}.{}, 数据源状态: {}, 异常: {}",
|
||||
point.getSignature().getDeclaringType().getSimpleName(),
|
||||
point.getSignature().getName(),
|
||||
dataSourceStatus,
|
||||
ex.getMessage());
|
||||
|
||||
// 可以在这里添加连接异常的处理逻辑
|
||||
// 例如:发送告警、记录到监控系统等
|
||||
}
|
||||
|
||||
// 确保在异常情况下数据源状态被正确清理
|
||||
DataSourceContextHolder.clearAll();
|
||||
|
||||
} catch (Exception handlerEx) {
|
||||
log.error("处理数据源异常时发生错误", handlerEx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是数据源连接异常
|
||||
*
|
||||
* @param ex 异常对象
|
||||
* @return boolean true-是数据源连接异常,false-不是
|
||||
*/
|
||||
private boolean isDataSourceConnectionException(Throwable ex) {
|
||||
if (ex == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String message = ex.getMessage();
|
||||
if (message == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据异常信息判断是否是数据源连接问题
|
||||
return message.contains("Connection") ||
|
||||
message.contains("DataSource") ||
|
||||
message.contains("SQL") ||
|
||||
message.contains("JDBC") ||
|
||||
ex.getClass().getName().contains("SQL");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取返回值信息(用于日志记录)
|
||||
*
|
||||
* @param result 方法返回值
|
||||
* @return String 返回值描述信息
|
||||
*/
|
||||
private String getReturnValueInfo(Object result) {
|
||||
if (result == null) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
try {
|
||||
if (result instanceof Iterable) {
|
||||
// 如果是集合类型,返回元素数量
|
||||
int size = 0;
|
||||
for (Object item : (Iterable<?>) result) {
|
||||
size++;
|
||||
if (size > 10) break; // 防止大集合的遍历
|
||||
}
|
||||
return String.format("Collection[%d elements]", size);
|
||||
} else if (result.getClass().isArray()) {
|
||||
// 如果是数组,返回长度
|
||||
return String.format("Array[%d]", java.lang.reflect.Array.getLength(result));
|
||||
} else {
|
||||
// 其他类型返回简单类名
|
||||
return result.getClass().getSimpleName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return "Error getting return info: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源注解配置
|
||||
* 按照优先级查找@DataSource注解:方法级别 > 类级别
|
||||
*
|
||||
* @param point 连接点对象
|
||||
* @return DataSource 数据源注解实例,如果未找到返回null
|
||||
*/
|
||||
private DataSource getDataSource(JoinPoint point) {
|
||||
if (!(point.getSignature() instanceof MethodSignature)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
|
||||
// 查找方法级别的@DataSource注解
|
||||
DataSource methodAnnotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
|
||||
|
||||
if (Objects.nonNull(methodAnnotation)) {
|
||||
return methodAnnotation;
|
||||
}
|
||||
|
||||
// 查找类级别的@DataSource注解
|
||||
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取切面状态信息(用于监控和调试)
|
||||
*
|
||||
* @return String 切面状态描述
|
||||
*/
|
||||
public String getAspectStatus() {
|
||||
return String.format("DataSourceAspect[激活状态: 已启用, 切面顺序: %d, 通知类型: 完整, 当前线程数据源: %s]",
|
||||
1,
|
||||
DataSourceContextHolder.getDataSourceStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查切面是否正常工作(用于健康检查)
|
||||
*
|
||||
* @return boolean true-正常,false-异常
|
||||
*/
|
||||
public boolean isAspectHealthy() {
|
||||
try {
|
||||
// 测试基本的切面功能
|
||||
DataSourceContextHolder.getDataSource();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("数据源切面健康检查失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+677
@@ -0,0 +1,677 @@
|
||||
// +======================================================================
|
||||
// | 小蚂蚁云·企业数字化开发平台 [ 加速数字化转型,构建智能未来 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 核心价值:赋能开发者 · 服务企业 · 共建生态
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2020-2026 小蚂蚁云团队 版权所有
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方平台: https://www.xiaomayicloud.com
|
||||
// +======================================================================
|
||||
// | 【知识产权声明】
|
||||
// | 1. 权属声明:本软件是受《中华人民共和国著作权法》
|
||||
// | 保护的专有财产,完整版权归小蚂蚁云团队所有
|
||||
// | 2. 权利范围:所有知识产权(商标、专利、著作权、
|
||||
// | 商业秘密等)均归小蚂蚁云团队所有,受法律保护
|
||||
// | 3. 授权性质:用户仅可获得有限的、非独占的使用许可
|
||||
// | 4. 禁止行为:未经书面授权,禁止对本软件本身
|
||||
// | 申请任何知识产权
|
||||
// | 5. 权利保留:未在本协议中明示授予的一切权利
|
||||
// | 均由小蚂蚁云团队保留
|
||||
// +======================================================================
|
||||
// | 【授权状态说明】
|
||||
// | 1. 未授权状态:用户未购买有效的商业许可证
|
||||
// | 2. 商业授权:已购买正式商业许可证的用户
|
||||
// +======================================================================
|
||||
// | 【绝对禁止条款】
|
||||
// | 无论授权状态如何,任何用户均严格禁止以下行为:
|
||||
// | 1. 逆向工程:对软件进行反编译、反汇编、技术破解或任何形式的逆向分析
|
||||
// | 2. 版权破坏:移除、修改、遮挡软件中的版权标识、许可控制或技术保护措施
|
||||
// | 3. 非法传播:复制、分发、出租、出借、转售、再许可本软件或其许可权利
|
||||
// | 4. 竞争性使用:利用本软件的代码、架构、设计开发直接竞争的产品或服务
|
||||
// | 5. 侵权使用:将软件用于侵犯第三方知识产权或其他合法权益的用途
|
||||
// +======================================================================
|
||||
// | 【商业授权许可范围】
|
||||
// | 购买有效商业许可证的用户,在遵守上述禁止条款的前提下,获得:
|
||||
// | 1. 使用权利:在许可证约定的项目数量、开发者数量范围内使用本软件
|
||||
// | 2. 技术支持:商业授权用户可以获取本地开发部署的技术支持服务
|
||||
// | 3. 版本更新:在许可证有效期内获取软件更新、安全补丁和功能增强
|
||||
// | 4. 商业部署:将基于本软件开发的应用程序用于商业运营和生产环境
|
||||
// +======================================================================
|
||||
// | 【许可证获取】
|
||||
// | 1. 获取途径:仅通过小蚂蚁云官方渠道购买(官网、授权代理商),购买记录凭证
|
||||
// | 2. 转让限制:许可证与购买主体绑定,未经书面同意不得转让
|
||||
// | 3. 终止情形:许可证到期、违反协议、破产清算等情况下自动终止
|
||||
// +======================================================================
|
||||
// | 【合法使用要求】
|
||||
// | 1. 仅限用于合法合规的软件研发
|
||||
// | 2. 严禁用于任何违法、侵权或恶意行为
|
||||
// | 3. 严禁违反我国法律法规和政策
|
||||
// +======================================================================
|
||||
// | 【免责声明】
|
||||
// | 因使用本框架导致的以下情况,我们概不负责:
|
||||
// | 1. 项目研发中的意外、疏忽、违约等风险
|
||||
// | 2. 侵犯第三方知识产权或其他合法权益
|
||||
// | 3. 任何直接、间接、附带或衍生一切损失
|
||||
// | 4. 用户违规违法行为的一切法律后果
|
||||
// +======================================================================
|
||||
// | 【协议重要提示】
|
||||
// | 1. 本声明是《小蚂蚁云最终用户许可协议》的摘要版本
|
||||
// | 2. 下载、安装或使用本软件即表示您接受全部协议条款
|
||||
// | 3. 我们保留单方面修改本声明的权利,修改后的版本在官网公布即生效
|
||||
// | 4. 用户有义务定期查看最新版本,继续使用视为接受修改
|
||||
// +======================================================================
|
||||
// | 【侵权举报与合规】
|
||||
// | 1. 知识产权侵权举报:请联系官方客服
|
||||
// | 2. 许可证合规咨询:xiaomayicloud@163.com
|
||||
// | 3. 违规使用将终止授权并追究责任
|
||||
// | 4. 无条件配合司法机关调查取证
|
||||
// | 5. 保留一切法律追诉权利
|
||||
// | 6. 对有效举报和合规建议,我们将给予感谢和奖励
|
||||
// +======================================================================
|
||||
|
||||
package com.xiaomayi.datasource.config;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
|
||||
import com.alibaba.druid.support.jakarta.StatViewServlet;
|
||||
import com.alibaba.druid.support.jakarta.WebStatFilter;
|
||||
import com.xiaomayi.datasource.context.DynamicDataSource;
|
||||
import com.xiaomayi.datasource.enums.DataSourceType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 数据源配置类 - 修复多个@Primary Bean冲突问题
|
||||
* <p>
|
||||
* 问题背景:
|
||||
* 之前的配置中存在多个被标记为@Primary的DataSource Bean,导致Spring无法确定注入哪个Bean:
|
||||
* - masterDataSource (标记了@Primary)
|
||||
* - slave1DataSource (标记了@Primary)
|
||||
* - dataSource (标记了@Primary)
|
||||
* <p>
|
||||
* Spring抛出异常:No qualifying bean of type 'javax.sql.DataSource' available:
|
||||
* more than one 'primary' bean found among candidates
|
||||
* <p>
|
||||
* 解决方案:
|
||||
* 1. 只保留动态数据源dataSource为@Primary,作为系统的主要数据源
|
||||
* 2. 移除其他单个数据源Bean的@Primary注解
|
||||
* 3. 使用@Qualifier明确标识各个数据源Bean的名称
|
||||
* 4. 确保整个Spring容器中只有一个主要的DataSource Bean
|
||||
* <p>
|
||||
* 设计原则:
|
||||
* - 单一主要原则:只有一个DataSource Bean可以标记为@Primary
|
||||
* - 明确标识原则:使用@Qualifier明确指定Bean名称
|
||||
* - 依赖清晰原则:通过构造函数明确依赖关系
|
||||
*
|
||||
* @author 小蚂蚁云团队
|
||||
* @since 2024-05-25
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
/**
|
||||
* 数据源映射缓存
|
||||
* <p>
|
||||
* 用途:
|
||||
* - 存储所有已创建的数据源实例,便于统一管理
|
||||
* - 提供数据源状态监控和调试信息
|
||||
* - 支持动态数据源的路由和切换
|
||||
* <p>
|
||||
* 数据结构:
|
||||
* key: 数据源名称(DataSourceType枚举的name,如:"MASTER", "SLAVE1")
|
||||
* value: 对应的DataSource实例
|
||||
* <p>
|
||||
* 线程安全:
|
||||
* 使用ConcurrentHashMap保证多线程环境下的安全访问
|
||||
*/
|
||||
private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Spring环境配置对象
|
||||
* <p>
|
||||
* 用途:
|
||||
* - 读取application.yml中的配置属性
|
||||
* - 获取Druid监控相关的配置信息
|
||||
* - 避免使用SpringContextHolder可能出现的初始化问题
|
||||
* <p>
|
||||
* 注入方式:
|
||||
* 通过构造函数注入,确保依赖在对象创建时就已经可用
|
||||
* 这种方式比字段注入更安全,支持不可变对象和更好的测试性
|
||||
*/
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* 构造函数 - 依赖注入
|
||||
* <p>
|
||||
* 设计优势:
|
||||
* 1. 不可变性:environment字段被声明为final,确保线程安全
|
||||
* 2. 明确的依赖:所有必需的依赖都在构造时明确指定
|
||||
* 3. 易于测试:可以通过构造函数传入mock对象进行单元测试
|
||||
* 4. 避免空指针:确保environment在对象使用时已经初始化
|
||||
* 5. 循环依赖检测:Spring能更早地发现和报告循环依赖问题
|
||||
*
|
||||
* @param environment Spring环境配置对象,由Spring容器自动注入
|
||||
*/
|
||||
public DataSourceConfig(Environment environment) {
|
||||
this.environment = environment;
|
||||
log.info("DataSourceConfig初始化完成 - 使用构造函数注入Environment");
|
||||
}
|
||||
|
||||
/**
|
||||
* 主数据源配置Bean
|
||||
* <p>
|
||||
* 重要变更:移除了@Primary注解
|
||||
* <p>
|
||||
* 原因分析:
|
||||
* 在之前的配置中,masterDataSource标记了@Primary,这会导致:
|
||||
* 1. 当其他组件使用@Autowired DataSource dataSource时,Spring会注入masterDataSource
|
||||
* 2. 但系统中还有dataSource(动态数据源)也标记了@Primary
|
||||
* 3. Spring无法确定哪个是真正的主要数据源,抛出异常
|
||||
* <p>
|
||||
* 解决方案:
|
||||
* 移除单个数据源Bean的@Primary注解,只保留动态数据源的@Primary
|
||||
* <p>
|
||||
* 配置绑定:
|
||||
* 使用@ConfigurationProperties将配置文件中的属性绑定到数据源实例
|
||||
* 前缀:spring.datasource.dynamic.datasource.master
|
||||
*
|
||||
* @return DruidDataSource 主数据源实例
|
||||
*/
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
|
||||
public DataSource masterDataSource() {
|
||||
log.info("初始化主数据源...");
|
||||
// 使用DruidDataSourceBuilder创建数据源,自动应用配置属性
|
||||
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
|
||||
// 将数据源添加到缓存映射中,便于统一管理
|
||||
dataSourceMap.put(DataSourceType.MASTER.name(), dataSource);
|
||||
log.info("主数据源初始化完成");
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据源1配置Bean
|
||||
* <p>
|
||||
* 注解说明:
|
||||
* - @Bean: 声明为Spring Bean,方法名作为默认的Bean名称
|
||||
* - @Qualifier("slave1DataSource"): 明确指定Bean的名称为"slave1DataSource"
|
||||
* - @ConfigurationProperties: 绑定配置属性,前缀为spring.datasource.dynamic.datasource.slave1
|
||||
* - @ConditionalOnProperty: 条件化创建Bean,只有当slave1.enabled=true时才创建
|
||||
* <p>
|
||||
* 条件化创建:
|
||||
* 这个Bean只有在配置文件中spring.datasource.dynamic.datasource.slave1.enabled=true时才会创建
|
||||
* 如果enabled=false或未配置,Spring不会创建这个Bean,注入时会得到null
|
||||
*
|
||||
* @return DruidDataSource 从数据源1实例,如果未启用则返回null
|
||||
*/
|
||||
@Bean
|
||||
@Qualifier("slave1DataSource")
|
||||
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave1")
|
||||
@ConditionalOnProperty(prefix = "spring.datasource.dynamic.datasource.slave1", name = "enabled", havingValue = "true")
|
||||
public DataSource slave1DataSource() {
|
||||
log.info("初始化从数据源1...");
|
||||
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
|
||||
dataSourceMap.put(DataSourceType.SLAVE1.name(), dataSource);
|
||||
log.info("从数据源1初始化完成");
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据源2配置Bean
|
||||
* <p>
|
||||
* 配置方式与从数据源1相同,使用不同的配置前缀和Bean名称
|
||||
*
|
||||
* @return DruidDataSource 从数据源2实例,如果未启用则返回null
|
||||
*/
|
||||
@Bean
|
||||
@Qualifier("slave2DataSource")
|
||||
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave2")
|
||||
@ConditionalOnProperty(prefix = "spring.datasource.dynamic.datasource.slave2", name = "enabled", havingValue = "true")
|
||||
public DataSource slave2DataSource() {
|
||||
log.info("初始化从数据源2...");
|
||||
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
|
||||
dataSourceMap.put(DataSourceType.SLAVE2.name(), dataSource);
|
||||
log.info("从数据源2初始化完成");
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL数据源配置Bean
|
||||
* <p>
|
||||
* 用于连接PostgreSQL数据库,配置方式与其他数据源类似
|
||||
* 使用PostgreSQL特定的JDBC驱动和连接URL格式
|
||||
*
|
||||
* @return DruidDataSource PostgreSQL数据源实例,如果未启用则返回null
|
||||
*/
|
||||
@Bean
|
||||
@Qualifier("postgresDataSource")
|
||||
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.postgres")
|
||||
@ConditionalOnProperty(prefix = "spring.datasource.dynamic.datasource.postgres", name = "enabled", havingValue = "true")
|
||||
public DataSource postgresDataSource() {
|
||||
log.info("初始化PostgreSQL数据源...");
|
||||
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
|
||||
dataSourceMap.put(DataSourceType.POSTGRESQL.name(), dataSource);
|
||||
log.info("PostgreSQL数据源初始化完成");
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL Server数据源配置Bean
|
||||
* <p>
|
||||
* 用于连接SQL Server数据库,配置方式与其他数据源类似
|
||||
* 使用SQL Server特定的JDBC驱动和连接URL格式
|
||||
*
|
||||
* @return DruidDataSource SQL Server数据源实例,如果未启用则返回null
|
||||
*/
|
||||
@Bean
|
||||
@Qualifier("sqlServerDataSource")
|
||||
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.sqlserver")
|
||||
@ConditionalOnProperty(prefix = "spring.datasource.dynamic.datasource.sqlserver", name = "enabled", havingValue = "true")
|
||||
public DataSource sqlServerDataSource() {
|
||||
log.info("初始化SQL Server数据源...");
|
||||
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
|
||||
dataSourceMap.put(DataSourceType.SQLSERVER.name(), dataSource);
|
||||
log.info("SQL Server数据源初始化完成");
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态数据源配置Bean - 系统中唯一标记为@Primary的DataSource
|
||||
* <p>
|
||||
* 核心功能:
|
||||
* 将所有单个数据源组合成动态数据源,支持运行时数据源切换
|
||||
* <p>
|
||||
* 重要设计:
|
||||
* 这是整个Spring容器中唯一标记为@Primary的DataSource Bean
|
||||
* 当其他组件使用@Autowired DataSource dataSource时,Spring会自动注入这个Bean
|
||||
* <p>
|
||||
* 参数注入:
|
||||
* 使用@Qualifier明确指定要注入的Bean名称,解决多个同类型Bean的冲突
|
||||
* Spring会根据@Qualifier的值找到对应的Bean进行注入
|
||||
* <p>
|
||||
* 执行流程:
|
||||
* 1. 收集所有已启用的数据源(不为null的数据源)
|
||||
* 2. 创建目标数据源映射(Map<数据源名称, DataSource实例>)
|
||||
* 3. 创建DynamicDataSource实例,设置主数据源为默认数据源
|
||||
* 4. 返回配置完成的动态数据源
|
||||
*
|
||||
* @param masterDataSource 主数据源(必须存在)
|
||||
* @param slave1DataSource 从数据源1(可能为null,如果未启用)
|
||||
* @param slave2DataSource 从数据源2(可能为null,如果未启用)
|
||||
* @param postgresDataSource PostgreSQL数据源(可能为null,如果未启用)
|
||||
* @param sqlServerDataSource SQL Server数据源(可能为null,如果未启用)
|
||||
* @return DynamicDataSource 动态数据源实例,标记为@Primary
|
||||
*/
|
||||
@Bean
|
||||
@Primary // 重要:这是系统中唯一标记为@Primary的DataSource Bean
|
||||
public DynamicDataSource dataSource(
|
||||
// 主数据源必须存在
|
||||
@Qualifier("masterDataSource") DataSource masterDataSource,
|
||||
// 其他数据源都是可选的,允许为null
|
||||
@Autowired(required = false) @Qualifier("slave1DataSource") DataSource slave1DataSource,
|
||||
@Autowired(required = false) @Qualifier("slave2DataSource") DataSource slave2DataSource,
|
||||
@Autowired(required = false) @Qualifier("postgresDataSource") DataSource postgresDataSource,
|
||||
@Autowired(required = false) @Qualifier("sqlServerDataSource") DataSource sqlServerDataSource) {
|
||||
|
||||
log.info("开始配置动态数据源(主要数据源)...");
|
||||
|
||||
// 创建目标数据源映射,用于存储所有可用的数据源
|
||||
Map<Object, Object> targetDataSources = new HashMap<>();
|
||||
|
||||
// 添加主数据源(必须存在)
|
||||
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
|
||||
log.info("添加主数据源: {}", DataSourceType.MASTER.name());
|
||||
|
||||
// 按条件添加其他数据源,如果数据源为null(未启用),则跳过
|
||||
addDataSourceIfEnabled(targetDataSources, DataSourceType.SLAVE1.name(), slave1DataSource);
|
||||
addDataSourceIfEnabled(targetDataSources, DataSourceType.SLAVE2.name(), slave2DataSource);
|
||||
addDataSourceIfEnabled(targetDataSources, DataSourceType.POSTGRESQL.name(), postgresDataSource);
|
||||
addDataSourceIfEnabled(targetDataSources, DataSourceType.SQLSERVER.name(), sqlServerDataSource);
|
||||
|
||||
log.info("动态数据源初始化完成,已加载数据源数量: {}", targetDataSources.size());
|
||||
log.info("已加载数据源: {}", targetDataSources.keySet());
|
||||
|
||||
// 验证至少有一个数据源可用
|
||||
if (targetDataSources.isEmpty()) {
|
||||
throw new IllegalStateException("动态数据源配置错误:没有可用的数据源");
|
||||
}
|
||||
|
||||
// 创建动态数据源实例
|
||||
// 参数1: 默认数据源(当没有明确指定数据源时使用)
|
||||
// 参数2: 所有可用的目标数据源映射
|
||||
DynamicDataSource dynamicDataSource = new DynamicDataSource(masterDataSource, targetDataSources);
|
||||
log.info("动态数据源配置成功,已设置为主要数据源(@Primary)");
|
||||
|
||||
return dynamicDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件添加数据源辅助方法
|
||||
* <p>
|
||||
* 功能:安全地将数据源添加到目标映射中,如果数据源为null则跳过
|
||||
* <p>
|
||||
* 设计考虑:
|
||||
* - 避免在每个数据源添加时重复进行null检查
|
||||
* - 提供统一的日志记录格式
|
||||
* - 提高代码的可读性和可维护性
|
||||
* - 支持条件化数据源加载
|
||||
*
|
||||
* @param targetDataSources 目标数据源映射,用于存储所有可用的数据源
|
||||
* @param dataSourceName 数据源名称(使用DataSourceType枚举的name)
|
||||
* @param dataSource 数据源实例,可能为null(如果对应的Bean未创建)
|
||||
*/
|
||||
private void addDataSourceIfEnabled(Map<Object, Object> targetDataSources,
|
||||
String dataSourceName,
|
||||
DataSource dataSource) {
|
||||
if (dataSource != null) {
|
||||
// 检查是否已经存在相同名称的数据源
|
||||
if (targetDataSources.containsKey(dataSourceName)) {
|
||||
log.warn("数据源 '{}' 已存在,跳过重复添加", dataSourceName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证数据源是否有效
|
||||
if (isDataSourceValid(dataSource)) {
|
||||
targetDataSources.put(dataSourceName, dataSource);
|
||||
log.info("数据源 '{}' 已加载", dataSourceName);
|
||||
} else {
|
||||
log.warn("数据源 '{}' 无效,跳过加载", dataSourceName);
|
||||
}
|
||||
} else {
|
||||
log.debug("数据源 '{}' 未启用,跳过加载", dataSourceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据源有效性
|
||||
* <p>
|
||||
* 检查数据源的基本可用性,避免添加无效的数据源
|
||||
*/
|
||||
private boolean isDataSourceValid(DataSource dataSource) {
|
||||
try {
|
||||
// 简单的有效性检查:尝试获取连接
|
||||
if (dataSource.getConnection() != null) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("数据源连接测试失败: {}", e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务管理器配置Bean
|
||||
* <p>
|
||||
* 功能:配置Spring的事务管理器,用于管理数据库事务
|
||||
* <p>
|
||||
* 注解说明:
|
||||
* - @Primary: 标记为主要事务管理器,当有多个事务管理器时优先使用
|
||||
* - @Qualifier: 明确指定要使用的数据源Bean名称为"dataSource"
|
||||
* <p>
|
||||
* 事务管理:
|
||||
* - 支持声明式事务(使用@Transactional注解)
|
||||
* - 支持编程式事务(使用TransactionTemplate)
|
||||
* - 自动参与Spring的事务同步
|
||||
* - 支持事务传播行为和隔离级别配置
|
||||
* <p>
|
||||
* 注意:事务管理器可以标记为@Primary,因为它与DataSource类型不同
|
||||
* 不会与DataSource Bean产生@Primary冲突
|
||||
*
|
||||
* @param dataSource 动态数据源实例,由Spring根据@Qualifier注入
|
||||
* @return DataSourceTransactionManager 事务管理器实例
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DynamicDataSource dataSource) {
|
||||
log.info("配置动态数据源事务管理器...");
|
||||
// 创建事务管理器,使用动态数据源
|
||||
// 这样事务管理器就能支持跨数据源的事务管理
|
||||
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
|
||||
log.info("动态数据源事务管理器配置完成");
|
||||
return transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Druid 监控 Servlet 配置Bean
|
||||
* <p>
|
||||
* 功能:配置Druid内置的监控界面,用于监控数据库连接池状态
|
||||
* <p>
|
||||
* 注解说明:
|
||||
* - @ConditionalOnProperty: 条件化创建,只有当监控功能启用时才创建
|
||||
* matchIfMissing = true: 如果配置项不存在,默认创建此Bean
|
||||
* <p>
|
||||
* 监控功能:
|
||||
* - SQL监控:查看执行的SQL语句、执行时间、影响行数等
|
||||
* - 数据源监控:查看连接池的活跃连接、空闲连接、等待连接等
|
||||
* - Web应用监控:查看URI请求统计、Session监控等
|
||||
* - 安全控制:支持IP白名单、登录认证等
|
||||
* <p>
|
||||
* 访问地址:http://localhost:8080/druid/
|
||||
* 默认登录:admin / 123456(可在配置文件中修改)
|
||||
*
|
||||
* @return ServletRegistrationBean<StatViewServlet> Druid监控Servlet注册Bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
|
||||
log.info("初始化Druid监控Servlet...");
|
||||
|
||||
// 创建Servlet注册Bean
|
||||
// 参数1: Druid的StatViewServlet,提供监控界面
|
||||
// 参数2: 访问路径映射,所有以/druid/开头的请求都会由这个Servlet处理
|
||||
ServletRegistrationBean<StatViewServlet> servletRegistrationBean =
|
||||
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
|
||||
|
||||
// 从环境配置中获取监控页面登录用户名,默认值为"admin"
|
||||
String loginUsername = environment.getProperty("spring.datasource.druid.stat-view-servlet.login-username", "admin");
|
||||
// 从环境配置中获取监控页面登录密码,默认值为"123456"
|
||||
String loginPassword = environment.getProperty("spring.datasource.druid.stat-view-servlet.login-password", "123456");
|
||||
|
||||
// 配置IP白名单:允许访问监控页面的IP地址,空字符串表示允许所有IP访问
|
||||
servletRegistrationBean.addInitParameter("allow",
|
||||
environment.getProperty("spring.datasource.druid.stat-view-servlet.allow", ""));
|
||||
// 配置IP黑名单:拒绝访问监控页面的IP地址,空字符串表示不拒绝任何IP
|
||||
servletRegistrationBean.addInitParameter("deny",
|
||||
environment.getProperty("spring.datasource.druid.stat-view-servlet.deny", ""));
|
||||
// 设置监控页面登录用户名
|
||||
servletRegistrationBean.addInitParameter("loginUsername", loginUsername);
|
||||
// 设置监控页面登录密码
|
||||
servletRegistrationBean.addInitParameter("loginPassword", loginPassword);
|
||||
// 设置是否允许重置监控数据:false表示不允许重置,防止误操作清空统计数据
|
||||
servletRegistrationBean.addInitParameter("resetEnable",
|
||||
environment.getProperty("spring.datasource.druid.stat-view-servlet.reset-enable", "false"));
|
||||
|
||||
log.info("Druid监控Servlet初始化完成,访问路径: /druid/");
|
||||
log.info("Druid监控登录用户名: {}", loginUsername);
|
||||
|
||||
return servletRegistrationBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Druid 监控过滤器配置Bean
|
||||
* <p>
|
||||
* 功能:配置Druid的Web统计过滤器,用于收集Web请求和SQL执行统计信息
|
||||
* <p>
|
||||
* 过滤器作用:
|
||||
* - 统计Web应用的URI请求信息
|
||||
* - 统计Session相关信息
|
||||
* - 统计SQL执行情况
|
||||
* - 排除静态资源,避免统计干扰
|
||||
*
|
||||
* @return FilterRegistrationBean<WebStatFilter> Druid统计过滤器注册Bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
|
||||
log.info("初始化Druid统计过滤器...");
|
||||
|
||||
// 创建过滤器注册Bean
|
||||
FilterRegistrationBean<WebStatFilter> filterRegistrationBean =
|
||||
new FilterRegistrationBean<>(new WebStatFilter());
|
||||
|
||||
// 配置过滤器拦截所有请求
|
||||
filterRegistrationBean.addUrlPatterns("/*");
|
||||
// 配置排除路径:不统计静态资源和Druid监控路径
|
||||
// 这些路径的请求不会被Druid统计,避免干扰统计数据
|
||||
filterRegistrationBean.addInitParameter("exclusions",
|
||||
environment.getProperty("spring.datasource.druid.web-stat-filter.exclusions",
|
||||
"*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"));
|
||||
|
||||
log.info("Druid统计过滤器初始化完成");
|
||||
return filterRegistrationBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有数据源映射
|
||||
* <p>
|
||||
* 功能:提供数据源映射的只读视图,用于监控、管理和调试
|
||||
* <p>
|
||||
* 设计考虑:
|
||||
* - 返回新的HashMap实例,避免外部代码修改内部数据源映射
|
||||
* - 提供数据源状态信息给监控系统
|
||||
* - 支持动态数据源的管理和调试
|
||||
* - 便于在运行时查看所有可用的数据源
|
||||
*
|
||||
* @return Map<String, DataSource> 数据源名称到数据源实例的映射副本
|
||||
*/
|
||||
public Map<String, DataSource> getDataSourceMap() {
|
||||
return new HashMap<>(dataSourceMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据源配置验证器Bean
|
||||
* <p>
|
||||
* 功能:在应用启动时验证数据源配置是否正确
|
||||
* <p>
|
||||
* 验证内容:
|
||||
* - 数据源是否成功创建
|
||||
* - 数据源映射是否正确构建
|
||||
* - 配置属性是否正确加载
|
||||
*
|
||||
* @return DataSourceConfigValidator 配置验证器实例
|
||||
*/
|
||||
@Bean
|
||||
public DataSourceConfigValidator dataSourceConfigValidator() {
|
||||
return new DataSourceConfigValidator(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据源配置验证器内部类
|
||||
* <p>
|
||||
* 功能:在构造时执行配置验证,确保数据源配置正确
|
||||
* <p>
|
||||
* 设计模式:使用内部类封装验证逻辑,避免污染外部类
|
||||
*/
|
||||
public static class DataSourceConfigValidator {
|
||||
|
||||
private final DataSourceConfig dataSourceConfig;
|
||||
|
||||
/**
|
||||
* 构造函数 - 执行配置验证
|
||||
*
|
||||
* @param dataSourceConfig 数据源配置实例
|
||||
*/
|
||||
public DataSourceConfigValidator(DataSourceConfig dataSourceConfig) {
|
||||
this.dataSourceConfig = dataSourceConfig;
|
||||
// 在构造时立即执行验证
|
||||
validateConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置验证方法
|
||||
* <p>
|
||||
* 验证步骤:
|
||||
* 1. 获取数据源映射
|
||||
* 2. 检查数据源数量
|
||||
* 3. 记录验证结果
|
||||
* <p>
|
||||
* 异常处理:如果验证失败,抛出IllegalStateException阻止应用启动
|
||||
*/
|
||||
private void validateConfiguration() {
|
||||
log.info("开始验证数据源配置...");
|
||||
|
||||
try {
|
||||
// 获取数据源映射
|
||||
Map<String, DataSource> dataSourceMap = dataSourceConfig.getDataSourceMap();
|
||||
// 记录验证结果
|
||||
log.info("数据源配置验证成功,已加载 {} 个数据源: {}",
|
||||
dataSourceMap.size(), dataSourceMap.keySet());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("数据源配置验证失败", e);
|
||||
// 抛出运行时异常,阻止应用启动
|
||||
throw new IllegalStateException("数据源配置验证失败", e);
|
||||
}
|
||||
|
||||
log.info("数据源配置验证完成");
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 配置验证方法 - 增强版
|
||||
// */
|
||||
// private void validateConfiguration() {
|
||||
// log.info("开始验证数据源配置...");
|
||||
//
|
||||
// try {
|
||||
// Map<String, DataSource> dataSourceMap = dataSourceConfig.getDataSourceMap();
|
||||
//
|
||||
// // 验证主数据源必须存在
|
||||
// if (!dataSourceMap.containsKey(DataSourceType.MASTER.name())) {
|
||||
// throw new IllegalStateException("主数据源配置缺失,系统无法启动");
|
||||
// }
|
||||
//
|
||||
// // 验证数据源数量
|
||||
// int enabledCount = dataSourceMap.size();
|
||||
// log.info("数据源配置验证成功,已启用 {} 个数据源: {}", enabledCount, dataSourceMap.keySet());
|
||||
//
|
||||
// // 验证每个数据源的可用性
|
||||
// validateDataSourcesAvailability(dataSourceMap);
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// log.error("数据源配置验证失败", e);
|
||||
// throw new IllegalStateException("数据源配置验证失败: " + e.getMessage(), e);
|
||||
// }
|
||||
//
|
||||
// log.info("数据源配置验证完成");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 验证数据源可用性
|
||||
// */
|
||||
// private void validateDataSourcesAvailability(Map<String, DataSource> dataSourceMap) {
|
||||
// for (Map.Entry<String, DataSource> entry : dataSourceMap.entrySet()) {
|
||||
// String dataSourceName = entry.getKey();
|
||||
// DataSource dataSource = entry.getValue();
|
||||
//
|
||||
// try {
|
||||
// // 测试数据源连接
|
||||
// if (dataSource.getConnection() != null) {
|
||||
// log.debug("数据源 '{}' 连接测试成功", dataSourceName);
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.warn("数据源 '{}' 连接测试失败: {}", dataSourceName, e.getMessage());
|
||||
// // 对于主数据源,连接失败应该阻止启动
|
||||
// if (DataSourceType.MASTER.name().equals(dataSourceName)) {
|
||||
// throw new IllegalStateException("主数据源连接失败: " + e.getMessage(), e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
// +======================================================================
|
||||
// | 小蚂蚁云·企业数字化开发平台 [ 加速数字化转型,构建智能未来 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 核心价值:赋能开发者 · 服务企业 · 共建生态
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2020-2026 小蚂蚁云团队 版权所有
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方平台: https://www.xiaomayicloud.com
|
||||
// +======================================================================
|
||||
// | 【知识产权声明】
|
||||
// | 1. 权属声明:本软件是受《中华人民共和国著作权法》
|
||||
// | 保护的专有财产,完整版权归小蚂蚁云团队所有
|
||||
// | 2. 权利范围:所有知识产权(商标、专利、著作权、
|
||||
// | 商业秘密等)均归小蚂蚁云团队所有,受法律保护
|
||||
// | 3. 授权性质:用户仅可获得有限的、非独占的使用许可
|
||||
// | 4. 禁止行为:未经书面授权,禁止对本软件本身
|
||||
// | 申请任何知识产权
|
||||
// | 5. 权利保留:未在本协议中明示授予的一切权利
|
||||
// | 均由小蚂蚁云团队保留
|
||||
// +======================================================================
|
||||
// | 【授权状态说明】
|
||||
// | 1. 未授权状态:用户未购买有效的商业许可证
|
||||
// | 2. 商业授权:已购买正式商业许可证的用户
|
||||
// +======================================================================
|
||||
// | 【绝对禁止条款】
|
||||
// | 无论授权状态如何,任何用户均严格禁止以下行为:
|
||||
// | 1. 逆向工程:对软件进行反编译、反汇编、技术破解或任何形式的逆向分析
|
||||
// | 2. 版权破坏:移除、修改、遮挡软件中的版权标识、许可控制或技术保护措施
|
||||
// | 3. 非法传播:复制、分发、出租、出借、转售、再许可本软件或其许可权利
|
||||
// | 4. 竞争性使用:利用本软件的代码、架构、设计开发直接竞争的产品或服务
|
||||
// | 5. 侵权使用:将软件用于侵犯第三方知识产权或其他合法权益的用途
|
||||
// +======================================================================
|
||||
// | 【商业授权许可范围】
|
||||
// | 购买有效商业许可证的用户,在遵守上述禁止条款的前提下,获得:
|
||||
// | 1. 使用权利:在许可证约定的项目数量、开发者数量范围内使用本软件
|
||||
// | 2. 技术支持:商业授权用户可以获取本地开发部署的技术支持服务
|
||||
// | 3. 版本更新:在许可证有效期内获取软件更新、安全补丁和功能增强
|
||||
// | 4. 商业部署:将基于本软件开发的应用程序用于商业运营和生产环境
|
||||
// +======================================================================
|
||||
// | 【许可证获取】
|
||||
// | 1. 获取途径:仅通过小蚂蚁云官方渠道购买(官网、授权代理商),购买记录凭证
|
||||
// | 2. 转让限制:许可证与购买主体绑定,未经书面同意不得转让
|
||||
// | 3. 终止情形:许可证到期、违反协议、破产清算等情况下自动终止
|
||||
// +======================================================================
|
||||
// | 【合法使用要求】
|
||||
// | 1. 仅限用于合法合规的软件研发
|
||||
// | 2. 严禁用于任何违法、侵权或恶意行为
|
||||
// | 3. 严禁违反我国法律法规和政策
|
||||
// +======================================================================
|
||||
// | 【免责声明】
|
||||
// | 因使用本框架导致的以下情况,我们概不负责:
|
||||
// | 1. 项目研发中的意外、疏忽、违约等风险
|
||||
// | 2. 侵犯第三方知识产权或其他合法权益
|
||||
// | 3. 任何直接、间接、附带或衍生一切损失
|
||||
// | 4. 用户违规违法行为的一切法律后果
|
||||
// +======================================================================
|
||||
// | 【协议重要提示】
|
||||
// | 1. 本声明是《小蚂蚁云最终用户许可协议》的摘要版本
|
||||
// | 2. 下载、安装或使用本软件即表示您接受全部协议条款
|
||||
// | 3. 我们保留单方面修改本声明的权利,修改后的版本在官网公布即生效
|
||||
// | 4. 用户有义务定期查看最新版本,继续使用视为接受修改
|
||||
// +======================================================================
|
||||
// | 【侵权举报与合规】
|
||||
// | 1. 知识产权侵权举报:请联系官方客服
|
||||
// | 2. 许可证合规咨询:xiaomayicloud@163.com
|
||||
// | 3. 违规使用将终止授权并追究责任
|
||||
// | 4. 无条件配合司法机关调查取证
|
||||
// | 5. 保留一切法律追诉权利
|
||||
// | 6. 对有效举报和合规建议,我们将给予感谢和奖励
|
||||
// +======================================================================
|
||||
|
||||
package com.xiaomayi.datasource.config;
|
||||
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 数据源参数属性配置
|
||||
* </p>
|
||||
*
|
||||
* @author 小蚂蚁云团队
|
||||
* @since 2024-05-25
|
||||
*/
|
||||
@Configuration
|
||||
public class DruidProperties {
|
||||
|
||||
@Value("${spring.datasource.druid.initialSize}")
|
||||
private int initialSize;
|
||||
|
||||
@Value("${spring.datasource.druid.minIdle}")
|
||||
private int minIdle;
|
||||
|
||||
@Value("${spring.datasource.druid.maxActive}")
|
||||
private int maxActive;
|
||||
|
||||
@Value("${spring.datasource.druid.maxWait}")
|
||||
private int maxWait;
|
||||
|
||||
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
|
||||
private int timeBetweenEvictionRunsMillis;
|
||||
|
||||
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
|
||||
private int minEvictableIdleTimeMillis;
|
||||
|
||||
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
|
||||
private int maxEvictableIdleTimeMillis;
|
||||
|
||||
@Value("${spring.datasource.druid.validationQuery}")
|
||||
private String validationQuery;
|
||||
|
||||
@Value("${spring.datasource.druid.testWhileIdle}")
|
||||
private boolean testWhileIdle;
|
||||
|
||||
@Value("${spring.datasource.druid.testOnBorrow}")
|
||||
private boolean testOnBorrow;
|
||||
|
||||
@Value("${spring.datasource.druid.testOnReturn}")
|
||||
private boolean testOnReturn;
|
||||
|
||||
public DruidDataSource dataSource(DruidDataSource datasource) {
|
||||
// 配置初始化大小、最小、最大
|
||||
datasource.setInitialSize(initialSize);
|
||||
datasource.setMaxActive(maxActive);
|
||||
datasource.setMinIdle(minIdle);
|
||||
|
||||
// 配置获取连接等待超时的时间
|
||||
datasource.setMaxWait(maxWait);
|
||||
|
||||
// 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
|
||||
|
||||
// 配置一个连接在池中最小、最大生存的时间,单位是毫秒
|
||||
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
|
||||
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
|
||||
|
||||
// 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用
|
||||
datasource.setValidationQuery(validationQuery);
|
||||
// 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
|
||||
datasource.setTestWhileIdle(testWhileIdle);
|
||||
// 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
|
||||
datasource.setTestOnBorrow(testOnBorrow);
|
||||
// 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
|
||||
datasource.setTestOnReturn(testOnReturn);
|
||||
return datasource;
|
||||
}
|
||||
|
||||
}
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
// +======================================================================
|
||||
// | 小蚂蚁云·企业数字化开发平台 [ 加速数字化转型,构建智能未来 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 核心价值:赋能开发者 · 服务企业 · 共建生态
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2020-2026 小蚂蚁云团队 版权所有
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方平台: https://www.xiaomayicloud.com
|
||||
// +======================================================================
|
||||
// | 【知识产权声明】
|
||||
// | 1. 权属声明:本软件是受《中华人民共和国著作权法》
|
||||
// | 保护的专有财产,完整版权归小蚂蚁云团队所有
|
||||
// | 2. 权利范围:所有知识产权(商标、专利、著作权、
|
||||
// | 商业秘密等)均归小蚂蚁云团队所有,受法律保护
|
||||
// | 3. 授权性质:用户仅可获得有限的、非独占的使用许可
|
||||
// | 4. 禁止行为:未经书面授权,禁止对本软件本身
|
||||
// | 申请任何知识产权
|
||||
// | 5. 权利保留:未在本协议中明示授予的一切权利
|
||||
// | 均由小蚂蚁云团队保留
|
||||
// +======================================================================
|
||||
// | 【授权状态说明】
|
||||
// | 1. 未授权状态:用户未购买有效的商业许可证
|
||||
// | 2. 商业授权:已购买正式商业许可证的用户
|
||||
// +======================================================================
|
||||
// | 【绝对禁止条款】
|
||||
// | 无论授权状态如何,任何用户均严格禁止以下行为:
|
||||
// | 1. 逆向工程:对软件进行反编译、反汇编、技术破解或任何形式的逆向分析
|
||||
// | 2. 版权破坏:移除、修改、遮挡软件中的版权标识、许可控制或技术保护措施
|
||||
// | 3. 非法传播:复制、分发、出租、出借、转售、再许可本软件或其许可权利
|
||||
// | 4. 竞争性使用:利用本软件的代码、架构、设计开发直接竞争的产品或服务
|
||||
// | 5. 侵权使用:将软件用于侵犯第三方知识产权或其他合法权益的用途
|
||||
// +======================================================================
|
||||
// | 【商业授权许可范围】
|
||||
// | 购买有效商业许可证的用户,在遵守上述禁止条款的前提下,获得:
|
||||
// | 1. 使用权利:在许可证约定的项目数量、开发者数量范围内使用本软件
|
||||
// | 2. 技术支持:商业授权用户可以获取本地开发部署的技术支持服务
|
||||
// | 3. 版本更新:在许可证有效期内获取软件更新、安全补丁和功能增强
|
||||
// | 4. 商业部署:将基于本软件开发的应用程序用于商业运营和生产环境
|
||||
// +======================================================================
|
||||
// | 【许可证获取】
|
||||
// | 1. 获取途径:仅通过小蚂蚁云官方渠道购买(官网、授权代理商),购买记录凭证
|
||||
// | 2. 转让限制:许可证与购买主体绑定,未经书面同意不得转让
|
||||
// | 3. 终止情形:许可证到期、违反协议、破产清算等情况下自动终止
|
||||
// +======================================================================
|
||||
// | 【合法使用要求】
|
||||
// | 1. 仅限用于合法合规的软件研发
|
||||
// | 2. 严禁用于任何违法、侵权或恶意行为
|
||||
// | 3. 严禁违反我国法律法规和政策
|
||||
// +======================================================================
|
||||
// | 【免责声明】
|
||||
// | 因使用本框架导致的以下情况,我们概不负责:
|
||||
// | 1. 项目研发中的意外、疏忽、违约等风险
|
||||
// | 2. 侵犯第三方知识产权或其他合法权益
|
||||
// | 3. 任何直接、间接、附带或衍生一切损失
|
||||
// | 4. 用户违规违法行为的一切法律后果
|
||||
// +======================================================================
|
||||
// | 【协议重要提示】
|
||||
// | 1. 本声明是《小蚂蚁云最终用户许可协议》的摘要版本
|
||||
// | 2. 下载、安装或使用本软件即表示您接受全部协议条款
|
||||
// | 3. 我们保留单方面修改本声明的权利,修改后的版本在官网公布即生效
|
||||
// | 4. 用户有义务定期查看最新版本,继续使用视为接受修改
|
||||
// +======================================================================
|
||||
// | 【侵权举报与合规】
|
||||
// | 1. 知识产权侵权举报:请联系官方客服
|
||||
// | 2. 许可证合规咨询:xiaomayicloud@163.com
|
||||
// | 3. 违规使用将终止授权并追究责任
|
||||
// | 4. 无条件配合司法机关调查取证
|
||||
// | 5. 保留一切法律追诉权利
|
||||
// | 6. 对有效举报和合规建议,我们将给予感谢和奖励
|
||||
// +======================================================================
|
||||
|
||||
package com.xiaomayi.datasource.context;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 数据源上下文持有器
|
||||
* 使用ThreadLocal管理当前线程的数据源选择,实现线程安全的数据源切换
|
||||
*
|
||||
* 设计原理:
|
||||
* 1. ThreadLocal为每个线程提供独立的变量副本,避免多线程环境下的数据竞争
|
||||
* 2. 每个线程可以独立设置和获取自己的数据源,互不干扰
|
||||
* 3. 使用后必须清理,防止内存泄漏和线程池复用导致的数据源污染
|
||||
*
|
||||
* 使用场景:
|
||||
* - 在AOP切面中设置数据源
|
||||
* - 在业务方法中手动切换数据源
|
||||
* - 在事务管理中保持数据源一致性
|
||||
*
|
||||
* 线程安全说明:
|
||||
* - 每个线程操作自己的ThreadLocal变量,天然线程安全
|
||||
* - 无需额外的同步机制
|
||||
*/
|
||||
@Slf4j
|
||||
public class DataSourceContextHolder {
|
||||
|
||||
/**
|
||||
* 数据源上下文ThreadLocal
|
||||
* 存储当前线程选择的数据源标识
|
||||
*
|
||||
* ThreadLocal特性:
|
||||
* - 每个线程有独立的副本
|
||||
* - 线程结束时自动清理(但线程池中线程会复用,需要手动清理)
|
||||
* - 支持继承(InheritableThreadLocal),但这里不需要
|
||||
*/
|
||||
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 备用数据源上下文(用于嵌套数据源切换)
|
||||
* 当需要多层数据源切换时,保存上一级的数据源设置
|
||||
*/
|
||||
private static final ThreadLocal<String> BACKUP_CONTEXT_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 私有构造函数,防止实例化
|
||||
* 这是一个工具类,所有方法都是静态的
|
||||
*/
|
||||
private DataSourceContextHolder() {
|
||||
throw new UnsupportedOperationException("DataSourceContextHolder是工具类,不能实例化");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前线程的数据源
|
||||
*
|
||||
* @param dataSource 数据源标识,对应数据源配置中的key
|
||||
*
|
||||
* 使用示例:
|
||||
* {@code
|
||||
* DataSourceContextHolder.setDataSource("master");
|
||||
* try {
|
||||
* // 执行数据库操作
|
||||
* } finally {
|
||||
* DataSourceContextHolder.clearDataSource();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 注意事项:
|
||||
* 1. 必须在finally块中调用clearDataSource()清理,避免内存泄漏
|
||||
* 2. 数据源标识必须与DataSourceConfig中配置的数据源key一致
|
||||
* 3. 如果设置null,将使用默认数据源
|
||||
*/
|
||||
public static void setDataSource(String dataSource) {
|
||||
if (dataSource == null) {
|
||||
log.warn("尝试设置null数据源,这将使用默认数据源");
|
||||
CONTEXT_HOLDER.remove();
|
||||
} else {
|
||||
String previousDataSource = getDataSource();
|
||||
if (previousDataSource != null) {
|
||||
// 如果已经有数据源设置,保存到备用上下文(支持嵌套切换)
|
||||
BACKUP_CONTEXT_HOLDER.set(previousDataSource);
|
||||
log.info("保存备用数据源: {}", previousDataSource);
|
||||
}
|
||||
CONTEXT_HOLDER.set(dataSource);
|
||||
log.info("设置数据源: {}", dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前线程的数据源
|
||||
*
|
||||
* @return String 当前数据源标识,如果未设置返回null
|
||||
*
|
||||
* 返回值说明:
|
||||
* - null: 表示使用默认数据源(主数据源)
|
||||
* - 非null: 表示使用指定的数据源
|
||||
*
|
||||
* 使用场景:
|
||||
* - DynamicDataSource.determineCurrentLookupKey()中调用
|
||||
* - 调试和日志记录
|
||||
* - 手动数据源管理
|
||||
*/
|
||||
public static String getDataSource() {
|
||||
return CONTEXT_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前线程的数据源设置
|
||||
* 恢复到默认数据源(主数据源)
|
||||
*
|
||||
* 重要:必须在try-finally块中调用,确保资源清理
|
||||
*
|
||||
* 清理逻辑:
|
||||
* 1. 如果存在备用数据源,恢复到备用数据源(嵌套切换场景)
|
||||
* 2. 否则完全清除数据源设置,使用默认数据源
|
||||
*
|
||||
* 内存泄漏风险:
|
||||
* 如果不调用此方法,在线程池场景下会导致:
|
||||
* - 数据源污染:线程复用时使用错误的数据源
|
||||
* - 内存泄漏:ThreadLocal中的值不会被GC回收
|
||||
*/
|
||||
public static void clearDataSource() {
|
||||
String currentDataSource = getDataSource();
|
||||
|
||||
if (BACKUP_CONTEXT_HOLDER.get() != null) {
|
||||
// 存在备用数据源,恢复到备用数据源
|
||||
String backupDataSource = BACKUP_CONTEXT_HOLDER.get();
|
||||
CONTEXT_HOLDER.set(backupDataSource);
|
||||
BACKUP_CONTEXT_HOLDER.remove();
|
||||
log.info("恢复到备用数据源: {} (从 {} 恢复)", backupDataSource, currentDataSource);
|
||||
} else {
|
||||
// 没有备用数据源,完全清除
|
||||
CONTEXT_HOLDER.remove();
|
||||
log.info("清除数据源设置: {}", currentDataSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制清除所有数据源设置
|
||||
* 同时清除主上下文和备用上下文
|
||||
*
|
||||
* 使用场景:
|
||||
* - 请求处理完成后的最终清理
|
||||
* - 异常情况下的资源回收
|
||||
* - 测试环境下的状态重置
|
||||
*/
|
||||
public static void clearAll() {
|
||||
CONTEXT_HOLDER.remove();
|
||||
BACKUP_CONTEXT_HOLDER.remove();
|
||||
log.info("强制清除所有数据源设置");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前线程是否设置了数据源
|
||||
*
|
||||
* @return boolean true-已设置数据源,false-使用默认数据源
|
||||
*/
|
||||
public static boolean hasDataSource() {
|
||||
return CONTEXT_HOLDER.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前数据源状态的详细描述
|
||||
* 用于调试和监控
|
||||
*
|
||||
* @return String 数据源状态描述
|
||||
*/
|
||||
public static String getDataSourceStatus() {
|
||||
String current = getDataSource();
|
||||
String backup = BACKUP_CONTEXT_HOLDER.get();
|
||||
|
||||
StringBuilder status = new StringBuilder();
|
||||
status.append("当前数据源: ").append(current != null ? current : "默认(主库)");
|
||||
if (backup != null) {
|
||||
status.append(", 备用数据源: ").append(backup);
|
||||
}
|
||||
|
||||
return status.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前线程的ID和数据源信息
|
||||
* 用于多线程调试
|
||||
*
|
||||
* @return String 线程和数据源信息
|
||||
*/
|
||||
public static String getThreadDataSourceInfo() {
|
||||
return String.format("Thread[%d] - %s",
|
||||
Thread.currentThread().getId(),
|
||||
getDataSourceStatus());
|
||||
}
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
// +======================================================================
|
||||
// | 小蚂蚁云·企业数字化开发平台 [ 加速数字化转型,构建智能未来 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 核心价值:赋能开发者 · 服务企业 · 共建生态
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2020-2026 小蚂蚁云团队 版权所有
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方平台: https://www.xiaomayicloud.com
|
||||
// +======================================================================
|
||||
// | 【知识产权声明】
|
||||
// | 1. 权属声明:本软件是受《中华人民共和国著作权法》
|
||||
// | 保护的专有财产,完整版权归小蚂蚁云团队所有
|
||||
// | 2. 权利范围:所有知识产权(商标、专利、著作权、
|
||||
// | 商业秘密等)均归小蚂蚁云团队所有,受法律保护
|
||||
// | 3. 授权性质:用户仅可获得有限的、非独占的使用许可
|
||||
// | 4. 禁止行为:未经书面授权,禁止对本软件本身
|
||||
// | 申请任何知识产权
|
||||
// | 5. 权利保留:未在本协议中明示授予的一切权利
|
||||
// | 均由小蚂蚁云团队保留
|
||||
// +======================================================================
|
||||
// | 【授权状态说明】
|
||||
// | 1. 未授权状态:用户未购买有效的商业许可证
|
||||
// | 2. 商业授权:已购买正式商业许可证的用户
|
||||
// +======================================================================
|
||||
// | 【绝对禁止条款】
|
||||
// | 无论授权状态如何,任何用户均严格禁止以下行为:
|
||||
// | 1. 逆向工程:对软件进行反编译、反汇编、技术破解或任何形式的逆向分析
|
||||
// | 2. 版权破坏:移除、修改、遮挡软件中的版权标识、许可控制或技术保护措施
|
||||
// | 3. 非法传播:复制、分发、出租、出借、转售、再许可本软件或其许可权利
|
||||
// | 4. 竞争性使用:利用本软件的代码、架构、设计开发直接竞争的产品或服务
|
||||
// | 5. 侵权使用:将软件用于侵犯第三方知识产权或其他合法权益的用途
|
||||
// +======================================================================
|
||||
// | 【商业授权许可范围】
|
||||
// | 购买有效商业许可证的用户,在遵守上述禁止条款的前提下,获得:
|
||||
// | 1. 使用权利:在许可证约定的项目数量、开发者数量范围内使用本软件
|
||||
// | 2. 技术支持:商业授权用户可以获取本地开发部署的技术支持服务
|
||||
// | 3. 版本更新:在许可证有效期内获取软件更新、安全补丁和功能增强
|
||||
// | 4. 商业部署:将基于本软件开发的应用程序用于商业运营和生产环境
|
||||
// +======================================================================
|
||||
// | 【许可证获取】
|
||||
// | 1. 获取途径:仅通过小蚂蚁云官方渠道购买(官网、授权代理商),购买记录凭证
|
||||
// | 2. 转让限制:许可证与购买主体绑定,未经书面同意不得转让
|
||||
// | 3. 终止情形:许可证到期、违反协议、破产清算等情况下自动终止
|
||||
// +======================================================================
|
||||
// | 【合法使用要求】
|
||||
// | 1. 仅限用于合法合规的软件研发
|
||||
// | 2. 严禁用于任何违法、侵权或恶意行为
|
||||
// | 3. 严禁违反我国法律法规和政策
|
||||
// +======================================================================
|
||||
// | 【免责声明】
|
||||
// | 因使用本框架导致的以下情况,我们概不负责:
|
||||
// | 1. 项目研发中的意外、疏忽、违约等风险
|
||||
// | 2. 侵犯第三方知识产权或其他合法权益
|
||||
// | 3. 任何直接、间接、附带或衍生一切损失
|
||||
// | 4. 用户违规违法行为的一切法律后果
|
||||
// +======================================================================
|
||||
// | 【协议重要提示】
|
||||
// | 1. 本声明是《小蚂蚁云最终用户许可协议》的摘要版本
|
||||
// | 2. 下载、安装或使用本软件即表示您接受全部协议条款
|
||||
// | 3. 我们保留单方面修改本声明的权利,修改后的版本在官网公布即生效
|
||||
// | 4. 用户有义务定期查看最新版本,继续使用视为接受修改
|
||||
// +======================================================================
|
||||
// | 【侵权举报与合规】
|
||||
// | 1. 知识产权侵权举报:请联系官方客服
|
||||
// | 2. 许可证合规咨询:xiaomayicloud@163.com
|
||||
// | 3. 违规使用将终止授权并追究责任
|
||||
// | 4. 无条件配合司法机关调查取证
|
||||
// | 5. 保留一切法律追诉权利
|
||||
// | 6. 对有效举报和合规建议,我们将给予感谢和奖励
|
||||
// +======================================================================
|
||||
|
||||
package com.xiaomayi.datasource.context;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态数据源路由类
|
||||
* 继承Spring的AbstractRoutingDataSource,实现运行时数据源动态切换
|
||||
* <p>
|
||||
* 核心功能:
|
||||
* 1. 管理多个数据源的映射关系
|
||||
* 2. 根据当前线程上下文路由到正确的数据源
|
||||
* 3. 提供数据源查找和切换的基础设施
|
||||
* <p>
|
||||
* 工作流程:
|
||||
* 1. 应用启动时,加载所有配置的数据源到targetDataSources
|
||||
* 2. 每次数据库操作时,调用determineCurrentLookupKey()获取数据源key
|
||||
* 3. 根据key从targetDataSources中获取对应的DataSource实例
|
||||
* 4. 使用获取的DataSource执行数据库操作
|
||||
* <p>
|
||||
* 设计模式:策略模式 + 模板方法模式
|
||||
*/
|
||||
@Slf4j
|
||||
public class DynamicDataSource extends AbstractRoutingDataSource {
|
||||
|
||||
/**
|
||||
* 默认数据源(主数据源)
|
||||
* 当没有明确指定数据源时使用
|
||||
* -- GETTER --
|
||||
* 获取默认数据源
|
||||
*
|
||||
* @return DataSource 默认数据源实例
|
||||
*/
|
||||
@Getter
|
||||
private final DataSource defaultDataSource;
|
||||
|
||||
/**
|
||||
* 目标数据源映射
|
||||
* key: 数据源标识(如:MASTER, SLAVE1, SLAVE2等)
|
||||
* value: 对应的DataSource实例
|
||||
*/
|
||||
private final Map<Object, Object> targetDataSources;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param defaultTargetDataSource 默认目标数据源(主数据源)
|
||||
* @param targetDataSources 所有可用的数据源映射
|
||||
* <p>
|
||||
* 参数说明:
|
||||
* - defaultTargetDataSource: 当determineCurrentLookupKey()返回null时使用
|
||||
* - targetDataSources: 所有已配置的数据源,包括主数据源和从数据源
|
||||
* <p>
|
||||
* 注意:defaultTargetDataSource通常也包含在targetDataSources中
|
||||
*/
|
||||
public DynamicDataSource(DataSource defaultTargetDataSource,
|
||||
Map<Object, Object> targetDataSources) {
|
||||
// 保存参数用于日志和监控
|
||||
this.defaultDataSource = defaultTargetDataSource;
|
||||
this.targetDataSources = targetDataSources;
|
||||
|
||||
// 调用父类设置方法
|
||||
// 设置默认数据源(当无法确定数据源时使用)
|
||||
super.setDefaultTargetDataSource(defaultTargetDataSource);
|
||||
// 设置目标数据源映射(所有可用的数据源)
|
||||
super.setTargetDataSources(targetDataSources);
|
||||
|
||||
// 初始化数据源,这个方法会:
|
||||
// 1. 验证数据源配置
|
||||
// 2. 解析resolvedDataSources和resolvedDefaultDataSource
|
||||
// 3. 准备数据源路由
|
||||
super.afterPropertiesSet();
|
||||
|
||||
log.info("动态数据源初始化完成,默认数据源: {}, 可用数据源数量: {}",
|
||||
getDataSourceName(defaultTargetDataSource),
|
||||
targetDataSources.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 决定当前查找键(数据源标识)
|
||||
* 这是AbstractRoutingDataSource的核心抽象方法,每次数据库操作时都会被调用
|
||||
*
|
||||
* @return Object 数据源标识,对应targetDataSources中的key
|
||||
* <p>
|
||||
* 执行时机:
|
||||
* - 每次数据库操作前(getConnection()时)
|
||||
* - 事务开始时
|
||||
* - 连接获取时
|
||||
* <p>
|
||||
* 返回值处理:
|
||||
* - 非null: 从targetDataSources中查找对应的DataSource
|
||||
* - null: 使用defaultTargetDataSource
|
||||
* <p>
|
||||
* 异常处理:
|
||||
* - 如果返回的key在targetDataSources中不存在,抛出IllegalArgumentException
|
||||
*/
|
||||
@Override
|
||||
protected Object determineCurrentLookupKey() {
|
||||
// 从DataSourceContextHolder获取当前线程的数据源设置
|
||||
String dataSourceKey = DataSourceContextHolder.getDataSource();
|
||||
|
||||
if (dataSourceKey != null) {
|
||||
// 验证数据源是否存在
|
||||
if (!targetDataSources.containsKey(dataSourceKey)) {
|
||||
log.info("数据源 '{}' 不存在,使用默认数据源。可用数据源: {}",
|
||||
dataSourceKey, targetDataSources.keySet());
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("路由到数据源: {}", dataSourceKey);
|
||||
return dataSourceKey;
|
||||
}
|
||||
|
||||
// 没有设置数据源,使用默认数据源(主库)
|
||||
log.info("使用默认数据源");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有目标数据源映射
|
||||
*
|
||||
* @return Map<Object, Object> 数据源映射的不可修改视图
|
||||
*/
|
||||
public Map<Object, Object> getTargetDataSources() {
|
||||
return Map.copyOf(targetDataSources);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数据源标识获取数据源实例
|
||||
*
|
||||
* @param dataSourceKey 数据源标识
|
||||
* @return DataSource 数据源实例,如果不存在返回null
|
||||
*/
|
||||
public DataSource getDataSourceByKey(String dataSourceKey) {
|
||||
Object dataSource = targetDataSources.get(dataSourceKey);
|
||||
return dataSource instanceof DataSource ? (DataSource) dataSource : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据源是否存在
|
||||
*
|
||||
* @param dataSourceKey 数据源标识
|
||||
* @return boolean true-存在,false-不存在
|
||||
*/
|
||||
public boolean containsDataSource(String dataSourceKey) {
|
||||
return targetDataSources.containsKey(dataSourceKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用的数据源标识
|
||||
*
|
||||
* @return Set<Object> 数据源标识集合
|
||||
*/
|
||||
public java.util.Set<Object> getAvailableDataSourceKeys() {
|
||||
return targetDataSources.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源名称(用于日志输出)
|
||||
*
|
||||
* @param dataSource 数据源实例
|
||||
* @return String 数据源描述
|
||||
*/
|
||||
private String getDataSourceName(DataSource dataSource) {
|
||||
if (dataSource == null) {
|
||||
return "null";
|
||||
}
|
||||
// 尝试获取有意义的名称
|
||||
if (dataSource instanceof org.springframework.jdbc.datasource.SimpleDriverDataSource) {
|
||||
return "SimpleDriverDataSource";
|
||||
} else if (dataSource instanceof com.alibaba.druid.pool.DruidDataSource) {
|
||||
return "DruidDataSource";
|
||||
} else {
|
||||
return dataSource.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据源状态信息
|
||||
* 用于监控和调试
|
||||
*
|
||||
* @return String 数据源状态描述
|
||||
*/
|
||||
public String getStatusInfo() {
|
||||
return String.format("DynamicDataSource[默认数据源: %s, 可用数据源: %s, 当前线程数据源: %s]",
|
||||
getDataSourceName(defaultDataSource),
|
||||
targetDataSources.keySet(),
|
||||
DataSourceContextHolder.getDataSourceStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁方法
|
||||
* 清理资源,防止内存泄漏
|
||||
*/
|
||||
public void destroy() {
|
||||
log.info("开始销毁动态数据源...");
|
||||
DataSourceContextHolder.clearAll();
|
||||
log.info("动态数据源销毁完成");
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package com.xiaomayi.datasource.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 数据源类型枚举
|
||||
* <p>
|
||||
* 功能:定义系统中所有可用的数据源类型,提供统一的数据源标识管理
|
||||
* <p>
|
||||
* 设计优势:
|
||||
* - 类型安全:使用枚举避免字符串硬编码
|
||||
* - 易于维护:所有数据源类型集中管理
|
||||
* - 可扩展性:添加新数据源只需新增枚举值
|
||||
* - 自文档化:枚举名称和描述说明数据源用途
|
||||
* <p>
|
||||
* 使用场景:
|
||||
* - 在@DataSource注解中指定数据源类型
|
||||
* - 在数据源映射中作为key
|
||||
* - 在日志和监控中标识数据源
|
||||
*/
|
||||
@Getter
|
||||
public enum DataSourceType {
|
||||
|
||||
/**
|
||||
* 主库 - 用于写操作和重要读操作
|
||||
* <p>
|
||||
* 典型用途:
|
||||
* - 数据插入、更新、删除操作
|
||||
* - 事务性操作
|
||||
* - 关键业务查询
|
||||
*/
|
||||
MASTER("master", "主库"),
|
||||
|
||||
/**
|
||||
* 从库1 - 用于读操作,分担主库压力
|
||||
* <p>
|
||||
* 典型用途:
|
||||
* - 报表查询
|
||||
* - 数据分析
|
||||
* - 非关键业务查询
|
||||
*/
|
||||
SLAVE1("slave1", "从库1"),
|
||||
|
||||
/**
|
||||
* 从库2 - 用于读操作,提供数据冗余和负载均衡
|
||||
* <p>
|
||||
* 典型用途:
|
||||
* - 备份查询
|
||||
* - 只读业务
|
||||
* - 大数据分析
|
||||
*/
|
||||
SLAVE2("slave2", "从库2"),
|
||||
|
||||
/**
|
||||
* PostgreSQL数据库 - 用于特定的PostgreSQL数据操作
|
||||
* <p>
|
||||
* 典型用途:
|
||||
* - GIS地理信息数据
|
||||
* - JSON文档存储
|
||||
* - 复杂查询场景
|
||||
*/
|
||||
POSTGRESQL("postgres", "PostgreSQL数据库"),
|
||||
|
||||
/**
|
||||
* SQL Server数据库 - 用于特定的SQL Server数据操作
|
||||
* <p>
|
||||
* 典型用途:
|
||||
* - Windows环境集成
|
||||
* - .NET系统集成
|
||||
* - 企业级应用
|
||||
*/
|
||||
SQLSERVER("sqlserver", "SQL Server数据库"),
|
||||
|
||||
/**
|
||||
* DEMO数据库 - 用于特定的演示数据操作
|
||||
*/
|
||||
DEMO("demo", "演示数据库");
|
||||
|
||||
/**
|
||||
* 数据源标识
|
||||
* 对应配置文件中数据源的key,用于从配置中读取对应数据源的属性
|
||||
* -- GETTER --
|
||||
* 获取数据源标识
|
||||
*
|
||||
* @return String 数据源标识,用于配置映射和日志记录
|
||||
*/
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 数据源描述
|
||||
* 用于日志输出、监控显示和文档说明
|
||||
* -- GETTER --
|
||||
* 获取数据源描述
|
||||
*
|
||||
* @return String 数据源描述,用于显示和文档
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 枚举构造函数
|
||||
* <p>
|
||||
* 设计为私有构造函数,确保枚举实例的不可变性和线程安全性
|
||||
*
|
||||
* @param value 数据源标识,必须与配置文件中的key一致
|
||||
* @param description 数据源描述,用于说明数据源的用途
|
||||
*/
|
||||
DataSourceType(String value, String description) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过标识获取枚举实例
|
||||
* <p>
|
||||
* 功能:将字符串标识转换为对应的枚举实例
|
||||
* <p>
|
||||
* 使用场景:
|
||||
* - 从配置文件中读取数据源类型时
|
||||
* - 动态确定数据源类型时
|
||||
* - 反序列化数据源类型时
|
||||
*
|
||||
* @param value 数据源标识字符串
|
||||
* @return DataSourceType 对应的枚举实例
|
||||
* @throws IllegalArgumentException 如果标识不存在于枚举值中
|
||||
*/
|
||||
public static DataSourceType fromValue(String value) {
|
||||
// 遍历所有枚举值,查找匹配的标识
|
||||
for (DataSourceType type : DataSourceType.values()) {
|
||||
if (type.getValue().equals(value)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
// 如果没有找到匹配的枚举值,抛出明确的异常信息
|
||||
throw new IllegalArgumentException("未知的数据源类型: " + value + ",可用类型: " + getAvailableTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用的数据源类型
|
||||
* <p>
|
||||
* 功能:提供所有数据源类型的字符串表示,用于错误信息和日志
|
||||
*
|
||||
* @return String 所有数据源类型的逗号分隔列表
|
||||
*/
|
||||
private static String getAvailableTypes() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (DataSourceType type : DataSourceType.values()) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(type.getValue());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写toString方法,提供更有意义的输出
|
||||
*
|
||||
* @return String 枚举的字符串表示,包含标识和描述
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", value, description);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user