Forráskód Böngészése

adb接入,多数据源切换

wangyunpeng 1 napja
szülő
commit
420b107b8a

+ 197 - 0
ADB_MULTI_DATASOURCE_README.md

@@ -0,0 +1,197 @@
+# Growth Manager 多数据源配置说明
+
+## 概述
+
+本项目已集成阿里云 AnalyticDB (ADB) 并实现多数据源数据库访问功能。支持在同一个应用中同时访问 MySQL 主数据源和 ADB 数据源。
+
+## 功能特性
+
+- ✅ 支持多数据源动态切换
+- ✅ 基于注解的声明式数据源切换
+- ✅ 支持手动编程式数据源切换
+- ✅ 线程安全的数据源上下文管理
+- ✅ 完整的 Spring 事务支持
+
+## 配置说明
+
+### 1. 数据源配置
+
+在 `application.properties` 和相关环境配置文件中,需要配置两个数据源:
+
+#### 主数据源(MySQL)
+```properties
+# 主数据源配置
+spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
+spring.datasource.primary.url=jdbc:mysql://your-mysql-host:3306/your_database
+spring.datasource.primary.username=your_username
+spring.datasource.primary.password=your_password
+spring.datasource.primary.initialSize=5
+spring.datasource.primary.maxActive=20
+```
+
+#### ADB 数据源
+```properties
+# ADB 数据源配置
+spring.datasource.adb.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.adb.url=jdbc:mysql://your-adb-instance-address:3306/your_database?useSSL=true&serverTimezone=Asia/Shanghai
+spring.datasource.adb.username=your_adb_username
+spring.datasource.adb.password=your_adb_password
+spring.datasource.adb.initialSize=5
+spring.datasource.adb.maxActive=20
+```
+
+### 2. ADB 连接信息获取
+
+1. 登录阿里云控制台
+2. 进入 AnalyticDB 控制台
+3. 选择对应的 ADB 实例
+4. 在实例详情中获取:
+   - 连接地址(VPC 地址或公网地址)
+   - 端口(通常为 3306)
+   - 数据库名称
+   - 用户名和密码
+5. 确保应用服务器 IP 已添加到 ADB 白名单
+
+## 使用方式
+
+### 方式一:使用 @DataSource 注解(推荐)
+
+在 Service 方法或类上使用 `@DataSource` 注解指定数据源:
+
+```java
+@Service
+public class YourService {
+    
+    @Autowired
+    private YourMapper yourMapper;
+    
+    // 方法级别注解
+    @DataSource("adb")
+    public List<YourEntity> queryFromAdb() {
+        return yourMapper.selectAll();
+    }
+    
+    // 默认使用主数据源
+    public List<YourEntity> queryFromPrimary() {
+        return yourMapper.selectAll();
+    }
+}
+```
+
+### 方式二:类级别注解
+
+在整个类上使用 `@DataSource` 注解,该类所有方法都使用指定数据源:
+
+```java
+@Service
+@DataSource("adb")
+public class AdbService {
+    // 所有方法都使用 ADB 数据源
+    public List<YourEntity> query() {
+        return yourMapper.selectAll();
+    }
+}
+```
+
+### 方式三:手动切换数据源
+
+在需要动态切换数据源的场景下,可以手动设置:
+
+```java
+@Service
+public class YourService {
+    
+    public void queryWithManualSwitch() {
+        try {
+            // 切换到 ADB 数据源
+            DynamicDataSourceContextHolder.setDataSource("adb");
+            // 执行数据库操作
+            List<YourEntity> result = yourMapper.selectAll();
+            return result;
+        } finally {
+            // 务必清除数据源标识
+            DynamicDataSourceContextHolder.clearDataSource();
+        }
+    }
+}
+```
+
+## 数据源标识
+
+系统预定义了以下数据源标识:
+
+- `primary` - 主数据源(MySQL),默认数据源
+- `adb` - ADB 数据源
+
+## 示例代码
+
+项目已包含完整的示例代码,位于:
+
+- `AdbExampleMapper` - ADB Mapper 示例
+- `AdbExampleService` - ADB Service 使用示例
+- `AdbExampleController` - ADB Controller 测试接口
+
+### 测试接口
+
+启动应用后,可以通过以下接口测试多数据源功能:
+
+```
+GET /adb/example/query?tableName=test_table&limit=10
+GET /adb/example/count?tableName=test_table
+```
+
+## 注意事项
+
+1. **数据源切换是线程安全的**:使用 ThreadLocal 实现,每个线程独立的数据源上下文
+2. **务必在 finally 中清除数据源**:手动切换数据源时,必须在 finally 块中调用 `clearDataSource()`
+3. **事务管理**:多数据源场景下,每个数据源有独立的事务管理器
+4. **ADB 连接配置**:ADB 通常需要 SSL 连接,URL 中建议添加 `useSSL=true`
+5. **驱动兼容性**:ADB for MySQL 兼容 MySQL 协议,可以使用标准 MySQL JDBC 驱动
+6. **白名单配置**:确保应用服务器 IP 已添加到 ADB 实例白名单
+
+## 核心组件说明
+
+### DynamicDataSource
+动态数据源路由类,继承自 `AbstractRoutingDataSource`,根据上下文决定使用哪个数据源。
+
+### DynamicDataSourceContextHolder
+数据源上下文持有者,使用 ThreadLocal 保存当前线程的数据源标识。
+
+### DataSourceAspect
+AOP 切面,自动拦截 `@DataSource` 注解并切换数据源。
+
+### DataSourceConfig
+数据源配置类,定义主数据源、ADB 数据源和动态数据源 Bean。
+
+## 扩展更多数据源
+
+如需添加更多数据源,按以下步骤操作:
+
+1. 在配置文件中添加新数据源的配置
+2. 在 `DataSourceConfig` 中创建新的 DataSource Bean
+3. 在 `DynamicDataSource` 的 targetDataSources 中添加新数据源
+4. 在 `DynamicDataSourceContextHolder` 中添加新的数据源常量
+5. 使用 `@DataSource("新数据源名称")` 注解切换
+
+## 故障排查
+
+### 连接失败
+- 检查 ADB 实例是否正常运行
+- 确认 IP 白名单配置正确
+- 验证用户名密码是否正确
+- 检查网络连通性
+
+### 数据源切换不生效
+- 确认 `@DataSource` 注解使用正确
+- 检查 AOP 是否生效(确认 spring-boot-starter-aop 依赖存在)
+- 查看日志确认数据源切换信息
+
+### 事务问题
+- 多数据源场景下,跨数据源操作不支持分布式事务
+- 如需分布式事务,考虑使用 Seata 等分布式事务框架
+
+## 相关文档
+
+- [阿里云 AnalyticDB 官方文档](https://help.aliyun.com/product/26161.html)
+- [Spring Boot 多数据源配置](https://spring.io/guides)
+

+ 59 - 3
api-module/src/main/java/com/tzld/piaoquan/api/config/DataSourceConfig.java

@@ -1,6 +1,8 @@
 package com.tzld.piaoquan.api.config;
 
 import com.alibaba.druid.pool.DruidDataSource;
+import com.tzld.piaoquan.growth.common.config.datasource.DynamicDataSource;
+import com.tzld.piaoquan.growth.common.config.datasource.DynamicDataSourceContextHolder;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.mybatis.spring.SqlSessionFactoryBean;
 import org.mybatis.spring.annotation.MapperScan;
@@ -14,6 +16,8 @@ import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 import org.springframework.transaction.PlatformTransactionManager;
 
 import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.Map;
 
 
 @Configuration
@@ -22,12 +26,42 @@ import javax.sql.DataSource;
 public class DataSourceConfig {
     static final String MAPPER_LOCATION_MASTER = "classpath*:mapper/**/*.xml";
 
-    @Bean(name = "dataSource")
-    @ConfigurationProperties("spring.datasource")
-    public DataSource getDataSource(){
+    /**
+     * 主数据源(MySQL)
+     */
+    @Bean(name = "primaryDataSource")
+    @ConfigurationProperties("spring.datasource.primary")
+    public DataSource primaryDataSource(){
         return new DruidDataSource();
     }
 
+    /**
+     * ADB 数据源
+     */
+    @Bean(name = "adbDataSource")
+    @ConfigurationProperties("spring.datasource.adb")
+    public DataSource adbDataSource(){
+        return new DruidDataSource();
+    }
+
+    /**
+     * 动态数据源
+     */
+    @Primary
+    @Bean(name = "dataSource")
+    public DataSource dynamicDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
+                                         @Qualifier("adbDataSource") DataSource adbDataSource) {
+        DynamicDataSource dynamicDataSource = new DynamicDataSource();
+        // 设置默认数据源
+        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
+        // 设置多数据源
+        Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DynamicDataSourceContextHolder.DEFAULT_DATASOURCE, primaryDataSource);
+        targetDataSources.put(DynamicDataSourceContextHolder.ADB_DATASOURCE, adbDataSource);
+        dynamicDataSource.setTargetDataSources(targetDataSources);
+        return dynamicDataSource;
+    }
+
     @Primary
     @Bean(name = "growthTransactionManager")
     public PlatformTransactionManager growthTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
@@ -45,4 +79,26 @@ public class DataSourceConfig {
         return sessionFactoryBean.getObject();
     }
 
+    /**
+     * ADB 专用的 SqlSessionFactory
+     * 用于需要明确使用 ADB 数据源的 Mapper
+     */
+    @Bean(name = "adbSqlSessionFactory")
+    public SqlSessionFactory adbSqlSessionFactory(@Qualifier("adbDataSource") DataSource adbDataSource) throws Exception {
+        final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
+        sessionFactoryBean.setDataSource(adbDataSource);
+        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(DataSourceConfig.MAPPER_LOCATION_MASTER));
+        sessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
+        sessionFactoryBean.getObject().getConfiguration().setUseGeneratedKeys(true);
+        return sessionFactoryBean.getObject();
+    }
+
+    /**
+     * ADB 专用的事务管理器
+     */
+    @Bean(name = "adbTransactionManager")
+    public PlatformTransactionManager adbTransactionManager(@Qualifier("adbDataSource") DataSource adbDataSource) {
+        return new DataSourceTransactionManager(adbDataSource);
+    }
+
 }

