Spring+MyBatis實現數據庫讀寫分離方案


本文重點介紹兩種方案實現讀寫分離,推薦第二種方案

方案一:

通過Spring AOP在Service業務層實現讀寫分離,在調用DAO數據層前定義切面,利用Spring的AbstractRoutingDataSource解決多數據源的問題,實現動態選擇數據源 

  • 優點:通過注解的方法在Service業務層(接口或者實現類)每個方法上配置數據源,原有代碼改動量少,支持多讀,易擴展
  • 缺點:需要在Service業務層(接口或者實現類)每個方法上配置注解,人工管理,容易出錯

方案二: 

如果后台結構是spring+mybatis,可以通過spring的AbstractRoutingDataSource和mybatis Plugin攔截器實現非常友好的讀寫分離,原有代碼不需要任何改變

  • 優點:原有代碼不變,支持多讀,易擴展
  • 缺點:

下面就詳細介紹這兩種方案的具體實現,先貼上用Maven構建的SSM項目目錄結構圖:

 

方案一實現方式介紹:

1. 定義注解

package com.demo.annotation;

import java.lang.annotation.*;

/**
 * 自定義注解
 * 動態選擇數據源時使用
 */
@Documented
@Target(ElementType.METHOD) //可以應用於方法
@Retention(RetentionPolicy.RUNTIME) //標記的注釋由JVM保留,因此運行時環境可以使用它
public @interface DataSourceChange {
    boolean slave() default false;
}

2. 定義類DynamicDataSourceHolder 

package com.demo.datasource;

import lombok.extern.slf4j.Slf4j;

/**
 * @ProjectName: ssm-maven
 * @Package: com.demo.datasource
 * @ClassName: DynamicDataSourceHolder
 * @Description: 設置和獲取動態數據源KEY
 * @Author: LiDan
 * @Date: 2019/7/10 16:15
 * @Version: 1.0
 */
@Slf4j
public class DynamicDataSourceHolder {
    /**
     * 線程安全,記錄當前線程的數據源key
     */
    private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    /**
     * 主庫,只允許一個
     */
    public static final String DB_MASTER = "master";
    /**
     * 從庫,允許多個
     */
    public static final String DB_SLAVE = "slave";

    /**
     * 獲取當前線程的數據源
     * @return
     */
    public static String getDataSource() {
        String db = contextHolder.get();
        if(db == null) {
            //默認是master庫
            db = DB_MASTER;
        }
        log.info("所使用的數據源為:" + db);
        return db;
    }

    /**
     * 設置當前線程的數據源
     * @param dataSource
     */
    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    /**
     * 清理連接類型
     */
    public static void clearDataSource() {
        contextHolder.remove();
    }

    /**
     * 判斷是否是使用主庫,提高部分使用
     * @return
     */
    public static boolean isMaster() {
        return DB_MASTER.equals(getDataSource());
    }
}
View Code

3. 定義類DynamicDataSource繼承自AbstractRoutingDataSource

