java并发

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

2017-11-04  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读