提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
最近因为公司需求需要,所以稍微调研了下多数据源的处理,记录一下。
一、pom文件
主要的jar包引用,各自按自己的需要引用吧。
<dependencies>
<!-- 分布式定时任务 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
</dependency>
<!--添加openfeign相关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!--哨兵-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.14.1</version>
</dependency>
<!--比较差异-->
<dependency>
<groupId>com.github.colincatsu</groupId>
<artifactId>fast-object-diff</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
<!-- MyBatis 核心库 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
</dependencies>
二、数据源配置
这里设置数据源配置,目前做的是两个数据源,如果自己有需求多加就在后面按同样的操作添加即可,以master、slave、slave1…等等名字自己按自己的喜好取,但是需要注意的是这里的前缀spring.datasource.master/slave/slave1在配置文件里要一样才行。(后面会提及)
spring.datasource.master.jdbc-url=jdbc:mysql://xx.xx.xx.xx:3306/dili_card?createDatabaseIfNotExist=true&autoReconnect=true&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
spring.datasource.master.username=root
spring.datasource.master.password=******
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slave.jdbc-url=jdbc:mysql://127.0.0.1:3306/dili_card?createDatabaseIfNotExist=true&autoReconnect=true&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
spring.datasource.slave.username=root
spring.datasource.slave.password=******
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
三、 配置文件
1、数据源配置类
这里的配置类就是前面提到的需要注意的地方,前缀要同nacos上的一致。
前面两个是创建数据源实体。后的routingDataSource方法就是设置多数据源的关键,将多数据源设置到map中,后期根据key找到自己的需要的数据源,所以这里的key也需要自定义。
@Configuration
@Slf4j
public class DataSourceConfig {
@Primary
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public AbstractRoutingDataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
MyRoutingDataSource routingDataSource = new MyRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
targetDataSources.put(DBTypeEnum.SLAVE, slaveDataSource);
routingDataSource.setDefaultTargetDataSource(masterDataSource);
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}
2、自定义数据源路由类
自定义一个数据源路由类,其中只需重新determineCurrentLookupKey方法即可,其主要作用就是拿到我们前面配置里设置进去的map的key。其他的方法按原来的执行即可。
public class MyRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}
}
3、自定义路由key
这个是配置类提到的需要自己定义的map的key,有几个数据源就定义几个值即可。
public enum DBTypeEnum {
MASTER,SLAVE;
}
4、基础配置
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
@Resource(name = "routingDataSource")
private DataSource routingDataSource;
@Bean
public SqlSessionFactory sessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(routingDataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager platformTransactionManager(){
return new DataSourceTransactionManager(routingDataSource);
}
}
5、线程处理
这里使用ThreadLocal来处理数据源key,具体好处这里就不多言了。
@Slf4j
public class DBContextHolder {
private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();
public static void set(DBTypeEnum dbTypeEnum) {
contextHolder.set(dbTypeEnum);
}
public static DBTypeEnum get(){
return contextHolder.get();
}
public static void master() {
set(DBTypeEnum.MASTER);
log.info("切换到主库");
}
public static void slave() {
set(DBTypeEnum.SLAVE);
log.info("切换到从库");
}
public static void clear(){
contextHolder.remove();
}
}
到此基本的配置就完成了,直接调用DBContextHolder的master方法和slave方法就可以进行数据源切换了。当然使用后一定要记住调用clear方法,避免内存溢出。
四、简化操作
1、自定义注解类
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RoutingWith {
DBTypeEnum value() default DBTypeEnum.MASTER;
}
2、AOP
使用aop对前面的自定义注解进行切面处理,凡是使用了该注解的方法,就可以根据注解上的值进行选择对应的数据源。如果没有的话,默认走的master数据源,这个可以根据自己的想法定。
@Aspect
@Component
public class DataSourceAop {
@Pointcut("@annotation(com.dili.ann.RoutingWith)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
RoutingWith annotation = method.getAnnotation(RoutingWith.class);
if (annotation != null) {
DBContextHolder.set(annotation.value());
}
try {
return point.proceed();
} finally {
DBContextHolder.clear();
}
}
}
五、测试
1、测试查询
@RestController
@RequestMapping("/data")
@Slf4j
public class DataSourceController {
@Resource
private DataSourceService dataSourceService;
@RequestMapping("/select")
@RoutingWith(value = DBTypeEnum.SLAVE)
public AccountCycleDo select(@RequestBody AccountCycleDo accountCycleDto){
AccountCycleDo accountCycleDo = dataSourceService.select(accountCycleDto);
log.info("测试查询是否走的从库,{}",accountCycleDo);
return accountCycleDo;
}
@RequestMapping("/insert")
@RoutingWith(value = DBTypeEnum.MASTER)
public String insert(){
log.info("测试插入数据走主库");
dataSourceService.insert();
return "ok";
}
}
先测试查询类的,这里我们指定的是SLAVE,看看最终连接的哪个数据源
首先进入aop类给ThreadLocal设置一个SLAVE,然后来到AbstractRoutingDataSource类,在执行determineCurrentLookupKey方法时,会到我们自定义的路由类
执行了这个方法后拿到map中的key,最后就根据key拿到对应的数据源了。
master的测试也是同样的情况,加上事务后也能正常回滚。但是如果是一个请求中既有查询又有新增的情况那就需要再细分一下了,我们的注解就不能放在controller的方法上了,需要在实现层的具体方法内直接使用DBContextHolder来设置数据源
这样操作就能在同一个方法内连接不同的数据源,但是还是那句话,记得清理threadlocal。
总结
以上就是关于多数据源的一些简单记录,还有很多其他的场景没有考虑到。视具体业务而定吧,加油!!!