1. 程式人生 > >Spring + Mybatis項目實現數據庫讀寫分離

Spring + Mybatis項目實現數據庫讀寫分離

eterm nec trac vid () lose classpath mls exc

主要思路:通過實現AbstractRoutingDataSource類來動態管理數據源,利用面向切面思維,每一次進入service方法前,選擇數據源。

1、首先pom.xml中添加aspect依賴

      <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>     

2、實現AbstractRoutingDataSource類 作為數據源

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

/**
 * 實現AbstractRoutingDataSource類 作為數據源
 * @author 木瓜牛奶泡咖啡
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource {  

    @Override  
    protected Object determineCurrentLookupKey() {      
        System.out.println(
"DynamicDataSourceHolder.getDataSouce()====="+DynamicDataSourceHolder.getDataSouce()); return DynamicDataSourceHolder.getDataSouce(); } }

3、用ThreadLcoal管理當前數據源

/**
 * 用ThreadLcoal管理當前數據源
 * @author 木瓜牛奶泡咖啡
 *
 */
public class DynamicDataSourceHolder {  
    public static final ThreadLocal<String> holder = new
ThreadLocal<String>(); public static void putDataSource(String name) { holder.set(name); } public static String getDataSouce() { return holder.get(); } public static void clearDataSource() { holder.remove(); } }

4、用註解的形式實現AOP管理數據源

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

/**
 * 用註解的形式實現AOP管理數據源
 * @author 木瓜牛奶泡咖啡
 *
 */
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
public @interface DataSource {  
    String value();  
}  

5、創建切面類,將註解放在service實現類的方法前,自動設置當前數據源為註解中數據源。

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * 切面類
 * 將註解放在service實現類的方法前,自動設置當前數據源為註解中數據源。
 * @author 木瓜牛奶泡咖啡
 *
 */

/**
 * 切換數據源(不同方法調用不同數據源)
 */
