MyBatis源码学习(二)

633 阅读3分钟

看完趣味数学书后,开始技术填坑之路


通常在业务中,需要进行数据分页查询,这样一来,每条SQL语句都加上limit限制,会多了很多重复的代码,而且每次需要自己在代码中进行偏移量的计算,略微有些麻烦。

还好有大神在Github里贡献了分页插件,而且使用起来很方便,了解了一下使用原理,发现是使用了MyBatis里面的拦截器Interceptor,学习记录一下。


简单🌰

项目中进行引用

一般JavaWeb项目使用到的是maven和gradle进行项目管理

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>最新版本</version>
</dependency>

···

// https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper
compile group: 'com.github.pagehelper', name: 'pagehelper', version: '5.1.4'

在mybatis-config或者spring中进行配置

mybatis:
<plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
        <property name="param1" value="value1"/>
	</plugin>
</plugins>

spring:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 自动扫描mapping.xml文件 -->
        <property name="mapperLocations" value="classpath:mapping/*.xml">
        </property>
        <!-- 配置分页插件 -->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <value>
                            reasonable=true
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>

我是用的是第二种配置方式(Spring),更加细详细配置说明可以在Github文档中进行查看。

mapper查询语句

为了简单,使用了单表查询

<select id="selectByUserId" resultMap="BaseResultMap">
    SELECT
    <include refid="Base_Column_List" />
    FROM course_user
    WHERE user_id = #{userId, jdbcType=INTEGER}
</select>

代码使用

查询时,在代码中使用的是官网推荐的第二种实现方式,

// 使用这个方法,接下来的第一个select方法会进行分页
PageHelper.startPage(1, 5);
List<CourseUser> list = mapper.selectByUserId(userId);
// 查询结果list类型是Page<E>,要取出分页信息,可以强转,也可以使用PageInfo
// 用PageInfo对结果进行包装
PageInfo page = new PageInfo(list);
// 获取总数量
int count = page.getTotal();

分页插件,源码实现原理

该插件支持很多数据库,Oracle、MySQL、mongdb等都支持,一般常用的话是MySQL,测试用的项目也是使用MySQL作为持久层,该分页插件可以通过autoDialect自动方言适配了MySQL。

插件通过MyBatis的拦截器Interceptor进行SQL重写,可以从源码中看出

统计数量

拦截器入口:com.github.pagehelper.PageInterceptor#intercept

获取统计数量的方法:com.github.pagehelper.parser.CountSqlParser#getSmartCountSql(java.lang.String, java.lang.String)

/**
 * 获取智能的countSql
 *
 * @param sql
 * @param name 列名,默认 0
 * @return
 */
public String getSmartCountSql(String sql, String name) {
    //解析SQL
    Statement stmt = null;
    //特殊sql不需要去掉order by时,使用注释前缀
    if(sql.indexOf(KEEP_ORDERBY) >= 0){
        return getSimpleCountSql(sql);
    }
    try {
        stmt = CCJSqlParserUtil.parse(sql);
    } catch (Throwable e) {
        //无法解析的用一般方法返回count语句
        return getSimpleCountSql(sql);
    }
    Select select = (Select) stmt;
    SelectBody selectBody = select.getSelectBody();
    try {
        //处理body-去order by
        processSelectBody(selectBody);
    } catch (Exception e) {
        //当 sql 包含 group by 时,不去除 order by
        return getSimpleCountSql(sql);
    }
    //处理with-去order by
    processWithItemsList(select.getWithItemsList());
    //处理为count查询
    sqlToCount(select, name);
    String result = select.toString();
    return result;
}

通过该方法,去掉参数args,然后改成count(*),修改成统计数量的SQL。

进行分页查询

具体调用方法:com.github.pagehelper.dialect.helper.MySqlDialect#getPageSql

@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
    StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
    sqlBuilder.append(sql);
    // 判断是不是第一页,如果是第一页,不需要设置偏移量
    if (page.getStartRow() == 0) {
        sqlBuilder.append(" LIMIT ? ");
    } else {
        sqlBuilder.append(" LIMIT ?, ? ");
    }
    pageKey.update(page.getPageSize());
    return sqlBuilder.toString();
}

从这里看出,该插件不是通过内存进行分页,而是通过修改SQL进行物理分页。


总结

感觉这个分页插件没有入侵代码,实现思想也很优雅,体现了拦截器思想,最后结果就不贴出来了,一般查询出来total总量和分页数据就足够了。

参考资料

1 分页插件地址Github