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

 

posted @ 2017-11-15 15:32  一只菜机  阅读(535)  评论(0编辑  收藏  举报