项目总结68:Springboot集成动态数据源示例

START

代码示例

  POM文件

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

 

 

   RoutingDataSource类:继承AbstractRoutingDataSource 类;重写determineCurrentLookupKey()方法和setTargetDataSources()方法

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.Map;

public class RoutingDataSource  extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return RoutingDataSourceContext.getDataSourceRouteKey();
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        RoutingDataSourceContext.putDataSourceKeys(targetDataSources.keySet());
    }
}

 

  RoutingDataSourceContext 类

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

public class RoutingDataSourceContext {
    private static final Set<Object> dataSourceKeys =  new HashSet<>();

    private static final ThreadLocal<DataSourceRouteEnums> threadLocalDataSourceKey = new ThreadLocal<DataSourceRouteEnums>(){
        @Override
        protected DataSourceRouteEnums initialValue(){
            return DataSourceRouteEnums.DEFAULT_MYSQL;
        }
    };

    public static  DataSourceRouteEnums getDataSourceRouteKey(){
        return threadLocalDataSourceKey.get();
    }

    public static void setThreadLocalDataSourceKey(DataSourceRouteEnums dataSourceRoutekey){
        threadLocalDataSourceKey.set(dataSourceRoutekey);
    }

    public static void remove(){
        threadLocalDataSourceKey.remove();;
    }
    public static void putDataSourceKeys(Collection<Object> keys){
        dataSourceKeys.addAll(keys);
    }

    public static boolean containKey(DataSourceRouteEnums dataSourceRoutekey){
        return dataSourceKeys.contains(dataSourceRoutekey);
    }
}

 

  RoutingDataSourceConfig类

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class RoutingDataSourceConfig {


    @Bean("defaultMysql")
    @ConfigurationProperties("spring.datasource")
    @Lazy
    public DataSource defaultMysql(){
        return new DruidDataSource();
    }

    @Bean("specMysql")
    @ConfigurationProperties("spring.datasource-mysql")
    public DataSource specMysql(){
        return new DruidDataSource();
    }

    @Bean("dynamicDataSource")
    @Primary
    public DataSource dynamicDataSource(){
        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setDefaultTargetDataSource(defaultMysql());//默认数据源
        Map<Object,Object> targetDataSourceMap = new HashMap<>();
        targetDataSourceMap.put(DataSourceRouteEnums.DEFAULT_MYSQL,defaultMysql());//数据源1(默认数据源)
        targetDataSourceMap.put(DataSourceRouteEnums.SPEC_MYSQL,specMysql());//数据源2
        routingDataSource.setTargetDataSources(targetDataSourceMap);
        return routingDataSource;
    }
}

 

  DataSourceRouteEnums枚举类

public enum DataSourceRouteEnums {

    //默认数据源
    DEFAULT_MYSQL,

    //数据源2
    SPEC_MYSQL,
}

 

  DataSourceAnno注解类

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DataSourceAnno {


    DataSourceRouteEnums value();
}

 

  RoutingDataSourceAspect类

import com.tyj.study.dynamicdatasource.config.DataSourceRouteEnums;
import com.tyj.study.dynamicdatasource.config.RoutingDataSourceContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Slf4j
@Component
public class RoutingDataSourceAspect {


    @Before("@annotation(dataSourceAnno)")
    public void before(JoinPoint point, DataSourceAnno dataSourceAnno){
        DataSourceRouteEnums dataSourceKey = dataSourceAnno.value();
        if(!RoutingDataSourceContext.containKey(dataSourceKey)){
            log.info("RoutingDataSource AOP before : method[{}],datasource [{}] not exist, use default",point.getSignature(),dataSourceKey);
        }else{
            RoutingDataSourceContext.setThreadLocalDataSourceKey(dataSourceKey);
            log.info("RoutingDataSource AOP before : method[{}],use default [{}]",point.getSignature(),dataSourceKey);
        }
    }

    @Before("@annotation(dataSourceAnno)")
    public void after(JoinPoint point, DataSourceAnno dataSourceAnno){
        RoutingDataSourceContext.remove();
        log.info("RoutingDataSource AOP after : restore DataSource to [{}] in  [{}]",dataSourceAnno.value(),point.getSignature());

    }

}
DynamicDataSourceTestController类
import com.tyj.study.dynamicdatasource.aop.DataSourceAnno;
import com.tyj.study.dynamicdatasource.service.DynamicDataSourceService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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.RestController;

import java.util.List;
import java.util.Map;

@Api(tags = "动态数据源测试")
@RestController
@RequestMapping("dynamicdatasource")
public class DynamicDataSourceTestController {


    @Autowired
    private DynamicDataSourceService dynamicDataSourceService;

