Plugins
摘一段来自MyBatis官方文档的文字。
MyBatis允许你在某一点拦截已映射语句执行的调用。默认情况下,MyBatis允许使用插件来拦截方法调用:
Executor(update、query、flushStatements、commint、rollback、getTransaction、close、isClosed)
ParameterHandler(getParameterObject、setParameters)
ResultSetHandler(handleResultSets、handleOutputParameters)
StatementHandler(prepare、parameterize、batch、update、query)
这些类中方法的详情可以通过查看每个方法的签名来发现,而且它们的源代码存在于MyBatis发行包中。你应该理解你所覆盖方法的行为,假设你所做的要比监视调用要多。如果你尝试修改或覆盖一个给定的方法,你可能会打破MyBatis的核心。这是低层次的类和方法,要谨慎使用插件。
插件示例:打印每条SQL语句及其执行时间
以下通过代码来演示一下如何使用MyBatis的插件,要演示的场景是:打印每条真正执行的SQL语句及其执行的时间。这是一个非常有用的需求,MyBatis本身的日志可以记录SQL,但是有以下几个问题:
MyBatis日志打印出来的SQL日志,参数都被占位符”?”替换,无法知道真正执行的SQL语句中的参数是什么
MyBatis日志打印出来的SQL日志,有大量的换行符,通常一句SQL语句要通过十几行显示,阅读体验非常差
无法记录SQL执行时间,有SQL执行时间就可以精准定位到执行时间比较慢的SQL
写MyBatis插件非常简单,只需要实现Interceptor接口即可,我这里将我的Interceptor命名为SqlCostInterceptor:
1 /** 2 * Sql执行时间记录拦截器 3 */ 4 @Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), 5 @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), 6 @Signature(type = StatementHandler.class, method = "batch", args = { Statement.class })}) 7 public class SqlCostInterceptor implements Interceptor { 8 9 @Override 10 public Object intercept(Invocation invocation) throws Throwable { 11 Object target = invocation.getTarget(); 12 13 long startTime = System.currentTimeMillis(); 14 StatementHandler statementHandler = (StatementHandler)target; 15 try { 16 return invocation.proceed(); 17 } finally { 18 long endTime = System.currentTimeMillis(); 19 long sqlCost = endTime - startTime; 20 21 BoundSql boundSql = statementHandler.getBoundSql(); 22 String sql = boundSql.getSql(); 23 Object parameterObject = boundSql.getParameterObject(); 24 List<parametermapping> parameterMappingList = boundSql.getParameterMappings(); 25 26 // 格式化Sql语句,去除换行符,替换参数 27 sql = formatSql(sql, parameterObject, parameterMappingList); 28 29 System.out.println("SQL:[" + sql + "]执行耗时[" + sqlCost + "ms]"); 30 } 31 } 32 33 @Override 34 public Object plugin(Object target) { 35 return Plugin.wrap(target, this); 36 } 37 38 @Override 39 public void setProperties(Properties properties) { 40 41 } 42 43 @SuppressWarnings("unchecked") 44 private String formatSql(String sql, Object parameterObject, List<parametermapping> parameterMappingList) { 45 // 输入sql字符串空判断 46 if (sql == null || sql.length() == 0) { 47 return ""; 48 } 49 50 // 美化sql 51 sql = beautifySql(sql); 52 53 // 不传参数的场景,直接把Sql美化一下返回出去 54 if (parameterObject == null || parameterMappingList == null || parameterMappingList.size() == 0) { 55 return sql; 56 } 57 58 // 定义一个没有替换过占位符的sql,用于出异常时返回 59 String sqlWithoutReplacePlaceholder = sql; 60 61 try { 62 if (parameterMappingList != null) { 63 Class> parameterObjectClass = parameterObject.getClass(); 64 65 // 如果参数是StrictMap且Value类型为Collection,获取key="list"的属性,这里主要是为了处理<foreach>循环时传入List这种参数的占位符替换 66 // 例如select * from xxx where id in <foreach>...</foreach> 67 if (isStrictMap(parameterObjectClass)) { 68 StrictMap<collection>> strictMap = (StrictMap<collection>>)parameterObject; 69 70 if (isList(strictMap.get("list").getClass())) { 71 sql = handleListParameter(sql, strictMap.get("list")); 72 } 73 } else if (isMap(parameterObjectClass)) { 74 // 如果参数是Map则直接强转,通过map.get(key)方法获取真正的属性值 75 // 这里主要是为了处理<insert>、<delete>、<update>、<select>时传入parameterType为map的场景 76 Map, ?> paramMap = (Map, ?>) parameterObject; 77 sql = handleMapParameter(sql, paramMap, parameterMappingList); 78 } else { 79 // 通用场景,比如传的是一个自定义的对象或者八种基本数据类型之一或者String 80 sql = handleCommonParameter(sql, parameterMappingList, parameterObjectClass, parameterObject); 81 } 82 } 83 } catch (Exception e) { 84 // 占位符替换过程中出现异常,则返回没有替换过占位符但是格式美化过的sql,这样至少保证sql语句比BoundSql中的sql更好看 85 return sqlWithoutReplacePlaceholder; 86 } 87 88 return sql; 89 } 90 91 /** 92 * 美化Sql 93 */ 94 private String beautifySql(String sql) { 95 sql = sql.replace("\n", "").replace("\t", "").replace(" ", " ").replace("( ", "(").replace(" )", ")").replace(" ,", ","); 96 97 return sql; 98 } 99 100 /**101 * 处理参数为List的场景102 */103 private String handleListParameter(String sql, Collection> col) {104 if (col != null && col.size() != 0) {105 for (Object obj : col) {106 String value = null;107 Class> objClass = obj.getClass();108 109 // 只处理基本数据类型、基本数据类型的包装类、String这三种110 // 如果是复合类型也是可以的,不过复杂点且这种场景较少,写代码的时候要判断一下要拿到的是复合类型中的哪个属性111 if (isPrimitiveOrPrimitiveWrapper(objClass)) {112 value = obj.toString();113 } else if (objClass.isAssignableFrom(String.class)) {114 value = "\"" + obj.toString() + "\""; 115 }116 117 sql = sql.replaceFirst("\\?", value);118 }119 }120 121 return sql;122 }123 124 /**125 * 处理参数为Map的场景126 */127 private String handleMapParameter(String sql, Map, ?> paramMap, List<parametermapping> parameterMappingList) {128 for (ParameterMapping parameterMapping : parameterMappingList) {129 Object propertyName = parameterMapping.getProperty();130 Object propertyValue = paramMap.get(propertyName);131 if (propertyValue != null) {132 if (propertyValue.getClass().isAssignableFrom(String.class)) {133 propertyValue = "\"" + propertyValue + "\"";134 }135 136 sql = sql.replaceFirst("\\?", propertyValue.toString());137 }138 }139 140 return sql;141 }142 143 /**144 * 处理通用的场景145 */146 private String handleCommonParameter(String sql, List<parametermapping> parameterMappingList, Class> parameterObjectClass, 147 Object parameterObject) throws Exception {148 for (ParameterMapping parameterMapping : parameterMappingList) {149 String propertyValue = null;150 // 基本数据类型或者基本数据类型的包装类,直接toString即可获取其真正的参数值,其余直接取paramterMapping中的property属性即可151 if (isPrimitiveOrPrimitiveWrapper(parameterObjectClass)) {152 propertyValue = parameterObject.toString();153 } else {154 String propertyName = parameterMapping.getProperty();155 156 Field field = parameterObjectClass.getDeclaredField(propertyName);157 // 要获取Field中的属性值,这里必须将私有属性的accessible设置为true158 field.setAccessible(true);159 propertyValue = String.valueOf(field.get(parameterObject));160 if (parameterMapping.getJavaType().isAssignableFrom(String.class)) {161 propertyValue = "\"" + propertyValue + "\"";162 }163 }164 165 sql = sql.replaceFirst("\\?", propertyValue);166 }167 168 return sql;169 }170 171 /**172 * 是否基本数据类型或者基本数据类型的包装类173 */174 private boolean isPrimitiveOrPrimitiveWrapper(Class> parameterObjectClass) {175 return parameterObjectClass.isPrimitive() || 176 (parameterObjectClass.isAssignableFrom(Byte.class) || parameterObjectClass.isAssignableFrom(Short.class) ||177 parameterObjectClass.isAssignableFrom(Integer.class) || parameterObjectClass.isAssignableFrom(Long.class) ||178 parameterObjectClass.isAssignableFrom(Double.class) || parameterObjectClass.isAssignableFrom(Float.class) ||179 parameterObjectClass.isAssignableFrom(Character.class) || parameterObjectClass.isAssignableFrom(Boolean.class));180 }181 182 /**183 * 是否DefaultSqlSession的内部类StrictMap184 */185 private boolean isStrictMap(Class> parameterObjectClass) {186 return parameterObjectClass.isAssignableFrom(StrictMap.class);187 }188 189 /**190 * 是否List的实现类191 */192 private boolean isList(Class> clazz) {193 Class>[] interfaceClasses = clazz.getInterfaces();194 for (Class> interfaceClass : interfaceClasses) {195 if (interfaceClass.isAssignableFrom(List.class)) {196 return true;197 }198 }199 200 return false;201 }202 203 /**204 * 是否Map的实现类205 */206 private boolean isMap(Class> parameterObjectClass) {207 Class>[] interfaceClasses = parameterObjectClass.getInterfaces();208 for (Class> interfaceClass : interfaceClasses) {209 if (interfaceClass.isAssignableFrom(Map.class)) {210 return true;211 }212 }213 214 return false;215 }216 217 }</parametermapping></parametermapping></select></update></delete></insert></collection></collection></foreach></parametermapping></parametermapping>
分析一下这段代码(这个是改良过的版本,主要是增加了对select * from xxx where id in
首先是注解@Intercepts与@Signature,这两个注解是必须的,因为Plugin的wrap方法会取这两个注解里面参数。@Intercepts中可以定义多个@Signature,一个@Signature表示符合如下条件的方法才会被拦截:
接口必须是type定义的类型
方法名必须和method一致
方法形参的Class类型必须和args定义Class类型顺序一致
接着的一个问题是:有四个接口可以拦截,为什么使用StatementHandler去拦截?根据名字来看ParameterHandler和ResultSetHandler,前者处理参数,后者处理结果是不可能使用的,剩下的就是Executor和StatementHandler了。拦截StatementHandler的原因是而不是用Executor的原因是:
Executor的update与query方法可能用到MyBatis的一二级缓存从而导致统计的并不是真正的SQL执行时间
StatementHandler的update与query方法无论如何都会统计到PreparedStatement的execute方法执行时间,尽管也有一定误差(误差主要来自会将处理结果的时间也算上),但是相差不大
接着讲一下setProperties方法,可以将一些配置属性配置在
接着讲一下plugin方法,这里是为目标接口生成代理,不需要也没必要自己去写生成代理的方法,MyBatis的Plugin类已经为我们提供了wrap方法(当然如果自己有自己的逻辑也可以在Plugin.wrap方法前后加入,但是最终一定要使用Plugin.wrap方法生成代理),看一下该方法的实现:
1 public static Object wrap(Object target, Interceptor interceptor) { 2 Map<class>, Set<method>> signatureMap = getSignatureMap(interceptor); 3 Class> type = target.getClass(); 4 Class>[] interfaces = getAllInterfaces(type, signatureMap); 5 if (interfaces.length > 0) { 6 return Proxy.newProxyInstance( 7 type.getClassLoader(), 8 interfaces, 9 new Plugin(target, interceptor, signatureMap));10 }11 return target;12 }</method></class>
因为这里的target一定是一个接口,因此可以放心使用JDK本身提供的Proxy类,这里相当于就是如果该接口满足方法签名那么就为之生成一个代理。
最后就是intercept方法了,这里就是拦截器的核心代码了,方法的逻辑我就不解释了,可以自己看一下,唯一要注意的一点就是无论如何最终一定要返回invocation.proceed(),保证拦截器的层层调用。
xml文件配置即效果演示
写完了插件,只需要在config.xml文件中进行一次配置即可,非常简单:
1 <plugins>2 <plugin></plugin>3 </plugins>
这里每个
有了类和这段配置,就可以使用SqlCostInterceptor了,SqlCostInterceptor是通用的,但是每个人的CRUD是不同的,我打印一下我这里CRUD执行的结果:
1 SQL:[insert into mail(id, create_time, modify_time, web_id, mail, use_for) values(null, now(), now(), "1", "123@sina.com", "个人使用");]执行耗时[1ms]2 SQL:[insert into mail(id, create_time, modify_time, web_id, mail, use_for) values(null, now(), now(), "2", "123@qq.com", "企业使用");]执行耗时[1ms]3 SQL:[insert into mail(id, create_time, modify_time, web_id, mail, use_for) values(null, now(), now(), "3", "123@sohu.com", "注册账号使用");]执行耗时[0ms]
看到打印了完整的SQl语句以及SQL语句执行时间。
不过要说明一点,这个插件只是一个简单的Demo,我并没有完整测试过,应该是无法覆盖所有场景的,所以如果想用这段代码片段打印真正的SQL及其执行时间的朋友,还需要在这个基础上做修改,不过即使不改代码,这个插件起到美化SQL的作用,去除一些换行符还是没问题的。
至于MyBatis插件的实现原理,会在我【MyBatis源码分析】系列文章中详细解读,文章地址为【MyBatis源码分析】插件实现原理。
后记
MyBatis插件机制非常有用,用得好可以解决很多问题,不只是这里的打印SQL语句以及记录SQL语句执行时间,分页、分表都可以通过插件来实现。用好插件的关键是我开头就列举的,这里再列一次:
Executor(update、query、flushStatements、commint、rollback、getTransaction、close、isClosed)
ParameterHandler(getParameterObject、setParameters)
ResultSetHandler(handleResultSets、handleOutputParameters)
StatementHandler(prepare、parameterize、batch、update、query)
只有理解这四个接口及相关方法是干什么的,才能写出好的拦截器,开发出符合预期的功能。
The above is the detailed content of Use of MyBatis plug-in--Print SQL and execution time. For more information, please follow other related articles on the PHP Chinese website!

缓存的概述和分类概述缓存就是一块内存空间.保存临时数据为什么使用缓存将数据源(数据库或者文件)中的数据读取出来存放到缓存中,再次获取的时候,直接从缓存中获取,可以减少和数据库交互的次数,这样可以提升程序的性能!缓存的适用情况适用于缓存的:经常查询但不经常修改的(eg:省市,类别数据),数据的正确与否对最终结果影响不大的不适用缓存的:经常改变的数据,敏感数据(例如:股市的牌价,银行的汇率,银行卡里面的钱)等等MyBatis缓存类别一级缓存:它是sqlSession对象的缓存,自带的(不需要配置)不

MyBatis允许使用插件来拦截的方法Executor(update,query,flushStatements,commit,rollback,getTransaction,close,isClosed)ParameterHandler(getParameterObject,setParameters)ResultSetHandler(handleResultSets,handleOutputParameters)StatementHandler(prepare,parameterize,ba

mybatis分页的方式:1、借助数组进行分页,首先查询出全部数据,然后再list中截取需要的部分。2、借助Sql语句进行分页,在sql语句后面添加limit分页语句即可。3、利用拦截器分页,通过拦截器给sql语句末尾加上limit语句来分页查询。4、利用RowBounds实现分页,需要一次获取所有符合条件的数据,然后在内存中对大数据进行操作即可实现分页效果。

背景实际开发过程中经常需要查询节点树,根据指定节点获取子节点列表,以下记录了获取节点树的操作,以备不时之需。使用场景可以用于系统部门组织机构、商品分类、城市关系等带有层级关系的数据结构;设计思路递归模型即根节点、枝干节点、叶子节点,数据模型如下:idcodenameparent_code110000电脑0220000手机0310001联想笔记本10000410002惠普笔记本1000051000101联想拯救者1000161000102联想小新系列10001实现代码表结构CREATETABLE`

当某些sql因为不知名原因堵塞时,为了不影响后台服务运行,想要给sql增加执行时间限制,超时后就抛异常,保证后台线程不会因为sql堵塞而堵塞。一、yml全局配置单数据源可以,多数据源时会失效二、java配置类配置成功抛出超时异常。importcom.alibaba.druid.pool.DruidDataSource;importcom.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;importorg.apache.

简介今天开发时想将自己写好的代码拿来优化,因为不想在开发服弄,怕搞坏了到时候GIT到生产服一大堆问题,然后把它分离到我轮子(工具)项目上,最后运行后发现我获取List的时候很卡至少10秒,我惊了平时也就我的正常版本是800ms左右(不要看它很久,因为数据量很大,也很正常。),前提是我也知道很慢,就等的确需要优化时,我在放出我优化的plus版本,回到10秒哪里,最开始我刚刚接到这个app项目时,在我用PageHelper.startPage(page,num);(分页),还没等查到的数据封装(Pa

一、什么是缓存缓存是内存当中一块存储数据的区域,目的是提高查询效率。MyBatis会将查询结果存储在缓存当中,当下次执行相同的SQL时不访问数据库,而是直接从缓存中获取结果,从而减少服务器的压力。什么是缓存?存在于内存中的一块数据。缓存有什么作用?减少程序和数据库的交互,提高查询效率,降低服务器和数据库的压力。什么样的数据使用缓存?经常查询但不常改变的,改变后对结果影响不大的数据。MyBatis缓存分为哪几类?一级缓存和二级缓存如何判断两次Sql是相同的?查询的Sql语句相同传递的参数值相同对结

一、思路将分页所需的内容都放到一个实体类中分页数据所需要的实体类!内包含页码,页大小,总条数,总页数,起始行pagehelpr提供了这个类pageInfo,不需要我们自己创建二、主要逻辑select*from表名limit起始行,展示几条数据#第n页每页展示五条数据select*from表名limit(n-1)*5,5#每页展示多少条pageSize3#总共有多少条totalselectcount(*)from表名#总页数pagespages=total%pagesSize==0?total/p


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

SAP NetWeaver Server Adapter for Eclipse
Integrate Eclipse with SAP NetWeaver application server.

MinGW - Minimalist GNU for Windows
This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.

VSCode Windows 64-bit Download
A free and powerful IDE editor launched by Microsoft

MantisBT
Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),
