首先,先说说什么是插件(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代理
在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);
}
// 取出即将执行的sql
String 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;
// 重写要执行的sql
metaStmtHandler.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 configuration
PUBLIC "-//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)需要了解四大对象及其方法的作用,准确判断需要拦截什么对象,什么方法,参数是什么,才能确定签名如何编写。