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

 更新时间:2018年03月29日 10:02:19   投稿:zx  
本篇文章主要介绍了SpringBoot AOP方式实现多数据源切换的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

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

设计总体思路

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();
  }
}

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

相关文章

  • Zuul 实现网关转发的五种方式小结

    Zuul 实现网关转发的五种方式小结

    这篇文章主要介绍了Zuul 实现网关转发的五种方式小结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 全方位讲解Java的面向对象编程思想

    全方位讲解Java的面向对象编程思想

    这篇文章主要介绍了Java的面相对象编程思想,包括类对象方法和封装继承多态等各个方面的OOP基本要素,非常推荐,需要的朋友可以参考下
    2016-01-01
  • ObjectInputStream 和 ObjectOutputStream 介绍_动力节点Java学院整理

    ObjectInputStream 和 ObjectOutputStream 介绍_动力节点Java学院整理

    ObjectInputStream 和 ObjectOutputStream 的作用是,对基本数据和对象进行序列化操作支持。本文给大家详细介绍了ObjectInputStream 和 ObjectOutputStream的相关知识,感兴趣的朋友一起学习吧
    2017-05-05
  • Java类和对象习题及详细答案解析

    Java类和对象习题及详细答案解析

    这篇文章主要介绍了Java类和对象的相关知识,包括局部变量初始化、静态方法、静态导入、构造方法、代码块执行顺序、toString方法重写、类变量和静态成员变量的访问等,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-02-02
  • SpringBoot结合Redis实现缓存

    SpringBoot结合Redis实现缓存

    本文主要介绍了SpringBoot结合Redis实现缓存,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • SpringBoot集成WebSocket的两种方式(JDK内置版和Spring封装版)

    SpringBoot集成WebSocket的两种方式(JDK内置版和Spring封装版)

    这篇文章主要介绍了SpringBoot集成WebSocket的两种方式,这两种方式为JDK内置版和Spring封装版,本文结合示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • SpringBoot中的@PreAuthorize注解详解

    SpringBoot中的@PreAuthorize注解详解

    这篇文章主要介绍了SpringBoot中的@PreAuthorize注解详解,@PreAuthorize注解会在方法执行前进行权限验证,支持Spring EL表达式,它是基于方法注解的权限解决方案,需要的朋友可以参考下
    2023-09-09
  • Java 数据库连接池DBPool 介绍

    Java 数据库连接池DBPool 介绍

    这篇文章主要给大家分享了Java 数据库连接池DBPool 介绍,<BR>DBPool是一个高效的易配置的数据库连接池。它除了支持连接池应有的功能之外,还包括了一个对象池使你能够开发一个满足自已需求的数据库连接池,下面一起来看看文章内容的详细介绍吧,需要的朋友可以参考一下
    2021-11-11
  • Java基于UDP协议的聊天室功能

    Java基于UDP协议的聊天室功能

    这篇文章主要为大家详细介绍了Java基于UDP协议的聊天室功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • MybatisPlus创建时间不想用默认值的问题

    MybatisPlus创建时间不想用默认值的问题

    MybatisPlus通过FieldFill注解和MpMetaObjectHandler类支持自动填充字段功能,特别地,可以设置字段在插入或更新时自动填充创建时间和更新时间,但在特定场景下,如导入数据时,可能需要自定义创建时间
    2024-09-09

最新评论