zoukankan      html  css  js  c++  java
  • 项目总结68:Springboot集成动态数据源示例

    项目总结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:workspacestudy	argetclassescom	yjstudydynamicdatasourcemapperDynamicDataSourceMapper.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

  • 相关阅读:
    mysql面试题
    Zookeeper与Kafka基础概念和原理
    Docker资源限制
    企业级仓库harbor搭建
    基于容器制作镜像
    docker基础学习(一)
    docker往阿里云推镜像和打包镜像
    Dockfile制作镜像
    算法Sedgewick第四版-第1章基础-006一封装输出(文件)
    算法Sedgewick第四版-第1章基础-005一封装输入(可以文件,jar包里的文件或网址)
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/13644679.html
Copyright © 2011-2022 走看看