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

SpringBoot AOP方式实现多数据源切换的方法

程序员文章站 2023-11-24 16:57:52
最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库。所以涉及到需要...

最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库。所以涉及到需要在一个项目中配置多数据源,并且能够动态切换。经过一番摸索,完美实现动态切换,记录一下配置方法供大家参考。

设计总体思路

spring-boot+aop方式实现多数据源切换,继承abstractroutingdatasource实现数据源动态的获取,在service层使用注解指定数据源。

步骤

一、多数据源配置

在application.properties中,我们的配置是这样的

#主数据源
druid.master.url=jdbc:mysql://url/masterdb?useunicode=true&characterencoding=utf8&zerodatetimebehavior=converttonull
druid.master.username=xxx
druid.master.password=123
druid.master.driver-class-name=com.mysql.jdbc.driver
druid.master.max-wait=5000
druid.master.max-active=100
druid.master.test-on-borrow=true
druid.master.validation-query=select 1

#从数据源
druid.slave.url=jdbc:mysql://url/slavedb?useunicode=true&characterencoding=utf8&zerodatetimebehavior=converttonull
druid.slave.username=xxx
druid.slave.password=123
druid.slave.driver-class-name=com.mysql.jdbc.driver
druid.slave.max-wait=5000
druid.slave.max-active=100
druid.slave.test-on-borrow=true
druid.slave.validation-query=select 1

读取配置

<!-- master数据源 -->
<bean primary="true" id="masterdb" class="com.alibaba.druid.pool.druiddatasource" init-method="init" destroy-method="close">
  <!-- 基本属性 url、user、password -->
  <property name="driverclassname" value="com.mysql.jdbc.driver"/>
  <property name="url" value="${druid.master.url}"/>
  <property name="username" value="${druid.master.username}"/>
  <property name="password" value="${druid.master.password}"/>
  <!-- 配置初始化最大 -->
  <property name="maxactive" value="${druid.master.max-active}"/>
  <!-- 配置获取连接等待超时的时间 -->
  <property name="maxwait" value="${druid.master.max-wait}"/>
  <property name="validationquery" value="${druid.master.validation-query}"/>
  <property name="testonborrow" value="${druid.master.test-on-borrow}"/>

</bean>

<!-- slave数据源 -->
<bean primary="true" id="slavedb" class="com.alibaba.druid.pool.druiddatasource" init-method="init" destroy-method="close">
  <!-- 基本属性 url、user、password -->
  <property name="driverclassname" value="com.mysql.jdbc.driver"/>
  <property name="url" value="${druid.slave.url}"/>
  <property name="username" value="${druid.slave.username}"/>
  <property name="password" value="${druid.slave.password}"/>

  <!-- 配置初始化大小、最小、最大 -->
  <property name="maxactive" value="${druid.slave.max-active}"/>
  <!-- 配置获取连接等待超时的时间 -->
  <property name="maxwait" value="${druid.slave.max-wait}"/>
  <property name="validationquery" value="${druid.slave.validation-query}"/>
  <property name="testonborrow" value="${druid.slave.test-on-borrow}"/>
</bean>

<!-- 动态数据源,根据service接口上的注解来决定取哪个数据源 -->
<bean id="datasource" class="datasource.dynamicdatasource">
  <property name="targetdatasources">
    <map key-type="java.lang.string">
      <entry key="slave" value-ref="slavedb"/>
      <entry key="master" value-ref="masterdb"/>
    </map>
  </property>
  <property name="defaulttargetdatasource" ref="masterdb"/>
</bean>

<!-- spring jdbctemplate -->
<bean id="jdbctemplate" class="org.springframework.jdbc.core.jdbctemplate">
  <property name="datasource" ref="datasource" />
</bean>

<!-- spring事务管理器 -->
<bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager">
  <property name="datasource" ref="datasource" />
</bean>

<bean id="transactiontemplate" class="org.springframework.transaction.support.transactiontemplate">
  <property name="transactionmanager" ref="transactionmanager"/>
</bean>

