SpringBoot 代码实现读写分离

337 阅读1分钟

springboot读写分离

1.配置数据源

spring.datasource.master.username=root
spring.datasource.master.password= 123456
spring.datasource.master.jdbcUrl= jdbc:mysql://192.168.0.155:3306/testdb
spring.datasource.master.driver-class-name= com.mysql.cj.jdbc.Driver
​
spring.datasource.slave1.username=root
spring.datasource.slave1.password=1qaz!QAZ2wsx
spring.datasource.slave1.jdbcUrl= jdbc:mysql:///192.168.0.155:3306/firstdb
spring.datasource.slave1.driver-class-name= com.mysql.cj.jdbc.Driver

2.读取配置文件

@Configuration
@PropertySource(value= {"file:./config/db.properties", "classpath:db.properties"},ignoreResourceNotFound = true)
@ConfigurationProperties(prefix = "db")
@Data
public class DBConfiguration {
    private String username;
    private String password;
    private String url;
    private String driverClassName;
​
    @Bean("db1")
    public DataSource getDataSource() {
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDriverClassName(this.driverClassName);
        hikariDataSource.setJdbcUrl(this.url);
        hikariDataSource.setUsername(this.username);
        hikariDataSource.setPassword(this.password);
        return hikariDataSource;
    }
​
}
  1. 多数据源配置
@Configuration
public class DataSourceConfig {
​
    @Bean
    public DataSource myRoutingDataSource(@Qualifier("db1") DataSource masterDataSource,
                                          @Qualifier("db2") DataSource slave1DataSource,
                                          @Qualifier("db3") 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;
    }
​
}

4.MyBatis配置

@EnableTransactionManagement
@Configuration
public class MyBatisConfig {
    @Resource(name = "myRoutingDataSource")
    private DataSource myRoutingDataSource;
​
    /**
     * 扫描mybatis下的xml文件
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }
​
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(myRoutingDataSource);//因为Spring容器中如今有4个数据源,因此咱们须要为事务管理器和MyBatis手动指定一个明确的数据源。
    }
}

5.定义一个枚举来表明这三个数据源

public enum DBTypeEnum {
    MASTER,SLAVE1,SLAVE2
}

6.经过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");
        }
    }
​
}

7.获取路由key

public class MyRoutingDataSource extends AbstractRoutingDataSource {
    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }
}

8.全部的查询都走从库,插入/修改/删除走主库。咱们经过方法名来区分操做类型(CRUD)

@Aspect
@Component
public class DataSourceAop {
    /**
     * 只读:
     * 不是Master注解的对象或方法  && select开头的方法  ||  get开头的方法
     */
    @Pointcut("!@annotation(com.microservice.readwriteseparat.annotation.Master) " +
            "&& (execution(* com.microservice.readwriteseparat.service..*.select*(..)) " +
            "|| execution(* com.microservice.readwriteseparat.service..*.get*(..)))")
    public void readPointcut() {
​
    }
​
    /**
     * 写:
     * Master注解的对象或方法 || insert开头的方法  ||  add开头的方法 || update开头的方法
     * || edlt开头的方法 || delete开头的方法 || remove开头的方法
     */
    @Pointcut("@annotation(com.microservice.readwriteseparat.annotation.Master) " +
            "|| execution(* com.microservice.readwriteseparat.service..*.insert*(..)) " +
            "|| execution(* com.microservice.readwriteseparat.service..*.add*(..)) " +
            "|| execution(* com.microservice.readwriteseparat.service..*.update*(..)) " +
            "|| execution(* com.microservice.readwriteseparat.service..*.edit*(..)) " +
            "|| execution(* com.microservice.readwriteseparat.service..*.delete*(..)) " +
            "|| execution(* com.microservice.readwriteseparat..*.remove*(..))")
    public void writePointcut() {
​
    }
​
    @Before("readPointcut()")
    public void read() {
        DBContextHolder.slave();
    }
​
    @Before("writePointcut()")
    public void write() {
        DBContextHolder.master();
    }
}

9.定义一个注解 标记使用主库

public @interface Master {
}