zoukankan      html  css  js  c++  java
  • spring boot多数据源的动态切换

    添加配置文件

    spring:
      jpa:
        properties:
          hibernate:
            session_factory:
              statement_inspector: com.gutousu.dynamic_switch_data_source.config.StatementInspectorImpl
      datasource:
        primary:
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://0.0.0.0:1/gutousu_dynamic_switch_data_source_test_1?useSSL=true&verifyServerCertificate=false&useUnicode=true&characterEncoding=UTF8
          username: 
          password: 
    
        secondary:
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://0.0.0.0:2/gutousu_dynamic_switch_data_source_test_2?useSSL=true&verifyServerCertificate=false&useUnicode=true&characterEncoding=UTF8
          username: 
          password: 
    

      

    这个是拦截最终执行的sql语句用的,用于测试,观察事务

    spring:
      jpa:
        properties:
          hibernate:
            session_factory:
              statement_inspector: com.gutousu.dynamic_switch_data_source.config.StatementInspectorImpl
    

      

    这两个是数据源的配置

    datasource:
        primary:
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://0.0.0.0:1/gutousu_dynamic_switch_data_source_test_1?useSSL=true&verifyServerCertificate=false&useUnicode=true&characterEncoding=UTF8
          username:
          password:
    
        secondary:
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://0.0.0.0:2/gutousu_dynamic_switch_data_source_test_2?useSSL=true&verifyServerCertificate=false&useUnicode=true&characterEncoding=UTF8
          username:
          password:
    

      

    添加数据源配置类

    @Setter
    @ConfigurationProperties(prefix = "spring.datasource")
    @Configuration
    public class DataSourceConfig
    {
        private static final ThreadLocal<String> datasourceHolder = new ThreadLocal<>();
    
        private HikariDataSource primary;
        private HikariDataSource secondary;
    
        private static final AbstractRoutingDataSource abstractRoutingDataSource = new AbstractRoutingDataSource()
        {
            @Override
            protected Object determineCurrentLookupKey()
            {
                return datasourceHolder.get();
            }
        };
    
        public static void setDataSource(String sourceName)
        {
            datasourceHolder.set(sourceName);
        }
    
        public static void clearDataSource()
        {
            datasourceHolder.remove();
        }
    
        @Bean
        public DataSource dataSource()
        {
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DataSourceEnum.PRIMARY.getValue(), primary);
            targetDataSources.put(DataSourceEnum.SECONDARY.getValue(), secondary);
    
            abstractRoutingDataSource.setTargetDataSources(targetDataSources);
            abstractRoutingDataSource.setDefaultTargetDataSource(primary);
            return abstractRoutingDataSource;
        }
    
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource)
        {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    

      

    每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本

    private static final ThreadLocal<String> datasourceHolder = new ThreadLocal<>();
    

      

    通过ConfigurationProperties注解读取配置文件中的数据库连接信息,将他们放到这里

    private HikariDataSource primary;
    private HikariDataSource secondary;
    

      

    获取连接信息的类,实现determineCurrentLookupKey方法获取数据源的key

    private static final AbstractRoutingDataSource abstractRoutingDataSource = new AbstractRoutingDataSource()
        {
            @Override
            protected Object determineCurrentLookupKey()
            {
                return datasourceHolder.get();
            }
        };
    

      

    用两个静态方法,切换数据源,和清除key信息

    public static void setDataSource(String sourceName)
        {
            datasourceHolder.set(sourceName);
        }
    
        public static void clearDataSource()
        {
            datasourceHolder.remove();
        }
    

      

    将数据源放到一个Map中

    然后将Map放到abstractRoutingDataSource中

    返回abstractRoutingDataSource到spring的bean容器中

    @Bean
        public DataSource dataSource()
        {
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DataSourceEnum.PRIMARY.getValue(), primary);
            targetDataSources.put(DataSourceEnum.SECONDARY.getValue(), secondary);
    
            abstractRoutingDataSource.setTargetDataSources(targetDataSources);
            abstractRoutingDataSource.setDefaultTargetDataSource(primary);
            return abstractRoutingDataSource;
        }
    

      

    获取刚刚添加的bean,将其放到事务中,再返回到spring的bean容器中

    @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource)
        {
            return new DataSourceTransactionManager(dataSource);
        }
    

      

    添加一个枚举

    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public enum DataSourceEnum
    {
        PRIMARY("primary"),
        SECONDARY("secondary");
    
        private String value;
    }
    

      

    添加一个注解

    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource
    {
        DataSourceEnum value();
    }
    

      

    添加一个aop,拦截方法,切换数据源

    @Aspect
    @Order(-1)
    @Component
    public class DataSourceAop
    {
        @Before("@annotation(dataSource)")
        public void before(JoinPoint joinPoint, DataSource dataSource)
        {
            DataSourceConfig.setDataSource(dataSource.value().getValue());
        }
    
        @After("@annotation(dataSource)")
        public void after(JoinPoint joinPoint, DataSource dataSource)
        {
            DataSourceConfig.clearDataSource();
        }
    }
    

      

    这里是为了,在事务开启之前切换数据源

    @Order(-1)
    

      

    实现一下StatementInspector接口,这个接口会拦截所有将要执行的sql语句,用于测试事务

    @Component
    public class StatementInspectorImpl implements StatementInspector
    {
    
        @Override
        public String inspect(String sql)
        {
            return sql;
        }
    }
    

      

    --------------------------------------------------------------------------------------------------------------

    最后添加一些测试

    两个方法测试的接口

    public interface ITestService
    {
        String t1();
        String t2();
    }
    

      

    实现方法测试接口,在方法上加上 aop直接

    @Service
    public class TestService implements ITestService
    {
        @Autowired
        private IUserRepository userRepository;
    
        @DataSource(value = DataSourceEnum.SECONDARY)
        //@Transactional
        public String t1()
        {
            user user = userRepository.save(new user("张三",21));
            System.out.print("");
            return "";
        }
    
        @DataSource(value = DataSourceEnum.PRIMARY)
        @Transactional
        public String t2()
        {
            user user = userRepository.save(new user("张三",21));
            System.out.print("");
            return "";
        }
    }
    

      

    添加一个controller

    @RestController
    @RequestMapping("/test")
    public class TestController
    {
        @Autowired
        private ITestService testService;
    
        @GetMapping("/t1")
        public String t1()
        {
            testService.t1();
            return "";
        }
    
        @GetMapping("/t2")
        public String t2()
        {
            testService.t2();
            return "";
        }
    }
    

      

    测试一下没有事务的方法一

    被aop拦截到了,切换到了注解中指定的数据源

     进入方法,执行添加方法

     这里直接执行了添加的sql语句

    数据库中有数据了

    测试一下有事务的方法二

    被aop拦截到了,并切换到了另一个数据源

    进入方法添加数据

     这里执行完添加了,但是没有执行sql语句,说事务没有提交,只在事务中进行了添加

     最后结束方法的时候执行了添加的sql语句

     完结!撒花!

    栗子:https://github.com/gutousu/dynamic_switch_data_source

  • 相关阅读:
    用户、群组、权限
    分页提纲
    网页分页显示
    OMR数据查询
    ORM增删改查询例题
    人工智能将推动云存储和数据服务的创新
    如何在智能家居中提高IoT安全性?
    云计算是物联网的重要支柱
    一个高薪的码农,应具备的8种能力
    如何跨越比特币的认知障碍?
  • 原文地址:https://www.cnblogs.com/gutousu/p/10056869.html
Copyright © 2011-2022 走看看