读写分离(spring事务代理+mybaits拦截器实现)

1、读写分离

         采用继承DataSourceTransactionManager控制事务读写分离,以及mybatis拦截器控制方法读写分离,通过继承AbstractRoutingDataSource获取动态数据源。

1.1、代码说明

1.1.1、本地数据源管理

创建本地数据源类型管理类,使用ThreadLocal保存本地数据源类型

bodsite-common - com.bodsite.common.datasource. DataSourceHandler

package com.bodsite.common.datasource;

/**

 * @Description:本地线程数据源

 * @author bod

 * @date

 *

 */

public class DataSourceHandler {

   public enum DYNAMIC_DATA_SOURCE{

      MASTER,//主库(写)

      SLAVE;//从库(读)

   }

  

   private static final ThreadLocal dataSourceThreadLocal = new ThreadLocal();

  

   protected static void set(DYNAMIC_DATA_SOURCE dynamic_data_source){

      dataSourceThreadLocal.set(dynamic_data_source);

   }

  

   /**

    * 设置为主库

    * @author bod

    */

   protected static void setMaster(){

      dataSourceThreadLocal.set(DYNAMIC_DATA_SOURCE.MASTER);

   }

  

   /**

    * 设置为读库

    * @author bod

    */

   protected static void setSlave(){

      dataSourceThreadLocal.set(DYNAMIC_DATA_SOURCE.SLAVE);

   }

 

   /**

    * 判断是否为主库

    * @author bod

    */

   protected static boolean isMaster(){

      return isThis(DYNAMIC_DATA_SOURCE.MASTER);

   }

  

   /**

    * 判断是否为从

    * @author bod

    */

   protected static boolean isSlave(){

      return isThis(DYNAMIC_DATA_SOURCE.SLAVE);

   }

 

   protected static boolean isThis(DYNAMIC_DATA_SOURCE dynamic_data_source){

      if(dataSourceThreadLocal.get()==null){

         return false;

      }

      return dynamic_data_source == dataSourceThreadLocal.get();

   }

  

   protected static void DataSoruceClean(){

      dataSourceThreadLocal.remove();

   }

}

1.1.2、mybatis拦截,设置数据源

创建mybatis拦截器,拦截update、query方法,设置数据源,如果有事务,不做拦截,有事务的情况,在事务管理器中进行设置。

bodsite-common - com.bodsite.common.datasource. DynamicDataSourceInterceptor

package com.bodsite.common.datasource;

 

import java.util.Properties;

 

import org.apache.ibatis.executor.Executor;

import org.apache.ibatis.executor.keygen.SelectKeyGenerator;

import org.apache.ibatis.mapping.MappedStatement;

import org.apache.ibatis.mapping.SqlCommandType;

import org.apache.ibatis.plugin.Interceptor;

import org.apache.ibatis.plugin.Intercepts;

import org.apache.ibatis.plugin.Invocation;

import org.apache.ibatis.plugin.Plugin;

import org.apache.ibatis.plugin.Signature;

import org.apache.ibatis.session.ResultHandler;

import org.apache.ibatis.session.RowBounds;

import org.springframework.transaction.support.TransactionSynchronizationManager;

 

/**

 * @Description: mybatis plugin 拦截器-设置数据源

 * @author bod

 * @date

 *

 */

@Intercepts({

      @Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class,

            RowBounds.class, ResultHandler.class }), // method:方法名,type:类,args:方法参数

      @Signature(method = "update", type = Executor.class, args = { MappedStatement.class, Object.class }) })

public class DynamicDataSourceInterceptor implements Interceptor {

 

   @Override

   public Object intercept(Invocation invocation) throws Throwable {

      // 是否有事务

      boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();

      if (!synchronizationActive) {

         Object[] args = invocation.getArgs();

         MappedStatement ms = (MappedStatement) args[0];

         if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)){//查询

              //!selectKey 为自增id查询主键(SELECT LAST_INSERT_ID() )方法,使用主库

                if(ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {

                DataSourceHandler.setMaster();

                }else{

                DataSourceHandler.setSlave();

                }

         }else{//其他

            DataSourceHandler.setMaster();

         }

      }

      Object result = invocation.proceed();

      DataSourceHandler.DataSoruceClean();

      return result;

   }

 

   @Override

   public Object plugin(Object target) {

      if (target instanceof Executor) {

         return Plugin.wrap(target, this);

      } else {

         return target;

      }

   }

 

   @Override

   public void setProperties(Properties properties) {

 

   }

 

}

 

1.1.3、spring事务管理,设置数据源

创建动态事务管理类,继承DataSourceTransactionManager,根据读写判断,设置数据源。

