springBoot实现动态切换数据源,完成读写分离

前置: 这里说明一下动态数据源与多数据源,多数据源一般是配置多个数据源,然后然后通过mybatis配置把不同库的对应mapper放在不同的包,然后不同的库的mapper对应不同的DataSource。动态切换数据源一般是针对我们的需要将DataSource切换到对应的数据库。前者是写死的同一个mapper或者同一个function固定对应一个库,后者可以根据业务需求将同一个mapper和function都可以操作不同的库。例如多数据源可以在一个项目中实现多数据库,多业务,读写分离(sql层面,固定的查从库,写主库),相比多数据源,动态数据源同样可以实现,同时动态数据源可以更好的实现读写分离,可以做到同一个查询方法既可以做到查从库,同时在处理其他业务是查主库(具体的开始看业务需求) ##有不同看法的欢迎品论留言

1.开发环境
eclipse+jdk1.8+maven+mysql

2.pom配置 这里主要列出了数据库连接以及AOP的包

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
        </dependency>
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

3.application.yml 配置文件

spring:
  datasource: # 多数据源
    # type: com.alibaba.druid.pool.DruidDataSource
    # 主库
    master:
      type: com.alibaba.druid.pool.DruidDataSource
      url: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8
      username: root
      password: 111111
      driver-class-name: com.mysql.cj.jdbc.Driver
      # 配置初始值
      initial-size: 1
      min-idle: 1
      max-active: 20
      # 获取连接等待超时时间
      max-wait: 6000
      # 监控关闭空闲连接时间间隔
      time-between-eviction-runs-millis: 60000
      # 每个连接池最小的生命周期
      min-evictable-idle-time-millis: 360000
      validation-query: SELECT 1
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      remove-abandoned: true
      remove-abandoned-timeout: 1800
    # 从库
    slave:
      type: com.alibaba.druid.pool.DruidDataSource
      # 注意不是jdbcUrl, Durid是url
      url: jdbc:mysql://localhost:3306/testSlave?useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8
      username: root
      password: 111111
      driver-class-name: com.mysql.cj.jdbc.Driver
      # 配置初始值
      initial-size: 1
      min-idle: 1
      max-active: 20
      # 获取连接等待超时时间
      max-wait: 6000
      # 监控关闭空闲连接时间间隔
      time-between-eviction-runs-millis: 60000
      # 每个连接池最小的生命周期
      min-evictable-idle-time-millis: 360000
      validation-query: SELECT 1
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      remove-abandoned: true
      remove-abandoned-timeout: 1800    
 
mybatis:
  mapper-locations: classpath*:mapper/*Mapper.xml
  type-aliases-package: com.***.entity #按自己包名来
  check-config-location: true
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4.继承AbstractRoutingDataSource 抽象类,实现动态获取数据源

public class DataSourceRouter extends AbstractRoutingDataSource {

    private static String dataSourceconfig = "master"; //默认主库
    @Override
    protected Object determineCurrentLookupKey() {
//        log.info(" 当前数据源: " + DataSourceContextHolder.getCurrentDataSource());
//        return DataSourceContextHolder.getCurrentDataSource();
        return dataSourceconfig;
    }
    
    
	public static void setMater() {
		dataSourceconfig = "master";
		System.out.println("设置为主库");
	}
	public static void setSlave() {
		dataSourceconfig = "slave";
		System.out.println("设置为从库");
	}
}
@Configuration
public class DataSourceConfig {


    /***
     * 注意这里用的 Druid 连接池
     */
    @Bean(name = "dbMaster")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource dbMaster() {
//        log.info("master数据源");
        return new DruidDataSource();
    }

    @Bean(name = "dbSlave")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource dbSlave() {
//        log.info("slave数据源");
        return new DruidDataSource();
    }
 
    /***
     * @Primary: 相同的bean中,优先使用用@Primary注解的bean.
     * @Qualifier:: 这个注解则指定某个bean有没有资格进行注入。
     */
    @Primary
    @Bean(name = "dataSourceRouter") // 对应Bean: DataSourceRouter
    public DataSource dataSourceRouter(@Qualifier("dbMaster") DataSource master, @Qualifier("dbSlave") DataSource slave) {
        DataSourceRouter dataSourceRouter = new DataSourceRouter();

        //配置多数据源
        Map<Object, Object> map = new HashMap<>(5);
        map.put("master", master);    // key需要跟ThreadLocal中的值对应
        map.put("slave", slave);
     
        // master 作为默认数据源
        dataSourceRouter.setDefaultTargetDataSource(master);
        dataSourceRouter.setTargetDataSources(map);
        return dataSourceRouter;
    }

    // 注入动态数据源 DataSourceTransactionManager 用于事务管理(事务回滚只针对同一个数据源)
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("dataSourceRouter") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

5.使用AOP实现动态切换数据源

@Aspect
@Order(1) // 数据源的切换要在数据库事务之前, 设置AOP执行顺序(需要在事务之前,否则事务只发生在默认库中, 数值越小等级越高)
@Component
public class DataSourceAspect {

	private Logger log = LoggerFactory.getLogger(this.getClass());
	// 切点, 注意这里是在service层(具体是配置dao层还是service层需要看自己业务,原则就是当前业务需要在主库执行还是从库执行的判断依据在哪)
	@Pointcut("execution(* com.***.service.impl..*.*(..)))")
	public void aspect() {
	}

	@Before("aspect()")
	private void before(JoinPoint point) {
		//进入切面
		String method = point.getSignature().getName();//当前切入的方法名  
	//	point.getSignature().getDeclaringType().getName();//当前切入的class
		  
//		if (method.startsWith("query") || method.startsWith("select") || method.startsWith("get")
//				|| method.startsWith("find") || method.startsWith("read")) { //根据自己业务做判断主库从库切换
		DataSourceRouter.setMater();//设置为当前使用主库
//		DataSourceRouter.setSlave();//设置为当前使用从库
		}
	}
	// ----------


	// 切面结束, 重置线程变量 ,
	@After("aspect()")
	public void after(JoinPoint joinPoint) { 
		//切面结束 
	}


}

最后启动类上面需要加上
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })

需要注意的是当service里面的方法存在事务时@Transactional ,会先获取数据源,后进入切面,开始事务后无法在切换数据源

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值