美文网首页java并发
MyBatis使用拦截器实现分页功能

MyBatis使用拦截器实现分页功能

作者: 蒋座 | 来源:发表于2017-11-04 10:57 被阅读12次
    mybatis的拦截器实现分页(动态代理)

    拦截sql语句来实现分页
    1.拦截什么样的对象(以page作为参数传入;page对象)
    2.拦截对象什么行为
    3.什么时候拦截 (在prepareStatement的时候拦截)

    代人买票

    mybatis获取statement其实是在statementHandler中,这是一个处理接口,有个prepare方法,返回Statement,这个方法是在BaseStatementHandler中实现的,statement是在instantiateStatement这个方法中获取的,这个方法是一个抽象方法,看它的PrepareStatementHandler实现,在这里边看到了connection.prepareStatement(sql,PreparedStatement.),也就是和JDBC类似的代码了,这就是分页拦截器要拦截的位置了。如何实现拦截呢?mybatis提供了相应的注解:

    @Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})
    //成立代购公司 ----  implements Interceptor
    public class PageInterceptor implements Interceptor {
    

    ①type指向要连接的接口class,这里指向StatementHandler.class, ②Method指向要拦截的方法,这里是prepare
    ③args[]拦截的方法的参数类型,这里是Connection.class
    这样就准确描述了要拦截StatementHandler接口下的prepare方法。目标确定,接下来就可以做手脚了,在PrepareStatementHandler拿到sql语句之前将这个sql语句改装成我们的分页sql,然后在塞回去,让程序继续执行,这样就成功了。
    注意:过早过迟的拦截都不合适。所以在PreparedStatement pstmt=conn.prepareStatement(sql.toString());之前拦截即可(把SQL语句处理再放进去提交)

    注册公司 ------ plugin
    申报资产 ----- property

    <plugins>
      <plugin interceptor="com.imooc.interceptor.PageInterceptor">
        <property name="test" value="abc"/>
      </plugin>
    </plugins> 
    

    使用资产

    public class PageInterceptor implements Interceptor {
    private String test;
    // 执行顺序 1
    @Override
    public void setProperties(Properties properties) {
        this.test = properties.getProperty("test");
    }
    

    识别哪些是去买票的人
    (并不一定就是需要找代购的人,在正式代购的时候会更精确的定位客户群体)

    // 执行顺序 2
    @Override
    public Object plugin(Object target) {
        System.out.println(this.test);
        return Plugin.wrap(target, this);
    }
     plugin(Object target)方法参数就是被拦截的对象target,返回的就是满足条件的代理类,Plugin.wrap(target,this):this也就是自定义拦截器实例,通过获取注解得到要拦截的类型,比较target的类型与this获取的要拦截的类型是不是一致,如果满足条件就获取代理对象,并执行intercept方法,没有获取代理对象的将直接返回,不会经过intercept方法。
    

    开始代购

    // 执行顺序 3
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
     //拦截器的参数(Invocation)中保存了拦截器所拦截的所有对象,根据方法签名,这里仅仅只是对statementHandler中的关键信息进行处理,原理就是使用分页的sql替换拦截到的原始sql,拦截对象类型是StatementHandler,由方法签名决定的
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
    //StatementHandler将对配置文件中的sql语句进行处理(sql语句在MappedStatement中),但是在StatementHandler中,所有的对象属性均为受保护的以及私有的,首先想到的是通过反射读写信息,幸好Mybatis已经有一个类MetaObject,有个方法 MetaObject.forObject(statementHandler,__,__)可以对注解的拦截方法签名所对应的对象进行包装,这样我们得到的是被包装的statementHandler
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY);
        MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
        // 配置文件中SQL语句的ID
        String id = mappedStatement.getId();
               //定位更精准的用户群体(嫌麻烦不愿排队的)
        if(id.matches(".+ByPage$")) {
            BoundSql boundSql = statementHandler.getBoundSql();
    通过BoundSql获得原始的sql语句之后,再次使用的是BoundSql的getParameterObject()来获取配置文件中的参数,因为得到的参数是一个map,调用对象的get方法得到Page对象,得到page对象之后就可以拼接分页sql了。metaObject.setValue(“delegate.boundSql.sql”,pageSql)修改原本不可以修改的值,修改原来的属性值为新的sql。
    mybatis通过Invocation这个参数的proceed()方法交回主权,这个方法的源码 return method.invoke(target,args)
            // 原始的SQL语句
            String sql = boundSql.getSql();
            // 查询总条数的SQL语句
    //这里的问题在于sql是否能执行以及如何执行,需要connection对象,而此对象就是方法签名的参数,可以通过invocation.getArgs()[0]获得,然后通过connection.prepareStatement(countSql)将拼接好的sql语句进行预编译,并执行,就可以获得结果,由于此结果是统计总数的,只有一条记录,将此记录转换为int类型,并赋值给page对象。
            String countSql = "select count(*) from (" + sql + ")a";
            Connection connection = (Connection)invocation.getArgs()[0];
            PreparedStatement countStatement = connection.prepareStatement(countSql);
            ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
            parameterHandler.setParameters(countStatement);
            ResultSet rs = countStatement.executeQuery();
            
            Map<?,?> parameter = (Map<?,?>)boundSql.getParameterObject();
            //获取购票信息
            Page page = (Page)parameter.get("page");
            if(rs.next()) {
                page.setTotalNumber(rs.getInt(1));
            }
            // 改造后带分页查询的SQL语句
            //代购公司 开始购票
            String pageSql = sql + " limit " + page.getDbIndex() + "," + page.getDbNumber();
                   
            metaObject.setValue("delegate.boundSql.sql", pageSql);
        }
        return invocation.proceed();
    }
    
    代购过程总结:

    1.RoutingStatementHandler
    2.通过RoutingStatementHandler对象的属性delegate找到statement实现类BaseStatementHandler
    3.通过BaseStatementHandler类的反射得到对象的MappedStatement对象
    4.通过MappedStatement的属性getID得到配置文件sql语句的ID
    5.通过BaseStatementHandler属性的到原始sql语句
    6.拼接分页sql(
    1.需要查询总数的sql
    2.通过拦截Connection对象得到PrepareStatement对象
    3.得到对应的参数
    4.把参数设到prepareStatement对象里的?(该?号在配置文件以#{}形式存在,mybatis会把它转为?号)
    5.执行sql语句
    6.得到总数

    7.把属性值为新的sql

    相关文章

      网友评论

        本文标题:MyBatis使用拦截器实现分页功能

        本文链接:https://www.haomeiwen.com/subject/ywrfmxtx.html