bodsite-common - com.bodsite.common.datasource. DataSourceTransactionManager

   package com.bodsite.common.datasource;

 

import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import org.springframework.transaction.TransactionDefinition;

 

/**

 *

 * @Description:根据事务这是数据源(必须有事务才能进入)

 * @author bod

 * @date

 *

 */

public class  DynamicDataSourceTransactionManager extends DataSourceTransactionManager{

 

   /**

    *

    */

   private static final long serialVersionUID = 1L;

 

   @Override

   protected void doBegin(Object transaction, TransactionDefinition definition) {

      boolean readOnly = definition.isReadOnly();

      if(readOnly){//只读

         DataSourceHandler.setSlave();

      }else{//读写

         DataSourceHandler.setMaster();

      }

      super.doBegin(transaction, definition);

   }

 

   @Override

   protected void doCleanupAfterCompletion(Object transaction) {

      super.doCleanupAfterCompletion(transaction);

      DataSourceHandler.DataSoruceClean();

   }

  

}

 

1.1.4、动态获取数据源

创建动态获取数据源类,继承AbstractRoutingDataSource,根据读写判断,设置数据源。

提供两种获取数据源方式:

1、通过重写determineCurrentLookupKey方法,返回数据源名称,走AbstractRoutingDataSource的determineTargetDataSource(根据determineCurrentLookupKey返回的名称)方法获取数据源。

2、通过重写determineTargetDataSource。直接返回数据源。

bodsite-common - com.bodsite.common.datasource. DynamicDataSource

package com.bodsite.common.datasource;

 

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.concurrent.ThreadLocalRandom;

import java.util.concurrent.atomic.AtomicInteger;

 

import javax.sql.DataSource;

 

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

 

import com.bodsite.common.logger.Logger;

import com.bodsite.common.logger.LoggerFactory;

 

/**

 * @Description:动态数据源

 * @author bod

 * @date

 *

 */

public class DynamicDataSource extends AbstractRoutingDataSource {

   private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

   private DataSource master;

   private List slaves;

    private int slaveSize; //读数据源个数

   private Integer strategy;// 默认,0:轮询,1,随机

   private AtomicInteger counter = new AtomicInteger();

   private Map targetDataSources = new HashMap<>();

 

   /******************* 1、加载数据源关系, 设置数据源名,会根据数据源名返回数据源 ************/

   /**

    * 设置数据源名

    */

   @Override

   protected Object determineCurrentLookupKey() {

      return getDataSourceName();

   }

 

   /**

    * 设置数据源映射关系

    */

   @Override

   public void afterPropertiesSet() {

      if (this.master == null) {

         throw new IllegalArgumentException("Property 'targetDataSources' is required");

      }

      setDefaultTargetDataSource(master);

      targetDataSources.put(DataSourceHandler.DYNAMIC_DATA_SOURCE.MASTER.name(), master);

      if (slaves != null && !slaves.isEmpty()) {

         this.slaveSize = slaves.size();

         for (int i = 0; i < slaveSize; i++) {

            targetDataSources.put(DataSourceHandler.DYNAMIC_DATA_SOURCE.SLAVE.name() + i, slaves.get(i));

         }

      }

      setTargetDataSources(targetDataSources);

      super.afterPropertiesSet();

   }

 

   /******************* 2、直接返回数据源 ***********************/

   /**

    * 原方法:根据数据源名称返回数据源,自定义直接返回数据源

    */

   /*@Override

   protected DataSource determineTargetDataSource() {

      return (DataSource) targetDataSources.get(getDataSourceName());

   }*/

 

   /**

    * 获取数据源名称

    * @author bod

    */

   public String getDataSourceName() {

      String dataSourceName = null;

      if (DataSourceHandler.isMaster()) {

         dataSourceName = DataSourceHandler.DYNAMIC_DATA_SOURCE.MASTER.name();

      } else if (DataSourceHandler.isSlave() && slaves != null && !slaves.isEmpty()) {

         int index = 0;

         if (strategy == null || strategy == 0) {

            int count = counter.incrementAndGet();

            index = count%slaveSize;

         }else if(strategy == 1){

               index = ThreadLocalRandom.current().nextInt(0, slaveSize);

         }

         dataSourceName = DataSourceHandler.DYNAMIC_DATA_SOURCE.SLAVE.name()+index;

      }else{

         dataSourceName = DataSourceHandler.DYNAMIC_DATA_SOURCE.MASTER.name();

      }

      logger.info("This data source name is "+dataSourceName);

      return dataSourceName;

   }

   public DataSource getMaster() {

      return master;

   }

 

