文档章节

MyBatis Interceptor实现分页插件

y
 yozen17
发布于 2017/08/08 16:24
字数 1161
阅读 44
收藏 0

    MyBatis分页插件网上挺多的,基本都是基于plugin、Interceptor,当然我这个也不例外。基于每个人的表达方式不一样,万一我的特别符合某些人的胃口,就刚好给他们以启发了呢,所以还是记录下。(存在一些问题,后面附更新日志)

1、环境

    环境随便弄,你看你自己怎么方便测试怎么弄。

    新建maven项目,加入依赖:(mybatis、mysql驱动)

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.25</version>
        </dependency>

    测试下环境:

    User.class:

    public class User {
        private Integer id;
        private String nickname;
        private String email;

        // setter getter

    }

    userMapper.xml

    <mapper namespace="me.gacl.mapping.userMapper">

        <select id="getUser" parameterType="int" resultType="hw.test.mybatis.User">
            select
            id,nickname,email
            from sys_users where id=#{id}
        </select>

    </mapper>

    conf.xml

    <configuration>

        <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <!-- 配置数据库连接信息 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://node4:3306/test" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 注册userMapper.xml文件, userMapper.xml位于me.gacl.mapping这个包下,所以resource写成me/gacl/mapping/userMapper.xml -->
        <mapper resource="hw/test/mybatis/userMapper.xml" />
    </mappers>

    </configuration>

    Test.class

public class MybatisTest {

    SqlSession session;

    @Before
    public void openSession() {
        InputStream is = MybatisTest.class.getClassLoader().getResourceAsStream("hw/test/mybatis/conf.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        session = factory.openSession();
    }

    @After
    public void closeSession() {
        session.close();
    }

    @Test
    public void getUser() {
        User u = session.selectOne("me.gacl.mapping.userMapper.getUser", 1);
        System.out.println("邮箱:"+u.getEmail());
    }

}    

    运行结果:

    邮箱:xxxxx_1@163.com

    ok~

2、编写分页插件代码:

    org.apache.ibatis.plugin.Interceptor这个接口有三个方法:

    1) Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。

   2) Object plugin(Object target) 就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。

    3) setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。

    上面这个挺好理解的,看名字就明白了,这段解释我网上直接复制的,方便大家理解。

    先上PageInterceptor代码:

    

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }),
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })
public class PageInterceptor implements Interceptor {
    ThreadLocal<Integer> countTL = new ThreadLocal<Integer>();
    ThreadLocal<Boolean> forPage = new ThreadLocal<Boolean>();

    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();

        if (target instanceof StatementHandler) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
            String selectId = mappedStatement.getId();
            selectId = selectId.toLowerCase();
            // 是否是分页查询
            if (selectId.endsWith("page")) {
                forPage.set(true);
            } else {
                forPage.set(false);
                return invocation.proceed();
            }

            StatementHandler statementHandler = (StatementHandler) target;
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();
            StringBuilder buf = new StringBuilder("select count(*) from");
            sql = sql.toLowerCase();
            if (sql.indexOf("order") > sql.lastIndexOf(")")) {
                buf.append(sql.substring(sql.indexOf("from") + 4, sql.lastIndexOf("order")));
            } else {
                buf.append(sql.substring(sql.indexOf("from") + 4));
            }
            sql = buf.toString();
            Connection conn = (Connection) invocation.getArgs()[0];
            PreparedStatement stmt = conn.prepareStatement(sql);
            ResultSet rs = stmt.executeQuery();
            if (rs.next()) {
                int count = rs.getInt(1);
                countTL.set(count);
            }
            rs.close();
            stmt.close();
            return invocation.proceed();
        } else if (target instanceof ResultSetHandler) {
            Boolean fp = forPage.get();
            if (fp != null && fp) {
                @SuppressWarnings("unchecked")
                List<Object> result = (List<Object>) invocation.proceed();
                Page<Object> page = new Page<Object>(result);
                page.setCount(countTL.get());
                return page;
            }
        }
        return invocation.proceed();
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties arg0) {

    }
}

 

    先讲讲 @Signature 这个注解,这个注解标注了要拦截那个方法: type 指定某个类,method指定方法,args指定参数类型列表。

     Mybatis支持对ExecutorStatementHandlerPameterHandlerResultSetHandler进行拦截 ,所以说, type 只能为这四个中的一个,你具体想拦截哪个方法,可以点开源码copy下就好了。

    在 PageInterceptor 中我拦截了 StatementHandler. prepare(Connection,Integer)方法,在这个方法中获取查询id(就是xml中的selec id),判断是否是分页查询,是分页则根据查询sql拼接count sql,使用jdbc查询出count值放入ThreadLocal中,等处理结果的时候在set到结果中。

Page.class

public class Page<T> extends ArrayList<T> implements Collection<T>{
    private static final long serialVersionUID = 1L;

    private int count;
}