    @ApiOperation("默认数据库")
    @GetMapping("default")
    public List<Map> testDefault(){
        List<Map> maps = dynamicDataSourceService.listTables();
        return maps;

    }

    @ApiOperation("第二数据库")
    @GetMapping("spec")
    @DataSourceAnno(DataSourceRouteEnums.SPEC_MYSQL)
    public List<Map> testSpecMysql(){
        List<Map> maps = dynamicDataSourceService.listTables();
        return maps;
    }

}

 

 

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@MapperScan("com.tyj.study.dynamicdatasource.mapper")
public class StudyApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudyApplication.class, args);
    }

}

 

 

server.port=8080

spring.datasource.url=jdbc:mysql://XXX.XX.XXX.XXA:3306/lop_project?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
spring.datasource.username=wobuchifanqie
spring.datasource.password=wobuchifanqie123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource


spring.datasource-mysql.url=jdbc:mysql://XXX.XX.XXX.XXB:3306/mall?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
spring.datasource-mysql.username=wobuchifanqie
spring.datasource-mysql.password=wobuchifanqie1234
spring.datasource-mysql.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource-mysql.type=com.alibaba.druid.pool.DruidDataSource


mybatis.mapper-locations=classpath:/mapper/**/*.xml

 

 

非重点类

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tyj.study.dynamicdatasource.mapper.DynamicDataSourceMapper">

    <select id="listTables" parameterType="java.lang.String" resultType="java.util.Map">
        show tables
    </select>
    
</mapper>

 

 

public interface DynamicDataSourceMapper {

    List<Map> listTables();

}

public interface DynamicDataSourceService {

    List<Map> listTables();
}


@Service
public class DynamicDataSourceServiceImpl implements DynamicDataSourceService{

    @Autowired
    private DynamicDataSourceMapper dynamicDataSourceMapper;

    @Override
    public List<Map> listTables() {
         return dynamicDataSourceMapper.listTables();
    }
}

执行逻辑

  1- 项目启动

    1-1-启动类启动,自动扫描需要加载的类配置,这里需要手动排除DataSourceAutoConfiguration类;否则会报循环依赖异常

    1-2-加载多数据源配置类RoutingDataSourceConfig类;将多个数据源加载到IOC;具体细节去如下:

      (1) routingDataSource.setDefaultTargetDataSource(defaultMysql()); 配置默认数据源

      (2) routingDataSource.setTargetDataSources(targetDataSourceMap); 记录多个数据源,与此同时,多个数据源会被放在RoutingDataSourceContext类的Set<Object> dataSourceKeys中;

      (3)

  2-请求接口(自动切换数据源)

    2-1- 如果接口方法使用非默认数据源,加上@DataSourceAnno(DataSourceRouteEnums.SPEC_MYSQL);

    2-2-Controller收到请求时,AOP根据@DataSourceAnno(DataSourceRouteEnums.SPEC_MYSQL)匹配执行before通知,在before通知中,执行RoutingDataSourceContext.setThreadLocalDataSourceKey(dataSourceKey),即读取当前选择的数据源-放在ThreadLocal中;

    2-3-执行数据库请求;

    2-4-Controller返回请求结果后,AOP根据@DataSourceAnno(DataSourceRouteEnums.SPEC_MYSQL)匹配执行after通知,在after通知中,执行RoutingDataSourceContext.remove();,即移除数据源-从ThreadLocal中移除;

 

 

附录1-异常:循环依赖异常

The dependencies of some of the beans in the application context form a cycle:

   dynamicDataSourceTestController (field private com.tyj.study.dynamicdatasource.service.DynamicDataSourceService com.tyj.study.dynamicdatasource.config.DynamicDataSourceTestController.dynamicDataSourceService)
      ↓
   dynamicDataSourceServiceImpl (field private com.tyj.study.dynamicdatasource.mapper.DynamicDataSourceMapper com.tyj.study.dynamicdatasource.service.DynamicDataSourceServiceImpl.dynamicDataSourceMapper)
      ↓
   dynamicDataSourceMapper defined in file [D:\workspace\study\target\classes\com\tyj\study\dynamicdatasource\mapper\DynamicDataSourceMapper.class]
      ↓
   sqlSessionFactory defined in class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]
┌─────┐
|  dynamicDataSource defined in class path resource [com/tyj/study/dynamicdatasource/config/RoutingDataSourceConfig.class]
↑     ↓
|  defaultMysql defined in class path resource [com/tyj/study/dynamicdatasource/config/RoutingDataSourceConfig.class]
↑     ↓
|  org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
└─────┘

 

 

END

posted on 2020-09-10 11:26  我不吃番茄  阅读(390)  评论(0编辑  收藏  举报