一.分頁
MyBatis有兩種分頁方法:內存分頁,也就是假分頁,本質是查出所有的數據然后根據游標的方式,截取需要的記錄,如果數據量大,執行效率低,可能造成內存溢出。物理分頁,就是數據庫本身提供了分頁方式,如MySql的limit,執行效率高,不同數據庫實現不同。
MyBatis Generator使用:MyBatis Generator使用示例
Spring集成MyBatis:Spring集成MyBatis持久層框架
二.MyBatis執行流程
MyBatis執行sql流程如下圖,實現數據庫的物理分頁,需要通過攔截StatementHandler重寫的sql語句。

三.分頁實現
1.實現MyBatis的Interceptor接口,創建PageInterceptor類
@Intercepts({@Signature(type=StatementHandler.class, method = "prepare", args={Connection.class, Integer.class})})
public class PageInterceptor implements Interceptor {
private String sqlRegEx = ".*Page";
public Object intercept(Invocation invocation) throws Throwable {
RoutingStatementHandler handler = (RoutingStatementHandler)invocation.getTarget();
StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
BoundSql boundSql = delegate.getBoundSql();
MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");
// 獲取參數
Object parameterObject = boundSql.getParameterObject();
// 判斷是否分頁
if (mappedStatement.getId().matches(sqlRegEx)) {
Page page = (Page) ((Map<?, ?>) parameterObject).get("page");
if (page != null) {
Connection connection = (Connection) invocation.getArgs()[0];
// 獲取mapper映射文件中對應的sql語句
String sql = boundSql.getSql();
// 給當前page參數設置總記錄數
this.setPageParameter(mappedStatement, connection, boundSql, page);
// 獲取分頁sql語句
String pageSql = this.getPageSql(page, sql);
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
}
return invocation.proceed();
}
/**
* 從數據庫里查詢總的記錄數並計算總頁數,回寫進分頁參數page
* @param mappedStatement
* @param connection
* @param boundSql
* @param page
*/
private void setPageParameter(MappedStatement mappedStatement, Connection connection, BoundSql boundSql, Page page) {
// 獲取mapper映射文件中對應的sql語句
String sql = boundSql.getSql();
// 獲取計算總記錄數的sql語句
String countSql = this.getCountSql(sql);
// 獲取BoundSql參數映射
List<ParameterMapping> parameterMappinglist = boundSql.getParameterMappings();
// 構造查詢總量的BoundSql
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappinglist, boundSql.getParameterObject());
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql);
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 通過connection建立countSql對應的PreparedStatement對象
pstmt = connection.prepareStatement(countSql);
parameterHandler.setParameters(pstmt);
// 執行countSql語句
rs = pstmt.executeQuery();
if (rs.next()) {
int totalRecord = rs.getInt(1);
page.setTotalRecord(totalRecord);
page.setTotalPage(totalRecord/page.getPageSize() + (totalRecord % page.getPageSize() == 0? 0: 1));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 根據源sql語句獲取對應的查詢總記錄數的sql語句
* @param sql
* @return
*/
private String getCountSql(String sql) {
int index = sql.indexOf("from");
return "select count(*) " + sql.substring(index);
}
/**
* 獲取MySql數據庫的分頁查詢語句
* @param page
* @param sql
* @return
*/
private String getPageSql(Page<?> page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql);
int offset = (page.getPageNum() - 1) * page.getPageSize();
sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
return sqlBuffer.toString();
}
/**
* 只處理StatementHandler類型
* @param o
* @return
*/
public Object plugin(Object o) {
if (o instanceof StatementHandler) {
return Plugin.wrap(o, this);
} else {
return o;
}
}
/**
* 攔截器屬性設定
* @param properties
*/
public void setProperties(Properties properties) {
}
public String getSqlRegEx() {
return sqlRegEx;
}
public void setSqlRegEx(String sqlRegEx) {
this.sqlRegEx = sqlRegEx;
}
}
2.保存頁面的相關信息,創建Page類
public class Page<T> { private int pageNum = 1; private int pageSize = 5; private int totalRecord; private int totalPage; private List<T> results; public int getPageNum() { return pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public int getTotalRecord() { return totalRecord; } public void setTotalRecord(int totalRecord) { this.totalRecord = totalRecord; } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public List<T> getResults() { return results; } public void setResults(List<T> results) { this.results = results; } }
3.通過反射獲取對象的屬性,創建ReflectUtil工具類,用於獲取RoutingStatementHandler對象的私有屬性delegate
public class ReflectUtil { public static Object getFieldValue(Object obj, String fieldName) { Object result = null; Field field = ReflectUtil.getField(obj, fieldName); if (null != field) { field.setAccessible(true); try { result = field.get(obj); } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } } return result; } private static Field getField(Object obj, String fieldName) { Field field = null; for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) { try { field = clazz.getDeclaredField(fieldName); break; } catch (NoSuchFieldException e) { } } return field; } public static void setFieldValue(Object obj, String fieldName, String fieldValue) { Field field = ReflectUtil.getField(obj, fieldName); if (null != field) { try { field.setAccessible(true); field.set(obj, fieldValue); } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } } } }
4.啟用分頁Interceptor,編輯applicationContext_database.xml
<!-- mybatis配置,mapper.xml文件掃描 --> <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:/mybatis/mybatis.xml"/> <property name="mapperLocations" value="classpath:/mybatis/mapper/*Mapper.xml"/> <property name="dataSource" ref="dataSource"/> <property name="plugins"> <array> <!-- 分頁 --> <bean class="com.learn.spring.server.intercept.PageInterceptor"> <property name="properties"> <value> sqlRegEx = ".*Page" </value> </property> </bean> </array> </property> </bean>
四.調用示例
1.編輯UserServiceImpl類
@Service public class UserServiceImpl implements UserService { @Resource private UserDOMapper userDao; @Override public Page<UserDO> listByCondPage(Integer status, Page page) { Map<String, Object> param = new HashMap<>(); param.put("status", status); param.put("page", page); List<UserDO> userDOList = userDao.selectByCondPage(param); page.setResults(userDOList); return page; } }
2.編輯IndexController類,調用Service
@Controller @RequestMapping("/server") public class IndexController { @Resource private UserService userService; @ResponseBody @RequestMapping("/list") public Object list(Integer status, Integer pageNum, Integer pageSize) { Page<UserDO> userDOPage = new Page<>(); userDOPage.setPageNum(pageNum); userDOPage.setPageSize(pageSize); return userService.listByCondPage(status, userDOPage); } }