+ 47 - 0
api-module/src/main/java/com/tzld/piaoquan/api/controller/adb/AdbExampleController.java

@@ -0,0 +1,47 @@
+package com.tzld.piaoquan.api.controller.adb;
+
+import com.tzld.piaoquan.api.service.adb.AdbExampleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ADB 数据源使用示例 Controller
+ * 用于测试多数据源功能
+ */
+@RestController
+@RequestMapping("/adb/example")
+public class AdbExampleController {
+    
+    @Autowired
+    private AdbExampleService adbExampleService;
+    
+    /**
+     * 测试使用注解方式访问 ADB
+     * @param tableName 表名
+     * @param limit 限制条数
+     * @return 查询结果
+     */
+    @GetMapping("/query")
+    public List<Map<String, Object>> queryFromAdb(
+            @RequestParam(defaultValue = "test_table") String tableName,
+            @RequestParam(defaultValue = "10") Integer limit) {
+        return adbExampleService.queryFromAdbWithAnnotation(tableName, limit);
+    }
+    
+    /**
+     * 测试手动切换数据源访问 ADB
+     * @param tableName 表名
+     * @return 记录数
+     */
+    @GetMapping("/count")
+    public Long countFromAdb(@RequestParam(defaultValue = "test_table") String tableName) {
+        return adbExampleService.countFromAdbManually(tableName);
+    }
+}
+

