分页逻辑实现

本文介绍了分页在大数据量展示时的重要性,详细讲解了分页的分类,包括真分页(物理分页)和假分页(逻辑分页),并分析了两者之间的区别。在实现部分,通过示例展示了如何在Java中使用假分页和真分页的实现方式,包括DAO层、Service层和Controller层的关键代码,强调了在不同场景下选择合适分页策略的必要性。

分页简介

用来将数据分割成多个部分来分页面展示。

什么时候用?

数据量达到一定的时候,就需要使用分页来进行数据分割。要不然可能会面临以下问题:

  • 客户端一次性显示太多数据会影响到用户的体验,比如很难找到客户想要的信息,以及加载页面数据过慢。
  • 对于服务端来说,一次性传送的数据过多,可能造成内存溢出。

分页的分类

分页分为真分页和假分页:

  • 真分页(物理分页):
    在mysql中使用select * from table where …limit start , size(在oracle中复杂些)
    (page_num 代表具体第多少页,pages代表每页有多少条数据)
    第一个参数start代表(page_num-1)*pages
    第二个参数是 pages,即页面数据量最大是多少
    还有limit如果后面只有一个参数,则相当于0,num即0行到num-1行
  • 假分页(逻辑分页):
    直接使用select * from table where …
    这样将所有数据查询出来存放到内存中,每次需要查询时直接从内存中去取出相应索引区间的数据

区别

真分页相比于假分页不会造成内存溢出,但翻页的数据相比于假分页又慢,所以根据实际情况选择分页方式,如果数据量不大,可以考虑使用假分页使翻页速度加快。

实现

假分页

假分页实际上使用List去存储所有查询出的值,再用subList方法获取两个索引之间的数据。

先在dao层,创建StudentMapper接口

List<Student> queryStudentsByArray();

再创建StudentMapper.xml文件,编写查询语句:

<select id="queryStudentsByArray" resultType="Student">
   select * from student
</select>

然后定义Service接口并定义分页方法:

List<Student> queryStudentsByArray(int currPage, int pageSize);

再在Servlce实现类中重写该方法:

@Override
public List<Student> queryStudentsByArray(int currPage, int pageSize) {
     List<Student> students = studentMapper.queryStudentsByArray();
     // 从第几条数据开始
     int firstIndex = (currPage - 1) * pageSize;
     // 到第几条数据结束
     int lastIndex = currPage * pageSize;
     return students.subList(firstIndex, lastIndex);
}

这里控制器和前台就根据这个方法来编写。

真分页

这里以我自己在一个个人博客后台管理type和tag的分页为例:
先封装一个Page类,用来做分页(为了简便):

package com.yuer.entity;

import java.util.ArrayList;
import java.util.List;

// 进行分页的工具类
// 使用泛型的原因是可能对不同类型进行分页
// 在first,last,start的get方法中,进行一些处理,保证这三个字段正确
public class Page<T> {

	// 这里存放的每次查出来的一页内容
	private List<T> content = new ArrayList<>();

	// 当前页数 默认为1
	private int page = 1;

	// 总页数
	private int totalPages = 0;

	// 这个等于(page - 1) * size,代表该显示多少条数据的第一个索引
	private int start = 0;
	
	// 默认为4
	private int size = 4;

	// 当前是不是首页,默认为true
	private boolean first = true;

	// 当前是不是尾页,默认是false
	private boolean last = false;

	public List<T> getContent() {
		return content;
	}

	public void setContent(List<T> content) {
		this.content = content;
	}

	public int getPage() {
		return page;
	}

	public void setPage(int page) {
		this.page = page;
	}

	public int getTotalPages() {
		return totalPages;
	}

	public void setTotalPages(int totalPages) {
		this.totalPages = totalPages;
	}

	public int getStart() {
		this.start = (page - 1) * size;
		return start;
	}

	public void setStart(int start) {
		this.start = start;
	}
	

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public boolean isFirst() {
		if (page != 1) {
			this.first = false; 
		} else {
			this.first = true; 
		}
		
		return first;
	}

	public void setFirst(boolean first) {
		this.first = first;
	}

	public boolean isLast() {
		if (page != totalPages) {
			this.last = false; 
		} else {
			this.last = true; 
		}
		return last;
	}

	public void setLast(boolean last) {
		this.last = last;
	}

	public Page() {
		start = (page - 1) * size;
	}

	

}

接下来是显示层(只截取了关键部位):

