spring AOP 动态切换数据库 (读写库)

时间:2022-09-04 19:36:13

      为了减轻数据库的压力,同时也为了提高数据库的效率。我们在开发时都需要配置多个数据库,将 查询等读操作 和 增删改等写操作 分开来。这时候我们就需要对数据库的切换进行一些配置。

     AOP切面很好的实现了动态切换数据源。AbstractRoutingDataSource类是实现的关键,我们需要自己定义一个类继承路由类去实现determineCurrentLookupKey()方法。

1. DynamicDataSource 类

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource{

@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSouce();
}

}

 2. 多线程辅助类

package com.common.readwriteseparate;

public class DataBaseContextHolder {

public static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void putDataSource(String name) {
contextHolder .set(name);
}

public static String getDataSouce() {
return contextHolder .get();
}
}

 3.DataSource 类

import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}

 4. AOP 切面类

public class DataSourceAspect {
//log
private static final Logger logger = Logger.getLogger(DataSourceAspect.class);
public void pointcut(){
}

public void before(JoinPoint point) {
Object target = point.getTarget();
String method = point.getSignature().getName();
logger.debug("当前执行方法" + method);
Class<?>[] classz = target.getClass().getInterfaces();
//拦截的方法参数类型
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
try {
//通过反射获得拦截的方法
Method m = classz[0].getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDbType(data.value());

}
}
catch (Exception e) {
logger.error("数据源失败切面获取异常:", e);
}
}
}

 接下来及时数据源的配置了。

5.我们在spring 的配置文件中对数据库进行配置:

5.1 spring-mybatis.xml(spring + mybatis 配置)

 <!-- 引入属性配置文件 -->  
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:database.properties" />
</bean>
<!--或 <context:property-placeholder location="classpath*:*.properties" /> -->

 

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd" default-lazy-init="true">
<description>MyBatis的数据库持久层配置/配置主-从数据源</description>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
<property name="mapperLocations" value="classpath*:com/xxx/mapper/*.xml" />
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<!-- 扫描dao -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.user.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

<bean name="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最大空闲 error:maxIdle is deprecated -->
<!-- <property name="maxIdle" value="20" /> -->
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />

</bean>
<!-- 配置数据源-Master -->
<bean name="masterDataSource" parent="parentDataSource">

<property name="url" value="${master.jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />

</bean>
<!-- 配置数据源-Slave -->
<bean name="slaveDataSource" parent="parentDataSource">
<property name="url" value="${slave.jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 主-从数据源路由 -->
<bean id="dataSource" class="com.common.xxx.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- write -->
<entry key="master" value-ref="masterDataSource"/>
<!-- read -->
<entry key="slave" value-ref="slaveDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean>
<!-- 配置数据库注解aop -->
<aop:aspectj-autoproxy />
<bean id="dataSourceAspect" class="com.common.readwriteseparate.DataSourceAspect" />
<aop:config>
<aop:aspect id="c" ref="dataSourceAspect">
<aop:pointcut id="tx" expression="execution(* com.xxx.dao.*.*(..))" />
<aop:before pointcut-ref="tx" method="before" />
</aop:aspect>
</aop:config>
<!-- 注解方式配置事物 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<aop:config>
<aop:pointcut expression="(execution(* com.xxx.service.*.* (..)))" id="pointcut" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
</aop:config>
<!-- 事务控制 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="load*" read-only="true" />
<tx:method name="get*" read-only="true" />
<tx:method name="create*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="schedule*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="do*" propagation="REQUIRED" rollback-for="java.lang.Exception" />

<!-- 一个事务涉及一个数据源不能在事务内部去切换数据源成功,所以对多数据源的方法暂不开启事务~分布式事务 -->
<!-- <tx:method name="crud*" propagation="REQUIRED" rollback-for="java.lang.Exception" /> -->
<!-- <tx:method name="*" /> -->
</tx:attributes>
</tx:advice>

</beans>

 5.2 database.properties

validationQuery=SELECT 1
jdbc.initialSize=5
jdbc.maxActive=20
jdbc.maxWait=60000
jdbc.poolPreparedStatements=false
jdbc.poolMaximumIdleConnections=0
jdbc.driverClassName=org.gjt.mm.mysql.Driver

master.jdbc.url=jdbc:mysql://your ip:3306/master
slave.jdbc.url=jdbc:mysql://your ip:3306/slave
jdbc.username=username
jdbc.password=password

 6. 最后,我们需要在业务接口层进行注解来实现对数据库的切换

public interface UserService{  

@DataSource("master")
int updateByPrimaryKey(int id);

@DataSource("slave")
User selectByPrimaryKey(int id);
}