1. 简介
最近项目需要配置多数据源,本项目采用的技术是SpringBoot+mybatis-plus+Druid。为了图个方便直接想直接集成dynamic-datasource-spring-boot-starter进行多数据源配置。
dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。
其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x。
其官方文档的地址是:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611
该官方文档分为免费部分和付费部分。付费部分也仅仅只需要29块,29块也不多,就算原作者的支持,个人觉得这29块花的值。
强烈建议使用最新版本,可以在版本记录里查找最新版本
前提
这里默认你已经集成并配置好了mybatis-plus。
集成(第一种实现方式)
仅仅只看基础部分的集成手册是远远不够的。网上好多博客也仅仅只是介绍了基础部分的内容,最终还是达不到想要的效果。本文的集成亲测有效,欢迎读者老爷们阅读。
这里再次强烈建议采用最新版本的dynamic-datasource-spring-boot-starter,具体的版本记录请点击
1. 添加依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.1</version> </dependency>
2. 添加数据源配置
在application.yml文件中将单数据源配置成多数据源,数据源配置的语法结构如下所示:
spring: datasource: dynamic: primary: master #设置默认的数据源或者数据源组,默认值即为master strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源 datasource: master: url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置 slave_1: url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver slave_2: url: ENC(xxxxx) # 内置加密,使用请查看详细文档 username: ENC(xxxxx) password: ENC(xxxxx) driver-class-name: com.mysql.jdbc.Driver #......省略 #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
此处我的配置实例是:
spring: datasource: dynamic: primary: master #设置默认的数据源或者数据源组,默认值即为master strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源 datasource: master : url: jdbc:mysql://127.0.0.1:23306/db1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置 slave: url: jdbc:mysql://127.0.0.1:23306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver
3. 使用 @DS 切换数据源。
@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
注解 | 结果 |
没有@DS | 使用默认数据源 |
@DS(“dsName”) | dsName可以为组名也可以为具体某个库的名称 |
官方文档里配置到这里就结束了,实际上还远远不够。 |
4. 排除掉DruidDataSourceAutoConfigure
在启动类中需要排除掉DruidDataSourceAutoConfigure.class,就是取消Druid的数据源的自动配置类。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class}) @MapperScan(basePackages = {"com.jay.multidatasource.mapper"}) public class MultidatasourceApplication { public static void main(String[] args) { SpringApplication.run(MultidatasourceApplication.class, args); } }
原理解释(第二种实现方式)
多数据源的配置本质上就是加载多个数据源,并设置默认数据源,给每个数据源设置不同的键值对,当需要切换数据源时就传入目标数据源的键,然后重新设置数据源。下面就做一个简单的演示,就是不使用dynamic-datasource-spring-boot-starter。
1. 定义数据源配置
在application.yml文件中将单数据源配置成多数据源
spring: datasource: druid: db1: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:23306/db1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true username: root password: 123456 db2: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:23306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true username: root password: 123456 test-on-borrow: true
2. 定义全局的数据源构造类DynamicDataSourceContextHolder
这个类的作用就是管理每个数据源的键,设置当前数据源的键,获取当前数据源的键。
public class DynamicDataSourceContextHolder { private static ThreadLocal<Object> CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceKey.DCS.getName()); public static List<Object> dataSourceKeys = new ArrayList<Object>(); public static void setDataSourceKey(String key){ CONTEXT_HOLDER.set(key); } public static Object getDataSourceKey(){ return CONTEXT_HOLDER.get(); } public static void clearDataSourceKey(){ CONTEXT_HOLDER.remove(); } public static Boolean containDataSourceKey(String key){ return dataSourceKeys.contains(key); } }
2. 自定义DynamicRoutingDataSource
/** * 该类继承自 AbstractRoutingDataSource 类, * 在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key */ public class DynamicRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { logger.info("current datasource is : {}", DynamicDataSourceContextHolder.getDataSourceKey()); return DynamicDataSourceContextHolder.getDataSourceKey(); } }
3. 定义数据源配置类
该类的作用就是初始化数据源DataSource实例,以及初始化SqlSessionFactory实例。这里需要注意的是必须使用MybatisSqlSessionFactoryBean来获取会话工厂SqlSessionFactory,不然的话,baseMapper中的生成动态SQL的方法就不能使用了。
@Configuration public class DataSourceConfigurer { /** * 配置数据源 * * @return */ @Bean(name = "db1") @Primary @ConfigurationProperties(prefix = "spring.datasource.druid.db1") public DataSource db1() { return DruidDataSourceBuilder.create().build(); } /** * 配置数据源 * * @return */ @Bean(name = "db2") @ConfigurationProperties(prefix = "spring.datasource.druid.db2") public DataSource db2() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") public DataSource dynamicDataSource() { DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(); Map<Object, Object> dataSourceMap = new HashMap<Object, Object>(2); dataSourceMap.put("db1", db1()); dataSourceMap.put("db2", db2()); dynamicRoutingDataSource.setDefaultTargetDataSource(dcs()); dynamicRoutingDataSource.setTargetDataSources(dataSourceMap); DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet()); return dynamicRoutingDataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { //MybatisPlus使用的是MybatisSqlSessionFactory MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); //此处设置为了解决找不到mapper文件的问题 sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); return sqlSessionFactoryBean.getObject(); } @Bean public SqlSessionTemplate sqlSessionTemplate() throws Exception { return new SqlSessionTemplate(sqlSessionFactory()); } /** * 事务 * * @return */ @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dynamicDataSource()); }
4. 自定义注解TargetDataSource
该注解只是作用在方法上,这里默认的数据源是db1.
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String value() default "db1"; }
5. 定义切面DynamicDataSourceAspect
切面顾名思义就是拦击标注TargetDataSource注解的方法,并且根据注解指定的数据源的key切换数据源。
@Aspect @Component public class DynamicDataSourceAspect { private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); @Before("@annotation(targetDataSource))") public void switchDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) { if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value().getName())) { logger.error("DataSource [{}] doesn't exist, use default DataSource [{}]", targetDataSource.value()); } else { DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value().getName()); logger.info("Switch DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature()); } } @After("@annotation(targetDataSource))") public void restoreDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) { DynamicDataSourceContextHolder.clearDataSourceKey(); logger.info("Restore DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature()); } }
6. 使用注解
没有添加注解的方法使用的是默认数据源,当需要使用非默认数据源时,则需要在方法上添加 @TargetDataSource("db2") 注解。需要注意的是,该注解最好添加到xxxMapper类的方法上。
@TargetDataSource("db2") ClassVO getClassStudent(@Param("open_id") String openId);
总结
本文详细介绍了两种数据源配置的方式