Mybatis[4]
来源:互联网 发布:重庆时时彩遗漏数据 编辑:程序博客网 时间:2024/06/16 21:53
- 博由
- 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来参数化SQLParameterHandler ph = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");ph.setParameters(ps);// 执行查询ResultSet rs = ps.executeQuery();if(rs.next()){ pager.setTotalCount(rs.getInt(1));}
注入limit sql
// 修改SQLString 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>
单元测试
@Testpublic 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
0 0
- Mybatis(4)
- Mybatis[4]
- mybatis 4
- MyBatis-----4、MyBatis使用generator
- MyBatis(4)MyBatis入门程序
- 【Mybatis】深入浅出MyBatis(4)-Sqlsession
- 《MyBatis用户指南》----Part 4
- mybatis 学习4
- mybatis第4天
- mybatis 学习4
- MyBatis学习笔记(4)
- mybatis笔记-4-注解
- mybatis(4)动态sql
- Mybatis 框架 4
- MyBatis
- MyBatis
- Mybatis
- myBatis
- 如何跳出嵌套循环
- [swift]3.0swift原生数据类型
- Linux中常用操作命令
- 删除重复数据,建立唯一性索引
- 在CentOS6.8下rpm方式安装MySQL5.7
- Mybatis[4]
- 语句被终止。完成执行语句前已用完最大递归 100
- pyautogui--现货行情软件打开,自动下单函数
- cocos2d js scrollView
- 微信Tinker热修复集成
- springmvc校验注解
- Android自定义控件几个重要步骤
- 为什么不使用默认的980px的布局Viewport
- BZOJ 1492 [NOI2007 D1T2] 货币兑换Cash