<!--中间内容-->
  <div  class="m-container-small m-padded-tb-big main">
    <div class="ui container">
      <div class="ui success message" th:unless="${#strings.isEmpty(message)}">
        <i class="close icon"></i>
        <div class="header">提示:</div>
        <p th:text="${message}">恭喜,操作成功!</p>
      </div>
      <table class="ui compact  table">
        <thead>
          <tr>
            <th></th>
            <th>名称</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr th:each="type,iterStat : ${page.content}">
            <td th:text="${iterStat.count}">1</td>
            <td th:text="${type.typeName}">刻意练习清单</td>
            <td>
              <a href="#" th:href="@{/admin/types/{id}/input(id=${type.id})}" class="ui mini teal basic button">编辑</a>
              <a href="#" th:href="@{/admin/types/{id}/delete(id=${type.id})}" class="ui mini red basic button">删除</a>
            </td>
          </tr>
        </tbody>
        <tfoot>
          <tr>
            <th colspan="6" >
            <!-- ${page.totalPages}>1代表看总页数是否大于1,大于则显示这两个上一页,下一页 -->
              <div class="ui mini pagination menu" th:if="${page.totalPages}>1">
              <!-- 这里page.first为是否是第一页,是不显示,同理page.last
              page.number代表当前页数 -->
                <a href="#" th:href="@{/admin/types/{page}(page=${page.page}-1)}" class="  item" th:unless="${page.first}">上一页</a>
                <a href="#" th:href="@{/admin/types/{page}(page=${page.page}+1)}" class=" item" th:unless="${page.last}">下一页</a>
              </div>
              <a href="#" th:href="@{/admin/types/input}"  class="ui mini right floated teal basic button">新增</a>
            </th>
          </tr>
        </tfoot>
      </table>
    </div>
  </div>

接下来是Controller层:

package com.yuer.controller.admin;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.yuer.entity.Page;
import com.yuer.entity.Type;
import com.yuer.service.ITypeService;

@Controller
@RequestMapping("/admin")
public class TypeController {

	@Autowired
	private ITypeService typeService;

	// 这个是默认的访问,不带参数
	@GetMapping("/types")
	public String typesDefault(Model model) {
		Page<Type> page = getPage(-1, false);
		model.addAttribute("page", page);

		return "admin/types";
	}

	// page代表当前页数,当按上一页下一页会触发这个方法
	@GetMapping("/types/{page}")
	public String types(@PathVariable Integer page, Model model) {
		Page<Type> page1 = getPage(page, true);
		model.addAttribute("page", page1);

		return "admin/types";
	}

	

	public Page<Type> getPage(Integer page, boolean flag) {
		// 先 new Page
		Page<Type> page1 = new Page<Type>();

		// 先查出数据条数,再计算得出多少页
		int total = typeService.getTotal();
		int totalPages;
		if (total % page1.getSize() == 0) {
			 totalPages = total / page1.getSize();
		} else {
			 totalPages = total / page1.getSize() + 1;
		}
		page1.setTotalPages(totalPages);

		if (flag) {
			page1.setPage(page);
		}

		page1.setContent(typeService.listTypeByParam(page1.getStart(), page1.getSize()));

		return page1;
	}

}

控制层中的typesDefault方法代表首次进入分类管理,此时只需要显示第一页即可,即在Page类中配置好的默认值,而types方法则是点击上一页下一页时触发的方法,此时会传过来当前的页数,然后set进Page类中,此时不同的数据就会更新。

此时看typeService.listTypeByParam(page1.getStart(), page1.getSize()):这个方法是用来分页的关键方法:

@Transactional
	@Override
	public List<Type> listTypeByParam(int start, int size) {
		return typeDao.listTypeByParam(start, size);
	}

这个直接到mapper.xml文件中看此方法sql语句:

<!-- 这里返回值可能注入错误,需要进行Map映射 -->
	<select id ="listTypeByParam" resultMap="TypeMap" parameterType="Integer">
		select id,type_name from hi_type order by id limit #{start}, #{size}
	</select>
	
	<resultMap type="Type" id="TypeMap">
		<result property="id" column="id"/>
		<result property="typeName" column="type_name"/>
		
	</resultMap>

总结一下:没点击上一页下一页之前默认显示0到size-1条数据,然后点击上一页下一页触发翻页功能时,就在Page类中注入相应的值,取其中的值时,就会同步的发生变化,最后通过mysql的简便分页方法limit start,size方法完成。

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值