spring动态数据源实现读写分离

一、实现方式方式

读写分离要做的事情就是对于一条Sql语句,该去那个数据库执行;至于谁来做这件时间,无非有两种方式

  1. 中间件:我们可以使用中间来帮助我们路由这些SQL语句,你如说使用MyCat,MyCat启动以后就好像启动可以一个数据库一样,但是他不做数据库的的事情,他负责将不同的SQL语句发送到不同的数据库去执行;
  2. 程序自己做:就是在程序执行某个方法的时候,通过切面的方式,进行修改这个方法所需要的数据连接。

在这里我们采用后者,因为前者,主要使用spring的路由数据源和AOP完成。

二、配置方式

1. 配置数据源
spring:
  datasource:
    master:
      jdbc-url: jdbc:mysql://192.168.141.128:3306/my_db
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave1:
      jdbc-url: jdbc:mysql://192.168.141.129:3306/my_db
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave2:
      jdbc-url: jdbc:mysql://192.168.141.130:3306/my_db
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver
2. 配置数据链接
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave1")
    public DataSource slave1DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave2")
    public DataSource slave2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slave1DataSource") DataSource slave1DataSource,
                                          @Qualifier("slave2DataSource") DataSource slave2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
        targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        // 配置默认数据源
        myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;
    }
}
3. MyBatis配置
@EnableTransactionManagement
@Configuration
public class MyBatisConfig {

    @Resource(name = "myRoutingDataSource")
    private DataSource myRoutingDataSource;
	/**
     * 配置sqlSession工厂
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        sqlSessionFactoryBean.setMapperLocations();
        // 如果使用mapper.xml文件则需要配置此项
        // sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

	/**
     * 配置事务
     * @return
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(myRoutingDataSource);
    }
}
4. 使用枚举类型用做路由key
public enum DBTypeEnum {
    MASTER, SLAVE1, SLAVE2;
}
5. 通过ThreadLocal将数据源设置到每个线程上下文中
public class DBContextHolder {
    private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();

    private static final AtomicInteger counter = new AtomicInteger(-1);

    public static void set(DBTypeEnum dbType) {
        contextHolder.set(dbType);
    }

    public static DBTypeEnum get() {
        return contextHolder.get();
    }

    public static void master() {
        set(DBTypeEnum.MASTER);
        System.out.println("切换到master");
    }

    public static void slave() {
        //  采用轮询的方式,轮流使用数据连接
		int index = counter.getAndIncrement() % 2;
        if (counter.get() > 9999) {
            counter.set(-1);
        }
        if (index == 0) {
            set(DBTypeEnum.SLAVE1);
            System.out.println("切换到slave1");
        } else {
            set(DBTypeEnum.SLAVE2);
            System.out.println("切换到slave2");
        }
    }
}
6. 继承AbstractRoutingDataSource,获取路由key
public class MyRoutingDataSource extends AbstractRoutingDataSource {

    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }

}
7. 使用aop,设置路由key

@Aspect
@Component
public class DataSourceAop {

    /**
     * 设置读切面
     * 读取某包下以select和get开头,以及带有Master注解的方法
     */
    @Pointcut("!@annotation(com.simple.mysql.router.config.Master) " +
            "&& (execution(* com.simple.mysql.router.service..*.select*(..)) " +
            "|| execution(* com.simple.mysql.router.service..*.get*(..)))")
    public void readPointcut() {}

    /**
     * 设置写切面
     * 读取某包下以insert、add、update、edit、delete、remove开头的方法,以及使用Master注解的方法
     */
    @Pointcut("@annotation(com.simple.mysql.router.config.Master) " +
            "|| execution(* com.simple.mysql.router.service..*.insert*(..)) " +
            "|| execution(* com.simple.mysql.router.service..*.add*(..)) " +
            "|| execution(* com.simple.mysql.router.service..*.update*(..)) " +
            "|| execution(* com.simple.mysql.router.service..*.edit*(..)) " +
            "|| execution(* com.simple.mysql.router.service..*.delete*(..)) " +
            "|| execution(* com.simple.mysql.router.service..*.remove*(..))")
    public void writePointcut() {}

    @Before("readPointcut()")
    public void read() {
        DBContextHolder.slave();
    }

    @Before("writePointcut()")
    public void write() {
        DBContextHolder.master();
    }

    /**
     * 另一种写法:if...else...  判断哪些需要读从数据库,其余的走主数据库
     */
//    @Before("execution(* com.cjs.example.service.impl.*.*(..))")
//    public void before(JoinPoint jp) {
//        String methodName = jp.getSignature().getName();
//
//        if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
//            DBContextHolder.slave();
//        }else {
//            DBContextHolder.master();
//        }
//    }

}
8. 补充一个Master注解
public @interface Master {
}

到这里,配置就结束了,可以启动项目看一下了,不过前提是配置过主从复制的哦。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值