欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例

程序员文章站 2024-03-04 16:24:05
本文将介绍使用spring boot集成mybatis并实现主从库分离的实现(同样适用于多数据源)。延续之前的spring boot 集成mybatis。项目还将集成分页插...

本文将介绍使用spring boot集成mybatis并实现主从库分离的实现(同样适用于多数据源)。延续之前的spring boot 集成mybatis。项目还将集成分页插件pagehelper、通用mapper以及druid。

新建一个maven项目,最终项目结构如下:

Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例

多数据源注入到sqlsessionfactory

pom增加如下依赖:

<!--json-->
    <dependency>
      <groupid>com.fasterxml.jackson.core</groupid>
      <artifactid>jackson-core</artifactid>
    </dependency>
    <dependency>
      <groupid>com.fasterxml.jackson.core</groupid>
      <artifactid>jackson-databind</artifactid>
    </dependency>
    <dependency>
      <groupid>com.fasterxml.jackson.datatype</groupid>
      <artifactid>jackson-datatype-joda</artifactid>
    </dependency>
    <dependency>
      <groupid>com.fasterxml.jackson.module</groupid>
      <artifactid>jackson-module-parameter-names</artifactid>
    </dependency>

    <dependency>
      <groupid>org.springframework.boot</groupid>
      <artifactid>spring-boot-starter-jdbc</artifactid>
    </dependency>
    <dependency>
      <groupid>mysql</groupid>
      <artifactid>mysql-connector-java</artifactid>
    </dependency>
    <dependency>
      <groupid>com.alibaba</groupid>
      <artifactid>druid</artifactid>
      <version>1.0.11</version>
    </dependency>
    <!--mybatis-->
    <dependency>
      <groupid>org.mybatis.spring.boot</groupid>
      <artifactid>mybatis-spring-boot-starter</artifactid>
      <version>1.1.1</version>
    </dependency>
    <!--mapper-->
    <dependency>
      <groupid>tk.mybatis</groupid>
      <artifactid>mapper-spring-boot-starter</artifactid>
      <version>1.1.0</version>
    </dependency>
    <!--pagehelper-->
    <dependency>
      <groupid>com.github.pagehelper</groupid>
      <artifactid>pagehelper-spring-boot-starter</artifactid>
      <version>1.1.0</version>
      <exclusions>
        <exclusion>
          <artifactid>mybatis-spring-boot-starter</artifactid>
          <groupid>org.mybatis.spring.boot</groupid>
        </exclusion>
      </exclusions>
    </dependency>

这里需要注意的是:项目是通过扩展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration来实现多数据源注入的。在mybatis-spring-boot-starter:1.2.0中,该类取消了默认构造函数,因此本项目依旧使用1.1.0版本。需要关注后续版本是否会重新把扩展开放处理。

之所以依旧使用旧方案,是我个人认为开放扩展是合理的,相信在未来的版本中会回归。

如果你需要其他方案可参考

增加主从库配置(application.yml)

druid:
  type: com.alibaba.druid.pool.druiddatasource
  master:
    url: jdbc:mysql://192.168.249.128:3307/db-test?characterencoding=utf-8&autoreconnect=true&zerodatetimebehavior=converttonull&useunicode=true
    driver-class-name: com.mysql.jdbc.driver
    username: root
    password: root
    initial-size: 5
    min-idle: 1
    max-active: 100
    test-on-borrow: true
  slave:
    url: jdbc:mysql://192.168.249.128:3317/db-test?characterencoding=utf-8&autoreconnect=true&zerodatetimebehavior=converttonull&useunicode=true&characterencoding=utf-8
    driver-class-name: com.mysql.jdbc.driver
    username: root
    password: root
    initial-size: 5
    min-idle: 1
    max-active: 100
    test-on-borrow: true

创建数据源

@configuration
@enabletransactionmanagement
public class datasourceconfiguration {

  @value("${druid.type}")
  private class<? extends datasource> datasourcetype;

  @bean(name = "masterdatasource")
  @primary
  @configurationproperties(prefix = "druid.master")
  public datasource masterdatasource(){
    return datasourcebuilder.create().type(datasourcetype).build();
  }

  @bean(name = "slavedatasource")
  @configurationproperties(prefix = "druid.slave")
  public datasource slavedatasource1(){
    return datasourcebuilder.create().type(datasourcetype).build();
  }
}

将多数据源注入到sqlsessionfactory中

前面提到了这里通过扩展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration来实现多数据源注入的

@configuration
@autoconfigureafter({datasourceconfiguration.class})
public class mybatisconfiguration extends mybatisautoconfiguration {

  private static log logger = logfactory.getlog(mybatisconfiguration.class);

  @resource(name = "masterdatasource")
  private datasource masterdatasource;
  @resource(name = "slavedatasource")
  private datasource slavedatasource;

