5-3 代码层读写分离的实现A

  • dataSource 不能满足主从分离的需求,因为只能从单一的数据源里获取数据,也就是只能读取一个jdbc.url.

  • 我们需要实现,写的时候用主库数据源,读的时候用从库数据源

1.

package com.o2o.dao.split;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
//AbstractRoutingDataSource是个抽象类,具有路由功能能路由到不同数据源,类内还有determineTargetDataSource()方法来决定用哪个数据源,类内还有determineCurrentLookupKey方法来决定数据源的名字
public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();
        //返回对应key值
    }

}

2.

package com.o2o.dao.split;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



public class DynamicDataSourceHolder {
    private static Logger logger =  LoggerFactory.getLogger(DynamicDataSourceHolder.class);
    //能保证线程安全,可以理解为是一个十分安全的临时数据存储容器
    private static  ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static final String DB_MASTER = "master";
    public static final String DB_SLAVE  = "slave" ;
    /**
     * 获取线程的dbType
     * @return
     */
    public static String getDbType() {
        String db = contextHolder.get();
        if(db==null) {
            db=DB_MASTER;
        }
        return db;
    }
    /**
     * 设置线程的dbType
     * @param str
     */
    public static void setDbType(String str) {
        logger.debug("所使用的数据源为:" + str);
        contextHolder.set(str);
    }
    /**
     * 清理连接类型
     */
    public static void clearDbType() {
        contextHolder.remove();

    }

}

3.

package com.o2o.dao.split;

/**
 * 用来拦截mybatis的传递进来的SQL信息,如果是insert,update就用写数据源,如果是select就用读数据源
 * 
 * @author Hasee
 *
 */
@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 DynamicDataSourceInterceptor implements Interceptor {
    /**
     * 主要拦截方法,就是要进行拦截的时候要执行的方法
     */
    private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);
    //定义一个正则表达式来进行匹配SQL语句
    private static final String REGEX = ".insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";

    public Object intercept(Invocation invocations) throws Throwable {
        //要是用@tansaction处理的话synchronizationActive就为ture
        boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
        //获得方法的参数,ex:update中的MappedStatement
        Object[] objects = invocations.getArgs();
        MappedStatement ms = (MappedStatement) objects[0];
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;
        if (synchronizationActive != true) {
        //selectKey 为自增id查询主键(SELECT LAST_INSTER_ID())方法,使用主库
            if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                    lookupKey = DynamicDataSourceHolder.DB_MASTER;
                } else {
                    BoundSql boundSql = ms.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 {
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }
        logger.debug("设置方法[{}] use[{}] Strategy,SqlCommandType [{}]..", ms.getId(), lookupKey,
                ms.getSqlCommandType().name());
        DynamicDataSourceHolder.setDbType(lookupKey);
        return invocations.proceed();
    }

    // 决定返回的是本体,还是编织好的代理,Executor在mybatis里是用来支持一系列增删改查的操作的
    // 就是说如果检测的对象里有增删改查的操作我们就把他拦截下来
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
        // 判定传入的对象是不是mybatis里的Executor对象的实例其实不用,因为在注解里标注了
            return Plugin.wrap(target, this);
            // 将intercept包装到target里面(也就是编制好的代理)
        } else {
            return target;// 返回本体
        }

    }

    @Override
    //用于在Mybatis配置文件中指定一些属性的。在Configuration初始化当前的Interceptor时就会执行
    public void setProperties(Properties properties) {
        // TODO Auto-generated method stub

    }

}

  • 解释: mybatis-plugin(拦截器)

    MyBatis的Configuration配置中有一个Plugin配置,根据其名可以解释为“插件”,这个插件实质可以理解为“拦截器”。可以想想拦截器是怎么实现的。Plugin用到了Java中很重要的一个特性——动态代理。所以这个Plugin可以理解为,在调用一个方法时,我“拦截”其方法做一些我想让它做的事。它可以拦截以下方法:

image

定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。

对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。

对于实现自己的Interceptor而言有两个很重要的注解,一个是@Intercepts,其值是一个@Signature数组。@Intercepts用于表明当前的对象是一个Interceptor,而@Signature则表明要拦截的接口、方法以及对应的参数类型,每个Signature都是一个拦截点
  • 对注解@Intercepts的理解可以依靠下面的一段代码
@Intercepts( {
       @Signature(method = "query", type = Executor.class, args = {
              MappedStatement.class, Object.class, RowBounds.class,
              ResultHandler.class }),
       @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class MyInterceptor implements Interceptor {

    public Object intercept(Invocation invocation) throws Throwable {
       Object result = invocation.proceed();
       System.out.println("Invocation.proceed()");
       return result;
    }

    public Object plugin(Object target) {
       return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
       String prop1 = properties.getProperty("prop1");
       String prop2 = properties.getProperty("prop2");
       System.out.println(prop1 + "------" + prop2);
    }

}
  1. 首先看setProperties方法,这个方法在Configuration初始化当前的Interceptor时就会执行,这里只是简单的取两个属性进行打印。

  2. 其次看plugin方法中我们是用的Plugin的逻辑来实现Mybatis的逻辑的。

  3. 接着看MyInterceptor类上我们用@Intercepts标记了这是一个Interceptor,然后在@Intercepts义了两个@Signature,即两个拦截点。第一个@Signature我们定义了该Interceptor将拦截Executor接口中参数类型为MappedStatement、Object、RowBounds和ResultHandler的query方法;第二个@Signature我们定义了该Interceptor将拦截StatementHandler中参数类型为Connection的prepare方法。

  4. 最后再来看一下intercept方法,这里我们只是简单的打印了一句话,然后调用invocation的proceed方法,使当前方法正常的调用。

  5. 对于这个拦截器,Mybatis在注册该拦截器的时候就会利用定义好的n个property作为参数调用该拦截器的setProperties方法。之后在新建可拦截对象的时候会调用该拦截器的plugin方法来决定是返回目标对象本身还是代理对象。对于这个拦截器而言,当Mybatis是要Executor或StatementHandler对象的时候就会返回一个代理对象,其他都是原目标对象本身。然后当Executor代理对象在执行参数类型为MappedStatement、Object、RowBounds和ResultHandler的query方法或StatementHandler代理对象在执行参数类型为Connection的prepare方法时就会触发当前的拦截器的intercept方法进行拦截,而执行这两个接口对象的其他方法时都只是做一个简单的代理。


  • 解释: MappedStatement,SqlSource,BoundSql

    1. MappedStatement类在Mybatis框架中用于表示XML文件中一个sql语句节点,即一个、或者标签。Mybatis框架在初始化阶段会对XML配置文件进行读取,将其中的sql语句节点对象化为一个个MappedStatement对象。
    2. SqlSource是一个接口类,在MappedStatement对象中是作为一个属性出现的.SqlSource接口只有一个getBoundSql(Object parameterObject)方法,返回一个BoundSql对象。一个BoundSql对象,代表了一次sql语句的实际执行,而SqlSource对象的责任,就是根据传入的参数对象,动态计算出这个BoundSql,也就是说Mapper文件中的节点的计算,是由SqlSource对象完成的。SqlSource最常用的实现类是DynamicSqlSource.
  • 解释: 正则表达式

    1. u0020 - 表示空格
    2. .* - 匹配所有字符
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值