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 {
}

到這裏,配置就結束了,可以啓動項目看一下了,不過前提是配置過主從複製的哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章