推荐文章:
1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表;
2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据;
3、java后端接口API性能优化技巧
4、SpringBoot+MyBatis流式查询,处理大规模数据,提高系统的性能和响应能力。
一、概述
在项目的开发过程中,遇到了需要从数据库中动态查询新的数据源信息并切换到该数据源做相应的查询操作,这样就产生了动态切换数据源的场景。为了能够灵活地指定具体的数据库,本文基于注解和AOP的方法实现多数据源自动切换。在使用过程中,只需要添加注解就可以使用,简单方便。(代码获取方式:见文章底部(开箱即用))
二、构建核心代码
2.1、AbstractRoutingDataSource构建
package com.wonders.dynamic;import org.springframework.beans.factory.InitializingBean;import org.springframework.jdbc.datasource.AbstractDataSource;import org.springframework.jdbc.datasource.lookup.DataSourceLookup;import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;import org.springframework.lang.Nullable;import org.springframework.util.Assert;import org.springframework.util.CollectionUtils;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;import java.util.Map;/** * @Description: TODO:抽象类AbstractRoutingDataSource,实现动态数据源切换 * @Author: yyalin * @CreateDate: 2023/7/16 14:40 * @Version: V1.0 */public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { //目标数据源map集合,存储将要切换的多数据源bean信息 @Nullable private Map
2.2、DynamicDataSource类
/** * @Description: TODO:动态数据源 * @Author: yyalin * @CreateDate: 2023/7/16 14:46 * @Version: V1.0 *//** * * 调用AddDefineDataSource组件的addDefineDynamicDataSource()方法,获取原来targetdatasources的map, * 并将新的数据源信息添加到map中,并替换targetdatasources中的map * 切换数据源时可以使用@DataSource(value = "数据源名称"),或者DynamicDataSourceContextHolder.setContextKey("数据源名称") */@Data@AllArgsConstructor@NoArgsConstructorpublic class DynamicDataSource extends AbstractRoutingDataSource { //备份所有数据源信息, private MapdefineTargetDataSources; /** * 决定当前线程使用哪个数据源 */ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getDynamicDataSourceKey(); }}
2.3、DynamicDataSourceHolder
/** * @Description: TODO:数据源切换处理 * DynamicDataSourceHolder类主要是设置当前线程的数据源名称, * 移除数据源名称,以及获取当前数据源的名称,便于动态切换 * @Author: yyalin * @CreateDate: 2023/7/16 14:51 * @Version: V1.0 */@Slf4jpublic class DynamicDataSourceHolder { /** * 保存动态数据源名称 */ private static final ThreadLocalDYNAMIC_DATASOURCE_KEY = new ThreadLocal<>(); /** * 设置/切换数据源,决定当前线程使用哪个数据源 */ public static void setDynamicDataSourceKey(String key){ log.info("数据源切换为:{}",key); DYNAMIC_DATASOURCE_KEY.set(key); } /** * 获取动态数据源名称,默认使用mater数据源 */ public static String getDynamicDataSourceKey(){ String key = DYNAMIC_DATASOURCE_KEY.get(); return key == null ? DbsConstant.mysql_db_01 : key; } /** * 移除当前数据源 */ public static void removeDynamicDataSourceKey(){ log.info("移除数据源:{}",DYNAMIC_DATASOURCE_KEY.get()); DYNAMIC_DATASOURCE_KEY.remove(); }}
2.4、数据源工具类
/** * @Description: TODO:数据源工具类 * @Author: yyalin * @CreateDate: 2023/7/16 15:00 * @Version: V1.0 */@Slf4j@Componentpublic class DataSourceUtils { @Resource DynamicDataSource dynamicDataSource; /** * @Description: 根据传递的数据源信息测试数据库连接 * @Author zhangyu */ public DruidDataSource createDataSourceConnection(DataSourceInfo dataSourceInfo) { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setUrl(dataSourceInfo.getUrl()); druidDataSource.setUsername(dataSourceInfo.getUserName()); druidDataSource.setPassword(dataSourceInfo.getPassword()); druidDataSource.setDriverClassName(dataSourceInfo.getDriverClassName()); druidDataSource.setBreakAfterAcquireFailure(true); druidDataSource.setConnectionErrorRetryAttempts(0); try { druidDataSource.getConnection(2000); log.info("数据源连接成功"); return druidDataSource; } catch (SQLException throwables) { log.error("数据源 {} 连接失败,用户名:{},密码 {}",dataSourceInfo.getUrl(),dataSourceInfo.getUserName(),dataSourceInfo.getPassword()); return null; } } /** * @Description: 将新增的数据源加入到备份数据源map中 * @Author zhangyu */ public void addDefineDynamicDataSource(DruidDataSource druidDataSource, String dataSourceName){ MapdefineTargetDataSources = dynamicDataSource.getDefineTargetDataSources(); defineTargetDataSources.put(dataSourceName, druidDataSource); dynamicDataSource.setTargetDataSources(defineTargetDataSources); dynamicDataSource.afterPropertiesSet(); }
2.5、DynamicDataSourceConfig
/** * @Description: TODO:数据源信息配置类,读取数据源配置信息并注册成bean。 * @Author: yyalin * @CreateDate: 2023/7/16 14:54 * @Version: V1.0 */@Configuration@MapperScan("com.wonders.mapper")@Slf4jpublic class DynamicDataSourceConfig { @Bean(name = DbsConstant.mysql_db_01) @ConfigurationProperties("spring.datasource.mysqldb01") public DataSource masterDataSource() { log.info("数据源切换为:{}",DbsConstant.mysql_db_01); DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return dataSource; } @Bean(name = DbsConstant.mysql_db_02) @ConfigurationProperties("spring.datasource.mysqldb02") public DataSource slaveDataSource() { log.info("数据源切换为:{}",DbsConstant.mysql_db_02); DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return dataSource; } @Bean(name = DbsConstant.oracle_db_01) @ConfigurationProperties("spring.datasource.oracledb01") public DataSource oracleDataSource() { log.info("数据源切换为oracle:{}",DbsConstant.oracle_db_01); DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return dataSource; } @Bean @Primary public DynamicDataSource dynamicDataSource(){ MapdataSourceMap = new HashMap<>(3); dataSourceMap.put(DbsConstant.mysql_db_01,masterDataSource()); dataSourceMap.put(DbsConstant.mysql_db_02,slaveDataSource()); dataSourceMap.put(DbsConstant.oracle_db_01,oracleDataSource()); //设置动态数据源 DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setDefaultTargetDataSource(masterDataSource()); dynamicDataSource.setTargetDataSources(dataSourceMap); //将数据源信息备份在defineTargetDataSources中 dynamicDataSource.setDefineTargetDataSources(dataSourceMap); return dynamicDataSource; }}
三、测试代码
/** * @Description: TODO * @Author: yyalin * @CreateDate: 2023/7/16 15:02 * @Version: V1.0 */@Slf4j@Api(tags="动态切换多数据源测试")@RestControllerpublic class TestController { @Resource DataSourceUtils dataSourceUtils; @Autowired private StudentMapper studentMapper; @ApiOperation(value="动态切换多数据源测试", notes="test") @GetMapping("/test") public MapdynamicDataSourceTest(String id){ Map map = new HashMap<>(); //1、默认库中查询数据 Student student=studentMapper.selectById(id); map.put("1、默认库中查询到的数据",student); //2、指定库中查询的数据 DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02); Student student02=studentMapper.selectById(id); map.put("2、指定库中查询的数据",student02); //3、从数据库获取连接信息,然后获取数据 //模拟从数据库中获取的连接 DataSourceInfo dataSourceInfo = new DataSourceInfo( "jdbc:mysql://127.0.0.1:3308/test02?useUnicode=true&characterEncoding=utf-8&useSSL=false", "root", "root", "mysqldb03", "com.mysql.cj.jdbc.Driver"); map.put("dataSource",dataSourceInfo); log.info("数据源信息:{}",dataSourceInfo); //测试数据源连接 DruidDataSource druidDataSource = dataSourceUtils.createDataSourceConnection(dataSourceInfo); if (Objects.nonNull(druidDataSource)){ //将新的数据源连接添加到目标数据源map中 dataSourceUtils.addDefineDynamicDataSource(druidDataSource,dataSourceInfo.getDatasourceKey()); //设置当前线程数据源名称-----代码形式 DynamicDataSourceHolder.setDynamicDataSourceKey(dataSourceInfo.getDatasourceKey()); //在新的数据源中查询用户信息 Student student03=studentMapper.selectById(id); map.put("3、动态数据源查询的数据",student03); //关闭数据源连接 druidDataSource.close(); } //4、指定oracle库中查询的数据 DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.oracle_db_01); Student student04=studentMapper.selectById(id); map.put("4、指定oracle库中查询的数据",student04); return map; }}
测试结果如下:
从结果中可以明显的看出,通过切换不同的数据源,可以从不同的库中获取不同的数据,包括:常见库Mysql、oracle、sqlserver等数据库相互切换。也可以从数据库的某张表中获取连接信息,实现动态切换数据库。
四、使用注解方式切换数据源
从上述TestController 中代码不难看出,若要想切换数据源需要在mapper调用之前调用:
DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02);
不够简洁优雅,所以下面推荐使用注解的方式来动态进行数据源的切换。
4.1、创建注解类DataSource
/** * @Description: TODO:自定义多数据源切换注解 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 * @Author: yyalin * @CreateDate: 2023/7/17 14:00 * @Version: V1.0 */@Target({ ElementType.METHOD, ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface DataSource { //切换数据源名称,默认mysql_db_01 public String value() default DbsConstant.mysql_db_01;}
4.2、创建切面DataSourceAspect类
/** * @Description: TODO:创建切面DataSourceAspect类 * @Author: yyalin * @CreateDate: 2023/7/17 14:03 * @Version: V1.0 */@Aspect@Componentpublic class DataSourceAspect { // 设置DataSource注解的切点表达式 @Pointcut("@annotation(com.wonders.dynamic.DataSource)") public void dynamicDataSourcePointCut(){} //环绕通知 @Around("dynamicDataSourcePointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ String key = getDefineAnnotation(joinPoint).value(); DynamicDataSourceHolder.setDynamicDataSourceKey(key); try { return joinPoint.proceed(); } finally { DynamicDataSourceHolder.removeDynamicDataSourceKey(); } } /** * 功能描述:先判断方法的注解,后判断类的注解,以方法的注解为准 * @MethodName: getDefineAnnotation * @MethodParam: [joinPoint] * @Return: com.wonders.dynamic.DataSource * @Author: yyalin * @CreateDate: 2023/7/17 14:09 */ private DataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); DataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(DataSource.class); if (Objects.nonNull(methodSignature)) { return dataSourceAnnotation; } else { Class> dsClass = joinPoint.getTarget().getClass(); return dsClass.getAnnotation(DataSource.class); } }}
4.3、进行数据源切换
//@Mapper 与 启动类的@MapperScan({"com.example.demo.mapper"}) 二选一即可@Repositorypublic interface StudentMapper extends BaseMapper{ /** * 功能描述:在mysql_db_01中查询数据 * @MethodName: findStudentById * @MethodParam: [id] * @Return: com.wonders.entity.Student * @Author: yyalin * @CreateDate: 2023/7/17 14:20 */ @DataSource(value = DbsConstant.oracle_db_01) Student findStudentById(String id);}
或在service层
@Servicepublic class StudentServiceImpl implements StudentService{ @Autowired private StudentMapper studentMapper; //注解加在实现层才能生效 @DataSource(value = DbsConstant.mysql_db_01) @Override public Student findStudentById(String id) { return studentMapper.selectById(id); }}
4.3、测试效果
@ApiOperation(value="使用注解方式动态切换多数据源", notes="test02") @GetMapping("/test02") public Student test02(String id){ Student student=studentMapper.findStudentById(id); return student; }
--结果如下:
五、功能点
1、使用注解的方式来动态进行数据源的切换;
2、支持动态新增新的数据源;
3、支持oracle\mysql等常见数据库切换。
回复:源码,可以获取该项目对应的源码及表结构,开箱即可使用。
更多详细资料,请关注个人微信公众号或搜索“程序猿小杨”添加。
参考:
http://t.csdn.cn/KCW9r
觉得有用,请点这里↓↓↓