摘要
近期由于项目使用mybatis出现了数据源阻塞,导致应用程序假死,服务超时引发严重后果,故此下定决心重新梳理一下spring+mybatis+c3p0整合问题,主要分为:配置、源码(通过一次数据库操作分析)、myabatis缓存、问题总结
使用版本spring 4.1.7.RELEASE、mybatis 3.3.0、mybatis-spring 1.2.3
spring + mybatis + c3p0 整合(配置篇)
spring + mybatis + c3p0 整合(源码分析)
spring + mybatis + c3p0 整合(源码分析-mybati核心对象)
概述
分页查询就是将数据库查询的结果在有限的界面上分好多页显示;可以分为逻辑分页和物理分页
- 逻辑分页:数据库返回的是全部数据,在通过代码实现分页
- 物理分页:使用数据库自身所带的分页机制,返回分页数据;如Oracle使用的rownum,Mysql使用的limit等机制完成分页操作
建议日常工作中尽量使用物理分页;逻辑分页一般会消耗过多内存
RowBounds分页
mybatis提供可以进行逻辑分页的RowBounds类,任何select都可以使用它,但是该分页操作是对ResultSet结果进行分页,即逻辑分页,先看下RowBounds源码
public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
public static final RowBounds DEFAULT = new RowBounds();
private int offset;
private int limit;
public RowBounds() {
this.offset = NO_ROW_OFFSET;
this.limit = NO_ROW_LIMIT;
}
// 带参数构造函数,实现逻辑分页
public RowBounds(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
public int getOffset() {
return offset;
}
public int getLimit() {
return limit;
}
}
从源码可以看出定义了两个参数offset、limit;offset表示从第几行开始读取,limit表示限制返回条数;limit默认为最大整数
分页原理
从DefaultSqlSession源码可以看出,其提供的查询接口是以RowBounds作为参数用来进行分页的
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)
再看DefaultResultSetHandler结果集处理器,可以看出RowBounds分页是把offset之前数据跳过,超过limit之后数据不取出实现的;从这里也可以看出其实是先查询所有结果集,再从结果集中取出offset到limit之间数据实现分页
DefaultResultContext<Object> resultContext = new DefaultResultContext();
// 跳到offset位置,准备读取数据
this.skipRows(rsw.getResultSet(), rowBounds);
Object rowValue = null;
// while循环判断是否小于limit值,如果是读取limit条数据
while(this.shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, (String)null);
// 省略代码
}
}
}
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if(rs.getType() != 1003) {
if(rowBounds.getOffset() != 0) {
// 直接定位
rs.absolute(rowBounds.getOffset());
}
} else {
// 通过循环跳到offset位置,进行读取
for(int i = 0; i < rowBounds.getOffset(); ++i) {
rs.next();
}
}
}
分页插件PageHelper
spring配置方式
引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
xml配置
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- pageHelper插件 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置参数,一行配置一个 -->
<value>
helperDialect=mysql
</value>
</property>
</bean>
</array>
</property>
</bean>
SpringBoot
引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
配置
pagehelper:
helperDialect: mysql
reasonable: false
supportMethodsArguments: true
params: count=countSql
使用
在查询方法前执行
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
该方法后紧跟需要分页查询的sql,否则会导致其他不需要分页分页
原理
PageHelper实现分页主要依赖ThreadLocal和Mybatis插件技术;从前文四大核心对象创建知道,mybatis创建对象返回的不是原始对象,是经过包装之后的代理对象
this.interceptorChain.pluginAll(executor);
interceptor.plugin(target)
该方法获取所有实现插件Interceptor接口的实现类,如PageInterceptor,调用其plugin方法返回包装后的代理对象
在执行查询sql时会执行PageInterceptor的intercept方法,判断是否需要生成分页sql是通过从ThreadLocal中是否存在Page对象,这里就涉及前面的startPage方法,其会创建Page对象set到ThreadLocal中;
对源码有兴趣可自行debug
intercept方法部分源码
public Object intercept(Invocation invocation) throws Throwable {
try {
....
// 判断是否需要分页
if(!this.dialect.skip(ms, parameter, rowBounds)) {
if(this.dialect.beforeCount(ms, parameter, rowBounds)) {
Long count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql);
if(!this.dialect.afterCount(count.longValue(), parameter, rowBounds)) {
Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);
return var12;
}
}
// 执行分页查询
resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
Object var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
return var16;
} finally {
if(this.dialect != null) {
// 清除threadLocal
this.dialect.afterAll();
}
}
}
返回分页数据注意踩坑,可以创建工具类操作
public static CommonPageResponse bulidPageResponse(List list){
CommonPageResponse response = new CommonPageResponse();
PageInfo<Object> pageInfo = new PageInfo(list);
response.setPageNum(pageInfo.getPageNum());
response.setPageSize(pageInfo.getPageSize());
response.setTotalCount((int)pageInfo.getTotal());
response.setTotalPages(pageInfo.getPages());
response.setData(list);
return response;
}