【MySQL】SpringBoot实现MySQL读写分离

MySQL数据库安装请参考:CentOS7安装MySQL数据库及MariaDB
MySQL数据库主从复制环境搭建请参考:CentOS7实现MySQL读写分离环境搭建

读写分离实现

读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做。因此,一般来讲,读写分离有两种实现方式。第一种是依靠中间件(比如:MyCat),也就是说应用程序连接到中间件,中间件帮我们做SQL分离;第二种是应用程序自己去做分离。
读写分离

编码思想

所谓的手写读写分离,需要用户自定义一个动态的数据源,该数据源可以根据当前上下文中调用方法是读或者是写方法决定返回主库的链接还是从库的链接。这里我们使用Spring提供的一个代理数据源AbstractRoutingDataSource接口。
在这里插入图片描述该接口需要用户完善一个determineCurrentLookupKey抽象法,系统会根据这个抽象返回值决定使用系统中定义的数据源。

@Nullable
protected abstract Object determineCurrentLookupKey();

其次该类还有两个属性需要指定defaultTargetDataSourcetargetDataSources,其中defaultTargetDataSource需要指定为Master数据源。targetDataSources是一个Map需要将所有的数据源添加到该Map中,以后系统会根据determineCurrentLookupKey方法的返回值作为key从targetDataSources查找相应的实际数据源。如果找不到则使用defaultTargetDataSource指定的数据源。

实现步骤

添加pom依赖
<!--Junit测试-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

<!--MySQL & MyBatis-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
application.properties配置
server.port=8888
server.servlet.context-path=/

# 主节点数据源配置
spring.datasource.master.username=root
spring.datasource.master.password=root
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.master.jdbc-url=jdbc:mysql://CentOS:3306/dora?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false

# 从节点1数据源配置
spring.datasource.slave1.username=root
spring.datasource.slave1.password=root
spring.datasource.slave1.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave1.jdbc-url=jdbc:mysql://CentOSA:3306/dora?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false

# 从节点2数据源配置
spring.datasource.slave2.username=root
spring.datasource.slave2.password=root
spring.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave2.jdbc-url=jdbc:mysql://CentOSB:3306/dora?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false

# 从节点3数据源配置
spring.datasource.slave2.username=root
spring.datasource.slave2.password=root
spring.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave2.jdbc-url=jdbc:mysql://CentOSC:3306/dora?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false
配置数据源
/**
 * 该类是自定义数据源,由于必须将系统的数据源给替换掉。
 */
@Configuration
public class UserDefineDatasourceConfig {
    @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
    @ConfigurationProperties("spring.datasource.slave3")
    public DataSource slave3DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource proxyDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                      @Qualifier("slave1DataSource") DataSource slave1DataSource,
                                      @Qualifier("slave2DataSource") DataSource slave2DataSource,
                                      @Qualifier("slave3DataSource") DataSource slave3DataSource) {
        DynamicDataSource proxy = new DynamicDataSource();
        proxy.setDefaultTargetDataSource(masterDataSource);//设置默认数据源
        Map<Object, Object> mappedDataSource = new HashMap<>();
        mappedDataSource.put("master", masterDataSource);
        mappedDataSource.put("slave-01", slave1DataSource);
        mappedDataSource.put("slave-02", slave2DataSource);
        mappedDataSource.put("slave-03", slave3DataSource);

        proxy.setTargetDataSources(mappedDataSource); //注册所有数据源
        return proxy;
    }

    /**
     * 当自定义数据源,用户必须覆盖SqlSessionFactory创建
     *
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("proxyDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage("com.Dora.entities");
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/*.xml"));
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();

        return sqlSessionFactory;
    }

    /**
     * 当自定义数据源,用户必须覆盖SqlSessionTemplate,开启BATCH处理模式
     *
     * @param sqlSessionFactory
     * @return
     */
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
    }

    /***
     * 当自定义数据源,用户必须注入,否则事务控制不生效
     * @param dataSource
     * @return
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager(@Qualifier("proxyDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}
配置切面类
/**
 * 用户自定义切面,负责读取SlaveDB注解,并且在DBTypeContextHolder中设置读写类型
 */
