Mybatis插件学习与实践(含具体代码)

来源:互联网 发布:穿越火线网络连接异常 编辑:程序博客网 时间:2024/05/17 18:02

首先,先说说什么是插件(plugin),插件类似于spring里面的

拦截器,可以让你在mybatis生命周期某一阶段插入或修改相应逻辑代码。

可以和maven的插件做类似的比较理解。


在说说mybatis的生命周期,在mybatis里面,主要是对以mapper为为中心的生命周期拦截。


在Mapper执行过程中有四大对象,mybatis可以对他们进行拦截。分别是:

      #Executor:是执行sql的全过程,包括组装参数,组装结果集返回和执行sql过程,

都可以拦截。

      #StatementHandler:是执行sql的过程,我们可以重写执行sql的过程。

      #ParameterHandler:是主要拦截执行sql的参数的组装,你可以重写组装参数规则。

      #ResultSetHandler:用于拦截执行结果的组装。


在插件中,可以针对上述4个类进行拦截,可以进一步拦截类中所有方法。

首先要实现Interceptor接口,这里介绍一下Interceptor接口:

      #intercept方法:它将直接覆盖你所拦截对象原有的方法,因此它是插件的核心方法。

      #plugin方法:target被拦截对象,它的作用是给被拦截对象生成一个代理对象,并返回它。

      #setProperties方法:允许再plugin元素中配置所需参数,方法在插件初始化的时候就被调用一次,

然后把插件对象存入配置中。


再一个,需要了解插件实现的大体思路,插件用的是代理模式

具体关于职责链模式,可以参考我的文章:职责联模式-解决不了的事往后传 

还有是代理模式,可以参见我的这两篇,关于jdk代理以及cglib代理

cglib动态代理

jdk动态代理


在mybatis插件设计中,一般会用到一个工具类:MetaObject

它可以有效的读取或者修改一些重要对象的属性,具体是利用反射得到的。

它有3个方法常常被我们用到

      #SystemMetaObject.forObject(Object obj):用于包装对象MetaObject

      #Objetc getValue(String name):用于获取对象属性值,支持OGNL。

      #void setValue(String name,Object value):用于修改duiiang属性值,支持OGNL。

下面给出实例代码:

代码目的是拦截statementHandler,往sql语句中添加一个limit +num字串。

QueryLimitPlugin类:

package study.plugin;import java.sql.Connection;import java.util.Properties;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.javassist.tools.reflect.Metaobject;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.reflection.MetaObject;import org.apache.ibatis.reflection.SystemMetaObject;@Intercepts({ @Signature(type = StatementHandler.class, // 确定要拦截的对象method = "prepare", // 要拦截的方法args = { Connection.class, Integer.class }) }) // 拦截方法的参数public class QueryLimitPlugin implements Interceptor {/** * 默认限制查询返回行数 */private int limit;private String dbType;public Object intercept(Invocation invocation) throws Throwable {// 取出被拦截的对象StatementHandler stmtHandler = (StatementHandler) invocation.getTarget();MetaObject metaStmtHandler = SystemMetaObject.forObject(stmtHandler);// 分离代理对象,从而形成多次代理,通过两次循环最原始的被代理类,Mybatis使用的是JDK代理while (metaStmtHandler.hasGetter("h")) {Object object = metaStmtHandler.getValue("h");metaStmtHandler = SystemMetaObject.forObject(object);}// 分离最后一个代理目标类while (metaStmtHandler.hasGetter("target")) {Object object = metaStmtHandler.getValue("target");metaStmtHandler = SystemMetaObject.forObject(object);}// 取出即将执行的sqlString sql = (String) metaStmtHandler.getValue("delegate.boundSql.sql");String limitSql;// 判断参数是不是mysql数据库且sql有没有被插件重写过,重写过就不重写了if ("mysql".equals(this.dbType)) {// 去掉前后空格sql = sql.trim();// 将参数写入到sql// limitSql = "select * from ("+sql+")"+LMT_TABLE_NAME + "limit "+limit;limitSql = sql + " limit " + limit;// 重写要执行的sqlmetaStmtHandler.setValue("delegate.boundSql.sql", limitSql);}// 调用原来对象的方法,进入责任链的下一层级return invocation.proceed();}public Object plugin(Object target) {// 使用mybatis提供的默认的类生成代理对象return Plugin.wrap(target, this);}public void setProperties(Properties properties) {String strLimit = (String) properties.getProperty("limit", "50");this.limit = Integer.parseInt(strLimit);// 这里设置要读取的数据库类型this.dbType = (String) properties.getProperty("dbtype", "mysql");}}


接着再在mybatis-config.xml加入:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><typeAliases><typeAlias type="study.helloworld.po.Role" alias="role"/></typeAliases><plugins><plugin interceptor="study.plugin.QueryLimitPlugin"><property name="dbtype" value="mysql"/><property name="limit" value="50"/></plugin></plugins><environments default="development"><environment id="development"><transactionManager type="JDBC"><property name="autoCommit" value="false"/></transactionManager><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!-- resource="study/helloworld/mapper/roleMapper.xml"  --><mappers><mapper class="study.helloworld.mapper.RoleMapper"/></mappers></configuration>

注意要把plugins子标签放在environments前面,否则会报错。


这样以来,每当执行sql语句时,都会插入limit + num再原有sql语句后


这里有几个mybatis插件的设计注意:

1):能不用插件尽量不要用插件,因为它将修改mybatis的底层设计。

2):插件生层是层层代理对象的职责链模式,通过反射方法运行,性能不高,所以减少插件就能减少代理,从而提高

性能。

3)需要了解四大对象及其方法的作用,准确判断需要拦截什么对象,什么方法,参数是什么,才能确定签名如何编写。








阅读全文
0 0
原创粉丝点击