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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容