spring boot + mybatis如何实现数据库的读写分离

 更新时间:2019年09月05日 09:19:17   作者:别拿豆包不当干粮  
这篇文章主要给大家介绍了关于spring boot + mybatis如何实现数据库的读写分离的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用spring boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

介绍

随着业务的发展,除了拆分业务模块外,数据库的读写分离也是常见的优化手段。

方案使用了AbstractRoutingDataSource和mybatis plugin来动态的选择数据源

选择这个方案的原因主要是不需要改动原有业务代码,非常友好

注:

demo中使用了mybatis-plus,实际使用mybatis也是一样的
demo中使用的数据库是postgres,实际任一类型主从备份的数据库示例都是一样的
demo中使用了alibaba的druid数据源,实际其他类型的数据源也是一样的

环境

首先,我们需要两个数据库实例,一为master,一为slave。

所有的写操作,我们在master节点上操作

所有的读操作,我们在slave节点上操作

需要注意的是:对于一次有读有写的事务,事务内的读操作也不应该在slave节点上,所有操作都应该在master节点上
先跑起来两个pg的实例,其中15432端口对应的master节点,15433端口对应的slave节点:

docker run \
 --name pg-master \
 -p 15432:5432 \
 --env 'PG_PASSWORD=postgres' \
 --env 'REPLICATION_MODE=master' \
 --env 'REPLICATION_USER=repluser' \
  --env 'REPLICATION_PASS=repluserpass' \
 -d sameersbn/postgresql:10-2

docker run \
 --name pg-slave \
 -p 15433:5432 \
 --link pg-master:master \
 --env 'PG_PASSWORD=postgres' \
 --env 'REPLICATION_MODE=slave' \
 --env 'REPLICATION_SSLMODE=prefer' \
 --env 'REPLICATION_HOST=master' \
 --env 'REPLICATION_PORT=5432' \
 --env 'REPLICATION_USER=repluser' \
  --env 'REPLICATION_PASS=repluserpass' \
 -d sameersbn/postgresql:10-2

实现

整个实现主要有3个部分:

  • 配置两个数据源
  • 实现AbstractRoutingDataSource来动态的使用数据源
  • 实现mybatis plugin来动态的选择数据源

配置数据源

将数据库连接信息配置到application.yml文件中

spring:
 mvc:
  servlet:
   path: /api

datasource:
 write:
  driver-class-name: org.postgresql.Driver
  url: "${DB_URL_WRITE:jdbc:postgresql://localhost:15432/postgres}"
  username: "${DB_USERNAME_WRITE:postgres}"
  password: "${DB_PASSWORD_WRITE:postgres}"
 read:
  driver-class-name: org.postgresql.Driver
  url: "${DB_URL_READ:jdbc:postgresql://localhost:15433/postgres}"
  username: "${DB_USERNAME_READ:postgres}"
  password: "${DB_PASSWORD_READ:postgres}"


mybatis-plus:
 configuration:
  map-underscore-to-camel-case: true

write写数据源,对应到master节点的15432端口

read读数据源,对应到slave节点的15433端口

将两个数据源信息注入为DataSourceProperties:

@Configuration
public class DataSourcePropertiesConfig {

  @Primary
  @Bean("writeDataSourceProperties")
  @ConfigurationProperties("datasource.write")
  public DataSourceProperties writeDataSourceProperties() {
    return new DataSourceProperties();
  }

  @Bean("readDataSourceProperties")
  @ConfigurationProperties("datasource.read")
  public DataSourceProperties readDataSourceProperties() {
    return new DataSourceProperties();
  }
}

实现AbstractRoutingDataSource

spring提供了AbstractRoutingDataSource,提供了动态选择数据源的功能,替换原有的单一数据源后,即可实现读写分离:

@Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource {

  @Resource(name = "writeDataSourceProperties")
  private DataSourceProperties writeProperties;

  @Resource(name = "readDataSourceProperties")
  private DataSourceProperties readProperties;


  @Override
  public void afterPropertiesSet() {
    DataSource writeDataSource = 
      writeProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
    DataSource readDataSource = 
      readProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
    
    setDefaultTargetDataSource(writeDataSource);

    Map<Object, Object> dataSourceMap = new HashMap<>();
    dataSourceMap.put(WRITE_DATASOURCE, writeDataSource);
    dataSourceMap.put(READ_DATASOURCE, readDataSource);
    setTargetDataSources(dataSourceMap);

    super.afterPropertiesSet();
  }

  @Override
  protected Object determineCurrentLookupKey() {
    String key = DataSourceHolder.getDataSource();

    if (key == null) {
       // default datasource
      return WRITE_DATASOURCE;
    }

    return key;
  }

}

AbstractRoutingDataSource内部维护了一个Map<Object, Object>的Map

