程序员的资源宝库

网站首页 > gitee 正文

数据源读写分离(数据库读写分离怎么同步数据)

sanyeah 2024-04-01 11:33:06 gitee 5 ℃ 0 评论

以springboot2.x+mybatis+mysql+HikariCP为例,记录下自己的开发记录。

说点废话

从四种读写分离的方式看到了读写分离有四种实现方式。
我这里使用的是第三种方式实现的。目前实现的是一个主(Master),两个从(Slave)。废话少说,讲下我的思路。

  • 定义四个数据源,为什么是四个呢? Master1 ,Slave2,统一的数据源*1(一下称为DynamicDatasource,这个数据源是其他三个数据源的总和,可以执行它使用哪个数据源(Master,Slave1,Slave2)进行操作).
  • 定义一个DataSourceHolder,存放的是三个数据源(Master,Slave1,Slave2),可以按照条件去获取指定类型的数据源之一。
  • 将DynamicDataSource替换掉SpringBoot默认提供的数据源。
  • 在方法上使用注解或者使用某一类方法调用前指定使用的数据源。

上代码

  • 在pom.xml中加上如下内容。
<!--- mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.1</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- HikariCP 数据库连接池-->
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.3.0</version>
</dependency>
  • 我们首先定义好三个特定的数据源Master*1,Slave*2
public enum DBTypeEnum {
    /**
     * 主数据源
     */
    Master,
    /**
     * 从数据源1
     */
    Slave1,
    /**
     * 从数据源2
     */
    Slave2
}
  • 然后,设置DataSourceHolder。这样我们就定义好了数据源的调用的模型了。
@Slf4j
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);
        log.debug("使用的数据源是: master");
    }

    public static void slave() {
        int index = counter.getAndIncrement() % 2;
        if (counter.get() > 99) {
            counter.set(-1);
        }
        if (index == 0) {
            set(DBTypeEnum.Slave1);
            log.debug("使用的数据源是: slave1");
        } else {
            set(DBTypeEnum.Slave2);
            log.debug("使用的数据源是: slave2");
        }
    }
}
  • 设置动态数据源,可以指定上面的任何一种数据源去真正的操作数据库。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource  extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }
}
  • 创建四种数据源的实例,ConfigurationProperties注解中的配置项是在application.propreties中配置的。
@Configuration
public class DynamicDataSourceConfig {


    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public HikariDataSource masterDataSource() {
        return new HikariDataSource();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave1")
    public HikariDataSource slave1DataSource() {
        return new HikariDataSource();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave2")
    public HikariDataSource slave2DataSource() {
        return new HikariDataSource();
    }

    @Bean
    public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slave1DataSource") DataSource slave1DataSource,
                                        @Qualifier("slave2DataSource") DataSource slave2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>(3);
        // 设置所有的数据源
        targetDataSources.put(DBTypeEnum.Master, masterDataSource);
        targetDataSources.put(DBTypeEnum.Slave1, slave1DataSource);
        targetDataSources.put(DBTypeEnum.Slave2, slave2DataSource);

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 将写库设置为默认的数据源
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }   
}
  • 指定我们使用的数据源配置,url,username,password等。
spring.datasource.master.jdbc-url=jdbc:mysql://10.1.14.177:3306/dor_human?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
spring.datasource.master.username=root
spring.datasource.master.password=123456
#spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.slave1.jdbc-url=jdbc:mysql://10.1.14.177:3306/dor_human?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
spring.datasource.slave1.username=root
spring.datasource.slave1.password=123456
#spring.datasource.slave1.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.slave2.jdbc-url=jdbc:mysql://10.1.14.177:3306/dor_human?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
spring.datasource.slave2.username=root
spring.datasource.slave2.password=123456
#spring.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver
  • 这是我们要替换掉springboot默认的数据源
@EnableTransactionManagement
@Configuration
@Slf4j
public class MyBatisConfig {

    @Autowired
    private DataSource dynamicDataSource;

    /**
     * SqlSessionFactory
     *
     * @return SqlSessionFactory
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage("com.fxb.doraemon.human.entity");
        sqlSessionFactoryBean.setConfigLocation(
                new PathMatchingResourcePatternResolver().getResource("classpath:mapper/mybatis-config.xml"));
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml"));
        log.debug("初始化SqlSessionFactory");
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 事务管理器
     *
     * @return PlatformTransactionManager
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}

好了,这样我们的读写分离的准备工作就做好了,接下来要做的就是使用了,这里我们使用AOP的方式,来规定什么时候使用什么数据源。

@Aspect
@Component
@Slf4j
public class DataSourceAop {

    /**
     * 织入读库数据源
     */
    @Pointcut("execution(public * com.fxb.doraemon.human.service..*.get*(..)) " +
            "|| execution(public * com.fxb.doraemon.human.service..*.select*(..))")
    public void readPointcut() {
    }

    /***
     * @1:这里有点小问题.https://blog.csdn.net/wxy540843763/article/details/90112975
     */
    @Pointcut("@annotation(com.fxb.doraemon.human.annotation.Master) " +
            "|| execution(public * com.fxb.doraemon.human.service..*.save*(..)) " +
            "|| execution(public * com.fxb.doraemon.human.service..*.insert*(..)) " +
            "|| execution(public * com.fxb.doraemon.human.service..*.update*(..)) " +
            "|| execution(public * com.fxb.doraemon.human.service..*.edit*(..)) " +
            "|| execution(public * com.fxb.doraemon.human.service..*.delete*(..)) " +
            "|| execution(public * com.fxb.doraemon.human.service..*.del*(..)) " +
            "|| execution(public * com.fxb.doraemon.human.service..*.remove*(..)) ")
    public void writePointcut() {
    }

    @Before("readPointcut()")
    public void read() {
        DBContextHolder.slave();
    }

    @Before("writePointcut()")
    public void write() {
        DBContextHolder.master();
    }
}
  • 最后,最重要的一步,不然你会发现,aop怎么都切入不了。那是因为没有启动织入代理方式。在你的启动类上加上下面的这个注解。
@EnableAspectJAutoProxy

验证一下我们的结果:


好了,这样我们的数据分离就配置好了。
其他的三种方式,我也会陆续学习的。再来总结自己的感悟。
下一篇文章,我们来解释一下这个@1的这个问题。

参考文章

四种读写分离的方式

最后

如果你觉得写的还不错,就关注下公众号呗,关注后,有点小礼物回赠给你。
你可以获得5000+电子书,java,springCloud,adroid,python等各种视频教程,IT类经典书籍,各种软件的安装及破解教程。
希望一块学习,一块进步!

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表