签入版本

This commit is contained in:
2026-09-28 18:51:35 +08:00
commit ab0f064ea6
2692 changed files with 354751 additions and 0 deletions
@@ -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. 微服务环境下考虑分布式事务的一致性
*/
```
@@ -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;
}
@@ -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;
}
}
}
@@ -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);
// }
// }
// }
// }
}
}
@@ -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;
}
}
@@ -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());
}
}
@@ -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("动态数据源销毁完成");
}
}
@@ -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);
}
}