MyBatis(9)插件体系

插件体系

概述

插件机制是为了对MyBatis现有体系进行扩展 而提供的入口。底层通过动态代理实现。可供代理拦截的接口有四个:

  1. Executor:执行器

  2. StatementHandler:JDBC处理器

  3. ParameterHandler:参数处理器

  4. ResultSetHandler:结果集处理器

这四个接口已经涵盖从发起接口调用到SQl声明、参数处理、结果集处理的全部流程。接口中任何一个方法都可以进行拦截改变方法原有属性和行为。不过这是一个非常危险的行为,稍不注意就会破坏MyBatis核心逻辑还不自知。所以在在使用插件之前一定要非常清晰MyBatis内部机制。

插件的使用

创建一个插件在MyBatis当中是一件非常简单的事情 ,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

@Intercepts({@Signature(

  type= Executor.class,

  method = "update",

  args = {MappedStatement.class,Object.class})})

public class ExamplePlugin implements Interceptor {

        // 当执行目标方法时会被方法拦截

    public Object intercept(Invocation invocation) throws Throwable {

      long begin = System.currentTimeMillis();

        try {

            return invocation.proceed();// 继续执行原逻辑;

        } finally {

            System.out.println("执行时间:"+(System.currentTimeMillis() - begin));

        }

    }

        // 生成代理对象,可自定义生成代理对象,这样就无需配置@Intercepts注解。另外需要自行判断是否为拦截目标接口。

    public Object plugin(Object target) {

        return Plugin.wrap(target,this);// 调用通用插件代理生成机器

    }

}

在config.xml 中添加插件配置

<plugins>

  <plugin interceptor="org.mybatis.example.ExamplePlugin"/>

</plugins>

通过上述配置即可以监控 在执行过修改过程当中,所耗费的时间。

注:只有从外部类调用拦截目标时 拦截才会生效,如果在内部调用代理逻辑会生效。如在Executor中有两个Query 方法,第一个会调用第二个query。如果你拦截的是第二个Query 则不会成功。

插件代理机制

Configuration 中有一个InterceptorChain(拦截链)保存了所有拦截器,当创建四大对象之后就会调用拦截链,对目标对象进行拦截代理。

对于这个插件拦截实现类似Spring AOP 但其实现要简单很多。代理很轻量清晰,连注释都显得多余。

接下来通过一个自动分页插件全面掌握插件的用法

自动分页插件

自动分页是指查询时,指定页码和大小 等参数,插件就自动进行分页查询,并返回总数量。这个插件设计需要满足以下目特性:

  1. 易用性:不需要额外配置,参数中带上 Page 即可. Page尽可能简单

  2. 不对使用场景作假设:不限制用户使用方式,如接口调用,还是会话调。又或是对Executor 以及StatementHandler的选择等。不能影响缓存业务

  3. 友好性:当不符合分页情况下,作出友好的用户提示。如在修改操作中付入分页参数。或用户本身已在查询语句已自带分页语句 ,这种情况应作出提示。

拦截目标

接下来要解决的问题,是插件的入口写在哪里?去拦截的目标有哪些?

参数处理器 和结果集处理器显然不合适,而Executor.query() 又需要额外考虑 一、二级缓存逻辑。最后还是选定StatementHandler. 并拦截其prepare 方法。

@Intercepts(@Signature(type = StatementHandler.class,

        method = "prepare", args = {Connection.class,

        Integer.class}))

分页插件原理

首先设定一个Page类,其包含total、size、index 3个属性,在Mapper接口中声明该参数即表示需要执行自动分页逻辑。

总体实现步骤包含3个:

  1. 检测是否满足分页条件

  2. 自动求出当前查询的总行数

  3. 修改原有的SQL语句 ,添加 limit offset 关键字。

1.检测是否满足分页条件

分页条件是 1.是否为查询方法,2.查询参数中是否带上Page参数。在intercept 方法中可直接获得拦截目标StatementHandler ,通过它又可以获得BoundSql 里面就包含了SQL 和参数。遍历参数即可获得Page。

// 带上分页参数

StatementHandler target = (StatementHandler) invocation.getTarget();

// SQL包 sql、参数、参数映射

BoundSql boundSql = target.getBoundSql();

Object parameterObject = boundSql.getParameterObject();

Page page = null;

if (parameterObject instanceof Page) {

    page = (Page) parameterObject;

} else if (parameterObject instanceof Map) {

    page = (Page) ((Map) parameterObject).values().stream().filter(v -> v instanceof Page).findFirst().orElse(null);

}

2.查询总行数

实现逻辑是 将原查询SQL作为子查询进行包装成子查询,然后用原有参数,还是能过原来的参数处理器进行赋值。关于执行是采用JDBC 原生API实现。MyBatis执行器,从而绕开了一二级缓存。

private int selectCount(Invocation invocation) throws SQLException {

    int count = 0;

    StatementHandler target = (StatementHandler) invocation.getTarget();

    // SQL包 sql、参数、参数映射

    String countSql = String.format("select count(*) from (%s) as _page", target.getBoundSql().getSql());

    // JDBC

    Connection connection = (Connection) invocation.getArgs()[0];

    PreparedStatement preparedStatement = connection.prepareStatement(countSql);

    target.getParameterHandler().setParameters(preparedStatement);

    ResultSet resultSet = preparedStatement.executeQuery();

    if (resultSet.next()) {

        count = resultSet.getInt(1);

    }

    resultSet.close();

    preparedStatement.close();

 

    return count;

}

3.修改原SQL

最后一项就是修改原来的SQL,前面我是可以拿到BoundSql 的,但它没有提供修改SQL的方法,这里可以采用反射强行为SQL属性赋值。也可以采用MyBatis提供的工具类SystemMetaObject 来 赋值

String newSql= String.format("%s limit %s offset %s", boundSql.getSql(),page.getSize(),page.getOffset());

SystemMetaObject.forObject(boundSql).setValue("sql",newSql);

06-02 157
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值