| name | mybatis-pagehelper-xugudb-adapter |
| description | MyBatis PageHelper 分页插件适配虚谷数据库(XuguDB)的完整指南。当用户需要将基于 MyBatis PageHelper 的分页查询配置或适配到虚谷数据库时使用此技能,包括依赖配置、分页配置、查询优化、性能调优等。适用于需要分页查询的 MyBatis 项目。 |
MyBatis PageHelper 分页插件虚谷数据库适配指南
概述
本技能提供 MyBatis PageHelper 分页插件适配虚谷数据库(XuguDB)的完整配置指南。PageHelper 是一个 MyBatis 的物理分页插件,支持多种数据库,现在可以通过配置支持虚谷数据库。
适用场景:
- MyBatis 项目分页查询
- 列表数据分页展示
- 大数据量分页处理
- 复杂查询分页优化
核心特性:
- 物理分页,内存占用小
- 支持多种数据库
- 支持多种分页方式
- 支持排序和聚合查询
- 支持自定义方言
- 支持 RowBounds 分页
- 支持 PageHelper 自动 Count 查询
快速开始
1. 添加依赖
Maven 配置:
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>com.xugu</groupId>
<artifactId>xugu-jdbc</artifactId>
<version>12.0.0</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
</dependencies>
Gradle 配置:
dependencies {
implementation 'org.mybatis:mybatis:3.5.13'
implementation 'org.mybatis:mybatis-spring:2.1.1'
implementation 'com.github.pagehelper:pagehelper:5.3.2'
implementation 'com.xugu:xugu-jdbc:12.0.0'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1'
implementation 'com.github.pagehelper:pagehelper-spring-boot-starter:1.4.6'
}
2. 配置 PageHelper
MyBatis 配置文件 mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="xugudb"/>
<property name="reasonable" value="true"/>
<property name="supportMethodsArguments" value="true"/>
<property name="params" valuepageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero"/>
<property name="useRowBoundsWithPage" value="false"/>
</plugin>
</plugins>
</configuration>
Spring Boot 配置 application.yml:
pagehelper:
helper-dialect: xugudb
reasonable: true
support-methods-arguments: true
params: count=countSql
page-size-zero: false
auto-runtime-dialect: true
auto-dialect: true
close-conn: true
count-suffix: _COUNT
mybatis:
config-location: classpath:mybatis-config.xml
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
lazy-loading-enabled: true
aggressive-lazy-loading: false
3. 创建实体类
package com.example.entity;
import java.time.LocalDateTime;
public class User {
private Long id;
private String name;
private String email;
private String phone;
private Integer status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}
4. 创建 Mapper 接口
package com.example.mapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users")
List<User> findAll();
@Select("SELECT * FROM users WHERE status = #{status}")
List<User> findByStatus(@Param("status") Integer status);
@Select("SELECT * FROM users WHERE name LIKE CONCAT('%', #{name}, '%')")
List<User> findByNameLike(@Param("name") String name);
@Select("SELECT * FROM users WHERE status = #{status} AND name LIKE CONCAT('%', #{name}, '%') ORDER BY created_at DESC")
List<User> findByStatusAndNameLike(@Param("status") Integer status, @Param("name") String name);
@Select("SELECT COUNT(*) FROM users WHERE status = #{status}")
long countByStatus(@Param("status") Integer status);
}
5. 创建 Mapper XML 文件
UserMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="UserResultMap" type="com.example.entity.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="email" column="email"/>
<result property="phone" column="phone"/>
<result property="status" column="status"/>
<result property="createdAt" column="created_at"/>
<result property="updatedAt" column="updated_at"/>
</resultMap>
<select id="findAll" resultMap="UserResultMap">
SELECT * FROM users
</select>
<select id="findByStatus" resultMap="UserResultMap">
SELECT * FROM users WHERE status = #{status}
</select>
<select id="findByNameLike" resultMap="UserResultMap">
SELECT * FROM users WHERE name LIKE CONCAT('%', #{name}, '%')
</select>
<select id="findByStatusAndNameLike" resultMap="UserResultMap">
SELECT * FROM users
WHERE status = #{status}
AND name LIKE CONCAT('%', #{name}, '%')
ORDER BY created_at DESC
</select>
<select id="countByStatus" resultType="long">
SELECT COUNT(*) FROM users WHERE status = #{status}
</select>
</mapper>
分页查询
1. 基础分页查询
使用 PageHelper.startPage():
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.example.mapper.UserMapper;
import com.example.entity.User;
import java.util.List;
public class UserService {
private UserMapper userMapper;
public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.findAll();
PageInfo<User> pageInfo = new PageInfo<>(users);
return pageInfo;
}
public PageInfo<User> getUsersByCondition(Integer status, String name, int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.findByStatusAndNameLike(status, name);
PageInfo<User> pageInfo = new PageInfo<>(users);
return pageInfo;
}
}
2. 使用 PageInfo 获取分页信息
public void printPageInfo(PageInfo<User> pageInfo) {
System.out.println("当前页: " + pageInfo.getPageNum());
System.out.println("每页大小: " + pageInfo.getPageSize());
System.out.println("总记录数: " + pageInfo.getTotal());
System.out.println("总页数: " + pageInfo.getPages());
System.out.println("是否有上一页: " + pageInfo.isHasPreviousPage());
System.out.println("是否有下一页: " + pageInfo.isHasNextPage());
System.out.println("上一页页码: " + pageInfo.getPrePage());
System.out.println("下一页页码: " + pageInfo.getNextPage());
System.out.println("是否第一页: " + pageInfo.isIsFirstPage());
System.out.println("是否最后一页: " + pageInfo.isIsLastPage());
System.out.navigatepageNums: " + java.util.Arrays.toString(pageInfo.getNavigatepageNums()));
// 获取数据列表
List<User> users = pageInfo.getList();
for (User user : users) {
System.out.println("用户: " + user.getName());
}
}
3. 使用 RowBounds 分页
import org.apache.ibatis.session.RowBounds;
import java.util.List;
public List<User> getUsersByRowBounds(int offset, int limit) {
RowBounds rowBounds = new RowBounds(offset, limit);
return userMapper.findAllWithRowBounds(rowBounds);
}
4. 使用 Lambda 表达式
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import java.util.List;
public PageInfo<User> getUsersByLambda(int pageNum, int pageSize) {
return PageHelper.startPage(pageNum, pageSize)
.doSelectPageInfo(() -> userMapper.findAll());
}
public PageInfo<User> getUsersByLambda2(int pageNum, int pageSize) {
return PageHelper.startPage(pageNum, pageSize)
.doSelectPageInfo(() -> userMapper.findByStatus(1));
}
高级配置
1. 自定义虚谷数据库方言
创建自定义方言类:
package com.example.dialect;
import com.github.pagehelper.dialect.helper.AbstractHelperDialect;
import org.apache.ibatis.cache.CacheKey;
public class XuguDBDialect extends AbstractHelperDialect {
@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 ");
sqlBuilder.append(page.getPageSize());
} else {
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(page.getStartRow());
sqlBuilder.append(", ");
sqlBuilder.append(page.getPageSize());
}
return sqlBuilder.toString();
}
@Override
public Object processPageParameter(Mapped ms, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
pageKey.update(rowBounds.getOffset());
pageKey.update(rowBounds.getLimit());
return parameterObject;
}
}
注册自定义方言:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="com.example.dialect.XuguDBDialect"/>
<property name="reasonable" value="true"/>
<property name="supportMethodsArguments" value="true"/>
<property name="params" value="pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero"/>
</plugin>
</plugins>
2. 分页合理化配置
PageHelper.startPage(pageNum, pageSize, true);
3. 全局配置
Spring Boot 配置:
pagehelper:
helper-dialect: xugudb
reasonable: true
support-methods-arguments: true
params: count=countSql
page-size-zero: false
auto-runtime-dialect: true
auto-dialect: true
close-conn: true
count-suffix: _COUNT
4. 多数据源配置
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DataSourceConfig {
@Bean
public PageInterceptor pageInterceptor() {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "xugudb");
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
pageInterceptor.setProperties(properties);
return pageInterceptor;
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource, PageInterceptor pageInterceptor) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setPlugins(pageInterceptor);
return sessionFactory.getObject();
}
}
查询优化
1. 避免全表扫描
问题:分页查询时,如果 ORDER BY 字段没有索引,会导致全表扫描。
解决方案:
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_users_status ON users(status);
CREATE INDEX idx_users_covering ON users(status, created_at, name, email);
2. 优化 COUNT 查询
问题:复杂的 COUNT 查询可能很慢。
解决方案:
public long countUsers(Integer status) {
return userMapper.countByStatus(status);
}
@Cacheable(value = "userCount", key = "#status")
public long countUsersWithCache(Integer status) {
return userMapper.countByStatus(status);
}
3. 使用子查询优化
问题:大数据量分页时,LIMIT offset, limit 性能差。
解决方案:
<select id="findUsersByPage" resultMap="UserResultMap">
SELECT u.* FROM users u
INNER JOIN (
SELECT id FROM users
WHERE status = #{status}
ORDER BY created_at DESC
LIMIT #{offset}, #{limit}
) t ON u.id = t.id
</select>
4. 使用游标分页
问题:传统分页在深页时性能差。
解决方案:
public PageInfo<User> getUsersByCursor(Long lastId, int pageSize) {
PageHelper.startPage(1, pageSize);
List<User> users = userMapper.findUsersAfterId(lastId);
return new PageInfo<>(users);
}
@Select("SELECT * FROM users WHERE id > #{lastId} ORDER BY id ASC LIMIT #{limit}")
List<User> findUsersAfterId(@Param("lastId") Long lastId);
性能测试
1. 测试脚本
创建性能测试类:
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class PageHelperPerformanceTest {
@Autowired
private UserMapper userMapper;
@Test
public void testPaginationPerformance() {
int[] pageSizes = {10, 20, 50, 100};
int[] pageNumbers = {1, 10, 100, 1000};
for (int pageSize : pageSizes) {
for (int pageNum : pageNumbers) {
long startTime = System.currentTimeMillis();
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.findAll();
PageInfo<User> pageInfo = new PageInfo<>(users);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.printf("页码: %d, 每页大小: %d, 耗时: %d ms, 总记录数: %d%n",
pageNum, pageSize, duration, pageInfo.getTotal());
}
}
}
@Test
public void testCountPerformance() {
long startTime = System.currentTimeMillis();
long count = userMapper.countByStatus(1);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.printf("COUNT 查询耗时: %d ms, 结果: %d%n", duration, count);
}
}
2. 性能监控
添加性能监控:
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisConfig {
@Bean
public Interceptor pageInterceptor() {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "xugudb");
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("countSuffix", "_COUNT");
properties.setProperty("autoRuntimeDialect", "true");
pageInterceptor.setProperties(properties);
return pageInterceptor;
}
}
集成测试
1. 单元测试
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.List;
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserMapper userMapper;
@InjectMocks
private UserService userService;
private List<User> mockUsers;
@BeforeEach
void setUp() {
mockUsers = Arrays.asList(
createUser(1L, "张三", "zhangsan@example.com"),
createUser(2L, "李四", "lisi@example.com"),
createUser(3L, "王五", "wangwu@example.com")
);
}
@Test
void testGetUsersByPage() {
when(userMapper.findAll()).thenReturn(mockUsers);
PageInfo<User> pageInfo = userService.getUsersByPage(1, 10);
assertNotNull(pageInfo);
assertEquals(3, pageInfo.getList().size());
assertEquals(1, pageInfo.getPageNum());
assertEquals(10, pageInfo.getPageSize());
}
private User createUser(Long id, String name, String email) {
User user = new User();
user.setId(id);
user.setName(name);
user.setEmail(email);
return user;
}
}
2. 集成测试
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@SpringBootTest
@Transactional
public class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserMapper userMapper;
@Test
void testGetUsersByPage() {
PageInfo<User> pageInfo = userService.getUsersByPage(1, 10);
assertNotNull(pageInfo);
assertTrue(pageInfo.getList().size() <= 10);
assertEquals(1, pageInfo.getPageNum());
assertEquals(10, pageInfo.getPageSize());
}
@Test
void testGetUsersByCondition() {
PageInfo<User> pageInfo = userService.getUsersByCondition(1, "张", 1, 10);
assertNotNull(pageInfo);
for (User user : pageInfo.getList()) {
assertEquals(1, user.getStatus());
assertTrue(user.getName().contains("张"));
}
}
}
常见问题与解决方案
1. 分页不生效
问题:PageHelper.startPage() 后查询没有分页。
解决方案:
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.findAll();
PageHelper.startPage(pageNum, pageSize);
System.out.println("这里不能有其他查询");
List<User> users = userMapper.findAll();
2. COUNT 查询慢
问题:COUNT 查询执行时间很长。
解决方案:
@Select("SELECT COUNT(*) FROM users WHERE status = #{status}")
long countByStatus(@Param("status") Integer status);
@Cacheable(value = "userCount", key = "#status")
public long countUsersWithCache(Integer status) {
return userMapper.countByStatus(status);
}
3. 排序问题
问题:分页查询结果顺序不稳定。
解决方案:
<select id="findAll" resultMap="UserResultMap">
SELECT * FROM users ORDER BY id ASC
</select>
CREATE INDEX idx_users_id ON users(id);
4. 内存溢出
问题:查询大量数据导致内存溢出。
解决方案:
@Select("SELECT * FROM users")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
void findAllStream(Consumer<User> consumer);
public List<User> findAllInBatches(int batchSize) {
List<User> allUsers = new ArrayList<>();
int pageNum = 1;
while (true) {
PageHelper.startPage(pageNum, batchSize);
List<User> users = userMapper.findAll();
if (users.isEmpty()) {
break;
}
allUsers.addAll(users);
pageNum++;
}
return allUsers;
}
最佳实践
1. 分页参数校验
- 校验页码和每页大小
- 设置最大每页大小
- 处理异常参数
public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {
if (pageNum <= 0) {
pageNum = 1;
}
if (pageSize <= 0) {
pageSize = 10;
}
if (pageSize > 100) {
pageSize = 100;
}
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.findAll();
return new PageInfo<>(users);
}
2. 查询优化
- 为常用查询字段创建索引
- 避免 SELECT *
- 使用覆盖索引
- 优化 ORDER BY 字段
3. 性能监控
- 监控查询执行时间
- 记录慢查询
- 分析性能瓶颈
- 定期优化数据库
4. 缓存策略
- 缓存 COUNT 查询结果
- 缓存热点数据
- 设置合理的缓存过期时间
- 处理缓存一致性
5. 异常处理
- 捕获数据库异常
- 记录错误日志
- 提供友好的错误信息
- 实现重试机制
相关资源
参考文档
详细配置信息请参考:references/pagehelper-configuration.md