package com.demo.datasource;

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.ReflectionUtils;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ProjectName: ssm-maven
 * @Package: com.demo.datasource
 * @ClassName: DynamicDataSource
 * @Description: 動態數據源實現讀寫分離
 * @Author: LiDan
 * @Date: 2019/7/10 16:28
 * @Version: 1.0
 */
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 獲取讀數據源方式,0:隨機,1:輪詢
     */
    private int readDataSourcePollPattern = 0;

    /**
     * 讀數據源個數
     */
    private int slaveCount = 0;

    /**
     * 記錄讀庫的key
     */
    private List<Object> slaveDataSources = new ArrayList<Object>(0);

    /**
     * 輪詢計數,初始為0,AtomicInteger是線程安全的
     */
    private AtomicInteger counter = new AtomicInteger(0);

    /**
     * 每次操作數據庫都會調用此方法,根據返回值動態選擇數據源
     * 定義當前使用的數據源(返回值為動態數據源的key值)
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        //如果使用主庫,則直接返回
        if (DynamicDataSourceHolder.isMaster()) {
            return DynamicDataSourceHolder.getDataSource();
        }
        int index = 0;
        //如果不是主庫則選擇從庫
        if(readDataSourcePollPattern == 1) {
            //輪詢方式
            index = getSlaveIndex();
        }
        else {
            //隨機方式
            index = ThreadLocalRandom.current().nextInt(0, slaveCount);
        }
        log.info("選擇從庫索引:"+index);
        return slaveDataSources.get(index);
    }

    /**
     * 該方法會在Spring Bean 加載初始化的時候執行,功能和 bean 標簽的屬性 init-method 一樣
     * 把所有的slave庫key放到slaveDataSources里
     */
    @SuppressWarnings("unchecked")
    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();

        // 由於父類的resolvedDataSources屬性是私有的子類獲取不到,需要使用反射獲取
        Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class, "resolvedDataSources");
        // 設置可訪問
        field.setAccessible(true);

        try {
            Map<Object, DataSource> resolvedDataSources = (Map<Object, DataSource>) field.get(this);
            // 讀庫的數據量等於數據源總數減去寫庫的數量
            this.slaveCount = resolvedDataSources.size() - 1;
            for (Map.Entry<Object, DataSource> entry : resolvedDataSources.entrySet()) {
                if (DynamicDataSourceHolder.DB_MASTER.equals(entry.getKey())) {
                    continue;
                }
                slaveDataSources.add(entry.getKey());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 輪詢算法實現
     * @return
     */
    private int getSlaveIndex() {
        long currValue = counter.incrementAndGet();
        if (counter.get() > 9999) { //以免超出int范圍
            counter.set(0); //還原
        }
        //得到的下標為:0、1、2、3……
        int index = (int)(currValue % slaveCount);
        return index;
    }

    public void setReadDataSourcePollPattern(int readDataSourcePollPattern) {
        this.readDataSourcePollPattern = readDataSourcePollPattern;
    }
}
View Code

4. 定義AOP切面類DynamicDataSourceAspect

package com.demo.aop;

import com.demo.annotation.DataSourceChange;
import com.demo.datasource.DynamicDataSourceHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

/**
 * @ProjectName: ssm-maven
 * @Package: com.demo.aop
 * @ClassName: DynamicDataSourceAspect
 * @Description: 定義選擇數據源切面
 * @Author: LiDan
 * @Date: 2019/7/11 11:05
 * @Version: 1.0
 */
@Slf4j
public class DynamicDataSourceAspect {
    /**
     * 目標方法執行前調用
     * @param point
     */
    public void before(JoinPoint point) {
        log.info("before");
        //獲取代理接口或者類
        Object target = point.getTarget();
        String methodName = point.getSignature().getName();
        //獲取目標類的接口,所以注解@DataSourceChange需要寫在接口里面
        //Class<?>[] clazz = target.getClass().getInterfaces();
        //獲取目標類,所以注解@DataSourceChange需要寫在類里面
        Class<?>[] clazz = new Class<?>[]{target.getClass()};
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
        try {
            Method method = clazz[0].getMethod(methodName, parameterTypes);
            //判斷方法上是否使用了該注解
            if (method != null && method.isAnnotationPresent(DataSourceChange.class)) {
                DataSourceChange data = method.getAnnotation(DataSourceChange.class);
                if (data.slave()) {
                    DynamicDataSourceHolder.setDataSource(DynamicDataSourceHolder.DB_SLAVE);
                } else {
                    DynamicDataSourceHolder.setDataSource(DynamicDataSourceHolder.DB_MASTER);
                }
            }
        } catch (Exception ex) {
            log.error(String.format("Choose DataSource error, method:%s, msg:%s", methodName, ex.getMessage()));
        }
    }

    /**
     * 目標方法執行后調用
     * @param point
     */
    public void after(JoinPoint point) {
        log.info("after");
        DynamicDataSourceHolder.clearDataSource();
    }

    /**
     * 環繞通知
     * @param joinPoint
     * @return
     */
    public Object around(ProceedingJoinPoint joinPoint) {
        log.info("around");
        Object result = null;
        //獲取代理接口或者類
        Object target = joinPoint.getTarget();
        String methodName = joinPoint.getSignature().getName();
        //獲取目標類的接口,所以注解@DataSourceChange需要寫在接口上
        //Class<?>[] clazz = target.getClass().getInterfaces();
        //獲取目標類,所以注解@DataSourceChange需要寫在類里面
        Class<?>[] clazz = new Class<?>[]{target.getClass()};
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
        try {
            Method method = clazz[0].getMethod(methodName, parameterTypes);
            //判斷方法上是否使用了該注解
            if (method != null && method.isAnnotationPresent(DataSourceChange.class)) {
                DataSourceChange data = method.getAnnotation(DataSourceChange.class);
                if (data.slave()) {
                    DynamicDataSourceHolder.setDataSource(DynamicDataSourceHolder.DB_SLAVE);
                } else {
                    DynamicDataSourceHolder.setDataSource(DynamicDataSourceHolder.DB_MASTER);
                }
            }

            System.out.println("--環繞通知開始--開啟事務--自動--");
            long start = System.currentTimeMillis();

            //調用 proceed() 方法才會真正的執行實際被代理的目標方法
            result = joinPoint.proceed();

            long end = System.currentTimeMillis();
            System.out.println("總共執行時長" + (end - start) + " 毫秒");

            System.out.println("--環繞通知結束--提交事務--自動--");
        }
        catch (Throwable ex) {
            System.out.println("--環繞通知--出現錯誤");
            log.error(String.format("Choose DataSource error, method:%s, msg:%s", methodName, ex.getMessage()));
        }
        finally {
            DynamicDataSourceHolder.clearDataSource();
        }
        return result;
    }
}
View Code

5. 配置spring-mybatis.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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!-- 自動掃描 -->
    <!--<context:component-scan base-package="com.demo.dao" />-->

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

    <!-- DataSource數據庫配置-->
    <bean id="abstractDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
    </bean>
    <!-- 寫庫配置-->
    <bean id="dataSourceMaster" parent="abstractDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.master.url}"/>
        <property name="username" value="${jdbc.master.username}"/>
        <property name="password" value="${jdbc.master.password}"/>
    </bean>
    <!-- 從庫一配置-->
    <bean id="dataSourceSlave1" parent="abstractDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.slave.one.url}"/>
        <property name="username" value="${jdbc.slave.one.username}"/>
        <property name="password" value="${jdbc.slave.one.password}"/>
    </bean>
    <!-- 從庫二配置-->
    <bean id="dataSourceSlave2" parent="abstractDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.slave.two.url}"/>
        <property name="username" value="${jdbc.slave.two.username}"/>
        <property name="password" value="${jdbc.slave.two.password}"/>
    </bean>

    <!-- 設置自己定義的動態數據源 -->
    <bean id="dataSource" class="com.demo.datasource.DynamicDataSource">
        <!-- 設置動態切換的多個數據源 -->
        <property name="targetDataSources">
            <map>
                <!-- 這個key需要和程序中的key一致 -->
                <entry value-ref="dataSourceMaster" key="master"></entry>
                <entry value-ref="dataSourceSlave1" key="slave1"></entry>
                <entry value-ref="dataSourceSlave2" key="slave2"></entry>
            </map>
        </property>
        <!-- 設置默認的數據源,這里默認走寫庫 -->
        <property name="defaultTargetDataSource" ref="dataSourceMaster"/>
        <!-- 輪詢方式 0:隨機,1:輪詢 -->
        <property name="readDataSourcePollPattern" value="1" />
    </bean>

    <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
    <!--<bean id="mySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">-->
    <bean id="mySqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <!--mybatis的配置文件-->
        <!--<property name="configLocation" value="classpath:beans/mybatis-config.xml"/>-->
        <!-- 自動掃描sqlMapper下面所有xml文件 -->
        <property name="mapperLocations">
            <list>
                <value>classpath:sqlmapper/**/*.xml</value>
            </list>
        </property>
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.demo.model"/>
    </bean>

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

    <!-- JDBC事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--<bean id="transactionManager" class="com.demo.datasource.DynamicDataSourceTransactionManager">-->
        <property name="dataSource" ref="dataSource"/>
        <property name="rollbackOnCommitFailure" value="true"/>
    </bean>

    <!-- 開啟事務管理器的注解 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

</beans>

6. 配置spring-aop.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/aop
                        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置動態選擇數據庫全自動方式aop -->
    <!--定義切面類-->
    <bean id="dynamicDataSourceAspect" class="com.demo.aop.DynamicDataSourceAspect" />
    <aop:config>
        <!--定義切點,就是要監控哪些類下的方法-->
        <!--說明:該切點不能用於dao層,因為無法提前攔截到動態選擇的數據源-->
        <aop:pointcut id="myPointCut" expression="execution(* com.demo.service..*.*(..))"/>
        <!--order表示切面順序(多個切面時或者和JDBC事務管理器同時用時)-->
        <aop:aspect ref="dynamicDataSourceAspect" order="1">
            <aop:before method="before" pointcut-ref="myPointCut"/>
            <aop:after method="after" pointcut-ref="myPointCut"/>
            <!--<aop:around method="around" pointcut-ref="myPointCut"/>-->
        </aop:aspect>
    </aop:config>
    <!-- 配置動態選擇數據庫全自動方式aop -->

    <!--
           啟動AspectJ支持,開啟自動注解方式AOP
           使用配置注解,首先我們要將切面在spring上下文中聲明成自動代理bean
           默認情況下會采用JDK的動態代理實現AOP(只能對實現了接口的類生成代理,而不能針對類)
           如果proxy-target-class="true" 聲明時強制使用cglib代理(針對類實現代理)
    -->
    <!--<aop:aspectj-autoproxy proxy-target-class="true"/>-->
</beans>

注意在applicationContext.xml中導入這兩個xml

    <!-- 導入mybatis配置文件 -->
    <import resource="classpath:beans/spring-mybatis.xml"></import>
    <!-- 導入spring-aop配置文件 -->
    <import resource="classpath:beans/spring-aop.xml"></import>

最后可以在Service業務層接口或者實現類具體方法上打注解@DataSourceChange(slave = true)

注意:注解是寫在接口方法上還是實現類方法上要根據前面步驟4定義aop切面時獲取注解的方式定

package com.demo.serviceimpl;

import com.demo.annotation.DataSourceChange;
import com.demo.dao.CmmAgencyDao;
import com.demo.dao.CmmAgencystatusDao;
import com.demo.model.bo.TCmmAgencyBO;
import com.demo.model.bo.TCmmAgencystatusBO;
import com.demo.model.po.TCmmAgencyPO;
import com.demo.service.AgencyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @ProjectName: ssm-maven
 * @Package: com.demo.serviceimpl
 * @ClassName: AgencyServiceImpl
 * @Description: 業務邏輯實現層
 * @Author: LiDan
 * @Date: 2019/6/18 17:41
 * @Version: 1.0
 */
@Slf4j
@Service
public class AgencyServiceImpl implements AgencyService {
    @Autowired
    private CmmAgencyDao cmmAgencyDao;
    @Autowired
    private CmmAgencystatusDao cmmAgencystatusDao;

    /**
     * 查詢信息
     * @param bussnum
     * @return
     */
    @Override
    @DataSourceChange(slave = true) //讀庫
    @Transactional(readOnly = true) //指定事務是否為只讀取數據:只讀
    public TCmmAgencyPO selectAgencyByBussNum(String bussnum) {
       
    }

    /**
     * 修改信息
     * @param bussnum
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class) //聲明式事務控制
    public boolean updateAgencyByBussNum(String bussnum) {
     
    }
}

 方案二實現方式介紹:

 沿用上面方案一中的步驟2和3,並且去掉spring-aop切面和去掉業務層方法上面的自定義注解@DataSourceChange,再通過mybatis Plugin配置文件單獨實現,或者配合自定義JDBC事務管理器來實現動態選擇數據源

 注意說明一下:如果業務方法上面沒有打事務注解@Transactional,則默認直接通過mybatis Plugin攔截切面根據SQL語句動態選擇數據源,

 但是如果業務方法上面打上事務注解,則會首先通過JDBC事務管理器來動態選擇數據源,然后才進入mybatis Plugin攔截切面選擇數據源。

 通過測試后發現如果業務方法上使用事務注解,則在啟用事務時就確定了數據源,后面mybatis Plugin攔截已經沒效果了,其實就是事務優先的原則,同一個事務操作過程中不可能再修改數據源了。

 方案一中xml里配置切面時指定屬性order="1"也是為了讓碰到事務時讓切面優先事務執行攔截

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 
    <plugins>
        <!--對mybatis中操作進行攔截,動態選擇數據源-->
        <plugin interceptor="com.demo.aop.DynamicDataSourcePlugin"></plugin>
    </plugins>

</configuration>

需要新建自定義JDBC事務管理器DynamicDataSourceTransactionManager

package com.demo.datasource;

import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;

/**
 * @ProjectName: ssm-maven
 * @Package: com.demo.datasource
 * @ClassName: DynamicDataSourceTransactionManager
 * @Description: 自定義JDBC事務管理器,動態選擇數據源
 * @Author: LiDan
 * @Date: 2019/7/15 17:33
 * @Version: 1.0
 */
public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
    /**
     * 只讀事務到讀庫,讀寫事務到寫庫
     * @param transaction
     * @param definition
     */
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        //獲取事務的readOnly屬性值
        boolean readOnly = definition.isReadOnly();
        if(readOnly) {
            DynamicDataSourceHolder.setDataSource(DynamicDataSourceHolder.DB_SLAVE);
        } else {
            DynamicDataSourceHolder.setDataSource(DynamicDataSourceHolder.DB_MASTER);
        }
        super.doBegin(transaction, definition);
    }

    /**
     * 清理本地線程的數據源
     * @param transaction
     */
    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        super.doCleanupAfterCompletion(transaction);
        DynamicDataSourceHolder.clearDataSource();
    }
}

