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

  • 相关阅读:
    创建Variant数组
    ASP与存储过程(Stored Procedures)
    FileSystemObject对象成员概要
    Kotlin 朱涛9 委托 代理 懒加载 Delegate
    Kotlin 朱涛 思维4 空安全思维 平台类型 非空断言
    Kotlin 朱涛7 高阶函数 函数类型 Lambda SAM
    Kotlin 朱涛16 协程 生命周期 Job 结构化并发
    Proxy 代理模式 动态代理 cglib MD
    RxJava 设计理念 观察者模式 Observable lambdas MD
    动态图片 Movie androidgifdrawable GifView
  • 原文地址:https://www.cnblogs.com/li150dan/p/11193952.html
Copyright © 2011-2022 走看看