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(); } 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; } }