mybatis基于代码方式执行sql的定制化、执行cache的定制化

首先,可以看本人的 https://www.cnblogs.com/gougouyangzi/articles/9945648.html  这篇文章 《mybatis如何做到执行string形式的sql文件

在上述这篇文章里面,我们借鉴动态sql的原理,以及mysql的源码分析研制除了第一版本基于代码方式的执行sql的方式,下一步,我们打算整合ehcache框架。(为什么不说说整合 1级缓存、2级缓存,不好意思1级缓存是默认开启的,2级缓存只要new出一个 mybatis的默认的 PrepetualCache。

     这一点我就不再多说了,我再说说我的这个代码方式实现对sql的定制化、对cache的定制化(结合ehcache方式).

     不多说,我就直接上我的util类以及main方法测试类 ,然后再来讲讲如何结合使用的,其实只要理解了我前面所说的mybatis相关的知识,然后

全部应用在这里,是非常好理解的。

  

package root.report.util;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cache.TransactionalCacheManager;
import org.apache.ibatis.cache.decorators.TransactionalCache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.executor.CachingExecutor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.transaction.Transaction;
import org.apache.log4j.Logger;
import org.apache.poi.ss.formula.functions.T;
import org.mybatis.caches.ehcache.LoggingEhcache;
import root.report.db.DbFactory;
import root.report.util.cache.EhcacheManager;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Auther: pccw
 * @Date: 2018/11/9 16:14
 * @Description:
 */
public class ExecuteSqlUtil {

    private static Logger log = Logger.getLogger(ExecuteSqlUtil.class);

    private final static TransactionalCacheManager tcm = new TransactionalCacheManager();

    /**
     *
     * 功能描述:
     *      对符合 mybatis.dtd形式的sql进行动态sql解析并执行,返回Map结构的数据集
     * @param: executeSql 要执行的sql , sqlSession 数据库会话,namespace 命名空间,mapper_id mapper的ID,bounds 分页参数 ,statementType statement的类型
     * @auther:
     * @date: 2018/11/9 16:17
     */
    public static List<?> executeDataBaseSql(String executeSql, SqlSession sqlSession, String namespace, String mapper_id, RowBounds bounds,
                                               Class<?> clazz,Object param,StatementType statementType,Boolean cacheFlag){
        if(statementType==null){
            statementType = StatementType.PREPARED; // 默认为 prepared
        }
        if(cacheFlag==null){
            cacheFlag = false;  // 默认为false 默认不开启缓存
        }

        List<?> list = null;
        List<?> cacheList = null;
        CacheKey cacheKey = null;
        if(bounds==null){
            bounds = new RowBounds();
        }

        // 1. 对executeSql 加上script标签
        StringBuffer sb = new StringBuffer();
        sb.append("<script>");
        sb.append(executeSql);
        sb.append("</script>");
        log.info("转换后的sql为:->"+sb.toString());
        Configuration configuration = sqlSession.getConfiguration();
        configuration.setCacheEnabled(true);  // 开启二级缓存?

        LanguageDriver languageDriver = configuration.getDefaultScriptingLanguageInstance();  // 2. languageDriver 是帮助我们实现dynamicSQL的关键
        SqlSource sqlSource = languageDriver.createSqlSource(configuration,sb.toString(),clazz);  //  泛型化入参
      //  configuration.getCaches().forEach(e -> System.out.print(e.getId()));
        MappedStatement ms = null;

        // 如果我们从 configuration 当中可以取得到的话,则看缓存当中是否存在
        if(configuration.getMappedStatementNames().contains(namespace+"."+mapper_id)){
            ms = configuration.getMappedStatement(namespace+"."+mapper_id);
        }else {
            log.info("======不存在此mappedStatment,可以构建=====");
        }
        if(ms == null){
            // 构建ms,这个时候 configuration 当中是一定存在ms了
            ms =  newSelectMappedStatement(configuration,namespace+"."+mapper_id,sqlSource,clazz,statementType,cacheFlag);
        }

        if(!cacheFlag){
            // 如果不需要缓存  那么直接查询就行 ,并且也不需要装入到缓存当中去
            if(bounds!=null){
                list = sqlSession.selectList(namespace+"."+mapper_id,param,bounds);
                log.info("执行了一次查询");
            }else {
                list = sqlSession.selectList(namespace+"."+mapper_id,param);
                log.info("执行了一次查询");
            }
            return list;
        }else {
            // 组装cache
            cacheKey = sqlSession.getConfiguration().newExecutor(new Transaction() {
                @Override
                public Connection getConnection() throws SQLException {
                    return this.getConnection();
                }
                @Override
                public void commit() throws SQLException {
                    this.getConnection().commit();
                }
                @Override
                public void rollback() throws SQLException {
                    this.getConnection().rollback();
                }
                @Override
                public void close() throws SQLException {
                    this.getConnection().close();
                }
                @Override
                public Integer getTimeout() throws SQLException {
                    return 5000;
                }
            }, ExecutorType.SIMPLE).createCacheKey(ms,param,bounds,ms.getBoundSql(param));

            // 从 ehcache 当中去缓存的值,如果存在则返回不存在 则 查询并装入到缓存
            Element ehcacheElement  = EhcacheManager.getCache().get(cacheKey.toString());
            if(ehcacheElement!=null && ehcacheElement.getObjectValue()!=null){
                cacheList = (List<?>) ehcacheElement.getObjectValue();   // 强转
                log.info("cache hit  缓存命中,命中率为:");
                return cacheList;
            }else {
                if(bounds!=null){
                    list = sqlSession.selectList(namespace+"."+mapper_id,param,bounds);
                    log.info("执行了一次查询,并把结果集装入到缓存当中");
                }else {
                    list = sqlSession.selectList(namespace+"."+mapper_id,param);
                    log.info("执行了一次查询,并把结果集装入到缓存当中");
                }
                // 装入缓存
               /* if(cacheKey!=null){
                    tcm.putObject(ms.getCache(),cacheKey,list);
                }*/
                log.info("#############=>测试cacheKey呗重写了没有"+cacheKey.toString());
                // VERSION 3 切到 ehcache 缓存当中 ,cacheKey 的toString 方法已经被重写
                //  默认都配置到 mybatis-ys-cache 这个当中去了
                //  Ehcache ehcache = new Cache(cacheKey.toString(),5000,false,false,10,2);
                Element resultElement = new Element(cacheKey.toString(), list);
                EhcacheManager.getCache().put(resultElement);
            }
            // cacheList = (List<?>)tcm.getObject(ms.getCache(),cacheKey);
        }

        return list;
    }

    // cacheFlag 是否开启缓存标志位
    private  static MappedStatement newSelectMappedStatement(Configuration configuration,String msId, SqlSource sqlSource,
                                                             final Class<?> resultType,StatementType statementType,Boolean cacheFlag) {
        // 加强逻辑 : 一定要防止 MappedStatement 重复问题
        MappedStatement msTest = null;
        try{
            synchronized (configuration) {   // 防止并发插入多次
                msTest = configuration.getMappedStatement(msId);
                if (msTest != null) {
                    configuration.getMappedStatementNames().remove(msTest.getId());
                }

            }
        }catch (IllegalArgumentException e){
            log.info("没有此mappedStatment,可以注入此mappedStatement到configuration当中");
        }
        MappedStatement ms = null;
        // 构建一个 select 类型的ms ,通过制定SqlCommandType.SELECT
        ms = new MappedStatement.Builder(
                configuration, msId, sqlSource, SqlCommandType.SELECT)
                .statementType(statementType)
                .useCache(false)      // 切断掉 二级缓存 切换到 ehcache 当中去,即是保证执行的时候不去二级缓存找了,直接查询
 .resultMaps(new ArrayList<ResultMap>() { { add(new ResultMap.Builder(configuration, "defaultResultMap", resultType, new ArrayList<ResultMapping>(0)).build()); } }) .build(); synchronized (configuration){ configuration.addMappedStatement(ms); // 加入到此中去  } return ms; } }

对于这段代码,有不懂的地方可以在下方评论。下面,我们来分析下,我的需求,我的处理方法 。

需求 :  方法调用前,我们知道一个类似 mapper.xml 形式的 select 或者 insert、update等语句,即便是存储过程语句的话,但是我们约束要符合mybatis的dtd的规范,并且只能是一个完整的sql,而不是带着引用的。

      我们前面分析了 如果我们把这个sql用dom4j或者其他xml工具来生成类似 mapper.xml 形式的xml,那么其中有很多步骤需要处理,最重要的是,我们要重新刷新 SqlSessionFactory, 这样会导致在查询 或者进行数据库操作的用户连接中断。而且对select形式存到 mybatis的xml形式当中需要进行多个转换,特别是大于号、小于号要转换成 &lt; &gt; ,我们在开发的时候都是在xml当中写&gt;等的,但是用户是想写 > < 等,还想用<if>标签这种自定义的OGNL表达式,这不禁让我联想到了 动态sql的解析、注解方式的Mybatis.基于此,我对此进行了一次定制化代码的编写,如上的类当中,我们先看下面一个类,其是生成一个 MappedStatement,传入的sql是带<script></script>标签的,这样来解决用户的 sql 编写问题,然后我们结合 ehcache 的时候,我们有一个全局的 ehcacheManager 类来得到 ehcache.xml 当中的cache。

    下面 是关于 ehcache.xml 、EhcacheManager 的代码 : 

 ehcache的存放位置 : resource资源文件夹下  

  

  

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
    updateCheck="true" monitoring="autodetect"
    dynamicConfig="true">


    <defaultCache
       maxElementsInMemory="1"
       eternal="false"
       overflowToDisk="false"
       timeToIdleSeconds="1800"
       timeToLiveSeconds="1800">
    </defaultCache>

    <cache name="data-cache"
      maxElementsInMemory="500"
      overflowToDisk="false"
      eternal="true"
      timeToIdleSeconds="18000"
      timeToLiveSeconds="18000"
      memoryStoreEvictionPolicy="LRU"
      transactionalMode="off" />

            <!-- xx平台 mybatis定制化 执行的缓存存放处 -->
    <cache name="mybatis-ys-cache"
           maxElementsInMemory="500"
           overflowToDisk="false"
           eternal="true"
           timeToIdleSeconds="18000"
           timeToLiveSeconds="18000"
           memoryStoreEvictionPolicy="LRU"
           transactionalMode="off">
      <searchable keys="true"/> <!--可以根据Key进行查询,查询的Attribute就是keys-->
    </cache>
</ehcache>
package root.report.util.cache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;

import java.io.File;
import java.io.InputStream;
import java.net.URL;

/**
 * @Auther: pccw
 * @Date: 2018/11/16 16:36
 * @Description:
 */
public class EhcacheManager {

    private static Cache cache;

    public static synchronized  Cache getCache() {
        if(cache == null) initCache();
        return cache;
    }


    private static void initCache() {
        URL url = EhcacheManager.class.getClassLoader().getResource("ehcache.xml");
        CacheManager cm =  CacheManager.create(url);  // 下面流的读取方式在测试类执行的时候会得不到,故用此方式
       /* InputStream in = EhcacheManager.class.getClassLoader().getResourceAsStream("/ehcache.xml");
        CacheManager cm = CacheManager.create(in);*/
        cache = cm.getCache("mybatis-ys-cache");
    }

}

这里我还要提出一点,从最上面的 exec 执行sql 的代码当中,不难看出,我是吧项目的所有cache都放到这个 mybatis-ys-cache 的 element 上来管理了,而从前面我们分析 mybatis-ehcache 的源码执行过程来看,mapper.xml 当中的namespace就是一个cache,并且 在<cache type="" xxx="xx" /> 这种方式来指定当前namespace 的cache 策略,而现在我是直接 附着在 mybatis-ehcache 上,没有做到划分 namespace 了,其实,我上面代码也能做,这点后续可以再次改进。

posted @ 2018-11-19 17:18  白云是世界的公民  阅读(767)  评论(0)    收藏  举报