spring AOP 动态切换数据库 (读写库)
为了减轻数据库的压力,同时也为了提高数据库的效率。我们在开发时都需要配置多个数据库,将 查询等读操作 和 增删改等写操作 分开来。这时候我们就需要对数据库的切换进行一些配置。
AOP切面很好的实现了动态切换数据源。AbstractRoutingDataSource类是实现的关键,我们需要自己定义一个类继承路由类去实现determineCurrentLookupKey()方法。
1. DynamicDataSource 类
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource{ @Override protected Object determineCurrentLookupKey() { return DataBaseContextHolder.getDataSouce(); } }
2. 多线程辅助类
package com.common.readwriteseparate; public class DataBaseContextHolder { public static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static void setDbType(String name) { contextHolder.set(name); } public static String getDataSouce() { return contextHolder.get(); } public static void clearDBType() { contextHolder.remove(); } }
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); }