MyBatis分页插件PageHelper⾃定义分页逻辑实现因为PageHelper对于MySQL的分页逻辑采⽤的是SQL后⾯追加limit⼦句的⽅式,这样在⼩数据量情况下是没有问题的。但是对于⼤数据量的时候,⽐如limit 100000, 10,MySQL的运作机理是查出100010条数据,再抛弃掉前100000条,留下剩余10条数据。所以执⾏效率并不⾼。
对于MySQL分页SQL的优化我在之前总结过,⽆⾮就是写个⾃关联,先⾛内部的主键索引/覆盖索引,这个效率很⾼。因为每页的数据⼀般都是10条,所以查出来的数据在和外部做关联的时候即使没有⾛索引,也不会慢。
正好在⽹上也没有搜到相关的⾃定义实现,所以我就在想能不能⾃⼰重写PageHelper中的分页逻辑。在看了看PageHelper源码中相关的实现后发现,PageHelper的分页逻辑是写在了AbstractHelperDialect类中,⽽不同的数据库分页实现是通过继承实现抽象⽅法的⽅式来实现的。对于MySQL的分页就是MySqlDialect类,典型的模板⽅法模式。
再来查看MySqlDialect类的实现:
可以看到就是重写了⽗类的两个抽象⽅法,processPageParameter⽅法是⽤来处理分页参数的,这个⽅法不需要管。⽽下⾯的getPageSql⽅法才是⽤来拼接分页SQL的,可以看到就是简单追加limit⼦句的⽅式。看到这⾥其实就能想到⾃⼰再写⼀个Dialect类,继承MySqlDialect类,覆写其中的getPageSql⽅法即可实现想要的⾃定义分页逻辑:
public class MyMySqlDialect extends MySqlDialect {
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
//实现⾃定义分页逻辑
}
}
写完了⾃定义的分页逻辑后,要想⽤到它需要做下⾯的配置:
pagehelper.helperDialect=com.fig.MyMySqlDialect
因为暂时没到相关的抓取SQL中各个部分的源码(⽐如表名、字段名等等),只看到了分页的参数,所以我决定⾃⼰来实现。我是⽤正则表达式来实现的(正则⼤法好),我之前也写过讲解正则表达式的⽂章,感兴趣的可以查看。同时我之前也写过⼀个抓取出不同数据库SQL中的源表和⽬标表的解析器HSP,也是⽤正则表达式来实现的。
下⾯演⽰⼀下我重写的分页逻辑的执⾏效果。⾸先是XML中的SQL:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-////DTD//EN Mapper 3.0//EN" "/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hys.pagehelperplus.dao.UserDAO">
<sql id="allColumns">
id,
name,
sex,
address
</sql>
<select id="list" resultType="com.ity.UserDO">
SELECT
<include refid="allColumns"/>
FROM user
</select>
</mapper>
可以看到就是需要对user表进⾏分页处理。使⽤的话跟原⽣的PageHelper类的使⽤是没有区别的,manager层:
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.hys.pagehelperplus.dao.UserDAO;
import com.ity.Pager;
import com.ity.UserDO;
import com.hys.pagehelperplus.util.PageHelperUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* ⽤户Manager
*
* @author Robert Hou
* @since 2020年11⽉28⽇ 10:59
**/
@Component
public class UserManager {
@Autowired
private UserDAO userDAO;
public Pager<UserDO> list(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
Page<UserDO> list = userDAO.list();
return PageHelperUtils.pageTransform(list);
}
}
主要看⼀下重写的⾃定义分页实现的代码:
import com.github.pagehelper.Page;
import com.github.pagehelper.dialect.helper.MySqlDialect;
import com.ption.ParseException;
import com.hys.pagehelperplus.util.PageHelperUtils;
slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import java.util.List;
分页查询插件import Matcher;
import Pattern;
/**
/
**
* ⾃定义MySQL分页逻辑
*
* @author Robert Hou
* @since 2020年11⽉27⽇ 17:45
**/
@Slf4j
public class MyMySqlDialect extends MySqlDialect {
private static final Pattern PATTERN = Patternpile("SELECT\\s*([\\s|\\S]*?)\\s*?((FROM\\s*[0-9a-zA-Z_]*)\\s*[\\s|\\S]*)", Pattern.CASE_INSENSITIVE);
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
if (log.isDebugEnabled()) {
log.debug("\n原始SQL:\n" + sql);
}
if (ains("JOIN") || IsRelegated()) {
//TODO 多表分页逻辑暂时没实现,先⽤默认的SQL后⾯追加limit⼦句的⽅式,等以后有时间再研究(对于不是JOIN⽅式来进⾏表连接的SQL(⽐如笛卡尔积)            ve();
PageSql(sql, page, pageKey);
}
List<String> keyNames = KeyNames();
if (keyNames.size() == 0) {
//没有添加@KeyNamesStrategy注解,也将表主键名设置为”id“
PageHelperUtils.setKeyNames(new String[]{"id"});
keyNames = KeyNames();
}
String fromTable = null;
String fields = null;
String afterClause = null;
boolean isSucceeded = false;
Matcher m = PATTERN.matcher(sql);
if (m.find()) {
isSucceeded = true;
//SELECT后⾯FROM前⾯的查字段
fields = m.group(1);
for (String keyName : keyNames) {
if (ains(keyName)) {
fields = place(keyName, "pageHelperAlias1." + keyName);
}
}
//FROM+后⾯的⼦句
afterClause = m.group(2);
if (StartRow() == 0) {
afterClause = afterClause + "\n LIMIT ? ";
} else {
afterClause = afterClause + "\n LIMIT ?, ? ";
}
//FROM+表名
fromTable = m.group(3);
}
if (!isSucceeded) {
throw new ParseException("解析失败!需要排查SQL!");
}
String returnSql = "SELECT " + fields + " " + fromTable + " pageHelperAlias1 \n" +
" INNER JOIN ( SELECT " + getKeyNames(keyNames) + " " + afterClause + " ) pageHelperAlias2"
+ joinKeyNames(keyNames);
if (log.isDebugEnabled()) {
log.debug("\n拼接后的分页SQL:\n" + returnSql);
log.debug("\n拼接后的分页SQL:\n" + returnSql);
}
return returnSql;
}
/**
* KeyNames转换成String格式(逗号拼接)
*/
private String getKeyNames(List<String> keyNames) {
if (keyNames == null || keyNames.isEmpty()) {
return null;
}
StringBuilder stringBuilder = new StringBuilder();
for (String keyName : keyNames) {
stringBuilder.im()).append(", ");
}
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
String();
}
/**
* KeyNames转换成SQL JOIN的关联格式
*/
private String joinKeyNames(List<String> keyNames) {
if (keyNames == null || keyNames.isEmpty()) {
return null;
}
StringBuilder stringBuilder = new StringBuilder(" ON ");
for (int i = 0; i < keyNames.size(); i++) {
keyNames.set(i, (i).trim());
stringBuilder.append("pageHelperAlias1.").(i)).append(" = pageHelperAlias2.").(i));
if (i != keyNames.size() - 1) {
stringBuilder.append(" AND ");
}
}
String();
}
}
下⾯演⽰⼀下执⾏效果:
原始SQL:
SELECT
id,
name,
sex,
address
FROM user
拼接后的分页SQL:
SELECT pageHelperAlias1.id,
name,
sex,
address FROM user pageHelperAlias1
INNER JOIN ( SELECT id  FROM user
LIMIT ?, ?  ) pageHelperAlias2 ON pageHelperAlias1.id = pageHelperAlias2.id
==>  Preparing: SELECT pageHelperAlias1.id, name, sex, address FROM user pageHelperAlias1 INNER JOIN ( SELECT id FROM user LIMIT ?, ? ) pageHelperA ==> Parameters: 10(Long), 10(Integer)
<==      Total: 3

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。