+ 36 - 0
api-module/src/main/java/com/tzld/piaoquan/api/dao/mapper/adb/AdbExampleMapper.java

@@ -0,0 +1,36 @@
+package com.tzld.piaoquan.api.dao.mapper.adb;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ADB 数据源示例 Mapper
+ * 使用 @DataSource("adb") 注解指定使用 ADB 数据源
+ */
+@Mapper
+@Repository
+public interface AdbExampleMapper {
+    
+    /**
+     * 查询示例 - 从 ADB 查询数据
+     * @param tableName 表名
+     * @param limit 限制条数
+     * @return 查询结果
+     */
+    @Select("SELECT * FROM ${tableName} LIMIT #{limit}")
+    List<Map<String, Object>> selectFromAdb(@Param("tableName") String tableName, @Param("limit") Integer limit);
+    
+    /**
+     * 统计查询示例
+     * @param tableName 表名
+     * @return 记录数
+     */
+    @Select("SELECT COUNT(*) as count FROM ${tableName}")
+    Long countFromAdb(@Param("tableName") String tableName);
+}
+

+ 62 - 0
api-module/src/main/java/com/tzld/piaoquan/api/service/adb/AdbExampleService.java

@@ -0,0 +1,62 @@
+package com.tzld.piaoquan.api.service.adb;
+
+import com.tzld.piaoquan.api.dao.mapper.adb.AdbExampleMapper;
+import com.tzld.piaoquan.growth.common.config.annotation.DataSource;
+import com.tzld.piaoquan.growth.common.config.datasource.DynamicDataSourceContextHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ADB 数据源使用示例 Service
+ * 演示如何使用多数据源访问 ADB
+ */
+@Service
+public class AdbExampleService {
+    
+    private static final Logger logger = LoggerFactory.getLogger(AdbExampleService.class);
+    
+    @Autowired
+    private AdbExampleMapper adbExampleMapper;
+    
+    /**
+     * 方式一:使用 @DataSource 注解自动切换数据源
+     * 推荐使用这种方式,代码简洁
+     */
+    @DataSource("adb")
+    public List<Map<String, Object>> queryFromAdbWithAnnotation(String tableName, Integer limit) {
+        logger.info("使用 @DataSource 注解访问 ADB 数据源");
+        return adbExampleMapper.selectFromAdb(tableName, limit);
+    }
+    
+    /**
+     * 方式二:手动切换数据源
+     * 适用于需要在同一个方法中切换多个数据源的场景
+     */
+    public Long countFromAdbManually(String tableName) {
+        try {
+            // 手动设置数据源
+            DynamicDataSourceContextHolder.setDataSource(DynamicDataSourceContextHolder.ADB_DATASOURCE);
+            logger.info("手动切换数据源到 ADB");
+            return adbExampleMapper.countFromAdb(tableName);
+        } finally {
+            // 务必在 finally 中清除数据源标识
+            DynamicDataSourceContextHolder.clearDataSource();
+        }
+    }
+    
+    /**
+     * 方式三:在类级别使用 @DataSource 注解
+     * 整个类的所有方法都使用指定的数据源
+     */
+    @DataSource("adb")
+    public List<Map<String, Object>> queryWithClassLevelAnnotation(String tableName, Integer limit) {
+        logger.info("类级别注解访问 ADB 数据源");
+        return adbExampleMapper.selectFromAdb(tableName, limit);
+    }
+}
+

