概述
-
- 博由
- plugins是什么
- 案例实现分页
- interceptor
- 自定义Interceptor
- 配置插件
- 分页实践
- 分页插件
- 分页封装类
- 分页插件
- 拦截StatementHandler-prepare方法
- prepare code 源码
- 拦截实现分析
- 处理链路
- 路由处理RoutingStatementHandler
- 实际处理PreparedStatementHandlerBaseStatementHandler
- 如何操作达到分页操作
- 代码实现
- 配置
- Mapperxml
- 单元测试
- 总结
- 项目地址
博由
接着配置文件的问题,在描述完了如何使用typeHandler之后,接下来本文主要讲述如何使用plugins相关内容,以及从源码角度分析其基本原理。
plugins是什么?
简单理解为拦截器,既然是拦截器说白了一般都是动态代理来实现对目标方法的拦截,在前后做一些操作。
在mybatis将这种东西,称之为plugin,配置在mybatis-config.xml配置文件中,通过 <plugins></plugins>标签配置。在mybatis中,可以被拦截的目标主要是:
1. StatementHandler;
2. ParameterHandler;
3. ResultSetHandler;
4. Executor;
我们同一个简单的分页查询来解释一般plugin的使用方法;
案例(实现分页)
案例:ByPage后缀的查询,自动执行分页操作;不需要显性的limit SQL操作;
interceptor
通过实现Interceptor接口,来自定义plugin,
public interface Interceptor {
// 拦截逻辑,参数是代理类
Object intercept(Invocation invocation) throws Throwable;
// 加载插件,一般使用Plugin.wrap(target, this);加载当前插件
Object plugin(Object target);
// 初始化属性
void setProperties(Properties properties);
}
自定义Interceptor
可以通过implements Interceptor来自定义plugin,但是仅仅这样是不行的,额外需要通过@Inteceptors和@Signature源注解来指定拦截器需要拦截的目标(类、方法、参数);
@Intercepts(value={
@Signature(
type = Executor.class, // 只能是: StatementHandler | ParameterHandler | ResultSetHandler | Executor 类或者子类
method = "query", // 表示:拦截Executor的query方法
args = { // query 有很多的重载方法,需要通过方法签名来指定具体拦截的是那个方法
MappedStatement.class,
Object.class,
RowBounds.class,
ResultHandler.class
}
/**
* type:标记需要拦截的类
* method: 标记是拦截类的那个方法
* args: 标记拦截类方法的具体那个引用(尤其是重载时)
*/
)})
public class LogPlugin implements Interceptor{
/**
* 具体拦截的实现逻辑
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("----------- intercept query start.... ---------");
// 调用方法,实际上就是拦截的方法
Object result = invocation.proceed();
System.out.println("----------- intercept query end.... ---------");
return result;
}
// 插入插件
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this); // 调用Plugin工具类,创建当前的类的代理类
}
// 设置插件属性
@Override
public void setProperties(Properties properties) {
}
}
配置插件
<plugins>
<plugin interceptor="com.plugins.interceptors.LogPlugin" />
</plugins>
完成上述两个步骤就可以使用插件了,接下来我们具体到分页案例来进行。
分页实践
分页插件
分析:
1. 分页封装对象:Pager;
2. ThreadLocal存放每个线程的分页对象;
3. 分页操作是两个步骤:
3.1 分页查询,需要进行拼装limit sql;
3.2 总数量查询,需要进行数量查询内置操作;
4. 可以拦截StatementHandler的prepare方法,对SQL信息进行修改并重新组装,然后进行查询相关操作。
分页封装类
package com.plugins.entity;
import java.io.Serializable;
import java.util.List;
/**
* 分页类
* Created by wangzhiping on 17/3/10.
*/
public class Pager<T> implements Serializable{
/**
* 开始位置
*/
private int startPos;
/**
* 当前页码
*/
private int curPage;
/**
* 每页大小
*/
private int pageSize;
/**
* 每一页的数据
*/
private List<T> datas;
/**
* 总页数
*/
private int totalPage;
/**
* 总数量
*/
private int totalCount;
public Pager(int curPage, int pageSize) {
this.curPage = curPage;
this.pageSize = pageSize;
this.startPos = (this.curPage - 1) * this.pageSize;
}
public int getStartPos() {
return startPos;
}
public void setStartPos(int startPos) {
this.startPos = startPos;
}
public int getCurPage() {
return curPage;
}
public void setCurPage(int curPage) {
this.curPage = curPage;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public List<T> getDatas() {
return datas;
}
public void setDatas(List<T> datas) {
this.datas = datas;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
this.totalPage = (this.totalCount - 1) / this.pageSize + 1;
}
@Override
public String toString() {
return "Pager{" +
"startPos=" + startPos +
", curPage=" + curPage +
", pageSize=" + pageSize +
", datas=" + datas +
", totalPage=" + totalPage +
", totalCount=" + totalCount +
'}';
}
}
分页插件
拦截StatementHandler-prepare方法
@Intercepts(value={
@Sigunature(
type = StatementHandler.class, // 拦截目标类
method = "prepare", // 目标类的目标方法
args = { // prepare参数列表的参数类型
Connection.class,
Integer.class
}
)
})
prepare code <源码>
Statement prepare(
Connection connection,
Integer transactionTimeout
)throws SQLException;
实际上述的拦截就是拦截的StatementHandler -> prepare method。
拦截实现分析
处理链路
|--- StatementHandler
|--- --- RoutingStatementHandler
|--- --- BaseStatementHandler
|--- --- --- PreparedStatementHandler
路由处理(RoutingStatementHandler)
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); // 路由到Prepared
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
实际处理(PreparedStatementHandler|BaseStatementHandler )
BaseStatementHandler.java
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 实例化Statement
statement = instantiateStatement(connection);
// 设置操作超时时间
setStatementTimeout(statement, transactionTimeout);
// 设置获取大小
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
PreparedStatementHandler.java
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
//调用Connection.prepareStatement
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
// SQL参数化处理
@Override
public void parameterize(Statement statement) throws SQLException {
//调用ParameterHandler.setParameters
parameterHandler.setParameters((PreparedStatement) statement);
}
如何操作达到分页操作
获取SQL信息(BoundSql)
| --- RoutingStatementHandler(delegate)
| --- --- delegate = PreparedStatementHandler
| --- --- PreparedStatementHandler -> BaseStatementHandler
包含了BoundSql、MappedStatement等
protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final RowBounds rowBounds;
protected BoundSql boundSql;
获取StatementId(MappedStatement)
我们需要获取BaseStatementHandler中的mappedStatement属性,但是这些属性都是protected,没哟继承结构无法直接访问,在mybatis可以通过metaObject来访问。
// 实际上就是获取某个目标对象的属性操作
MetaObject metaObject = MetaObject.forObject(
handler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory()
);
// metaObject.getValue获取属性(ognl表达式获取)
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
执行SQL的数量查询
Connection conn = (Connection) invocation.getArgs()[0];
PreparedStatement ps = conn.prepareStatement(countSql);
// 获取参数处理器来处理参数
// 通过ParameterHandler来参数化SQL
ParameterHandler ph = (ParameterHandler)
metaObject.getValue("delegate.parameterHandler");
ph.setParameters(ps);
// 执行查询
ResultSet rs = ps.executeQuery();
if(rs.next()){
pager.setTotalCount(rs.getInt(1));
}
注入limit sql
// 修改SQL
String pageSql = sql + " LIMIT " + pager.getStartPos() + ", " + pager.getPageSize();
// 重新设定BoundSql的SQL属性
metaObject.setValue("delegate.boundSql.sql", pageSql);
代码实现
@Intercepts(value = {@Signature(
type = StatementHandler.class,
method = "prepare",
args = {
Connection.class,
Integer.class
}
)})
public class ThreadLocalPagePlugin implements Interceptor{
/**
* 这个方法是实际的拦截逻辑,我们的目的是在这里来实现分页,需要达到什么程度的使用。
* 假设从ThreadLocal获取分页信息,来进行分页操作;
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取目标对象,注意StatementHandler中的属性都是protected
// 不能直接访问,因此需要通过其他的方式来获取,就是MetaObject
// 其基本实现是BaseStatementHandler其中最重要的属性是MappedStatment
// 包含了SQL相关信息
// 实际返回的是RoutingStatementHandler
StatementHandler handler = (StatementHandler) invocation.getTarget();
// 获取指定对象的元信息
MetaObject metaObject = MetaObject.forObject(
handler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory()
);
// 然后就可以通过MetaObject获取对象的属性
// 获取RoutingStatementHandler->PrepareStatementHandler->BaseStatementHandler中的mappedStatement
// mappedStatement 包含了Sql的信息
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 获取statement id
String statementId = mappedStatement.getId();
// 会拦截每个属性
if (statementId.endsWith("ByPage")){
// ByPage 表示的是分页查询
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 获取当前线程分页信息
Pager<?> pager = ThreadLocalUtil.threadLocal.get();
String countSql = "SELECT COUNT(*) " + sql.substring(sql.indexOf("FROM"));
Connection conn = (Connection) invocation.getArgs()[0];
PreparedStatement ps = conn.prepareStatement(countSql);
// 获取参数处理器来处理参数
ParameterHandler ph = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");
ph.setParameters(ps);
// 执行查询
ResultSet rs = ps.executeQuery();
if(rs.next()){
pager.setTotalCount(rs.getInt(1));
}
String pageSql = sql + " LIMIT " + pager.getStartPos() + ", " + pager.getPageSize();
metaObject.setValue("delegate.boundSql.sql", pageSql);
}
return invocation.proceed();
}
// 指定需要拦截的对象
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 初始化属性
@Override
public void setProperties(Properties properties) {
}
}
配置
<plugins>
<plugin interceptor="com.plugins.interceptors.ThreadLocalPagePlugin" />
</plugins>
Mapper.xml
<select id="findByPage" resultType="User">
SELECT *
FROM user
</select>
单元测试
@Test
public void testQueryPageByPlugin() {
SqlSession session = instance.getSession();
Pager<User> pager = new Pager<User>(1, 10);
ThreadLocalUtil.threadLocal.set(pager);
List<User> users = session.selectList(User.class.getName() + ".findByPage");
pager.setDatas(users);
System.out.print(pager);
}
总结
通过实现一个简单分页的代码而言,要想真正了解plugin的使用,需要真正了解StatementHandler|ParameterHandler|ResultSetHandler|Executor
这四个类的执行链路和原理,只有了解到这些你才能知道,在哪里拦截,拦截什么,怎么拦截。
项目地址
branch v1.4: https://github.com/wzpthq/csdn_mybatis.git
最后
以上就是奋斗老鼠为你收集整理的Mybatis[4] - 配置文件 - plugins的全部内容,希望文章能够帮你解决Mybatis[4] - 配置文件 - plugins所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复