@Aspect
@Order(0) //控制切面顺序,保证在事务切面之前运行切面
@Component
public class ServiceMethodAOP {
    private static final Logger logger = LoggerFactory.getLogger(ServiceMethodAOP.class);

    @Around("execution(* com.Dora.service..*.*(..))")
    public Object methodInterceptor(ProceedingJoinPoint pjp) {
        Object result = null;
        try {
            //获取当前的方法信息
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            Method method = methodSignature.getMethod();
            //判断方法上是否存在注解@SlaveDB
            boolean present = method.isAnnotationPresent(SlaveDB.class);

            OperType operType = null;
            if (!present) {
                operType = OperType.WRIRTE;
            } else {
                operType = OperType.READ;
            }
            OperTypeContextHolder.setOperType(operType);
            logger.debug("当前操作:" + operType);
            result = pjp.proceed();
            //清除线程变量
            OperTypeContextHolder.clear();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }
}
用到的其他类
动态数据源
/**
 * 该类属于代理数据源,负责负载均衡
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

    private String masterDBKey = "master";
    private List<String> slaveDBKeys = Arrays.asList("slave-01", "slave-02", "slave-03");

    private static final AtomicInteger round = new AtomicInteger(0);

    /**
     * 需要在该方法中,判断当前用户的操作是读操作还是写操作
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        String dbKey = null;
        OperType operType = OperTypeContextHolder.getOperType();
        if (operType.equals(OperType.WRIRTE)) {
            dbKey = masterDBKey;
        } else {
            //轮询返回  0 1 2 3 4 5 6
            int value = round.getAndIncrement();
            if (value < 0) {
                round.set(0);
            }
            Integer index = round.get() % slaveDBKeys.size();

            dbKey = slaveDBKeys.get(index);
        }
        logger.debug("当前的DBkey:" + dbKey);
        return dbKey;
    }
}
读写类型
/**
 * 写、读类型
 */
public enum OperatorTypeEnum {
    WRITE, READ;
}
记录操作类型
/**
 * 该类主要是用于存储,当前用户的操作类型,将当前的操作存储在当前线程的上下文中
 */
public class OPTypeContextHolder {
    private static final ThreadLocal<OperatorTypeEnum> OPERATOR_TYPE_THREAD_LOCAL = new ThreadLocal<>();
    public static void set(OperatorTypeEnum dbType) {
        OPERATOR_TYPE_THREAD_LOCAL.set(dbType);
    }

