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形式当中需要进行多个转换,特别是大于号、小于号要转换成 < > ,我们在开发的时候都是在xml当中写>等的,但是用户是想写 > < 等,还想用<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 了,其实,我上面代码也能做,这点后续可以再次改进。

浙公网安备 33010602011771号