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

}

}

以上是 SpringBoot AOP方式实现多数据源切换的方法 的全部内容, 来源链接: utcz.com/z/361143.html

回到顶部