springboot中怎么利用mybatis实现数据库的读写分离
创始人
2024-12-22 12:41:30
0次

首先,我们需要两个数据库实例,一为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-2docker 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: /apidatasource: 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:

@Configurationpublic 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,提供了动态选择数据源的功能,替换原有的单一数据源后,即可实现读写分离:

@Componentpublic 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 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的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 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使用依赖版本

 org.springframework.boot  spring-boot-starter-parent  2.1.7.RELEASE        org.springframework.boot    spring-boot-starter        org.springframework.boot    spring-boot-starter-web        org.postgresql    postgresql    42.2.2        com.alibaba    druid-spring-boot-starter    1.1.9        com.baomidou    mybatisplus-spring-boot-starter    1.0.5        com.baomidou    mybatis-plus    2.1.9        io.springfox    springfox-swagger2    2.8.0        io.springfox    springfox-swagger-ui    2.8.0        org.projectlombok    lombok    1.16.20        org.springframework.boot    spring-boot-starter-test    test  

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...