在初始化过程中,我们将write、read两个数据源加入到这个map

调用数据源时:determineCurrentLookupKey()方法返回了需要使用的数据源对应的key

当前线程需要使用的数据源对应的key,是在DataSourceHolder类中维护的:

public class DataSourceHolder {

  public static final String WRITE_DATASOURCE = "write";
  public static final String READ_DATASOURCE = "read";

  private static final ThreadLocal<String> local = new ThreadLocal<>();


  public static void putDataSource(String dataSource) {
    local.set(dataSource);
  }

  public static String getDataSource() {
    return local.get();
  }

  public static void clearDataSource() {
    local.remove();
  }

}

实现mybatis plugin

上面提到了当前线程使用的数据源对应的key,这个key需要在mybatis plugin根据sql类型来确定
MybatisDataSourceInterceptor类:

@Component
@Intercepts({
    @Signature(type = Executor.class, method = "update",
        args = {MappedStatement.class, Object.class}),
    @Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
            CacheKey.class, BoundSql.class})})
public class MybatisDataSourceInterceptor implements Interceptor {

  @Override
  public Object intercept(Invocation invocation) throws Throwable {

    boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
    if(!synchronizationActive) {
      Object[] objects = invocation.getArgs();
      MappedStatement ms = (MappedStatement) objects[0];

      if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
        DataSourceHolder.putDataSource(DataSourceHolder.READ_DATASOURCE);
      }
    }

    return invocation.proceed();
  }

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  @Override
  public void setProperties(Properties properties) {
  }
}

仅当未在事务中,并且调用的sql是select类型时,在DataSourceHolder中将数据源设为read

其他情况下,AbstractRoutingDataSource会使用默认的write数据源

至此,项目已经可以自动的在读、写数据源间切换,无需修改原有的业务代码

最后,提供demo使用依赖版本

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.7.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.2</version>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.9</version>
  </dependency>
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatisplus-spring-boot-starter</artifactId>
    <version>1.0.5</version>
  </dependency>
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>2.1.9</version>
  </dependency>
  <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.8.0</version>
  </dependency>
  <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.8.0</version>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。

相关文章

  • 浅谈spring 常用注解

    浅谈spring 常用注解

    这篇文章主要介绍了浅谈spring 常用注解,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • MyBatis将查询出的两列数据装配成键值对的操作方法

    MyBatis将查询出的两列数据装配成键值对的操作方法

    这篇文章主要介绍了MyBatis将查询出的两列数据装配成键值对的操作代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • SpringCloud2020整合Nacos-Bootstrap配置不生效的解决

    SpringCloud2020整合Nacos-Bootstrap配置不生效的解决

    这篇文章主要介绍了SpringCloud2020整合Nacos-Bootstrap配置不生效的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • 图文详解Java中class的初始化顺序

    图文详解Java中class的初始化顺序

    网上有很多关于Java中class的初始化顺序文章,但是本文通过图文更加详细的介绍了Java中class的初始化顺序,并对class的装载顺序进行了讲解,下面一起来看看。
    2016-08-08
  • SpringBoot全局Controller返回值格式统一

    SpringBoot全局Controller返回值格式统一

    本文主要介绍了SpringBoot全局Controller返回值格式统一,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • SpringBoot替换默认的tomcat服务器的方法

    SpringBoot替换默认的tomcat服务器的方法

    Tomcat是Apache基金下的一个轻量级的Servlet容器,支持Servlet和JSP,Tomcat具有Web服务器特有的功能,在SpringBoot框架中,我们使用最多的是Tomcat,这是SpringBoot默认的容器技术,本文给大家介绍了Spring Boot如何替换默认的tomcat服务器,需要的朋友可以参考下
    2024-08-08
  • java Servlet 实现动态验证码图片示例

    java Servlet 实现动态验证码图片示例

    这篇文章主要介绍了java Servlet 实现动态验证码图片示例的资料,这里整理了详细的代码,有需要的小伙伴可以参考下。
    2017-02-02
  • mybatis水平分表实现动态表名的项目实例

    mybatis水平分表实现动态表名的项目实例

    本文主要介绍了mybatis水平分表实现动态表名的项目实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • 如何解决java获取时间相差8小时的问题

    如何解决java获取时间相差8小时的问题

    最近使用new date()获取的时间会和真实的本地时间相差8小时。本文就详细的来介绍一下解决java获取时间相差8小时的问题,感兴趣的可以了解一下
    2021-09-09
  • Spring AOP如何实现注解式的Mybatis多数据源切换详解

    Spring AOP如何实现注解式的Mybatis多数据源切换详解

    这篇文章主要给大家介绍了关于Spring AOP如何实现注解式的Mybatis多数据源切换的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11

最新评论