+ 11 - 3
api-module/src/main/resources/application-dev.properties

@@ -1,8 +1,16 @@
 server.port=8080
 
-spring.datasource.username=crawler
-spring.datasource.password=crawler123456@
-spring.datasource.url=jdbc:mysql://rm-bp17q95335a99272b.mysql.rds.aliyuncs.com:3306/growth-test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
+# 主数据源(MySQL)配置
+spring.datasource.primary.username=crawler
+spring.datasource.primary.password=crawler123456@
+spring.datasource.primary.url=jdbc:mysql://rm-bp17q95335a99272b.mysql.rds.aliyuncs.com:3306/growth-test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
+
+# ADB 数据源配置(请根据实际 ADB 实例信息修改)
+# 获取 ADB 连接信息:登录阿里云控制台 -> AnalyticDB -> 选择实例 -> 查看连接信息
+spring.datasource.adb.username=your_adb_username
+spring.datasource.adb.password=your_adb_password
+# ADB 连接地址格式:jdbc:mysql://adb-xxx-xxx.adb.rds.aliyuncs.com:3306/database_name
+spring.datasource.adb.url=jdbc:mysql://your-adb-instance-address:3306/your_database?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai
 
 spring.redis.host=r-bp1ikkpsbco7c26v23pd.redis.rds.aliyuncs.com
 spring.redis.port=6379

+ 11 - 3
api-module/src/main/resources/application-prod.properties

@@ -1,8 +1,16 @@
 server.port=8080
 
-spring.datasource.username=crawler
-spring.datasource.password=crawler123456@
-spring.datasource.url=jdbc:mysql://rm-bp17q95335a99272b.mysql.rds.aliyuncs.com:3306/growth?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
+# 主数据源(MySQL)配置
+spring.datasource.primary.username=crawler
+spring.datasource.primary.password=crawler123456@
+spring.datasource.primary.url=jdbc:mysql://rm-bp17q95335a99272b.mysql.rds.aliyuncs.com:3306/growth?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
+
+# ADB 数据源配置(请根据实际 ADB 实例信息修改)
+# 获取 ADB 连接信息:登录阿里云控制台 -> AnalyticDB -> 选择实例 -> 查看连接信息
+spring.datasource.adb.username=your_adb_username
+spring.datasource.adb.password=your_adb_password
+# ADB 连接地址格式:jdbc:mysql://adb-xxx-xxx.adb.rds.aliyuncs.com:3306/database_name
+spring.datasource.adb.url=jdbc:mysql://your-adb-instance-address:3306/your_database?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai
 
 spring.redis.host=r-bp1m4nvh130sfjjc6f.redis.rds.aliyuncs.com
 spring.redis.port=6379

