文档章节

MyBatis Interceptor实现分页插件

y
 yozen17
发布于 2017/08/08 16:24
字数 1161
阅读 41
收藏 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的拦截器实现分页

很早有这个想法,但具体的实现一直没去做,网上正好找到2篇,怕以后找不到,特地记录一下,原文地址: https://my.oschina.net/gaoguofan/blog/753406 https://my.oschina.net/dendy/blog/3...

sprouting
07/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spring详解

Spring详解(一)------概述 目录 1、什么是 Spring ? 2、Spring 起源 3、Spring 特点 4、Spring 框架结构 5、Spring 框架特征 6、Spring 优点   本系列教程我们将对 Spring 进行详解的介绍...

DemonsI
19分钟前
0
0
CentOS7系统Nginx安装

1、下载nginx,官方网站https://nginx.org wget https://nginx.org/download/nginx-1.14.0.tar.gz 2、下载Nginx Sticky Module,官方网站https://bitbucket.org/nginx-goodies/nginx-sticky-......

m_lm
23分钟前
0
0
使用zTree树控件(二)

1:treeNode.checked用于判断是勾选还是取消勾选。(treeNode指的是节点) 2:treeObj.transformToArray(nodes)用于查询nodes节点下的所有子节点,json格式。(treeObj为数的id)...

uug
23分钟前
0
0
export, import 和 export default的区别

ES6的两个功能: export 和 import export 对外输出模块 import 引入(加载)进来一个模块 一、export => import 单个变量 export var name = "lishi" 在其他文件里引用 import {name} f...

Js_Mei
28分钟前
1
0
打造RecyclerView的n级列表

先上效果图: 1.该多级列表的优势: 支持无限级列表展开 基于一个recyclerView实现 可以自定义每一级item的样式,定制化更强 2.设计的思路 数据结构List<ItemBean>,ItemBean类中有变量List<...

WelliJohn
37分钟前
1
1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部