  @bean
  public sqlsessionfactory sqlsessionfactory() throws exception {
    return super.sqlsessionfactory(roundrobindatasouceproxy());
  }

  public abstractroutingdatasource roundrobindatasouceproxy(){
    readwritesplitroutingdatasource proxy = new readwritesplitroutingdatasource();
    map<object,object> targetdataresources = new classloaderrepository.softhashmap();
    targetdataresources.put(dbcontextholder.dbtype.master,masterdatasource);
    targetdataresources.put(dbcontextholder.dbtype.slave,slavedatasource);
    proxy.setdefaulttargetdatasource(masterdatasource);//默认源
    proxy.settargetdatasources(targetdataresources);
    return proxy;
  }
}

实现读写分离(多数据源分离)

这里主要思路如下:

1-将不同的数据源标识记录在threadlocal中

2-通过注解标识出当前的service方法使用哪个库

3-通过spring aop实现拦截注解并注入不同的标识到threadlocal中

4-获取源的时候通过threadlocal中不同的标识给出不同的sqlsession

标识存放threadlocal的实现

public class dbcontextholder {

  public enum dbtype{
    master,slave
  }

  private static final threadlocal<dbtype> contextholder = new threadlocal<>();

  public static void setdbtype(dbtype dbtype){
    if(dbtype==null)throw new nullpointerexception();
    contextholder.set(dbtype);
  }

  public static dbtype getdbtype(){
    return contextholder.get()==null?dbtype.master:contextholder.get();
  }

  public static void cleardbtype(){
    contextholder.remove();
  }

}

注解实现

/**
 * 该注解注释在service方法上,标注为链接slaves库
 * created by jason on 2017/3/6.
 */
@target({elementtype.method,elementtype.type})
@retention(retentionpolicy.runtime)
public @interface readonlyconnection {
}

spring aop对注解的拦截

@aspect
@component
public class readonlyconnectioninterceptor implements ordered {

  public static final logger logger = loggerfactory.getlogger(readonlyconnectioninterceptor.class);

  @around("@annotation(readonlyconnection)")
  public object proceed(proceedingjoinpoint proceedingjoinpoint,readonlyconnection readonlyconnection) throws throwable {
    try {
      logger.info("set database connection to read only");
      dbcontextholder.setdbtype(dbcontextholder.dbtype.slave);
      object result = proceedingjoinpoint.proceed();
      return result;
    }finally {
      dbcontextholder.cleardbtype();
      logger.info("restore database connection");
    }
  }


  @override
  public int getorder() {
    return 0;
  }
}

根据标识获取不同源

这里我们通过扩展abstractroutingdatasource来获取不同的源。它是spring提供的一个可以根据用户发起的不同请求去转换不同的数据源,比如根据用户的不同地区语言选择不同的数据库。通过查看源码可以发现,它是通过determinecurrentlookupkey()返回的不同key到sqlsessionfactory中获取不同源(前面已经展示了如何在sqlsessionfactory中注入多个源)

public class readwritesplitroutingdatasource extends abstractroutingdatasource {

  @override
  protected object determinecurrentlookupkey() {
    return dbcontextholder.getdbtype();
  }
}

以上就完成了读写分离(多数据源)的配置方案。下面是一个具体的实例

使用方式

entity

@table(name = "t_sys_dic_type")
public class dictype extends baseentity{

  string code;

  string name;

  integer status;

  ...
}

mapper

public interface dictypemapper extends basemapper<dictype> {
}

service

@service
public class dictypeservice {
  @autowired
  private dictypemapper dictypemapper;

  @readonlyconnection
  public list<dictype> getall(dictype dictype){
    if (dictype.getpage() != null && dictype.getrows() != null) {
      pagehelper.startpage(dictype.getpage(), dictype.getrows());
    }
    return dictypemapper.selectall();
  }

}

注意这里的@readonlyconnection注解

controller

@restcontroller
@requestmapping("/dictype")
public class dictypecontroller {
  @autowired
  private dictypeservice dictypeservice;

  @requestmapping(value = "/all")
  public pageinfo<dictype> getall(dictype dictype){
    list<dictype> dictypelist = dictypeservice.getall(dictype);
    return new pageinfo<>(dictypelist);
  }
}

通过mvn spring-boot:run启动后,即可通过http://localhost:9090/dictype/all 获取到数据

后台打印出

c.a.d.m.readonlyconnectioninterceptor  : set database connection to read only

说明使用了从库的链接获取数据

备注:如何保证多源事务呢?

1-在读写分离场景中不会考虑主从库事务,在纯读的上下文上使用@readonlyconnection标签。其他则默认使用主库。

2-在多源场景中,spring的@transaction是可以保证多源的事务性的。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。