    public static OperatorTypeEnum get() {
        return OPERATOR_TYPE_THREAD_LOCAL.get();
    }
    public static void clear(){
        OPERATOR_TYPE_THREAD_LOCAL.remove();
    }
}
业务方法标记注解
/**
 * 该注解用于标注,当前用户的调用方法是读还是写
 */
@Retention(RetentionPolicy.RUNTIME) //表示运行时解析注解
@Target(value = {ElementType.METHOD})//表示只能在方法上加
public @interface SlaveDB { }
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Spring Boot可以通过配置数据源来实现MySQL分离。 1. 首先需要配置主数据源和从数据源,在application.properties中配置好主数据源的URL、用户名、密码等信息,并在从数据源中配置相同的信息。 2. 然后在配置类中配置数据源的负载均衡策略,例如采用轮询策略。 3. 最后在需要切换数据源的地方使用 @Transactional(readOnly = true) 设置当前事务为只事务,这样就会使用从数据源进行操作。 具体实现可以参考 Spring Boot 的文档或者第三方模块如 druid-spring-boot-starter。 ### 回答2: 在Spring Boot中实现MySQL分离可以通过以下步骤来完成: 1. 配置MySQL主从复制:首先,需要在服务器上设置好MySQL主从复制,确保主数据库和从数据库之间的数据同步。可以使用binlog日志来实现主从复制。 2. 配置数据源:在Spring Boot的application.properties(或application.yml)文件中,配置两个数据源,一个用于操作,一个用于操作。分别配置主数据库和从数据库的连接信息。 3. 设置数据源路由:使用Spring AOP(面向切面编程)和注解进行数据源的动态切换。可以定义一个切点,用于拦截数据库的访问操作,并根据具体的业务需求切换到对应的数据源。 4. 编分离的数据源配置类:在Spring Boot中,可以自定义一个分离的数据源配置类,用于管理数据源的切换。该类可以使用ThreadLocal来保存当前线程使用的数据源,然后根据具体的业务需求来选择具体的数据源。 5. 配置数据源切换的拦截器:在Spring Boot的配置文件中,配置AOP拦截器,将数据源的切换逻辑应用到具体的业务代码中。 6. 测试分离效果:可以编一些测试用例,测试操作是否成功切换到了对应的数据源。 需要注意的是,分离只能解决数据库的性能问题,并不能解决数据库的高可用问题。因此,在实际生产环境中,还需要考虑到主从数据库之间的数据同步延迟和故障切换等问题。 ### 回答3: 在Spring Boot中实现MySQL分离,可以采用以下步骤: 1. 引入相关依赖:需要在pom.xml文件中引入spring-boot-starter-data-jpa和mysql-connector-java相关依赖。 2. 配置数据源:在application.properties文件中配置主从数据源的连接信息。例如: ``` spring.datasource.url=jdbc:mysql://主数据库IP:主数据库端口/主数据库名称 spring.datasource.username=主数据库用户名 spring.datasource.password=主数据库密码 spring.datasource.slave.url=jdbc:mysql://从数据库IP:从数据库端口/从数据库名称 spring.datasource.slave.username=从数据库用户名 spring.datasource.slave.password=从数据库密码 ``` 3. 创建数据源和数据库连接池:通过@Configuration配置类,使用@Bean注解创建两个数据源和连接池的实例,分别代表主从数据库。例如: ``` @Configuration public class DataSourceConfig { // 主数据源 @Bean(name = "masterDataSource") @ConfigurationProperties(prefix = "spring.datasource") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } // 从数据源 @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } } ``` 4. 创建主从数据库的EntityManagerFactory:通过@Configuration配置类,使用@Primary和@Qualifier注解指定主从数据库分别对应的EntityManagerFactory。例如: ``` @Configuration @EnableJpaRepositories( basePackages = "com.example.repositories", entityManagerFactoryRef = "masterEntityManagerFactory", transactionManagerRef = "masterTransactionManager" ) public class MasterConfig { @Autowired @Qualifier("masterDataSource") private DataSource masterDataSource; @Primary @Bean(name = "masterEntityManagerFactory") public LocalContainerEntityManagerFactoryBean masterEntityManagerFactory(EntityManagerFactoryBuilder builder) { return builder .dataSource(masterDataSource) .packages("com.example.models") .build(); } @Primary @Bean(name = "masterTransactionManager") public PlatformTransactionManager masterTransactionManager(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(masterEntityManagerFactory(builder).getObject()); } } @Configuration @EnableJpaRepositories( basePackages = "com.example.repositories", entityManagerFactoryRef = "slaveEntityManagerFactory", transactionManagerRef = "slaveTransactionManager" ) public class SlaveConfig { @Autowired @Qualifier("slaveDataSource") private DataSource slaveDataSource; @Bean(name = "slaveEntityManagerFactory") public LocalContainerEntityManagerFactoryBean slaveEntityManagerFactory(EntityManagerFactoryBuilder builder) { return builder .dataSource(slaveDataSource) .packages("com.example.models") .build(); } @Bean(name = "slaveTransactionManager") public PlatformTransactionManager slaveTransactionManager(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(slaveEntityManagerFactory(builder).getObject()); } } ``` 5. 在需要取数据的地方进行选择:通过在JpaRepository接口上使用@Qualifier注解,指定使用主数据源还是从数据源。例如: ``` @Repository @Qualifier("masterEntityManagerFactory") public interface UserRepository extends JpaRepository<User, Long> { // ... } ``` 通过以上步骤,就可以在Spring Boot中实现MySQL分离,从而实现数据库的负载均衡和高可用性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@是小白吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值