接上文内容,本节介绍基于Mybatis的查询和分页功能,并展示一个自定义的分页标签,可重复使用以简化JSP页面的开发。
从头阅读传送门
在上一节中,我们已经使用Maven搭建好了项目的基础结构,包括一个父项目petstore-parent和数据库持久层模块petstore-persist及Web站点petstore-web,现在来为petstore-web添加一些功能。对于初学者来说,可能第一个遇到的较复杂问题就是分页查询,那么就先从解决它开始。
看一下完成的效果:
上面是四个可选的查询条件,用户可以根据需要组合查询条件。
中间是符合条件的数据展示表格,对查询结果可以执行修改和删除操作,但是暂未实现。
最下面是一个分页导航栏,以自定义标签(Tag)技术实现,可复用到多个jsp页面。
下面来介绍关键步骤和代码。首先是petstore-persist模块,目录结构如下:
Product.java是一个普通的Java Bean,这里略过。ProductMapper.java中定义了两个方法:
package com.example.petstore.persist.model; import java.util.List; import org.apache.ibatis.annotations.Param; public interface ProductMapper { /** * 查询符合条件的记录总数 * @param id * @param name * @param fromPrice * @param toPrice * @return */ int matches(@Param(value="id") int id, @Param(value="name") String name, @Param(value="fromPrice") float fromPrice, @Param(value="toPrice") float toPrice); /** * 按查询条件及分页条件分段查询记录 * @param id * @param name * @param fromPrice * @param toPrice * @param fetchIndex * @param fetchCount * @return */ List<Product> findProducts(@Param(value="id") int id, @Param(value="name") String name, @Param(value="fromPrice") float fromPrice, @Param(value="toPrice") float toPrice, @Param(value="fetchIndex") int fetchIndex, @Param(value="fetchCount") int fetchCount); }使用时,首先调用matches方法获得符合条件的记录总数,然后根据每页显示的记录数和当前页数计算读取数据的Limit偏移量和记录数,再调用findProducts方法读取数据。两个方法的参数都使用了@Param注解,例如@Param(value="id") int id,在映射文件中,可通过#{id}的格式来使用这个参数。
在Product.xml中添加两个方法的SQL映射:
<?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.petstore.persist.model.ProductMapper"> <resultMap type="com.example.petstore.persist.model.Product" id="productMap"> <id column="p_id" property="id" /> <result column="p_name" property="name" /> <result column="p_price" property="price" /> </resultMap> <select id="matches" resultType="int"> select count(*) from t_product <where> <if test="id>0"> p_id=#{id} </if> <if test="name!=null and name!='' "> and locate(#{name},p_name)>0 </if> <if test="fromPrice>-1"> and p_price>=#{fromPrice} </if> <if test="toPrice>-1"> and p_price<=#{toPrice} </if> </where> </select> <select id="findProducts" resultMap="productMap"> select * from t_product <where> <if test="id>0"> p_id=#{id} </if> <if test="name!=null and name!='' "> and locate(#{name},p_name)>0 </if> <if test="fromPrice>-1"> and p_price>=#{fromPrice} </if> <if test="toPrice>-1"> and p_price<=#{toPrice} </if> </where> limit #{fetchIndex},#{fetchCount} </select> </mapper>
可以看到上面两个方法中,就是通过<where><if>等元素来组装查询SQL。Mybatis的优点之一就是直接使用SQL语法,有SQL基础的情况下非常容易上手。
下面进入petstore-web模块,先来看整体结构:
其中com.example.petstore.web.tag.PagingTag.java是分页标签类,关键代码:
private int pageIndex = 1; //当前页数 private int pageSize = 20; //默认每页行数 private int pageCount = 0; //记录总页数 private int itemCount = 0; //记录总条数 private int numCount = 10; //分页栏数字导航链接个数 @Override public void doTag() throws JspException, IOException { JspWriter out = this.getJspContext().getOut(); out.write("<script type=\"text/javascript\">function navigatorPage(pageIndex) {document.getElementById('pageIndex').value = pageIndex;document.forms[0].submit();}</script>"); out.write("每页显示"); out.write("<select id='pageSize' name='pageSize' onchange='navigatorPage(" + pageIndex + ")'>"); out.write("<option value='5'" + (pageSize == 5 ? " selected='true'" : "") + ">5</option>"); out.write("<option value='10'" + (pageSize == 10 ? " selected='true'" : "") + ">10</option>"); out.write("<option value='20'" + (pageSize == 20 ? " selected='true'" : "") + ">20</option>"); out.write("<option value='50'" + (pageSize == 50 ? " selected='true'" : "") + ">50</option>"); out.write("<option value='100'" + (pageSize == 100 ? " selected='true'" : "") + ">100</option>"); out.write("<option value='500'" + (pageSize == 500 ? " selected='true'" : "") + ">500</option>"); out.write("</select>"); out.write("条 "); out.write(pageIndex + "/" + pageCount + "页 "); out.write("共" + itemCount + "条记录 "); out.write("<input type='button' value='第一页' onclick='javascript:navigatorPage(1);'" + (pageIndex > 1 ? "" : " disabled='true'") + " /> "); out.write("<input type='button' value='上一页' onclick='javascript:navigatorPage(" + (pageIndex - 1) + ");'" + (pageIndex > 1 ? "" : " disabled='true'") + " /> "); //数字导航栏 int iStartIndex = 1; int iEndIndex = pageCount; if(pageCount <= numCount) { } else if ((pageIndex + (numCount + 1) / 2) > pageCount) { iStartIndex = pageCount - (numCount - 1); iEndIndex = pageCount; } else if (pageIndex <= (numCount + 1) / 2) { iEndIndex = numCount; } else { if (numCount % 2 == 0) { iStartIndex = pageIndex - numCount / 2; iEndIndex = pageIndex + (numCount - 1) / 2; } else { iStartIndex = pageIndex - numCount / 2; iEndIndex = pageIndex + numCount / 2; } } for(int i = iStartIndex; i <= iEndIndex; i++) { if(i == pageIndex) { out.write("<strong>" + i + "</strong> "); } else { out.write("<a href='javascript:navigatorPage(" + i + ");'>" + i + "</a> "); } } out.write("<input type='button' value='下一页' onclick='javascript:navigatorPage(" + (pageIndex + 1) + ");'" + (pageIndex < pageCount ? "" : " disabled='true'") + " /> "); out.write("<input type='button' value='最后页' onclick='javascript:navigatorPage(" + pageCount + ");'" + (pageIndex < pageCount ? "" : " disabled='true'") + " />"); out.write("<input type='hidden' id='pageIndex' name='pageIndex' value='" + pageIndex + "'/>"); }接下来还需要一个标签配置文件来声明这个标签的使用方法。
在WEB-INF下建立目录tld,然后添加pagingTag.tld,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>2.0</tlib-version> <jsp-version>1.2</jsp-version> <short-name>Paging</short-name> <uri>http://blog.csdn.net/autfish/tag/</uri> <display-name>Paging Tag</display-name> <description>Paging Tag library</description> <tag> <name>pagingTag</name> <tag-class>com.example.petstore.web.tag.PagingTag</tag-class> <body-content>empty</body-content> <description>create navigation for paging</description> <attribute> <name>pageIndex</name> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>pageSize</name> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>pageCount</name> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>itemCount</name> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>注意其中的uri元素,这里并不需要配置真实存在的url,但该uri在你的classpath中应保持唯一,不能被其他组件声明使用。
在web.xml中启用这个标签:
<jsp-config> <taglib> <taglib-uri>http://blog.csdn.net/autfish/tag/</taglib-uri> <taglib-location>/WEB-INF/tld/pagingTag.tld</taglib-location> </taglib> </jsp-config>在jsp中使用:
<%@ taglib prefix="my" uri="http://blog.csdn.net/autfish/tag/" %>
<my:pagingTag pageIndex="${contentModel.pageIndex}" pageSize="${contentModel.pageSize}" pageCount="${contentModel.pageCount}" itemCount="${contentModel.itemCount}" />对于四个属性的赋值,contentModel是一个PagingList.java类的实例,用于辅助分页,由分页属性和数据表构成,在Controller中填充数据并传递到视图JSP。属性如下:
private int pageIndex = 1; private int pageSize = 20; private int pageCount = 0; private int itemCount = 0; private List<T> items;Controller代码:
@Controller @RequestMapping("/product") public class ProductController { @Autowired private ProductService productService; @RequestMapping(value="/list") public String listProduct(Model model, @ModelAttribute("searchModel") SearchModel formModel, @RequestParam(value=PagingList.PAGE_INDEX_NAME, defaultValue="1") int pageIndex, @RequestParam(value=PagingList.PAGE_SIZE_NAME, defaultValue="10") int pageSize) { int id = 0; String name = ""; float fromPrice = -1; float toPrice = -1; if(formModel != null) { id = NumberUtils.toInt(formModel.getId(), 0); name = formModel.getName(); fromPrice = NumberUtils.toFloat(formModel.getFromPrice(), -1); toPrice = NumberUtils.toFloat(formModel.getToPrice(), -1); } model.addAttribute("searchModel", formModel); PagingList<Product> contentModel = this.productService.findProducts(id, name, fromPrice, toPrice, pageIndex, pageSize); model.addAttribute("contentModel", contentModel); return "product/list"; } }Controller中注入了一个ProductService的实例,用于管理持久层的调用,主要代码如下:
@Service public class ProductServiceStdImpl implements ProductService { @Autowired private ProductMapper productMapper; @Override public PagingList<Product> findProducts(int id, String name, float fromPrice, float toPrice, int pageIndex, int pageSize) { int total = this.productMapper.matches(id, name, fromPrice, toPrice); int pageCount = total % pageSize == 0 ? total / pageSize : total / pageSize + 1; if(pageIndex > pageCount) pageIndex = pageCount; int fetchIndex = (pageIndex - 1) * pageSize; int fetchCount = fetchIndex + pageSize > total ? (total - fetchIndex) : pageSize; List<Product> list = this.productMapper.findProducts(id, name, fromPrice, toPrice, fetchIndex, fetchCount); PagingList<Product> paging = new PagingList<Product>(); paging.setItemCount(total); paging.setPageCount(pageCount); paging.setPageIndex(pageIndex); paging.setPageSize(pageSize); paging.setItems(list); return paging; } }
限于篇幅,不能把所有的源码一一粘贴,有兴趣可以下载源码。
在WEB容器如tomcat中运行petstore-web模块,使用http://localhost:8080/petstore-web/product/list访问,顺利的话就看到了一开始的画面。如果出错,比对源码检查差异即可。总结
分页查询功能使用频繁,且开发比较复杂,按需定制一套可复用的分页组件对提高开发效率有很大的帮助。下一节我们继续完善Web模块,增加增删改查以及权限控制功能。
本节源码下载
从头开始基于Maven搭建SpringMVC+Mybatis项目(1)
从头开始基于Maven搭建SpringMVC+Mybatis项目(2)
从头开始基于Maven搭建SpringMVC+Mybatis项目(3)
从头开始基于Maven搭建SpringMVC+Mybatis项目(4)