springboot+mysql读写分离(一)

系统在设计之初就应该对业务数据的耦合松紧进行考量,从而进行垂直分库、垂直分表,使数据层架构清晰明了。 若非必要,无需进行水平切分,应先从缓存技术着手降低对数据库的访问压力。如果缓存使用过后,数据库访问量还是非常大,可以考虑数据库读、写分离原则。若当前数据库压力依然大,且业务数据持续增长无法估量,最后可考虑水平分库、分表,单表拆分数据控制在1000万以内。
读写分离?读一个库,写一个库,依赖于mysql主从模式,可理解成是分库的一种业务场景(分库策略)。
读写分离可以通过sping的路由数据源和切面方式实现,也可以通过数据库中间件的方式实现,如sharding方式、MyCat方式,下面实战spring路由数据源+切面的方式,直接上代码。
pom.xml文件

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

application.yml文件

server:
  port: 9001

spring:
  datasource:
    master:
      jdbc-url: jdbc:mysql://localhost:3306/write
      username: root   # 读写账户
      password: 
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave1:
      jdbc-url: jdbc:mysql://localhost:3306/read1
      username: root   # 只读账户
      password: 
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave2:
      jdbc-url: jdbc:mysql://localhost:3306/read2
      username: root   # 只读账户
      password: 
      driver-class-name: com.mysql.cj.jdbc.Driver

读写分离核心代码

注解
public @interface Master {
}
切面
@Aspect
@Component
public class DataSourceAop {

    @Pointcut("!@annotation(com..annotation.Master) " +
            "&& (execution(* com.service..*.find*(..)) " +
            "|| execution(* com.service..*.get*(..)))")
    public void readPointcut() {

    }

    @Pointcut("@annotation(com.ldc.springboot_mysql1.annotation.Master) " +
            "|| execution(* com.service..*.insert*(..)) " +
            "|| execution(* com.service..*.add*(..)) " +
            "|| execution(* com.service..*.update*(..)) " +
            "|| execution(* com.service..*.edit*(..)) " +
            "|| execution(* com.service..*.delete*(..)) " +
            "|| execution(* com.service..*.remove*(..))")
    public void writePointcut() {

    }

    @Before("readPointcut()")
    public void read() {
        DataSourceContextHolder.setSlave();
    }

    @Before("writePointcut()")
    public void write() {
        DataSourceContextHolder.setMaster();
    }

}
枚举
public enum DataSourceTypeEnum {

    MASTER, SLAVE1, SLAVE2;

}
数据源
@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 routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slave1DataSource") DataSource slave1DataSource,
                                          @Qualifier("slave2DataSource") DataSource slave2DataSource) {
        RoutingDataSource routingDataSource = new RoutingDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DataSourceTypeEnum.SLAVE1, slave1DataSource);
        targetDataSources.put(DataSourceTypeEnum.SLAVE2, slave2DataSource);
        routingDataSource.setDefaultTargetDataSource(masterDataSource);
        routingDataSource.setTargetDataSources(targetDataSources);
        return routingDataSource;
    }

}
事务
@EnableTransactionManagement
@Configuration
public class MyBatisConfig {

    @Resource(name = "routingDataSource")
    private DataSource routingDataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(routingDataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(routingDataSource);
    }
}
数据源上下文
public class DataSourceContextHolder {

    private static final ThreadLocal<DataSourceTypeEnum> contextHolder = new ThreadLocal<>();

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

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

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

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

    public static void setSlave() {
        //  轮询
        int index = counter.getAndIncrement() % 2;
        if (counter.get() > 9999) {
            counter.set(-1);
        }
        if (index == 0) {
            set(DataSourceTypeEnum.SLAVE1);
            System.out.println("切换到slave1");
        }else {
            set(DataSourceTypeEnum.SLAVE2);
            System.out.println("切换到slave2");
        }
    }

}
路由数据源
public class RoutingDataSource extends AbstractRoutingDataSource {

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

}

业务类及测试类

@Data
public class Student {

    private int id;

    private int num;

    private String name;

}
public interface StudentMapper {

    int insert(Student student);

    List<Student> findAll();
}
@Service
public class StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Transactional
    public int save(Student student) {
        return studentMapper.insert(student);
    }

    @Master
    public int insert(Student student) {
        return studentMapper.insert(student);
    }

    public List<Student> findAll() {
        return studentMapper.findAll();
    }

}
@Test
    public void testWrite() {
        Student student = new Student();
        student.setId(001);
        student.setNum(001);
        student.setName("caocao");
        studentService.insert(student);
    }

    @Test
    public void testRead() {
        for (int i = 0; i < 5; i++) {
            List<Student> studentList = studentService.findAll();
            System.out.println(studentList.get(0).getName());
        }
    }

    @Test
    public void testSave() {
        Student student = new Student();
        student.setName("testSave");
        studentService.save(student);
    }

测试结果
日志如:
切换到master
切换到slave1
切换到slave2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值