例如,在数据库的某个表里有1000条数据,我们每次只显示100条数据,在第1页显示第0到第99条,在第2页显示第100到199条,依次类推,这就是分页。
分页可以分为逻辑分页和物理分页。逻辑分页是我们的程序在显示每页的数据时,首先查询得到表中的1000条数据,然后成熟根据当前页的“页码”选出其中的100条数据来显示。
物理分页是程序先判断出该选出这1000条的第几条到第几条,然后数据库根据程序给出的信息查询出程序需要的100条返回给我们的程序。
mybatis使用rowbounds实现的分页是逻辑分页,也就是先把数据记录全部查询出来,然在再根据 offset 和 limit 截断记录返回。
为了在数据库层面上实现物理分页,又不改变原来 mybatis 的函数逻辑,可以编写 plugin 截获 mybatis executor 的 statementhandler,重写sql来执行查询。
经常搭框架的人应该都清楚,框架搭建的核心就是配置文件。
在这里我们需要创建 web 工程。也需要用 mybatis与spring mvc 集成起来,源码在本文结尾处下载,主要有以下几个方面的配置。
整个mybatis分页示例要完成的步骤如下:
1、示例功能描述
2、创建工程
3、数据库表结构及数据记录
4、实例对象
5、配置文件
6、测试执行,输出结果
在本示例中,需要使用 mybatis和spring mvc整合完成分页,完成这样的一个简单功能,即指定一个用户(id=1),查询出这个用户关联的所有订单分页显示出来(使用的数据库是:mysql)。
在本示例中,用到两个表:用户表和订单表,其结构和数据记录如下:
create table `user` ( `id` int(10) unsigned not null auto_increment, `username` varchar(64) not null default '', `mobile` varchar(16) not null default '', primary key (`id`) ) engine=innodb auto_increment=3 default charset=utf8; -- ---------------------------- -- records of user -- ---------------------------- insert into `user` values ('1', 'h3', '13838009988'); insert into `user` values ('2', 'saya', '13838009988');订单表结构和数据如下:
create table `order` ( `order_id` int(10) unsigned not null auto_increment, `user_id` int(10) unsigned not null default '0', `order_no` varchar(16) not null default '', `money` float(10,2) unsigned default '0.00', primary key (`order_id`) ) engine=innodb auto_increment=17 default charset=utf8; -- ---------------------------- -- records of order -- ---------------------------- insert into `order` values ('1', '1', '1509289090', '99.90'); insert into `order` values ('2', '1', '1519289091', '290.80'); insert into `order` values ('3', '1', '1509294321', '919.90'); insert into `order` values ('4', '1', '1601232190', '329.90'); insert into `order` values ('5', '1', '1503457384', '321.00'); insert into `order` values ('6', '1', '1598572382', '342.00'); insert into `order` values ('7', '1', '1500845727', '458.00'); insert into `order` values ('8', '1', '1508458923', '1200.00'); insert into `order` values ('9', '1', '1504538293', '2109.00'); insert into `order` values ('10', '1', '1932428723', '5888.00'); insert into `order` values ('11', '1', '2390423712', '3219.00'); insert into `order` values ('12', '1', '4587923992', '123.00'); insert into `order` values ('13', '1', '4095378812', '421.00'); insert into `order` values ('14', '1', '9423890127', '678.00'); insert into `order` values ('15', '1', '7859213249', '7689.00'); insert into `order` values ('16', '1', '4598450230', '909.20');
用户表和订单表分别对应两个实例对象,分别是:user.java 和 order.java,它们都在 com.h3.pojo 包中。
user.java代码内容如下:
package com.h3.pojo; import java.util.list; /** * @describe: user * @author: h3 * @version: v1.0 * @copyright http://www.h3.com */ public class user { private int id; private string username; private string mobile; public int getid() { return id; } public void setid(int id) { this.id = id; } public string getusername() { return username; } public void setusername(string username) { this.username = username; } public string getmobile() { return mobile; } public void setmobile(string mobile) { this.mobile = mobile; } }
order.java代码内容如下:
package com.h3.pojo; /** * @describe: user - 订单 * @author: h3 * @version: v1.0 * @copyright http://www.h3.com */ public class order { private int orderid; private string orderno; private float money; private int userid; private user user; public int getuserid() { return userid; } public void setuserid(int userid) { this.userid = userid; } public int getorderid() { return orderid; } public void setorderid(int orderid) { this.orderid = orderid; } public user getuser() { return user; } public void setuser(user user) { this.user = user; } public string getorderno() { return orderno; } public void setorderno(string orderno) { this.orderno = orderno; } public float getmoney() { return money; } public void setmoney(float money) { this.money = money; } }
这个实例中有三个重要的配置文件,它们分别是:applicationcontext.xml , configuration.xml 以及 usermaper.xml。
applicationcontext.xml 配置文件里最主要的配置:
<?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemalocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd" default-autowire="byname" default-lazy-init="false"> <!--本示例采用dbcp连接池,应预先把dbcp的jar包复制到工程的lib目录下。 --> <context:property-placeholder location="classpath:/config/database.properties" /> <bean id="datasource" class="org.apache.commons.dbcp.basicdatasource" destroy-method="close" p:driverclassname="com.mysql.jdbc.driver" p:url="jdbc:mysql://127.0.0.1:3306/h3?characterencoding=utf8" p:username="root" p:password="" p:maxactive="10" p:maxidle="10"> </bean> <bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager"> <property name="datasource" ref="datasource" /> </bean> <bean id="sqlsessionfactory" class="org.mybatis.spring.sqlsessionfactorybean"> <!--datasource属性指定要用到的连接池--> <property name="datasource" ref="datasource" /> <!--configlocation属性指定mybatis的核心配置文件--> <property name="configlocation" value="classpath:config/configuration.xml" /> <!-- 所有配置的mapper文件 --> <property name="mapperlocations" value="classpath*:com/h3/mapepr/*.xml" /> </bean> <bean class="org.mybatis.spring.mapper.mapperscannerconfigurer"> <property name="basepackage" value="com.h3.maper" /> </bean> </beans>配置文件 configuration.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> <typealiases> <typealias alias="user" type="com.h3.pojo.user"/> <typealias alias="order" type="com.h3.pojo.order"/> </typealiases> <!-- 与spring 集成之后,这些可以完全删除,数据库连接的管理交给 spring 去管理 --> <!-- <environments default="development"> <environment id="development"> <transactionmanager type="jdbc"/> <datasource type="pooled"> <property name="driver" value="com.mysql.jdbc.driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?characterencoding=utf8" /> <property name="username" value="root"/> <property name="password" value="password"/> </datasource> </environment> </environments> --> <!-- 这里交给sqlsessionfactory 的 mapperlocations属性去得到所有配置信息 --> <!-- <mappers> <mapper resource="com/yihaomen/mapper/user.xml"/> </mappers> --> </configuration>
usermaper.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.h3.maper.usermaper"> <!-- 为了返回list 类型而定义的returnmap --> <resultmap type="user" id="resultuser"> <id column="id" property="id" /> <result column="username" property="username" /> <result column="mobile" property="mobile" /> </resultmap> <!-- user 联合 order 查询 方法的配置 (多对一的方式) --> <resultmap id="resultuserorders" type="order"> <id property="orderid" column="order_id" /> <result property="orderno" column="order_no" /> <result property="money" column="money" /> <result property="userid" column="user_id" /> <association property="user" javatype="user"> <id property="id" column="id" /> <result property="username" column="username" /> <result property="mobile" column="mobile" /> </association> </resultmap> <select id="getuserorders" parametertype="int" resultmap="resultuserorders"> select u.*,o.* from `user` u, `order` o where u.id=o.user_id and u.id=#{id} </select> <select id="getuserbyid" resultmap="resultuser" parametertype="int"> select * from user where id=#{id} </select> </mapper>
package com.h3.controller; import java.util.list; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.controller; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.servlet.modelandview; import com.h3.maper.usermaper; import com.h3.pojo.order; import com.h3.util.page; // http://localhost:8080/mybatis08-paging/user/orders @controller @requestmapping("/user") public class usercontroller { @autowired usermaper usermaper; /** * 某一个用户下的所有订单 * * @param request * @param response * @return */ @requestmapping("/orders") public modelandview listall(httpservletrequest request, httpservletresponse response) { list<order> orders = usermaper.getuserorders(1); system.out.println("orders"); modelandview mav = new modelandview("user_orders"); mav.addobject("orders", orders); return mav; } /** * 订单分页 * * @param request * @param response * @return */ @requestmapping("/orderpages") public modelandview pagelist(httpservletrequest request, httpservletresponse response) { int currentpage = request.getparameter("page") == null ? 1 : integer .parseint(request.getparameter("page")); int pagesize = 3; if (currentpage <= 0) { currentpage = 1; } int currentresult = (currentpage - 1) * pagesize; system.out.println(request.getrequesturi()); system.out.println(request.getquerystring()); page page = new page(); page.setshowcount(pagesize); page.setcurrentresult(currentresult); list<order> orders = usermaper.getorderlistpage(page, 1); system.out.println("current page =>" + page); int totalcount = page.gettotalresult(); int lastpage = 0; if (totalcount % pagesize == 0) { lastpage = totalcount % pagesize; } else { lastpage = 1 + totalcount / pagesize; } if (currentpage >= lastpage) { currentpage = lastpage; } string pagestr = ""; pagestr = string.format( "<a href=\"%s\">上一页</a> <a href=\"%s\">下一页</a>", request .getrequesturi() + "?page=" + (currentpage - 1), request.getrequesturi() + "?page=" + (currentpage + 1)); // 制定视图,也就是list.jsp modelandview mav = new modelandview("pagelist"); mav.addobject("orders", orders); mav.addobject("pagelist", pagestr); return mav; } }
注意,在这个分页工程中,在 com.h3.util 包下新增了几个类,它们分别是:pageplugin.java,page.java, pagehelper.java,其中 pageplugin 是针对 mybatis 分页的插件。由于代码太多,这里列出 pageplugin.java 的代码,其它两个类的代码有兴趣的读者可以在下载代码后阅读取研究。 pageplugin.java 的代码如下所示:
package com.h3.util; import java.lang.reflect.field; import java.sql.connection; import java.sql.preparedstatement; import java.sql.resultset; import java.sql.sqlexception; import java.util.list; import java.util.map; import java.util.properties; import javax.xml.bind.propertyexception; import org.apache.ibatis.executor.errorcontext; import org.apache.ibatis.executor.executor; import org.apache.ibatis.executor.executorexception; import org.apache.ibatis.executor.statement.basestatementhandler; import org.apache.ibatis.executor.statement.routingstatementhandler; import org.apache.ibatis.executor.statement.statementhandler; import org.apache.ibatis.mapping.boundsql; import org.apache.ibatis.mapping.mappedstatement; import org.apache.ibatis.mapping.parametermapping; import org.apache.ibatis.mapping.parametermode; import org.apache.ibatis.plugin.interceptor; import org.apache.ibatis.plugin.intercepts; import org.apache.ibatis.plugin.invocation; import org.apache.ibatis.plugin.plugin; import org.apache.ibatis.plugin.signature; import org.apache.ibatis.reflection.metaobject; import org.apache.ibatis.reflection.property.propertytokenizer; import org.apache.ibatis.scripting.xmltags.foreachsqlnode; import org.apache.ibatis.session.configuration; import org.apache.ibatis.session.resulthandler; import org.apache.ibatis.session.rowbounds; import org.apache.ibatis.type.typehandler; import org.apache.ibatis.type.typehandlerregistry; @intercepts( { @signature(type = statementhandler.class, method = "prepare", args = { connection.class }) }) public class pageplugin implements interceptor { private static string dialect = ""; private static string pagesqlid = ""; @suppresswarnings("unchecked") public object intercept(invocation ivk) throws throwable { if (ivk.gettarget() instanceof routingstatementhandler) { routingstatementhandler statementhandler = (routingstatementhandler) ivk .gettarget(); basestatementhandler delegate = (basestatementhandler) reflecthelper .getvaluebyfieldname(statementhandler, "delegate"); mappedstatement mappedstatement = (mappedstatement) reflecthelper .getvaluebyfieldname(delegate, "mappedstatement"); if (mappedstatement.getid().matches(pagesqlid)) { boundsql boundsql = delegate.getboundsql(); object parameterobject = boundsql.getparameterobject(); if (parameterobject == null) { throw new nullh3erexception("parameterobject error"); } else { connection connection = (connection) ivk.getargs()[0]; string sql = boundsql.getsql(); string countsql = "select count(0) from (" + sql + ") mycount"; system.out.println("总数sql 语句:" + countsql); preparedstatement countstmt = connection .preparestatement(countsql); boundsql countbs = new boundsql(mappedstatement .getconfiguration(), countsql, boundsql .getparametermappings(), parameterobject); setparameters(countstmt, mappedstatement, countbs, parameterobject); resultset rs = countstmt.executequery(); int count = 0; if (rs.next()) { count = rs.getint(1); } rs.close(); countstmt.close(); page page = null; if (parameterobject instanceof page) { page = (page) parameterobject; page.settotalresult(count); } else if (parameterobject instanceof map) { map<string, object> map = (map<string, object>) parameterobject; page = (page) map.get("page"); if (page == null) page = new page(); page.settotalresult(count); } else { field pagefield = reflecthelper.getfieldbyfieldname( parameterobject, "page"); if (pagefield != null) { page = (page) reflecthelper.getvaluebyfieldname( parameterobject, "page"); if (page == null) page = new page(); page.settotalresult(count); reflecthelper.setvaluebyfieldname(parameterobject, "page", page); } else { throw new nosuchfieldexception(parameterobject .getclass().getname()); } } string pagesql = generatepagesql(sql, page); system.out.println("page sql:" + pagesql); reflecthelper.setvaluebyfieldname(boundsql, "sql", pagesql); } } } return ivk.proceed(); } private void setparameters(preparedstatement ps, mappedstatement mappedstatement, boundsql boundsql, object parameterobject) throws sqlexception { errorcontext.instance().activity("setting parameters").object( mappedstatement.getparametermap().getid()); list<parametermapping> parametermappings = boundsql .getparametermappings(); if (parametermappings != null) { configuration configuration = mappedstatement.getconfiguration(); typehandlerregistry typehandlerregistry = configuration .gettypehandlerregistry(); metaobject metaobject = parameterobject == null ? null : configuration.newmetaobject(parameterobject); for (int i = 0; i < parametermappings.size(); i++) { parametermapping parametermapping = parametermappings.get(i); if (parametermapping.getmode() != parametermode.out) { object value; string propertyname = parametermapping.getproperty(); propertytokenizer prop = new propertytokenizer(propertyname); if (parameterobject == null) { value = null; } else if (typehandlerregistry .hastypehandler(parameterobject.getclass())) { value = parameterobject; } else if (boundsql.hasadditionalparameter(propertyname)) { value = boundsql.getadditionalparameter(propertyname); } else if (propertyname .startswith(foreachsqlnode.item_prefix) && boundsql.hasadditionalparameter(prop.getname())) { value = boundsql.getadditionalparameter(prop.getname()); if (value != null) { value = configuration.newmetaobject(value) .getvalue( propertyname.substring(prop .getname().length())); } } else { value = metaobject == null ? null : metaobject .getvalue(propertyname); } typehandler typehandler = parametermapping.gettypehandler(); if (typehandler == null) { throw new executorexception( "there was no typehandler found for parameter " + propertyname + " of statement " + mappedstatement.getid()); } typehandler.setparameter(ps, i + 1, value, parametermapping .getjdbctype()); } } } } private string generatepagesql(string sql, page page) { if (page != null && (dialect != null || !dialect.equals(""))) { stringbuffer pagesql = new stringbuffer(); if ("mysql".equals(dialect)) { pagesql.append(sql); pagesql.append(" limit " + page.getcurrentresult() + "," + page.getshowcount()); } else if ("oracle".equals(dialect)) { pagesql .append("select * from (select tmp_tb.*,rownum row_id from ("); pagesql.append(sql); pagesql.append(") tmp_tb where rownum<="); pagesql.append(page.getcurrentresult() + page.getshowcount()); pagesql.append(") where row_id>"); pagesql.append(page.getcurrentresult()); } return pagesql.tostring(); } else { return sql; } } public object plugin(object arg0) { // todo auto-generated method stub return plugin.wrap(arg0, this); } public void setproperties(properties p) { dialect = p.getproperty("dialect"); if (dialect == null || dialect.equals("")) { try { throw new propertyexception("dialect property is not found!"); } catch (propertyexception e) { // todo auto-generated catch block e.printstacktrace(); } } pagesqlid = p.getproperty("pagesqlid"); if (dialect == null || dialect.equals("")) { try { throw new propertyexception("pagesqlid property is not found!"); } catch (propertyexception e) { // todo auto-generated catch block e.printstacktrace(); } } } }
接下来部署 mybatis08-paging 这个工程,启动 tomcat ,打开浏览器输入网址:http://localhost:8080/mybatis08-paging/user/orderpages ,显示结果如下:
工程 mybatis08-paging 的代码下载:http://pan.baidu.com/s/1pjxhvnt
jar 包下载:http://pan.baidu.com/s/1bnyrj9h