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
原创粉丝点击