+ 11 - 3
api-module/src/main/resources/application-test.properties

@@ -1,8 +1,16 @@
 server.port=8080
 
-spring.datasource.username=crawler
-spring.datasource.password=crawler123456@
-spring.datasource.url=jdbc:mysql://rm-bp17q95335a99272b.mysql.rds.aliyuncs.com:3306/growth-test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
+# 主数据源(MySQL)配置
+spring.datasource.primary.username=crawler
+spring.datasource.primary.password=crawler123456@
+spring.datasource.primary.url=jdbc:mysql://rm-bp17q95335a99272b.mysql.rds.aliyuncs.com:3306/growth-test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
+
+# ADB 数据源配置(请根据实际 ADB 实例信息修改)
+# 获取 ADB 连接信息:登录阿里云控制台 -> AnalyticDB -> 选择实例 -> 查看连接信息
+spring.datasource.adb.username=your_adb_username
+spring.datasource.adb.password=your_adb_password
+# ADB 连接地址格式:jdbc:mysql://adb-xxx-xxx.adb.rds.aliyuncs.com:3306/database_name
+spring.datasource.adb.url=jdbc:mysql://your-adb-instance-address:3306/your_database?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai
 
 spring.redis.host=r-bp1ikkpsbco7c26v23pd.redis.rds.aliyuncs.com
 spring.redis.port=6379

+ 30 - 12
api-module/src/main/resources/application.properties

@@ -9,18 +9,36 @@ eureka.instance.lease-renewal-interval-in-seconds=30
 eureka.instance.lease-expiration-duration-in-seconds=90
 eureka.client.registry-fetch-interval-seconds=5
 
-spring.datasource.driver-class-name=com.mysql.jdbc.Driver
-spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
-spring.datasource.initialSize=5
-spring.datasource.maxActive=20
-spring.datasource.maxWait=60000
-spring.datasource.timeBetweenEvictionRunsMillis=60000
-spring.datasource.minEvictableIdleTimeMillis=300000
-spring.datasource.validationQuery=SELECT 1 FROM DUAL
-spring.datasource.testWhileIdle=true
-spring.datasource.testOnBorrow=false
-spring.datasource.testOnReturn=false
-spring.datasource.poolPreparedStatements=true
+# 主数据源配置(MySQL)
+spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
+spring.datasource.primary.type=com.alibaba.druid.pool.DruidDataSource
+spring.datasource.primary.initialSize=5
+spring.datasource.primary.maxActive=20
+spring.datasource.primary.maxWait=60000
+spring.datasource.primary.timeBetweenEvictionRunsMillis=60000
+spring.datasource.primary.minEvictableIdleTimeMillis=300000
+spring.datasource.primary.validationQuery=SELECT 1 FROM DUAL
+spring.datasource.primary.testWhileIdle=true
+spring.datasource.primary.testOnBorrow=false
+spring.datasource.primary.testOnReturn=false
+spring.datasource.primary.poolPreparedStatements=true
+
+# ADB 数据源配置(阿里云 AnalyticDB)
+# 注意:ADB for MySQL 兼容 MySQL 协议,可以使用 MySQL JDBC 驱动
+# 如果使用 MySQL 8.x 驱动,使用 com.mysql.cj.jdbc.Driver
+# 如果使用 MySQL 5.x 驱动,使用 com.mysql.jdbc.Driver
+spring.datasource.adb.driver-class-name=com.mysql.jdbc.Driver
+spring.datasource.adb.type=com.alibaba.druid.pool.DruidDataSource
+spring.datasource.adb.initialSize=5
+spring.datasource.adb.maxActive=20
+spring.datasource.adb.maxWait=60000
+spring.datasource.adb.timeBetweenEvictionRunsMillis=60000
+spring.datasource.adb.minEvictableIdleTimeMillis=300000
+spring.datasource.adb.validationQuery=SELECT 1
+spring.datasource.adb.testWhileIdle=true
+spring.datasource.adb.testOnBorrow=false
+spring.datasource.adb.testOnReturn=false
+spring.datasource.adb.poolPreparedStatements=true
 
 spring.redis.lettuce.pool.max-active=8
 spring.redis.lettuce.pool.max-wait=-1

+ 19 - 0
common-module/src/main/java/com/tzld/piaoquan/growth/common/config/annotation/DataSource.java

@@ -0,0 +1,19 @@
+package com.tzld.piaoquan.growth.common.config.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据源切换注解
+ * 用于指定使用哪个数据源
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataSource {
+    /**
+     * 数据源名称
+     * @return 数据源名称,如:primary, adb
+     */
+    String value() default "primary";
+}
+