   public void setMaster(DataSource master) {

      this.master = master;

   }

 

   public List getSlaves() {

      return slaves;

   }

 

   public void setSlaves(List slaves) {

      this.slaves = slaves;

   }

 

   public Integer getStrategy() {

      return strategy;

   }

 

   public void setStrategy(Integer strategy) {

      this.strategy = strategy;

   }

 

}

 

 

1.1.5、application-mybatis.xml配置

bodsite-site-service - application-mybatis.xml

"1.0" encoding="UTF-8"?>

"http://www.springframework.org/schema/beans"

   xmlns:context="http://www.springframework.org/schema/context"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"

   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.3.xsd

      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd

            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

  

  

   "masterDataSource" class="com.alibaba.druid.pool.DruidDataSource"

      init-method="init" destroy-method="close">

     

      "driverClassName" value="${master.jdbc.driver}" />

      "url" value="${master.jdbc.url}" />

      "username" value="${master.jdbc.username}" />

      "password" value="${master.jdbc.password}" />

 

     

      "initialSize" value="1" />

     

      "maxActive" value="50" />

     

      "minIdle" value="10" />

     

      "maxWait" value="60000" />

     

      "timeBetweenEvictionRunsMillis" value="60000" />

     

      "minEvictableIdleTimeMillis" value="300000" />

     

      "validationQuery" value="SELECT 'x' FROM DUAL" />

     

      "testWhileIdle" value="true" />

     

      "testOnBorrow" value="false" />

     

      "testOnReturn" value="false" />

     

      "filters" value="stat" />

  

 

   "slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource"

      init-method="init" destroy-method="close">

     

      "driverClassName" value="${slave1.jdbc.driver}" />

      "url" value="${slave1.jdbc.url}" />

      "username" value="${slave1.jdbc.username}" />

      "password" value="${slave1.jdbc.password}" />

 

     

      "initialSize" value="1" />

     

      "maxActive" value="50" />

     

      "minIdle" value="10" />

     

      "maxWait" value="60000" />

     

      "timeBetweenEvictionRunsMillis" value="60000" />

     

      "minEvictableIdleTimeMillis" value="300000" />

     

      "validationQuery" value="SELECT 'x' FROM DUAL" />

     

      "testWhileIdle" value="true" />

     

      "testOnBorrow" value="false" />

     

      "testOnReturn" value="false" />

     

      "filters" value="stat" />

  

  

   "dataSource" class="com.bodsite.common.datasource.DynamicDataSource">

      "master" ref="masterDataSource" />

      "slaves">

        

            "slaveDataSource" />

        

     

      "strategy" value="0"/>

  

 

 

   "sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

      "dataSource" ref="dataSource" />

      "configLocation" value="classpath:mybatis-config.xml" />

      "mapperLocations" value="classpath:/mappings/**/*.xml" />

  

  

   "transactionManager"

      class="com.bodsite.common.datasource.DynamicDataSourceTransactionManager">

      "dataSource" ref="dataSource" />

  

  

   "transactionManager"

      proxy-target-class="true" />

  

  

   "org.mybatis.spring.mapper.MapperScannerConfigurer">

      "basePackage" value="com.bodsite.**.dao" />

  

1.1.6、mybatis-generator.xml 配置-添加拦截器

bodsite-site-service - mybatis- generator.xml

"1.0" encoding="UTF-8"?>

  

     

      "cacheEnabled" value="true" />

     

      "lazyLoadingEnabled" value="true" />

     

      "multipleResultSetsEnabled" value="true" />

     

      "useColumnLabel" value="true" />

     

      "useGeneratedKeys" value="false" />

     

      "defaultExecutorType" value="SIMPLE" />

     

      "mapUnderscoreToCamelCase" value="true" />

  

  

  

   "com.bodsite.common.datasource.DynamicDataSourceInterceptor">

  

  

1.2、测试

1、启动bodsite-site-service,在bodsite-site中的DemoConsumer类,执行查询

读写分离(spring事务代理+mybaits拦截器实现)_第1张图片

读写分离(spring事务代理+mybaits拦截器实现)_第2张图片

142922_IrdS_1416405.png

142927_kHq1_1416405.png  

2、在bodsite-site中的DemoConsumer类,执行插入

读写分离(spring事务代理+mybaits拦截器实现)_第3张图片

读写分离(spring事务代理+mybaits拦截器实现)_第4张图片

项目地址:https://git.oschina.net/bodsite/bodsite

转载于:https://my.oschina.net/u/1416405/blog/837563

你可能感兴趣的:(读写分离(spring事务代理+mybaits拦截器实现))