<tx:annotation-driven transaction-manager="transactionmanager" proxy-target-class="true" order="2" />

<!-- depositdbsqlsessionfactory -->
<bean id="sqlsessionfactory" class="org.mybatis.spring.sqlsessionfactorybean">
  <property name="datasource" ref="datasource" />
  <property name="mapperlocations" value="classpath*:mapper-xxdb/*mapper*.xml" />
</bean>

<bean class="org.mybatis.spring.mapper.mapperscannerconfigurer">
  <property name="basepackage" value="xxdb.mapper"/>
  <property name="sqlsessionfactorybeanname" value="sqlsessionfactory"/>
</bean>

二、动态数据源

spring为我们提供了abstractroutingdatasource,即带路由的数据源。继承后我们需要实现它的determinecurrentlookupkey(),该方法用于自定义实际数据源名称的路由选择方法,由于我们将信息保存到了threadlocal中,所以只需要从中拿出来即可。

public class dynamicdatasource extends abstractroutingdatasource {
  private logger logger = loggerfactory.getlogger(this.getclass());

  @override
  protected object determinecurrentlookupkey() {
    string datasource = jdbccontextholder.getdatasource();
    logger.info("数据源为{}",datasource);
    return datasource;
  }
}

三. 数据源动态切换类

动态数据源切换是基于aop的,所以我们需要声明一个aop切面,并在切面前做数据源切换,切面完成后移除数据源名称。

@aspect
@order(1)  //设置aop执行顺序(需要在事务之前,否则事务只发生在默认库中)
@component
public class datasourceaspect {

  private logger logger = loggerfactory.getlogger(this.getclass());
  //切点
  @pointcut("execution(* com.xxx.service.*.*(..))")
  public void aspect() { }

  @before("aspect()")
  private void before(joinpoint point) {
    object target = point.gettarget();
    string method = point.getsignature().getname();
    class<?> classz = target.getclass();// 获取目标类
    class<?>[] parametertypes = ((methodsignature) point.getsignature())
        .getmethod().getparametertypes();
    try {
      method m = classz.getmethod(method, parametertypes);
      if (m != null && m.isannotationpresent(mydatasource.class)) {
        mydatasource data = m.getannotation(mydatasource.class);
        logger.info("method :{},datasource:{}",m.getname() ,data.value().getname());
        jdbccontextholder.putdatasource(data.value().getname());// 数据源放到当前线程中
      }
    } catch (exception e) {
      logger.error("get datasource error ",e);
      //默认选择master
      jdbccontextholder.putdatasource(datasourcetype.master.getname());// 数据源放到当前线程中
    }

  }

  @afterreturning("aspect()")
  public void after(joinpoint point) {
    jdbccontextholder.cleardatasource();
  }
}

四、数据源管理类

public class jdbccontextholder {

  private final static threadlocal<string> local = new threadlocal<>();

  public static void putdatasource(string name) {
    local.set(name);
  }

  public static string getdatasource() {
    return local.get();
  }

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

五、数据源注解和枚举

我们切换数据源时,一般都是在调用具体接口的方法前实现,所以我们定义一个方法注解,当aop检测到方法上有该注解时,根据注解中value对应的名称进行切换。

@retention(retentionpolicy.runtime)
@target(elementtype.method)
public @interface mydatasource {

  datasourcetype value();

}
public enum datasourcetype {
  // 主表
  master("master"),
  // 从表
  slave("slave");

  private string name;

  private datasourcetype(string name) {
    this.name = name;
  }

  public string getname() {
    return name;
  }

  public void setname(string name) {
    this.name = name;
  }
}

六、切点注解

由于我们的动态数据源配置了默认库,所以如果方法是操作默认库的可以不需要注解,如果要操作非默认数据源,我们需要在方法上添加@mydatasource("数据源名称")注解,这样就可以利用aop实现动态切换了

@component
public class xxxserviceimpl {
  @resource
  private xxxmapperext xxxmapperext;

  @mydatasource(value= datasourcetype.slave)
  public list<object> getall(){
    return xxxmapperext.getall();
  }
}

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