+ 60 - 0
common-module/src/main/java/com/tzld/piaoquan/growth/common/config/datasource/DataSourceAspect.java

@@ -0,0 +1,60 @@
+package com.tzld.piaoquan.growth.common.config.datasource;
+
+import com.tzld.piaoquan.growth.common.config.annotation.DataSource;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/**
+ * 数据源切换切面
+ * 根据 @DataSource 注解自动切换数据源
+ */
+@Aspect
+@Component
+@Order(1)
+public class DataSourceAspect {
+    
+    private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
+    
+    @Pointcut("@annotation(com.tzld.piaoquan.growth.common.config.annotation.DataSource) || " +
+              "@within(com.tzld.piaoquan.growth.common.config.annotation.DataSource)")
+    public void dataSourcePointcut() {
+    }
+    
+    @Around("dataSourcePointcut()")
+    public Object around(ProceedingJoinPoint point) throws Throwable {
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+        
+        // 优先使用方法上的注解
+        DataSource dataSource = method.getAnnotation(DataSource.class);
+        if (dataSource == null) {
+            // 如果方法上没有,则使用类上的注解
+            dataSource = AnnotationUtils.findAnnotation(method.getDeclaringClass(), DataSource.class);
+        }
+        
+        if (dataSource != null) {
+            String dataSourceName = dataSource.value();
+            logger.debug("切换数据源到: {}", dataSourceName);
+            DynamicDataSourceContextHolder.setDataSource(dataSourceName);
+        }
+        
+        try {
+            return point.proceed();
+        } finally {
+            // 执行完毕后清除数据源标识
+            DynamicDataSourceContextHolder.clearDataSource();
+            logger.debug("清除数据源标识");
+        }
+    }
+}
+

+ 18 - 0
common-module/src/main/java/com/tzld/piaoquan/growth/common/config/datasource/DynamicDataSource.java

@@ -0,0 +1,18 @@
+package com.tzld.piaoquan.growth.common.config.datasource;
+
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+
+/**
+ * 动态数据源路由
+ * 根据 DynamicDataSourceContextHolder 中的标识来决定使用哪个数据源
+ */
+public class DynamicDataSource extends AbstractRoutingDataSource {
+    
+    @Override
+    protected Object determineCurrentLookupKey() {
+        String dataSource = DynamicDataSourceContextHolder.getDataSource();
+        // 如果未指定数据源,使用默认数据源
+        return dataSource != null ? dataSource : DynamicDataSourceContextHolder.DEFAULT_DATASOURCE;
+    }
+}
+

+ 44 - 0
common-module/src/main/java/com/tzld/piaoquan/growth/common/config/datasource/DynamicDataSourceContextHolder.java

@@ -0,0 +1,44 @@
+package com.tzld.piaoquan.growth.common.config.datasource;
+
+/**
+ * 动态数据源上下文持有者
+ * 使用 ThreadLocal 保存当前线程的数据源标识
+ */
+public class DynamicDataSourceContextHolder {
+    
+    /**
+     * 默认数据源
+     */
+    public static final String DEFAULT_DATASOURCE = "primary";
+    
+    /**
+     * ADB 数据源
+     */
+    public static final String ADB_DATASOURCE = "adb";
+    
+    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
+    
+    /**
+     * 设置数据源
+     * @param dataSource 数据源名称
+     */
+    public static void setDataSource(String dataSource) {
+        contextHolder.set(dataSource);
+    }
+    
+    /**
+     * 获取当前数据源
+     * @return 数据源名称
+     */
+    public static String getDataSource() {
+        return contextHolder.get();
+    }
+    
+    /**
+     * 清除数据源
+     */
+    public static void clearDataSource() {
+        contextHolder.remove();
+    }
+}
+

+ 11 - 0
pom.xml

@@ -37,6 +37,17 @@
             <artifactId>mysql-connector-java</artifactId>
         </dependency>
 
+        <!-- 阿里云 AnalyticDB JDBC 驱动 -->
+        <!-- ADB for MySQL 兼容 MySQL 协议,可以使用 MySQL 驱动 -->
+        <!-- 如果需要使用 MySQL 8.x 驱动,可以添加以下依赖 -->
+        <!--
+        <dependency>
+            <groupId>com.mysql.cj</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <version>8.0.33</version>
+        </dependency>
+        -->
+
         <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>