userMapper.xml

    <select id="userPage" resultType="hw.test.mybatis.User">
        select id,nickname,email from
        sys_users
    </select>

conf.xml

    <plugins>
         <plugin interceptor="hw.test.mybatis.PageInterceptor" />
    </plugins>

    

    Test.class

    @Test
    public void userPage() {
        RowBounds rowBounds = new RowBounds(10, 10);
        List<?> users = session.selectList("me.gacl.mapping.userMapper.userPage", null, rowBounds);
        @SuppressWarnings("unchecked")
        Page<User> page = (Page<User>) users;
        System.out.println("count:" + page.getCount() + ",users.size:" + users.size());
    }

    运行结果:

    count:15,users.size:5

    ok~

    

    以上都为测试代码,实际项目中需要根据具体情况编写,比如:组装count sql,就是简单的把查询列表替换成count,判断去掉sql末尾的order by。

 

更新日志:

1、2017.9.20,查询count的时候忘了没有考虑参数问题,这么严重的问题,居然没发现,毕竟没经过生产环境的验证。参数自己设置的话,考虑的东西比较多,毕竟类结构比较复杂,这儿可以直接依赖Mybatis的东西:StatementHandler.parameterize(Statement)

        代码片段:  

/* jdbc查询count */
    private int executePageCount(StatementHandler statementHandler, Connection connection) throws SQLException {
        /* 分页,组装count sql */
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        String countSql = buildCountSql(sql);

        try (PreparedStatement stmt = connection.prepareStatement(countSql)) {
            /* 配置参数 */
            statementHandler.parameterize(stmt);
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return rs.getInt(1);
                }
            }
        }
        return 0;
    }

 

    

© 著作权归作者所有

共有 人打赏支持
上一篇: 有意思的问题
y
粉丝 0
博文 16
码字总数 3847
作品 0
浦东
程序员
私信 提问
Mybatis3.4.x技术内幕(十九):Mybatis之plugin插件设计原理

大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外。 我们从插件配置、插件编写、插件运行原理、插件注册与执行拦截的时机、初始化插件、分页插件的原理等六个方面...

祖大俊
2016/08/28
2.6K
2
Mybatis Interceptor 讲解

Mybatis Interceptor 讲解 简介 Mybatis是比较流行的数据库持久层架构,可以很方便的与spring集成。框架比较轻量化,所以学习和上手的时间短,是一个不错的选择。作为一个开源框架,Mybatis...

why_Dk37
2015/11/07
0
0
13.平凡之路-MyBatis拦截器 上卷

夜晚,寂寞,冷 概述 拦截器:我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。 My...

胖先森
2017/09/13
0
0
PageHelper版本差异造成的Interceptor和dialect问题。

问题描述。 在学习网上流传的电商项目,项目也比较老了,在对mybatis分页的时候使用的是pagehelper。在最初搭建项目的时候,我没有使用教程中给的本地maven,而是直接从网上拉取包。教程中p...

Leafage_M
02/22
0
0
Mybatis源码概览(二) ---Plugin扩展与Spring结合原理

本文主要介绍Mybatis通过动态代理避免对sqlSession直接调用,而是通过MapperProxy代理技术生成了具体dao接口的Mapper实例,里面封装了对sqlSession的调用;Mybatis预留了Interceptor接口,用...

robin-yao
2016/03/23
530
0

没有更多内容

加载失败,请刷新页面

加载更多

聊聊storm的AggregateProcessor的execute及finishBatch方法

序 本文主要研究一下storm的AggregateProcessor的execute及finishBatch方法 实例 TridentTopology topology = new TridentTopology(); topology.newStream("spout1", spout......

go4it
今天
3
0
大数据教程(7.5)hadoop中内置rpc框架的使用教程

博主上一篇博客分享了hadoop客户端java API的使用,本章节带领小伙伴们一起来体验下hadoop的内置rpc框架。首先,由于hadoop的内置rpc框架的设计目的是为了内部的组件提供rpc访问的功能,并不...

em_aaron
今天
4
0
CentOS7+git+github创建Python开发环境

1.准备CentOS7 (1)下载VMware Workstation https://pan.baidu.com/s/1miFU8mk (2)下载CentOS7镜像 https://mirrors.aliyun.com/centos/ (3)安装CentOS7系统 http://blog.51cto.com/fengyuns......

枫叶云
昨天
3
0
利用ibeetl 实现selectpicker 的三级联动

1. js 直接写在html页面上面,ibeetl 就可以动态地利用后台传上来的model List ,不需要每次点击都要ajax请求后台 2. 使用selectpicker 的时候,除了对selecct option的动态处理后,还需要 $("#...

donald121
昨天
3
0
Android SELinux avc dennied权限问题解决方法

1. 概述 SELinux是Google从android 5.0开始,强制引入的一套非常严格的权限管理机制,主要用于增强系统的安全性。 然而,在开发中,我们经常会遇到由于SELinux造成的各种权限不足,即使拥有“...

TreasureWe
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部