zoukankan      html  css  js  c++  java
  • 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) {}
    }

  • 相关阅读:
    fork 函数 和vfork 函数的区别
    进程时间
    输出子进程状态
    二维字符串数组字典排序
    括号匹配
    队列实现二叉树层序遍历
    二叉查找树
    分块查找
    JS中的className含义
    Java打印温度转换表
  • 原文地址:https://www.cnblogs.com/li150dan/p/11193952.html
Copyright © 2011-2022 走看看