Implementação da paginação PageHelper
PageHelper.startPage(1,3)
public static <E> Page<E> startPage(int pageNum, int pageSize) {
return startPage(pageNum, pageSize, DEFAULT_COUNT);
}
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
return startPage(pageNum, pageSize, count, (Boolean)null, (Boolean)null);
}
public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy) {
Page<E> page = startPage(pageNum, pageSize);
page.setOrderBy(orderBy);
return page;
}
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
Page<E> page = new Page(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
Page<E> oldPage = getLocalPage();
if(oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
return page;
}
Comece a executar a instrução de seleção real
public Page doSelectPage(ISeleccione) { select.doSelect(); devolva isso; }
Insira a classe MapperProxy para executar o método de invocação para obter o nome do método e os valores dos parâmetros
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
Em seguida, o método MapperMethod executa a instrução execute e o julgamento é adicionar, excluir, modificar e verificar. Julgando que existem vários valores de retorno, insira o método executeForMany
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
Este método começa a chamar SqlSessionTemplate, DefaultSqlSession e outras classes para obter a instrução SQL do arquivo Mapper.xml
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
Comece a inserir a implementação real do PageHelper, o Plugin obtém informações relevantes implementando o InvocationHandler para proxy dinâmico
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
PageInterceptor implementa a interface Interceptor do Mybatis para interceptar
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement)args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds)args[2];
ResultHandler resultHandler = (ResultHandler)args[3];
Executor executor = (Executor)invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
if(args.length == 4) {
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
cacheKey = (CacheKey)args[4];
boundSql = (BoundSql)args[5];
}
this.checkDialectExists();
List resultList;
if(!this.dialect.skip(ms, parameter, rowBounds)) {
if(this.dialect.beforeCount(ms, parameter, rowBounds)) {
Long count = this.count(executor, ms, parameter, rowBounds, resultHandler, 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 {
this.dialect.afterAll();
}
}
Vá para o método pageQuery da classe abstrata ExecutorUtil
public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey) throws SQLException {
if(!dialect.beforePage(ms, parameter, rowBounds)) {
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
} else {
parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
Iterator var12 = additionalParameters.keySet().iterator();
while(var12.hasNext()) {
String key = (String)var12.next();
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);
}
}
Obtenha o objeto Page correspondente em getPageSql da classe abstrata AbstractHelperDialect
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
String sql = boundSql.getSql();
Page page = this.getLocalPage();
String orderBy = page.getOrderBy();
if(StringUtil.isNotEmpty(orderBy)) {
pageKey.update(orderBy);
sql = OrderByParser.converToOrderBySql(sql, orderBy);
}
return page.isOrderByOnly()?sql:this.getPageSql(sql, page, pageKey);
}
Insira o método getPageSql da classe MySqlDialect para encapsular o SQL e aumente o Limit de acordo com as informações do objeto de página. É assim que as informações paginadas são montadas
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 ?, ? ");
}
return sqlBuilder.toString();
}
Retorne o SQL montado final para DefaultSqlSession para executar a consulta e retornar
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
A função de paginação do PageHelper é realizada por meio de Limit splicing SQL.
Aí vem o problema:
ao analisar a instrução SQL, a eficiência de consulta do limite é relativamente alta quando a quantidade de dados é pequena ou o número de páginas é relativamente alto. (Uma única tabela com milhões de dados para teste)
selecione * do usuário onde idade = 10 limite 1,10; o resultado mostra 0,43s
Quando o resultado definido após a condição where é grande e o número de páginas atinge uma ordem de magnitude, a eficiência da consulta de todo o SQL é muito baixa (mesmo se a condição where for adicionada com um índice).
selecione * do usuário onde idade = 10 limite 100000,10; o resultado mostra 4,73s
Então, qual é a solução?
Esquema de
paginação O SQL de paginação original
selecione * do limite do nome da tabela 155555,20
1
instrução sql otimizada
selecione *
FROM table name WHERE 'id' in (selecione id do nome da tabela LIMIT 155555,20)
Primeiro abra PageHelper.startPage(pageParam.getPageNum(), pageParam.getPageSize()); 155555,20 inevitavelmente será adicionado na parte de trás do sql e o limite do nosso SQL otimizado é uma instrução de subconsulta. Como resolver? à prática anterior obterá
SELECT * FROM nome da tabela WHERE 'id' in (selecione id do nome da tabela) LIMIT 155555,20
Solução de problemas
PageHelper.startPage(pageParam.getPageNum(), pageParam.getPageSize());
//在这里算出select id from 表名 LIMIT 0,30
List<Integer> pageProcessLogList = processLogMapper.getCount(processLog);
if (pageProcessLogList.size() == 0) {
processLog.setIds(null);
} else {
processLog.setIds(pageProcessLogList);
}
//这里我们不用processlists查询的数量,而是用pageProcessLogList
PageInfo<Integer> countInfo = new PageInfo<>(pageProcessLogList);
List<ProcessLog> processlists =processLogMapper.pageProcessLogList(processLog);
PageInfo<ProcessLog> infolist = new PageInfo<>(processlists);
//countinfo含有的信息是pagehepler封装过的,我们换个对象
BeanUtils.copyProperties(countInfo,infolist);
infolist.setList(processlists);
return infolist;
Arquivo mapeador
Mapper
``java
<select id="getCount" resultType="java.lang.Integer" parameterType="com.tvunetworks.useractivity.model.ProcessLog">
select id from
<choose>
<when test="dateSuffix.length()==6">
t_process_email_log${dateSuffix}
</when>
<otherwise>
t_process_log${dateSuffix}
</otherwise>
</choose>
<where>
<if test="serverName != null and serverName != ''">
`server_name` = #{serverName}
</if>
<if test="method != null and method != ''">
and `method` = #{method}
</if>
<if test="requestUri != null and requestUri != ''">
and `request_uri` like CONCAT('%',#{requestUri},'%')
</if>
<if test="params != null and params != ''">
and `params` like CONCAT('%',#{params},'%')
</if>
<if test="user != null and user != ''">
and `user` like CONCAT('%',#{user},'%')
</if>
<if test="status != null and status != ''">
and `status` = #{status}
</if>
<if test="result != null and result != ''">
and `result` like CONCAT('%',#{result},'%')
</if>
<if test="requestTime != null and requestTime != ''">
and `request_time` >= #{requestTime}
</if>
<if test="responseTime != null and responseTime != ''">
and `response_time` = #{responseTime}
</if>
<if test="email != null and email != ''">
and `email` like CONCAT('%',#{email},'%')
</if>
<if test="peerId != null and peerId != ''">
and `peer_id` like CONCAT('%',#{peerId},'%')
</if>
<if test="ip != null and ip != ''">
and `ip` like CONCAT('%',#{ip},'%')
</if>
<if test="userAgent != null and userAgent != ''">
and `user_agent` like CONCAT('%',#{userAgent},'%')
</if>
<if test="dpi != null and dpi != ''">
and `dpi` like CONCAT('%',#{dpi},'%')
```sql
<select id="pageProcessLogList" resultType="com.tvunetworks.useractivity.model.ProcessLog">
select * from
<choose>
<when test="dateSuffix.length()==6">
表名${dateSuffix} a
</when>
<otherwise>
表名${dateSuffix} a
</otherwise>
</choose>
<where>
<if test="ids!=null">
id in
<foreach collection="ids" item="s" open=" (" separator="," close=")">
#{s}
</foreach>
</if>
<if test="serverName != null and serverName != ''">
and `server_name` = #{serverName}
</if>
<if test="method != null and method != ''">
and `method` = #{method}
</if>
<if test="requestUri != null and requestUri != ''">
and `request_uri` like CONCAT('%',#{requestUri},'%')
</if>
<if test="params != null and params != ''">
and `params` like CONCAT('%',#{params},'%')
</if>
<if test="user != null and user != ''">
and `user` like CONCAT('%',#{user},'%')
</if>
<if test="status != null and status != ''">
and `status` = #{status}
</if>
<if test="result != null and result != ''">
and `result` like CONCAT('%',#{result},'%')
</if>
<if test="requestTime != null and requestTime != ''">
and `request_time` >= #{requestTime}
</if>
<if test="responseTime != null and responseTime != ''">
and `response_time` = #{responseTime}
</if>
<if test="email != null and email != ''">
and `email` like CONCAT('%',#{email},'%')
</if>
<if test="peerId != null and peerId != ''">
and `peer_id` like CONCAT('%',#{peerId},'%')
</if>
<if test="ip != null and ip != ''">
and `ip` like CONCAT('%',#{ip},'%')
</if>
<if test="userAgent != null and userAgent != ''">
and `user_agent` like CONCAT('%',#{userAgent},'%')
</if>
<if test="dpi != null and dpi != ''">
and `dpi` like CONCAT('%',#{dpi},'%')
</if>
</where>
<if test="sortModel != null and sortModel != ''">
order by request_time ${sortModel}
</if>
</select>
```</if>
</where>
</select>