依赖
<!-- Mybatis Plus (其中还包含了 Mybatis 和 jdbc) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
分页插件
@Configuration
public class MybatisConfig {
// 分页插件
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
controller
/**
* 分页查询数据
* @param pn 页码
* @param model
* @return
*/
@GetMapping("/userList")
public String userList(@RequestParam(value = "pn", defaultValue = "1")Integer pn, Model model) {
// 分页查询数据
Page<User> userPage = new Page<>(pn, 2); //pn:第几页 size:每页记录条数
Page<User> page = userService.page(userPage, null);
model.addAttribute("users", page);
long current = page.getCurrent(); // 当前页码
long pages = page.getPages(); // 总页数
long total = page.getTotal(); // 总记录数
return "test_table";
}
thymeleaf
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>table</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="js/jquery-3.4.1.min.js"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="js/bootstrap.min.js"></script>
</head>
<body>
<div class="panel-body">
<div>
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>id</th>
<th>name</th>
<th>age</th>
<th>email</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="user,stat: ${users.records}">
<td th:text="${stat.count}"></td>
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td th:text="${user.email}"></td>
<td>
更新
</td>
</tr>
</table>
<div class="row-fluid">
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第 [[${users.current}]] 页 总计 [[${users.pages}]] 页 共 [[${users.total}]] 条记录
</div>
</div>
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a th:if="${users.current > 1}" th:href="@{/userList(pn=${users.current - 1})}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
<a th:if="${users.current == 1}" href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li th:class="${num == users.current ? 'active' : ''}" th:each="num:${#numbers.sequence(1, users.pages)}">
<a th:href="@{/userList(pn=${num})}">[[${num}]]</a>
</li>
<li>
<a th:if="${users.current < users.pages}" th:href="@{/userList(pn=${users.current + 1})}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
<a th:if="${users.current >= users.pages}" href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</body>
</html>
分页类详细:
public class Page<T> implements IPage<T> {
private static final long serialVersionUID = 8545996863226528798L;
protected List<T> records; //分页对象记录列表
protected long total; //总记录跳数
protected long size; //每页显示条数
protected long current; //当前页码
protected List<OrderItem> orders; //排序信息
protected boolean optimizeCountSql; //自动优化 COUNT SQL【 默认:true 】
protected boolean isSearchCount; //进行 count 查询 【 默认: true 】
protected boolean hitCount; //设置是否命中count缓存
protected String countId; //MappedStatement 的 id
protected Long maxLimit; //最大每页分页数限制,优先级高于分页插件内的 maxLimit
public Page() {
this.records = Collections.emptyList();
this.total = 0L;
this.size = 10L;
this.current = 1L;
this.orders = new ArrayList();
this.optimizeCountSql = true;
this.isSearchCount = true;
this.hitCount = false;
}
public Page(long current, long size) {
this(current, size, 0L);
}
public Page(long current, long size, long total) {
this(current, size, total, true);
}
public Page(long current, long size, boolean isSearchCount) {
this(current, size, 0L, isSearchCount);
}
public Page(long current, long size, long total, boolean isSearchCount) {
this.records = Collections.emptyList();
this.total = 0L;
this.size = 10L;
this.current = 1L;
this.orders = new ArrayList();
this.optimizeCountSql = true;
this.isSearchCount = true;
this.hitCount = false;
if (current > 1L) {
this.current = current;
}
this.size = size;
this.total = total;
this.isSearchCount = isSearchCount;
}
public boolean hasPrevious() {
return this.current > 1L;
}
public boolean hasNext() {
return this.current < this.getPages();
}
public List<T> getRecords() {
return this.records;
}
public Page<T> setRecords(List<T> records) {
this.records = records;
return this;
}
public long getTotal() {
return this.total;
}
public Page<T> setTotal(long total) {
this.total = total;
return this;
}
public long getSize() {
return this.size;
}
public Page<T> setSize(long size) {
this.size = size;
return this;
}
public long getCurrent() {
return this.current;
}
public Page<T> setCurrent(long current) {
this.current = current;
return this;
}
public String countId() {
return this.getCountId();
}
public Long maxLimit() {
return this.getMaxLimit();
}
private String[] mapOrderToArray(Predicate<OrderItem> filter) {
List<String> columns = new ArrayList(this.orders.size());
this.orders.forEach((i) -> {
if (filter.test(i)) {
columns.add(i.getColumn());
}
});
return (String[])columns.toArray(new String[0]);
}
private void removeOrder(Predicate<OrderItem> filter) {
for(int i = this.orders.size() - 1; i >= 0; --i) {
if (filter.test(this.orders.get(i))) {
this.orders.remove(i);
}
}
}
public Page<T> addOrder(OrderItem... items) {
this.orders.addAll(Arrays.asList(items));
return this;
}
public Page<T> addOrder(List<OrderItem> items) {
this.orders.addAll(items);
return this;
}
/** @deprecated */
@Deprecated
public Page<T> setAscs(List<String> ascs) {
return CollectionUtils.isNotEmpty(ascs) ? this.setAsc((String[])ascs.toArray(new String[0])) : this;
}
/** @deprecated */
@Deprecated
public Page<T> setAsc(String... ascs) {
this.removeOrder(OrderItem::isAsc);
String[] var2 = ascs;
int var3 = ascs.length;
for(int var4 = 0; var4 < var3; ++var4) {
String s = var2[var4];
this.addOrder(OrderItem.asc(s));
}
return this;
}
/** @deprecated */
@Deprecated
public Page<T> setDescs(List<String> descs) {
if (CollectionUtils.isNotEmpty(descs)) {
this.removeOrder((item) -> {
return !item.isAsc();
});
Iterator var2 = descs.iterator();
while(var2.hasNext()) {
String s = (String)var2.next();
this.addOrder(OrderItem.desc(s));
}
}
return this;
}
/** @deprecated */
@Deprecated
public Page<T> setDesc(String... descs) {
this.setDescs(Arrays.asList(descs));
return this;
}
public List<OrderItem> orders() {
return this.getOrders();
}
public boolean optimizeCountSql() {
return this.optimizeCountSql;
}
public boolean isOptimizeCountSql() {
return this.optimizeCountSql();
}
public boolean isSearchCount() {
return this.total < 0L ? false : this.isSearchCount;
}
public Page<T> setSearchCount(boolean isSearchCount) {
this.isSearchCount = isSearchCount;
return this;
}
public Page<T> setOptimizeCountSql(boolean optimizeCountSql) {
this.optimizeCountSql = optimizeCountSql;
return this;
}
public void hitCount(boolean hit) {
this.hitCount = hit;
}
public void setHitCount(boolean hit) {
this.hitCount = hit;
}
public boolean isHitCount() {
return this.hitCount;
}
public List<OrderItem> getOrders() {
return this.orders;
}
public void setOrders(final List<OrderItem> orders) {
this.orders = orders;
}
public String getCountId() {
return this.countId;
}
public void setCountId(final String countId) {
this.countId = countId;
}
public Long getMaxLimit() {
return this.maxLimit;
}
public void setMaxLimit(final Long maxLimit) {
this.maxLimit = maxLimit;
}
}
/**
* 分页 Page 对象接口
*
* @author hubin
* @since 2018-06-09
*/
public interface IPage<T> extends Serializable {
/**
* 获取排序信息,排序的字段和正反序
*
* @return 排序信息
*/
List<OrderItem> orders();
/**
* KEY/VALUE 条件
*
* @return ignore
* @deprecated 3.4.0 @2020-06-30
*/
@Deprecated
default Map<Object, Object> condition() {
return null;
}
/**
* 自动优化 COUNT SQL【 默认:true 】
*
* @return true 是 / false 否
*/
default boolean optimizeCountSql() {
return true;
}
/**
* 进行 count 查询 【 默认: true 】
*
* @return true 是 / false 否
*/
default boolean isSearchCount() {
return true;
}
/**
* 计算当前分页偏移量
*/
default long offset() {
long current = getCurrent();
if (current <= 1L) {
return 0L;
}
return (current - 1) * getSize();
}
/**
* 最大每页分页数限制,优先级高于分页插件内的 maxLimit
*
* @since 3.4.0 @2020-07-17
*/
default Long maxLimit() {
return null;
}
/**
* 当前分页总页数
*/
default long getPages() {
if (getSize() == 0) {
return 0L;
}
long pages = getTotal() / getSize();
if (getTotal() % getSize() != 0) {
pages++;
}
return pages;
}
/**
* 内部什么也不干
* <p>只是为了 json 反序列化时不报错</p>
*/
default IPage<T> setPages(long pages) {
// to do nothing
return this;
}
/**
* 设置是否命中count缓存
*
* @param hit 是否命中
* @since 3.3.1
* @deprecated 3.4.0 @2020-06-30 缓存遵循mybatis的一或二缓
*/
@Deprecated
default void hitCount(boolean hit) {
}
/**
* 是否命中count缓存
*
* @return 是否命中count缓存
* @since 3.3.1
* @deprecated 3.4.0 @2020-06-30 缓存遵循mybatis的一或二缓
*/
@Deprecated
default boolean isHitCount() {
return false;
}
/**
* 分页记录列表
*
* @return 分页对象记录列表
*/
List<T> getRecords();
/**
* 设置分页记录列表
*/
IPage<T> setRecords(List<T> records);
/**
* 当前满足条件总行数
*
* @return 总条数
*/
long getTotal();
/**
* 设置当前满足条件总行数
*/
IPage<T> setTotal(long total);
/**
* 获取每页显示条数
*
* @return 每页显示条数
*/
long getSize();
/**
* 设置每页显示条数
*/
IPage<T> setSize(long size);
/**
* 当前页
*
* @return 当前页
*/
long getCurrent();
/**
* 设置当前页
*/
IPage<T> setCurrent(long current);
/**
* IPage 的泛型转换
*
* @param mapper 转换函数
* @param <R> 转换后的泛型
* @return 转换泛型后的 IPage
*/
@SuppressWarnings("unchecked")
default <R> IPage<R> convert(Function<? super T, ? extends R> mapper) {
List<R> collect = this.getRecords().stream().map(mapper).collect(toList());
return ((IPage<R>) this).setRecords(collect);
}
/**
* 老分页插件不支持
* <p>
* MappedStatement 的 id
*
* @return id
* @since 3.4.0 @2020-06-19
*/
default String countId() {
return null;
}
/**
* 生成缓存key值
*
* @return 缓存key值
* @since 3.3.2
* @deprecated 3.4.0 @2020-06-30
*/
@Deprecated
default String cacheKey() {
StringBuilder key = new StringBuilder();
key.append(offset()).append(StringPool.COLON).append(getSize());
List<OrderItem> orders = orders();
if (CollectionUtils.isNotEmpty(orders)) {
for (OrderItem item : orders) {
key.append(StringPool.COLON).append(item.getColumn()).append(StringPool.COLON).append(item.isAsc());
}
}
return key.toString();
}
}