下面案例源码可参考:
- springboot-dydamic-datasources分包方式实现多数据源
- springboot-dydamic-datasources自定义注解实现多数据源
1.为什么微服务还要使用多数据源
:::tips 我们知道面对微服务化的拆分,我们希望一个微服务能做到小而精(做到只干一件事,比如账单服务,库存服务)。但是有些时候拆分不是很测地,需要在一个微服务项目中使用多数据源的情形(eg:如果满足a条件入a库,满足b条件入b库)。基于此,这里简单采用springboot分包方式实现多数据源。仅供参考。 :::2.分包方式实现多数据源切换
2.1 项目目录结构
我们在进行分包的方式来实现多数据源的动态切换,重点就是分包。项目分包如下:├─src│ ├─main│ │ ├─java│ │ │ └─com│ │ │ └─itmck│ │ │ ├─config 多数据源配置包│ │ │ ├─dao│ │ │ │ ├─master 主数据源mapper│ │ │ │ └─slave 从数据源mapper│ │ │ ├─entity 实体│ │ │ └─service 接口│ │ └─resources│ │ └─mapper│ │ ├─master 主数据源mapper.xml│ │ └─slave 从数据源mapper.xml│ └─test
2.2 数据源配置清单
```yaml spring: datasource: master: url: jdbc:mysql://localhost:3306/spring_db?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8 driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 slave: url: jdbc:mysql://localhost:3306/spring_db2?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8 driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 type: com.zaxxer.hikari.HikariDataSource hikari: connection-timeout: 30000 idle-timeout: 30000 auto-commit: ‘true’ minimum-idle: 5 maximum-pool-size: 15 pool-name: HikariCP connection-test-query: SELECT 1 FROM DUAL max-lifetime: 1800000
mybatis-plus: mapper-locations: ‘classpath:mapper//*Mapper.xml’ type-aliases-package: com.itmck.entity configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
<a name="PLVX3"></a>## 2.3 master数据源:::info@MapperScan(basePackages = "com.itmck.dao.master", sqlSessionTemplateRef = "primarySqlSessionTemplate")<br />basePackages代表要扫描的mapper接口,这是分包切换数据源的关键:::```javapackage com.itmck.filterconfig;import com.baomidou.mybatisplus.core.MybatisConfiguration;import com.baomidou.mybatisplus.core.config.GlobalConfig;import com.baomidou.mybatisplus.extension.incrementer.OracleKeyGenerator;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;import com.zaxxer.hikari.HikariDataSource;import lombok.extern.slf4j.Slf4j;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionTemplate;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import javax.sql.DataSource;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/17 15:07**/@Slf4j@Configuration@MapperScan(basePackages = "com.itmck.dao.master", sqlSessionTemplateRef = "primarySqlSessionTemplate")public class PrimaryDataSourceConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.master")public DataSourceProperties primaryDataSourceProperties() {return new DataSourceProperties();}@Bean@Primarypublic DataSource primaryDateSource() {return primaryDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();}@Bean@Primarypublic SqlSessionFactory primarySqlSessionFactory() throws Exception {MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();mybatisSqlSessionFactoryBean.setPlugins(new PaginationInterceptor());mybatisSqlSessionFactoryBean.setDataSource(primaryDateSource());// mybatisSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/master/*.xml"));// mybatisSqlSessionFactoryBean.setConfiguration(this.mybatisConfiguration());//解决oracle下不能使用sequence问题return mybatisSqlSessionFactoryBean.getObject();}private MybatisConfiguration mybatisConfiguration() {GlobalConfig globalConfig = new GlobalConfig();GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig().setKeyGenerator(new OracleKeyGenerator());globalConfig.setDbConfig(dbConfig);MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();mybatisConfiguration.setGlobalConfig(globalConfig);return mybatisConfiguration;}@Bean@Primarypublic DataSourceTransactionManager primaryDataSourceTransactionManager() {return new DataSourceTransactionManager(primaryDateSource());}@Bean@Primarypublic SqlSessionTemplate primarySqlSessionTemplate() throws Exception {return new SqlSessionTemplate(primarySqlSessionFactory());}}
2.4 slave数据源
:::info
@MapperScan(basePackages = “com.itmck.dao.slave”,sqlSessionTemplateRef = “slaveSqlSessionTemplate”)
basePackages代表要扫描的mapper接口,这是分包切换数据源的关键
:::
package com.itmck.filterconfig;import com.baomidou.mybatisplus.core.MybatisConfiguration;import com.baomidou.mybatisplus.core.config.GlobalConfig;import com.baomidou.mybatisplus.extension.incrementer.OracleKeyGenerator;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;import com.zaxxer.hikari.HikariDataSource;import lombok.extern.slf4j.Slf4j;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionTemplate;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import javax.sql.DataSource;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/17 15:07* <p>* 从数据源**/@Slf4j@Configuration@MapperScan(basePackages = "com.itmck.dao.slave",sqlSessionTemplateRef = "slaveSqlSessionTemplate")public class SlaveDataSourceConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.slave")public DataSourceProperties slaveDataSourceProperties() {return new DataSourceProperties();}@Beanpublic DataSource slaveDateSource() {return slaveDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();}@Beanpublic SqlSessionFactory slaveSqlSessionFactory() throws Exception {MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();mybatisSqlSessionFactoryBean.setPlugins(new PaginationInterceptor());mybatisSqlSessionFactoryBean.setDataSource(slaveDateSource());// mybatisSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/slave/*.xml"));// mybatisSqlSessionFactoryBean.setConfiguration(this.mybatisConfiguration());//解决oracle下不能使用sequence问题return mybatisSqlSessionFactoryBean.getObject();}private MybatisConfiguration mybatisConfiguration() {GlobalConfig globalConfig = new GlobalConfig();GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig().setKeyGenerator(new OracleKeyGenerator());globalConfig.setDbConfig(dbConfig);MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();mybatisConfiguration.setGlobalConfig(globalConfig);return mybatisConfiguration;}@Beanpublic DataSourceTransactionManager slaveDataSourceTransactionManager() {return new DataSourceTransactionManager(slaveDateSource());}@Beanpublic SqlSessionTemplate slaveSqlSessionTemplate() throws Exception {return new SqlSessionTemplate(slaveSqlSessionFactory());}}
2.5 启动类 :::tips 启动类上exclude排除默认的数据源自动装配配置类。否则会出现无法确认数据源的错误。 :::
package com.itmck;import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;import org.springframework.transaction.annotation.EnableTransactionManagement;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/17 15:03**/@SpringBootApplication(//排除默认的数据源自动装配配置类exclude = {DataSourceAutoConfiguration.class,DataSourceTransactionManagerAutoConfiguration.class,MybatisPlusAutoConfiguration.class})@EnableTransactionManagementpublic class SpringbootMultipartDataSourcesApplication {public static void main(String[] args) {SpringApplication.run(SpringbootMultipartDataSourcesApplication.class, args);}}
3.自定义注解方式实现多数据源
:::tips
使用多数据源也可以完成的.这里简单自定义注解实现多数据源切换.
场景:针对动态数据源的切换操作可以扩展成读写分离.举例:如果读写分离可以使用中间件mycat.
:::
3.1 项目目录结构
├─src│ ├─main│ │ ├─java│ │ │ └─com│ │ │ └─itmck│ │ │ ├─annotation 自定义数据源切换注解│ │ │ ├─config 切面以及配置类│ │ │ ├─dao│ │ │ ├─entity│ │ │ ├─enums│ │ │ ├─handler│ │ │ └─service│ │ └─resources│ └─test│ └─java│ └─com│ └─itmck│ └─service
3.2 项目所需依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.2.0</version></dependency><!--这里引入aop目的自定义注解实现切面--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
3.3 application.yml配置
:::tips 注意: url改成 jdbc-url 否则报错: jdbcUrl is required with driverClassName 原文链接:解决方案 :::
server:port: 8080spring:datasource:master:#这里url必须改为:jdbc-url 用来重写自定义连接池jdbc-url: jdbc:mysql://localhost:3306/spring_db?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456slave:jdbc-url: jdbc:mysql://localhost:3306/spring_db2?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456type: com.zaxxer.hikari.HikariDataSourcehikari:connection-timeout: 30000idle-timeout: 30000auto-commit: 'true'minimum-idle: 5maximum-pool-size: 15pool-name: HikariCPconnection-test-query: SELECT 1 FROM DUALmax-lifetime: 1800000mybatis-plus:mapper-locations: 'classpath*:mapper/*/*Mapper.xml'type-aliases-package: com.itmck.entityconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.4 自定义注解
package com.itmck.annotation;import com.itmck.enums.DataSourceType;import java.lang.annotation.*;/*** 自定义多数据源切换注解** @author miaochangke*/@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Ds{/*** 切换数据源名称*/public DataSourceType value() default DataSourceType.MASTER;}
package com.itmck.enums;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/22 14:28* <p>* 定义数据源枚举**/public enum DataSourceType {/*** 主库*/MASTER,/*** 从库*/SLAVE}
3.5 自定义DynamicDataSourceContextHolder
:::tips 用来管理线程变量,根据线程进行数据源切换 :::
package com.itmck.handler;import lombok.extern.slf4j.Slf4j;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/22 14:31**/@Slf4jpublic class DynamicDataSourceContextHolder {/*** 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。*/private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();/*** 设置数据源的变量*/public static void setDateSourceType(String dsType) {log.info("切换到{}数据源", dsType);CONTEXT_HOLDER.set(dsType);}/*** 获得数据源的变量*/public static String getDateSourceType() {return CONTEXT_HOLDER.get();}/*** 清空数据源变量*/public static void clearDateSourceType() {CONTEXT_HOLDER.remove();}}
3.6 自定义DynamicDataSource
:::tips 通过自定义DynamicDataSource extends AbstractRoutingDataSource 重写determineCurrentLookupKey()来选择当前线程对应数据源 :::
package com.itmck.handler;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;import java.util.Map;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/22 14:37* <p>* 动态数据源**/public class DynamicDataSource extends AbstractRoutingDataSource {public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {super.setDefaultTargetDataSource(defaultTargetDataSource);//设置默认数据源super.setTargetDataSources(targetDataSources);//设置目标数据源super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDateSourceType();//选择数据源}}
3.7 创建DynamicDataSourceConfig
:::tips 初始化扫描数据源 :::
package com.itmck.filterconfig;import com.itmck.enums.DataSourceType;import com.itmck.handler.DynamicDataSource;import com.zaxxer.hikari.HikariDataSource;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import javax.sql.DataSource;import java.util.HashMap;import java.util.Map;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/22 14:43**/@Configuration@MapperScan("com.itmck.dao")public class DynamicDataSourceConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.master")public DataSource primaryDateSource() {return DataSourceBuilder.create().type(HikariDataSource.class).build();}@Bean@ConfigurationProperties(prefix = "spring.datasource.slave")public DataSource slaveDateSource() {return DataSourceBuilder.create().type(HikariDataSource.class).build();}@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource dataSource() {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceType.MASTER.name(), primaryDateSource());targetDataSources.put(DataSourceType.SLAVE.name(), slaveDateSource());return new DynamicDataSource(primaryDateSource(), targetDataSources);}}
3.8 创建切面
使用切面针对使用了注解@Ds的方法进行数据源的切换
package com.itmck.filterconfig;import com.itmck.annotation.Ds;import com.itmck.handler.DynamicDataSourceContextHolder;import lombok.extern.slf4j.Slf4j;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.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/22 14:39**/@Slf4j@Aspect@Order(1)@Componentpublic class DataSourceAspect {@Pointcut("@annotation(com.itmck.annotation.Ds)")public void dsPointCut() {}@Around("dsPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();Ds ds = method.getAnnotation(Ds.class);if (null != ds) {DynamicDataSourceContextHolder.setDateSourceType(ds.value().name());}try {return point.proceed();} finally {// 销毁数据源 在执行方法之后DynamicDataSourceContextHolder.clearDateSourceType();}}}
3.9 注解使用
:::tips 参考DsChangeImpl#getStudentList() 切换数据源: @Ds(value = DataSourceType.SLAVE) :::
package com.itmck.service;import com.baomidou.mybatisplus.extension.service.additional.query.impl.LambdaQueryChainWrapper;import com.itmck.annotation.Ds;import com.itmck.dao.StudentMapper;import com.itmck.dao.UserMapper;import com.itmck.entity.Student;import com.itmck.entity.User;import com.itmck.enums.DataSourceType;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.util.List;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/17 15:49**/@Servicepublic class DsChangeImpl implements DsChange {@Resourceprivate UserMapper userMapper;@Resourceprivate StudentMapper studentMapper;//使用默认数据源@Overridepublic List<User> getUserList() {return new LambdaQueryChainWrapper<>(userMapper).list();}//使用从数据源@Override@Ds(value = DataSourceType.SLAVE)public List<Student> getStudentList() {return new LambdaQueryChainWrapper<>(studentMapper).list();}}
3.10 启动类排除数据源自动注册
:::tips @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) 排除数据源自动配置类 :::
package com.itmck;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import org.springframework.transaction.annotation.EnableTransactionManagement;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/17 15:03**///@Import(DynamicDataSourceConfig.class)@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})@EnableTransactionManagementpublic class SpringbootDynamicDataSourcesApplication {public static void main(String[] args) {SpringApplication.run(SpringbootDynamicDataSourcesApplication.class, args);}}
3.11 测试
package com.itmck.service;import com.itmck.entity.Student;import com.itmck.entity.User;import lombok.extern.slf4j.Slf4j;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;import java.util.List;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/17 15:49**/@Slf4j@RunWith(SpringRunner.class)@SpringBootTestpublic class DsChangeImplTest {@Resourceprivate DsChange dsChange;@Testpublic void userMapper() {List<User> list = dsChange.getUserList();log.info("list:{}", list);}@Testpublic void studentMapper() {List<Student> list = dsChange.getStudentList();log.info("list:{}", list);}}
分别输出:
==> Preparing: SELECT id,name,email,age FROM user ==> Parameters: <== Columns: id, name, email, age <== Row: 1, mck, 1735580535, 26 <== Total: 1 list:[User(id=1, name=mck, age=26, email=1735580535)]
==> Preparing: SELECT id,name,age FROM student ==> Parameters: <== Columns: id, name, age <== Row: 1, wxp, 25 <== Total: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@51b01550] list:[Student(id=1, name=wxp, age=25)]
