• 项目总结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-基本sql命令
    Java for LeetCode 203 Remove Linked List Elements
    Java for LeetCode 202 Happy Number
    Java for LeetCode 201 Bitwise AND of Numbers Range
    Java for LeetCode 200 Number of Islands
    Java for LeetCode 199 Binary Tree Right Side View
    Java for LeetCode 198 House Robber
    Java for LeetCode 191 Number of 1 Bits
    Java for LeetCode 190 Reverse Bits
    Java for LeetCode 189 Rotate Array
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/13644679.html
Copyright © 2020-2023  润新知