【SpringBoot】SpringBoot + Mysql 读写分离最佳实践

7 篇文章 0 订阅
3 篇文章 0 订阅
本文详细介绍了如何在SpringBoot项目中配置数据库读写分离,包括YAML文件配置、使用枚举定义主从库、DruidDataSource的扩展、抽象RoutingDataSource的运用以及通过AOP进行数据源切换的实现。特别提到了依赖注入和注意事项。
摘要由CSDN通过智能技术生成

一.前言

  为了应对数据库服务器的压力,在数据库的架构上需要进行读写分离的架构,代码也需要支持读写分离。

二.实践操作

2.1  yml 配置

spring:
  datasource:
      master:
        url: jdbc:mysql://xxxxxxx:3306/xxxxxx?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useAffectedRows=true
        username: xxxx
        password: xxx
        driver-class-name: com.mysql.cj.jdbc.Driver
      #从库
      slave:
        url: jdbc:mysql://localhost:3306/xxxx?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useAffectedRows=true
        username: xxxxx
        password: xxxxx
        driver-class-name: com.mysql.cj.jdbc.Driver

2.2 枚举

public enum EDataSource {
    MASTER("master", "主库"),
    SLAVE("slave", "从库");

    private String code;
    private String desc;

    EDataSource(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

2.3  数据源Bean的初始化



import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Configuration
public class DataSourceConfigurer {

    public final static String MASTER_DATASOURCE = "master";
    public final static String SLAVE_DATASOURCE = "slave";

  

    @Bean(MASTER_DATASOURCE)
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DruidDataSource masterDataSource() {
        DruidDataSource build = DataSourceBuilder.create().type(DruidDataSource.class).build();
        log.info("配置主数据库:{}", build);
        return build;
    }

    @Bean(SLAVE_DATASOURCE)
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DruidDataSource slaveDataSource() {
        DruidDataSource build = DataSourceBuilder.create().type(DruidDataSource.class).build();
        log.info("配置从数据库:{}", build);
        return build;
    }

  

    /**
     * Primary 优先使用该Bean
     * DependsOn 先执行主从数据库的配置
     * Qualifier 指定使用哪个Bean
     *
     * @param masterDataSource
     * @param slaveDataSource
     * @return
     */
    @Bean
    @Primary
    @DependsOn(value = {MASTER_DATASOURCE, SLAVE_DATASOURCE})
    public DataSource routingDataSource(@Qualifier(MASTER_DATASOURCE) DruidDataSource masterDataSource,
                                        @Qualifier(SLAVE_DATASOURCE) DruidDataSource slaveDataSource) {
        if (StringUtils.isBlank(slaveDataSource.getUrl())) {
            log.info("没有配置从数据库,默认使用主数据库");
            return masterDataSource;
        }
        Map<Object, Object> map = new HashMap<>();
        map.put(DataSourceConfigurer.MASTER_DATASOURCE, masterDataSource);
        map.put(DataSourceConfigurer.SLAVE_DATASOURCE, slaveDataSource);
        RoutingDataSource routing = new RoutingDataSource();
        //设置动态数据源
        routing.setTargetDataSources(map);
        //设置默认数据源
        routing.setDefaultTargetDataSource(masterDataSource);
        log.info("主从数据库配置完成");
        return routing;
    }
}

2.4 继承 AbstractRoutingDataSource  这一步是关键

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

@Slf4j
public class RoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = RoutingDataSourceHolder.getDataSource();
        if(StringUtils.isEmpty(dataSource)){
            dataSource = EDataSource.MASTER.getCode();
        }
        log.info("使用数据源:{}", dataSource);
        return dataSource;
    }
}

2.5  数据源注解 DataSourceSwitch

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface DataSourceSwitch {
    EDataSource value() default EDataSource.MASTER;
}

2.6 Aop 切面实现数据源的前置设置

@Aspect
@Order(-1)// 保证该AOP在@Transactional之前运行
@Component
@Slf4j
public class DataSourceWithAspect {

    /**
     * 使用DataSourceWith注解就拦截
     * 
     */
    @Pointcut("@annotation(com.xxx.record.Interceptor.datasource.DataSourceSwitch)")
    public void doPointcut() {

    }

    /**
     * 方法前,为了在事务前设置
     */
    @Before("doPointcut()")
    public void doBefore(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 获取注解对象
        DataSourceSwitch dataSource = method.getAnnotation(DataSourceSwitch.class);
        if (dataSource == null) {
            //方法没有就获取类上的
            dataSource = method.getDeclaringClass().getAnnotation(DataSourceSwitch.class);
        }
        String key = (dataSource == null?EDataSource.MASTER.getCode():dataSource.value().getCode());
        RoutingDataSourceHolder.setDataSource(key);
    }

    @After("doPointcut()")
    public void doAfter(JoinPoint joinPoint) {
        RoutingDataSourceHolder.clearDataSource();
    }
}

三.注意

  1.有一些包需要自己依赖注入

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

上善若水-学者至上

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

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

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

打赏作者

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

抵扣说明:

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

余额充值