@Aspect
@Order(1)
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {  
    
    @Pointcut("execution(* com.navi.shell.shop.service.*.*(..))")
    public void aspect() {
    }
    
    /**
     * 配置前置處理,使用在方法aspect()上註冊的切入點,綁定數據源信息
     */
    @Before("aspect()")
    public void before(JoinPoint point)  
    {  
        Object target = point.getTarget();  
        String method = point.getSignature().getName();  
        System.out.println("method============" +method);  
        Class<?> classz = target.getClass();  
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())  
                .getMethod().getParameterTypes();  
        try {  
            Method m = classz.getMethod(method, parameterTypes);  
            System.out.println(m.getName());  
            if (m != null && m.isAnnotationPresent(DataSource.class)) {  
                DataSource data = m.getAnnotation(DataSource.class);  
                System.out.println("value==========="+data.value());
                DynamicDataSourceHolder.putDataSource(data.value());  
            }  

        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
    
    /**
     * 配置後置處理,清空數據源信息
     * @param point
     */
    @After("aspect()")
    public void after(JoinPoint point) {
       DynamicDataSourceHolder.clearDataSource();
    }
    
}  

6、配置數據源applicationContext-project.xml

<?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:aop="http://www.springframework.org/schema/aop"  
           xsi:schemaLocation="http://www.springframework.org/schema/beans  
                          http://www.springframework.org/schema/beans/spring-beans-4.1.xsd  
                          http://www.springframework.org/schema/context    
                          http://www.springframework.org/schema/context/spring-context-4.1.xsd  
                          http://www.springframework.org/schema/aop  
                          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">  
        <!-- 引入配置文件 -->  
        <context:property-placeholder location="classpath:db.properties" ignore-unresolvable="true"/>  

        <!-- 數據源配置 -->  
        <bean id="dataSource_wr" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">  
            <property name="url" value="${db.url}"/>  
            <property name="username" value="${db.username}"/>  
            <property name="password" value="${db.password}"/>  
            <property name="connectionProperties" value="${db.driver}"></property>  

            <!-- 配置初始化大小、最小、最大 -->  
            <property name="initialSize" value="1"/>  
            <property name="minIdle" value="1"/>  
            <property name="maxActive" value="20"/>  

            <!-- 配置獲取連接等待超時的時間 -->  
            <property name="maxWait" value="60000"/>  

            <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->  
            <property name="timeBetweenEvictionRunsMillis" value="60000"/>  

            <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->  
            <property name="minEvictableIdleTimeMillis" value="300000"/>  

            <property name="validationQuery" value="SELECT ‘x‘"/>  
            <property name="testWhileIdle" value="true"/>  
            <property name="testOnBorrow" value="true"/>  
            <property name="testOnReturn" value="false"/>  
        </bean>  
        <bean id="dataSource_r" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">  
            <property name="url" value="${db1.url}"/>  
            <property name="username" value="${db1.username}"/>  
            <property name="password" value="${db1.password}"/>  
            <property name="connectionProperties" value="${db.driver}"></property>  

            <!-- 配置初始化大小、最小、最大 -->  
            <property name="initialSize" value="1"/>  
            <property name="minIdle" value="1"/>  
            <property name="maxActive" value="20"/>  

            <!-- 配置獲取連接等待超時的時間 -->  
            <property name="maxWait" value="60000"/>  

            <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->  
            <property name="timeBetweenEvictionRunsMillis" value="60000"/>  

            <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->  
            <property name="minEvictableIdleTimeMillis" value="300000"/>  

            <property name="validationQuery" value="SELECT ‘x‘"/>  
            <property name="testWhileIdle" value="true"/>  
            <property name="testOnBorrow" value="true"/>  
            <property name="testOnReturn" value="false"/>  
        </bean>  

        <bean id="dataSource" class="com.ifeng.auto.we_provider.common.db.DynamicDataSource">  
            <property name="targetDataSources">  
                <map key-type="java.lang.String">  
                    <!-- write -->  
                    <entry key="write" value-ref="dataSource_wr"/>  
                    <!-- read -->  
                    <entry key="read" value-ref="dataSource_r"/>  
                </map>  

            </property>  
            <property name="defaultTargetDataSource" ref="dataSource_wr"/>  
        </bean>  

        <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件-->  
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
            <property name="dataSource" ref="dataSource"/>  
            <!-- 自動掃描mapping.xml文件-->  
            <!--  
            <property name="mapperLocations" value="classpath:com/ifeng/auto/we_provider/mapping/*.xml" />  
             -->  
            <property name="mapperLocations" value="classpath:mapping/*.xml"/>  
        </bean>

      <!-- 配置SQLSession模板 -->
      <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory" />
      </bean>

      <!-- 設定transactionManager -->
      <bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
      </bean>

      <!-- 使用annotation定義事務 -->
      <tx:annotation-driven transaction-manager="transactionManager" />

        <!-- DAO接口所在包名,Spring會自動查找其下的類 -->  
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
            <property name="basePackage" value="com.ifeng.auto.we_provider.dao"/>  
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>  
        </bean>  

    </beans>  

8、在service實現類中添加註解

   @DataSource("write")  
    public void savetag(UserTag userTag) {  
        userTagMapper.addUserTag(userTag);  
    }  

   @DataSource("read")  
    public UserTag getUserTagByUUID(String uuid) {  
        return userTagMapper.getUserTagByUUID(uuid);  
    }  

自此數據庫讀寫分離實現。

在配置過程中遇到的問題:

1、起初配置DataSourceAspect類的時候將其配置在applicationContext-project.xml,如下所示:

<aop:aspectj-autoproxy proxy-target-class="true"/>  
        <bean id="dataSourceAspect" class="com.ifeng.auto.we_provider.common.proxy.DataSourceAspect"/>  
        <aop:config>  
        <aop:aspect id="c" ref="dataSourceAspect" order="-9999">  
        <aop:pointcut id="tx" expression="execution(* com.ifeng.auto.we_provider.service..*.*(..))"/>  
        <aop:before pointcut-ref="tx" method="before"/>  
        </aop:aspect>  
        </aop:config>

這樣配置的結果導致在正常調用被註釋過的service時,無法進入切面類,網上說是因為有事務的原因導致,說數據庫事務的優先級總是高於AOP導致,所有我在aop配置文件中加入了“order=-9999”,這樣aop的優先級總最高了吧,但是依然沒有起作用,糾結了半天,通過註解的方式寫在切面類中,竟然可以了,這兩種方式應該是一樣的,但實際卻不同,不知道是什麽原因。

2、之前在切面類中,少加了 public void after(JoinPoint point),導致在查詢一次註解為read的service後,在去請求未被註解的service(沒有註解默認走write),卻走了read,原因就是少加了after的處理,加入後就好了。

參考博客:http://blog.csdn.net/byp502955177/article/details/68927230

      http://www.jianshu.com/p/2222257f96d3

     http://www.cnblogs.com/zrbfree/p/6484940.html

Spring + Mybatis項目實現數據庫讀寫分離