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

posted on 2018-12-03 09:54  骨头酥  阅读(1482)  评论(0编辑  收藏  举报

导航