並定義mybatis Plugin攔截切面DynamicDataSourcePlugin

package com.demo.aop;

import com.demo.datasource.DynamicDataSourceHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.Locale;
import java.util.Properties;

/**
 * @ProjectName: ssm-maven
 * @Package: com.demo.aop
 * @ClassName: DynamicDataSourcePlugin
 * @Description: 對mybatis中操作進行攔截,增刪改使用master,查詢使用slave
 * @Author: LiDan
 * @Date: 2019/7/15 13:30
 * @Version: 1.0
 */
@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class DynamicDataSourcePlugin implements Interceptor {
    /**
     * sql匹配規則
     */
    private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";

    /**
     * 進行攔截操作,增刪改和事務操作使用master,查詢使用slave,里面有具體的實現代碼,感興趣可以學習mybatis源碼去理解
     * 你也可以根據自己的實際業務邏輯去控制
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = null;
        Object[] objects = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) objects[0];
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;
        try {
            //是否使用事務管理
            boolean syschronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
            if (!syschronizationActive) {
                //讀方法
                if (mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                    //如果selectKey為自增id查詢主鍵(SELECT LAST INSERT_ID)方法,使用主庫
                    if (mappedStatement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;
                    } else {
                        BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(objects[1]);
                        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                        //判斷是否為“增刪改”
                        if (sql.matches(REGEX)) {
                            lookupKey = DynamicDataSourceHolder.DB_MASTER;
                        } else {
                            lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                        }
                    }
                }
            } else {
                //說明:如果啟用事務管理器,那么這里就無法再修改數據源了,因為一旦啟用事務時就確定了數據源(除非在自定義JDBC事務管理器類中重寫doBegin方法來動態選擇數據源)
                lookupKey = DynamicDataSourceHolder.DB_MASTER;
            }
            System.out.println("設置方法:"+mappedStatement.getId()+"; use:"+lookupKey+"; SqlCommanType:"+mappedStatement.getSqlCommandType().name());
            DynamicDataSourceHolder.setDataSource(lookupKey);
            result = invocation.proceed();
        }
        catch (Throwable ex) {
            log.error(String.format("Choose DataSource error, method:%s, msg:%s", mappedStatement.getId(), ex.getMessage()));
        }
        finally {
            DynamicDataSourceHolder.clearDataSource();
        }
        return result;
    }

    /**
     * 設置攔截對象
     * Executor在mybatis中是用來增刪改查的,進行攔截
     * @param target 攔截的對象
     * @return
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {}
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM