SpringMVCDemo中,遇到的问题(四) 之分页功能
1、背景:
线上经常出现因为研发代码编写不规范,sql语句全表查询,数据过多,硬生生把内存塞爆,不断GC,整个服务宕掉的情况。
引发这种场景的原因之一,归咎在使用mybatis编写sql语句时使用了万能查询语句。where 1=1之后,拼接的if条件都不符合条件。如下:
如果下面sql语句中,if条件都匹配不上,最后会执行 select * from retailer where 1=1
1 <!--sql片段--> 2 <sql id="query_retailer_where"> 3 <if test="name!=null">and name like '%${name}%'</if> 4 <if test="address!=null">and address like '%${address}%'</if> 5 <if test="status!=null">and status like '%${status}%'</if> 6 <if test="telphone!=null">and telphone = #{telphone}</if> 7 <if test="createtime!=null"> 8 and createtime = DATE_FORMAT(#{createtime},'%Y-%m-%d %H:%i:%S') 9 </if> 10 <if test="starttime != null"> <![CDATA[ and createtime >= to_date(#{starttime},'yyyy-MM-dd HH:mm:ss')]]></if> 11 <if test="endtime != null"> <![CDATA[ and createtime <= to_date(#{endtime},'yyyy-MM-dd HH:mm:ss')]]></if> 12 </sql> 13 14 <!--查询--> 15 <select id="find" resultMap="resultMap" parameterType="java.util.Map"> 16 select * from retailer 17 where 1=1 18 <include refid="query_retailer_where"></include> 19 <if test="startPage != null and pageSize !=null"> 20 order by createtime desc 21 -- LIMIT #{startPage},#{pageSize} 22 </if> 23 </select>
这类场景的解决办法,
一种是使用<choose></choose>,如果匹配不到就查询不到,或查询个默认范围。
<where> <choose> <when> </when> <otherwise> AND 1=0 </otherwise>
</choose>
</where>
另外一种就是使用分页查询,引入Limit,限制查询出的数据条数。
2、使用思路:
<!--查询--> <select id="find" resultMap="resultMap" parameterType="java.util.Map"> select * from retailer where 1=1 <include refid="query_retailer_where"></include> <if test="startPage != null and pageSize !=null"> order by createtime desc LIMIT #{startPage},#{pageSize} </if> </select>
由底向上的思路,从limit的使用,来考虑一下传值的逻辑。
limit的语法是,select * from table where ... limit start,size;
start:从第几条记录开始。
size : 读取几条记录。
首先需要知道,从第几条[下标]开始读,要往后读取多少条。
即分页显示的话,需要传给sql语句两个参数,某一页面中第一条在表中的下标,以及页面中数据的条数。
size好说,默认指定一个,或者从前台输入后取一个。
start下标,
一种方式是,前端js计算完,直接传值过来。不涉及sql语句的计算。
另一种方式是,前端传过来要跳转到的页码,后台sql中加入(pageNum-1)*size的计算,即为对应页面的start下标值。
3、实现:
以第一种为例,实现不同页码的跳转。
1)编写分页查询的PageEntity类,首次查询列表,需要给定个默认值。
/** * 分页类,包含三个属性 * 开始页面、起始数据位置、每页要取的数据 */ public class PageEntity { //当前页 private Integer currentPage; //起始页 private Integer startPage; //页面的数据大小 private Integer pageSize; public Integer getCurrentPage() { if(currentPage==null){ currentPage = 1; } return currentPage; } public void setCurrentPage(Integer currentPage) { this.currentPage = currentPage; } public Integer getStartPage() { if (startPage==null){ startPage=0; } return startPage; } public void setStartPage(Integer startPage) { this.startPage = startPage; } public Integer getPageSize() { if (pageSize==null){ pageSize=5; } return pageSize; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; } }
2)使用实际要分页的实体类,继承PageEntity。拥有分页的属性,DAO层(见2使用思路中的sql语句),Service层实际代码略
3)controller层代码如下,主要对视图传过来的数据进行处理,并回传给视图层。
//展示零售商 @RequestMapping(value = "/list") public String list(RetailerEntity retailer, Model model){ Map<String,Object> map = retailerToMap(retailer); List<RetailerEntity> retailerList = retailerService.find(map); //1.设置当前页码数 model.addAttribute("currentPage",retailer.getCurrentPage()); //2.获取开始的页码 model.addAttribute("startPage",retailer.getStartPage()); //3.获取总条数 int countNumber = retailerService.count(map); model.addAttribute("countNumber",countNumber); //4.获取每页显示的数据条数,默认显示10条 int pageSize = retailer.getPageSize(); model.addAttribute("pageSize",pageSize); //5.获取总页数 int sumPageNumber = countNumber%pageSize==0?(countNumber/pageSize):((countNumber/pageSize)+1); model.addAttribute("sumPageNumber",sumPageNumber); model.addAttribute("list",retailerList);
return "/retailer/retailerHome.jsp"; }
4)页面及页面代码
页面将定义的这几个属性,设置为hidden的input标签。每次展示前,从后台中获取。
第一次打开显示时,默认显示第1页,第0条,共显示5条。
<form id="listForm" action="list.action" method="post"> 姓名:<input type="text" name="name" style="width:120px"/> 手机:<input type="text" name="telphone" style="width:120px"/> 地址:<input type="text" name="address" style="width:120px"/><br/><br/> 状态:<select id="indexStatus" onchange="changeStatus()"> <option value="-1" selected="selected">全部</option> <option value="1">启用</option> <option value="0">停用</option> </select> <input type="hidden" name="status" id="status" value="-1"> 创建日期:<input type="text" name="createtime"/> <input type="submit" value="搜索" style="background-color:#173e65;color:#ffffff;width:70px;"/> <br/> <!-- 显示错误信息 --> <c:if test="${errorMsg}"> <font color="red">${errorMsg}</font><br/> </c:if> <input type="hidden" name="startPage" id="startPage" value="${startPage}"/> <input type="hidden" name="currentPage" id="currentPage" value="${currentPage}"/> <input type="hidden" name="pageSize" id="pageSize" value="${pageSize}"/> <input type="hidden" name="sumPageNumber" id="sumPageNumber" value="${sumPageNumber}"/> <input type="hidden" name="countNumber" id="countNumber" value="${countNumber}"/> </form>
在下方输入页码,点击go后。执行toLocationPage方法。
<div style="margin-top: 10px;"> <a onclick="toPrePage()">上一页</a><a onclick="toNextPage()">下一页</a> <input type="text" id="pageNumber" style="width:50px"> <button onclick="toLocationPage()">go</button> <div id="pageInfo"></div> </div>
js方法如下:
function toLocationPage(){ //获取要跳转到的页码 var pageNumber = document.getElementById("pageNumber").value; //获取当前页码这个对象 var currentPageObject = document.getElementById("currentPage"); //取出当前页码的值 var currentPage = currentPageObject.value; //进行非空校验 if(pageNumber==null||pageNumber==""){ alert("请输入要跳转的页数!"); }else{ pageNumber = parseInt(pageNumber); //取出总页码数 var sumPage = parseInt(document.getElementById("sumPageNumber").value); if(pageNumber<1){ alert("数据已到顶!"); }else if(pageNumber>sumPage){ alert("数据已到底!"); }else{ var pageSize = parseInt(document.getElementById("pageSize").value); // 取出开始的数据下标,默认进入第一面时为0 var startPageObject =document.getElementById("startPage"); // 如果要跳转的页码大于当前页码,逻辑如下【注意:currentPage不同于currentPageObject.value,currentPage为1】 if(pageNumber>=currentPage){ //开始的下标+页码差*每页条数 startPageObject.value = parseInt(startPageObject.value)+pageSize*(pageNumber-currentPage); }else if(pageNumber<currentPage){ startPageObject.value = parseInt(startPageObject.value)-pageSize*(currentPage-pageNumber); } // 变更当前页码对象的值为要跳转到的页面,相当于公共的变量值,以后后续使用 currentPageObject.value = pageNumber; document.getElementById("listForm").submit(); } } }