MyBatis分頁
最早使用普通JDBC使用分頁的時候,都是
SELECT * FROM students LIMIT num1,num2;
意思就是從students這個表中找第num1的記錄開始往下的num2個記錄。
每次分頁都得計算總條數,然後計算頁數等等。後來使用MyBatis後, 該如何進行分頁呢?當然也可以用以上的方法進行傳參,但是每次遇到分頁都得重寫一個,很麻煩。
實作MyBatis的攔截器實作
MyBatis攔截器是基于Java動态代理實作的。動态代理知識可參考http://blog.csdn.net/qq407388356/article/details/79313972。
頁面實體類Page.java
首先寫一個Page的pojo類來封裝分頁的一些資料資訊:
public class Page {
private int totalNumber;//總條數
private int currentPage;//目前第幾頁
private int totalPage;//總頁數
private int pageNumber = 5;//每頁顯示條數
private int dbIndex;//資料庫中limit的參數,從第幾條開始取
private int dbNumber;//資料庫中limit的參數,一共取多少條
/**
* 根據目前對象中屬性值計算并設定相關屬性值
*/
public void count() {
// 計算總頁數
int totalPageTemp = this.totalNumber / this.pageNumber;
int plus = (this.totalNumber % this.pageNumber) == 0 ? 0 : 1;
totalPageTemp = totalPageTemp + plus;
if (totalPageTemp <= 0) {
totalPageTemp = 1;
}
this.totalPage = totalPageTemp;
// 設定目前頁數
// 總頁數小于目前頁數,應将目前頁數設定為總頁數
if (this.totalPage < this.currentPage) {
this.currentPage = this.totalPage;
}
// 目前頁數小于1設定為1
if (this.currentPage < 1) {
this.currentPage = 1;
}
// 設定limit的參數
this.dbIndex = (this.currentPage - 1) * this.pageNumber;
this.dbNumber = this.pageNumber;
}
//設定totalNumber時候需要計算count
public void setTotalNumber(int totalNumber) {
this.totalNumber = totalNumber;
this.count();
}
//設定pageNumber時候需要計算count
public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
this.count();
}
...//其他setter、getter略
}
mapper檔案
在StudentsMapper.java接口中聲明該方法:
List<Students> selectAllByPage(Map<String,Object>parameter);
在StudentsMapper.xml檔案中配置對應id的sql語句,注意parameterType需要傳入page對象的資訊在該map中,其他查詢資訊也可放入該map中。
<select id="selectAllByPage"parameterType="java.util.Map" resultMap="BaseResultMap">
select id, name, age, hobby,classAndGradeId
from students
</select>
分頁攔截器PageInterceptor.java
編寫分頁攔截器,實作org.apache.ibatis.plugin.Interceptor接口。
@Signature(type=StatementHandler.class,method="prepare",args={Connection.class}表示我們要攔截StatementHandler接口下面的prepare的方法,參數是Connection.class。因為該方法正是處理我們送出sql的方法,是以攔截後可以做修改(加入分頁功能)。
根據攔截下來的SQL的id是否ByPage結尾(使用正規表達式比對),這個可以在一個項目中統一規範哪些SQL需要分頁。如果是,則進行SQL語句修改,通過參數中Page對象的資訊進行分頁查詢。
具體代碼如下:
/**
* 分頁攔截器
*/
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
// 配置檔案中SQL語句的ID
String id = mappedStatement.getId();
if(id.matches(".+ByPage$")) {
BoundSql boundSql = statementHandler.getBoundSql();
// 原始的SQL語句
String sql = boundSql.getSql();
// 查詢總條數的SQL語句
String countSql = "select count(*) from (" + sql + ")a";
Connection connection = (Connection)invocation.getArgs()[0];
PreparedStatement countStatement = connection.prepareStatement(countSql);
ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
parameterHandler.setParameters(countStatement);
ResultSet rs = countStatement.executeQuery();
Map<?,?> parameter = (Map<?,?>)boundSql.getParameterObject();
Page page = (Page)parameter.get("page");
if(rs.next()) {
page.setTotalNumber(rs.getInt(1));
}
// 改造後帶分頁查詢的SQL語句
String pageSql = sql + " limit " + page.getDbIndex() + "," + page.getDbNumber();
metaObject.setValue("delegate.boundSql.sql", pageSql);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
添加插件mybatis-config.xml
在mybatis的配置檔案中添加插件配置,也就是我們剛剛編寫的那個實作Interceptor接口的類。
<plugins>
<plugin interceptor="com.seu.fn.interceptor.PageInterceptor">
</plugin>
</plugins>
測試
參數中需要在map中傳入“page”對象,如果需要同時也可以map.put()其他的查詢條件。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/applicationContext-dao.xml"})
public class StudentsMapperTest {
@Autowired
StudentsMapper studentsMapper;
@Test
public void selectAllByPage(){
Map<String,Object> map = new HashMap<>();
Page page = new Page();
page.setCurrentPage(2);
map.put("page",page);
List<Students> list = studentsMapper.selectAllByPage(map);
for (Students s:list){
System.out.println(s);
}
}
}
測試輸出:
Students{id='06', name='name6', age=null,hobby='null', classandgradeid='1'}
Students{id='07', name='name7', age=null,hobby='null', classandgradeid='1'}
Students{id='08', name='name8', age=null,hobby='null', classandgradeid='1'}
Students{id='09', name='name9', age=null,hobby='null', classandgradeid='1'}
Students{id='10', name='name10', age=null,hobby='null', classandgradeid='1'}
使用PageHelper插件實作
PageHelper插件用起來比較友善,不用改自己的代碼。該方法其實也是基于攔截器實作的,隻是用起來更友善,不用自己實作Interceptor接口,隻需要在傳回List<?>的select方法前調用PageHelper.startPage(int,int)方法,即可。
pom.xml依賴:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
在mybatis-config.xml中配置攔截器插件(同上):
<plugins>
<!-- com.github.pagehelper為PageHelper類所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 設定資料庫類型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六種資料庫可以自動識别,不用再配置下面的property-->
<!--<property name="dialect" value="mysql"/>-->
</plugin>
</plugins>
mapper檔案:
在StudentsMapper.java接口中聲明該方法(不需要傳page資訊參數)。
List<Students> selectAll();
在StudentsMapper.xml檔案中配置對應id的sql語句,普通的SQL語句配置。
<select id="selectAll"resultMap="BaseResultMap">
select id, name, age, hobby,classAndGradeId
from students
</select>
如何使用:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/applicationContext-dao.xml"})
public class StudentsMapperTest {
@Autowired
StudentsMapper studentsMapper;
@Test
public void selectAll() {
//擷取第2頁,5條内容,預設查詢總數count
PageHelper.startPage(2, 5);
//緊跟着的第一個select方法會被分頁
List<Students> list = studentsMapper.selectAll();
PageInfo page = new PageInfo(list);
//PageInfo包含了非常全面的分頁屬性
System.out.println(page.getPageNum());//2
System.out.println(page.getPageSize());//5
System.out.println(page.getStartRow());//6
System.out.println(page.getEndRow());//10
System.out.println(page.getTotal());//15
System.out.println(page.getPages());//3
System.out.println(page.isIsFirstPage());//false
System.out.println(page.isIsLastPage());//false
System.out.println(page.isHasPreviousPage());//true
System.out.println(page.isHasNextPage());//true
for (Students s:list){
System.out.println(s);
}
}
}
測試輸出:
Students{id='06', name='name6', age=null,hobby='null', classandgradeid='1'}
Students{id='07', name='name7', age=null,hobby='null', classandgradeid='1'}
Students{id='08', name='name8', age=null,hobby='null', classandgradeid='1'}
Students{id='09', name='name9', age=null,hobby='null', classandgradeid='1'}
Students{id='10', name='name10', age=null,hobby='null', classandgradeid='1'}
和上面結果相同,這種方法是使用第三方插件(實作原理都相同),比較友善,容易上手。