MyBatis Interceptor实现分页插件
博客专区 > yozen17 的博客 > 博客详情
MyBatis Interceptor实现分页插件
yozen17 发表于9个月前
MyBatis Interceptor实现分页插件
  • 发表于 9个月前
  • 阅读 37
  • 收藏 0
  • 点赞 0
  • 评论 0

移动开发云端新模式探索实践 >>>   

摘要: 利用MyBatis Interceptor自己实现分页插件

    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;
    }

 

    

标签: MyBatis 分页
  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 0
博文 8
码字总数 3847
×
yozen17
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: