1. 程式人生 > >mybatis框架的兩種分頁

mybatis框架的兩種分頁

mybatis有兩種分頁方法

1、記憶體分頁,也就是假分頁。本質是查出所有的資料然後根據遊標的方式,擷取需要的記錄。如果資料量大,開銷大和記憶體溢位。

使用方式:

利用自動生成的example類,加入mybatis的RowBounds類,在呼叫的介面中新增給類的引數

	@Override
	public List<UserVo> selectSelective(UserVo userVo) {
		
		List<UserVo> listVo = new ArrayList<UserVo>();
		UserPoExample example = new UserPoExample();
		/**
		 * 使用mybatis的RowBounds類,該類構造方法中要設定兩個int型別引數
		 * 第一個是從該條記錄開始,第二個是開始後查詢的條數
		 */
		<strong>RowBounds rowBounds = new RowBounds(0, 5);</strong>
		Criteria criteria = example.createCriteria();
		criteria.andUsernameEqualTo("123");
		criteria.andRoleEqualTo(userVo.getRole());
		example.setOrderByClause("userId desc");//設定排序方式
		example.setStart(10);
		example.setLimit(10);
		UserPo userPo = new UserPo();
		userPo.setUsername("123");
		userPo.setRole(1);
		Page<UserVo> page = new Page<UserVo>();
		
		List<UserPo> listPo =null;
		try {
			//int countByExample = userPoMapper.countByExample(example);//按照條件查詢總數
			//listPo = userPoMapper.selectBySelective(userPo,page);
			listPo = userPoMapper.selectByExample(example,<strong>rowBounds</strong>);
			
			for(UserPo po:listPo){
				UserVo vo = new UserVo();
				BeanUtils.copyProperties(po, vo);
				listVo.add(vo);
			}
		} catch (Exception e) {
			logger.error(e);
		}
		
		return listVo;
	}

第二中是,真正的物理分頁

在自動生成的example物件中,加入兩個成員變數start、和limit

public class UserPoExample {
	<strong>private int start;//設定分頁開始
	
	private int limit;//設定分頁的每頁的數量</strong>
	
    public int getStart() {
		return start;
	}

	public void setStart(int start) {
		this.start = start;
	}

	public int getLimit() {
		return limit;
	}

	public void setLimit(int limit) {
		this.limit = limit;
	}

最後在對應的xml的方法中新增剛剛加入的條件,直接新增在自動生成的orderByClause後面
 <if test="orderByClause != null" >
      order by ${orderByClause}
    </if>
    
    <strong><if test="start !=0 or limit!=0">  
      limit #{start},#{limit}</if><span style="font-family: Arial, Helvetica, sans-serif;">  </span></strong>

通過日誌可以看到是真正的分頁查詢

還有一種是使用分頁攔截器實現的

首先在spring-dao的sqlsession工廠裡面配置攔截器

<!-- sqlSessionFactory -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 資料庫連線池 -->
		<property name="dataSource" ref="dataSource" />
		
		<!-- 批量掃描別名 -->
		<property name="typeAliasesPackage" value="ssm.po" />  
		
		<!-- spring與mybatis整合不需要mybatis配置檔案了,直接掃描mapper下的對映檔案 -->
		<property name="mapperLocations" value="classpath:ssm/mapper/*.xml" />
		
		<!-- MybatisSpringPageInterceptor分頁攔截器 -->
		<property name="plugins">  
        	<bean class="ssm.utils.MybatisSpringPageInterceptor"></bean>
    	</property>
    	 
	</bean>

攔截器程式碼:
package ssm.utils;

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 org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.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.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }),
	@Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) })
public class MybatisSpringPageInterceptor implements Interceptor {
	private static final Logger log = LoggerFactory.getLogger(MybatisSpringPageInterceptor.class);

	public static final String MYSQL = "mysql";
	public static final String ORACLE = "oracle";

	protected String databaseType;// 資料庫型別,不同的資料庫有不同的分頁方法

	@SuppressWarnings("rawtypes")
	protected ThreadLocal<Page> pageThreadLocal = new ThreadLocal<Page>();

	public String getDatabaseType() {
		return databaseType;
	}

	public void setDatabaseType(String databaseType) {
		if (!databaseType.equalsIgnoreCase(MYSQL) && !databaseType.equalsIgnoreCase(ORACLE)) {
			throw new PageNotSupportException("Page not support for the type of database, database type [" + databaseType + "]");
		}
		this.databaseType = databaseType;
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		String databaseType = properties.getProperty("databaseType");
		if (databaseType != null) {
			setDatabaseType(databaseType);
		}
	}

	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object intercept(Invocation invocation) throws Throwable {
		if (invocation.getTarget() instanceof StatementHandler) { // 控制SQL和查詢總數的地方
			Page page = pageThreadLocal.get();
			if (page == null) { //不是分頁查詢
				return invocation.proceed();
			}

			RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
			StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
			BoundSql boundSql = delegate.getBoundSql();
			
			Connection connection = (Connection) invocation.getArgs()[0];
			prepareAndCheckDatabaseType(connection); // 準備資料庫型別

			if (page.getTotalPage() > -1) {
				if (log.isTraceEnabled()) {
					log.trace("已經設定了總頁數, 不需要再查詢總數.");
				}
			} else {
				Object parameterObj = boundSql.getParameterObject();
				MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
				queryTotalRecord(page, parameterObj, mappedStatement, connection);
			}

			String sql = boundSql.getSql();
			String pageSql = buildPageSql(page, sql);
			if (log.isDebugEnabled()) {
				log.debug("分頁時, 生成分頁pageSql: " + pageSql);
			}
			ReflectUtil.setFieldValue(boundSql, "sql", pageSql);

			return invocation.proceed();
		} else { // 查詢結果的地方
			// 獲取是否有分頁Page物件
			Page<?> page = findPageObject(invocation.getArgs()[1]);
			if (page == null) {
				if (log.isTraceEnabled()) {
					log.trace("沒有Page物件作為引數, 不是分頁查詢.");
				}
				return invocation.proceed();
			} else {
				if (log.isTraceEnabled()) {
					log.trace("檢測到分頁Page物件, 使用分頁查詢.");
				}
			}
			//設定真正的parameterObj
			invocation.getArgs()[1] = extractRealParameterObject(invocation.getArgs()[1]);

			pageThreadLocal.set(page);
			try {
				Object resultObj = invocation.proceed(); // Executor.query(..)
				if (resultObj instanceof List) {
					/* @SuppressWarnings({ "unchecked", "rawtypes" }) */
					page.setResults((List) resultObj);
				}
				return resultObj;
			} finally {
				pageThreadLocal.remove();
			}
		}
	}

	protected Page<?> findPageObject(Object parameterObj) {
		if (parameterObj instanceof Page<?>) {
			return (Page<?>) parameterObj;
		} else if (parameterObj instanceof Map) {
			for (Object val : ((Map<?, ?>) parameterObj).values()) {
				if (val instanceof Page<?>) {
					return (Page<?>) val;
				}
			}
		}
		return null;
	}

	/**
	 * <pre>
	 * 把真正的引數物件解析出來
	 * Spring會自動封裝對個引數物件為Map<String, Object>物件
	 * 對於通過@Param指定key值引數我們不做處理,因為XML檔案需要該KEY值
	 * 而對於沒有@Param指定時,Spring會使用0,1作為主鍵
	 * 對於沒有@Param指定名稱的引數,一般XML檔案會直接對真正的引數物件解析,
	 * 此時解析出真正的引數作為根物件
	 * </pre>
	 * @param parameterObj
	 * @return
	 */
	protected Object extractRealParameterObject(Object parameterObj) {
		if (parameterObj instanceof Map<?, ?>) {
			Map<?, ?> parameterMap = (Map<?, ?>) parameterObj;
			if (parameterMap.size() == 2) {
				boolean springMapWithNoParamName = true;
				for (Object key : parameterMap.keySet()) {
					if (!(key instanceof String)) {
						springMapWithNoParamName = false;
						break;
					}
					String keyStr = (String) key;
					if (!"0".equals(keyStr) && !"1".equals(keyStr)) {
						springMapWithNoParamName = false;
						break;
					}
				}
				if (springMapWithNoParamName) {
					for (Object value : parameterMap.values()) {
						if (!(value instanceof Page<?>)) {
							return value;
						}
					}
				}
			}
		}
		return parameterObj;
	}

	protected void prepareAndCheckDatabaseType(Connection connection) throws SQLException {
		if (databaseType == null) {
			String productName = connection.getMetaData().getDatabaseProductName();
			if (log.isTraceEnabled()) {
				log.trace("Database productName: " + productName);
			}
			productName = productName.toLowerCase();
			if (productName.indexOf(MYSQL) != -1) {
				databaseType = MYSQL;
			} else if (productName.indexOf(ORACLE) != -1) {
				databaseType = ORACLE;
			} else {
				throw new PageNotSupportException("Page not support for the type of database, database product name [" + productName + "]");
			}
			if (log.isInfoEnabled()) {
				log.info("自動檢測到的資料庫型別為: " + databaseType);
			}
		}
	}

	/**
	 * <pre>
	 * 生成分頁SQL
	 * </pre>
	 * 
	 * @param page
	 * @param sql
	 * @return
	 */
	protected String buildPageSql(Page<?> page, String sql) {
		if (MYSQL.equalsIgnoreCase(databaseType)) {
			return buildMysqlPageSql(page, sql);
		} else if (ORACLE.equalsIgnoreCase(databaseType)) {
			return buildOraclePageSql(page, sql);
		}
		return sql;
	}

	/**
	 * <pre>
	 * 生成Mysql分頁查詢SQL
	 * </pre>
	 * 
	 * @param page
	 * @param sql
	 * @return
	 */
	protected String buildMysqlPageSql(Page<?> page, String sql) {
		// 計算第一條記錄的位置,Mysql中記錄的位置是從0開始的。
		int offset = (page.getPageNo() - 1) * page.getPageSize();
		return new StringBuilder(sql).append(" limit ").append(offset).append(",").append(page.getPageSize()).toString();
	}

	/**
	 * <pre>
	 * 生成Oracle分頁查詢SQL
	 * </pre>
	 * 
	 * @param page
	 * @param sql
	 * @return
	 */
	protected String buildOraclePageSql(Page<?> page, String sql) {
		// 計算第一條記錄的位置,Oracle分頁是通過rownum進行的,而rownum是從1開始的
		int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
		StringBuilder sb = new StringBuilder(sql);
		sb.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());
		sb.insert(0, "select * from (").append(") where r >= ").append(offset);
		return sb.toString();
	}

	/**
	 * <pre>
	 * 查詢總數
	 * </pre>
	 * 
	 * @param page
	 * @param parameterObject
	 * @param mappedStatement
	 * @param connection
	 * @throws SQLException
	 */
	protected void queryTotalRecord(Page<?> page, Object parameterObject, MappedStatement mappedStatement, Connection connection) throws SQLException {
		BoundSql boundSql = mappedStatement.getBoundSql(page);
		String sql = boundSql.getSql();
		String countSql = this.buildCountSql(sql);
		if (log.isDebugEnabled()) {
			log.debug("分頁時, 生成countSql: " + countSql);
		}

		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, parameterObject);
		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBoundSql);
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = connection.prepareStatement(countSql);
			parameterHandler.setParameters(pstmt);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				long totalRecord = rs.getLong(1);
				page.setTotalRecord(totalRecord);
			}
		} finally {
			if (rs != null)
				try {
					rs.close();
				} catch (Exception e) {
					if (log.isWarnEnabled()) {
						log.warn("關閉ResultSet時異常.", e);
					}
				}
			if (pstmt != null)
				try {
					pstmt.close();
				} catch (Exception e) {
					if (log.isWarnEnabled()) {
						log.warn("關閉PreparedStatement時異常.", e);
					}
				}
		}
	}

	/**
	 * 根據原Sql語句獲取對應的查詢總記錄數的Sql語句
	 * 
	 * @param sql
	 * @return
	 */
	protected String buildCountSql(String sql) {
		int index = sql.indexOf("from");
		return "select count(*) " + sql.substring(index);
	}

	/**
	 * 利用反射進行操作的一個工具類
	 * 
	 */
	private static class ReflectUtil {
		/**
		 * 利用反射獲取指定物件的指定屬性
		 * 
		 * @param obj 目標物件
		 * @param fieldName 目標屬性
		 * @return 目標屬性的值
		 */
		public static Object getFieldValue(Object obj, String fieldName) {
			Object result = null;
			Field field = ReflectUtil.getField(obj, fieldName);
			if (field != null) {
				field.setAccessible(true);
				try {
					result = field.get(obj);
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			return result;
		}

		/**
		 * 利用反射獲取指定物件裡面的指定屬性
		 * 
		 * @param obj 目標物件
		 * @param fieldName 目標屬性
		 * @return 目標欄位
		 */
		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) {
					// 這裡不用做處理,子類沒有該欄位可能對應的父類有,都沒有就返回null。
				}
			}
			return field;
		}

		/**
		 * 利用反射設定指定物件的指定屬性為指定的值
		 * 
		 * @param obj 目標物件
		 * @param fieldName 目標屬性
		 * @param fieldValue 目標值
		 */
		public static void setFieldValue(Object obj, String fieldName, String fieldValue) {
			Field field = ReflectUtil.getField(obj, fieldName);
			if (field != null) {
				try {
					field.setAccessible(true);
					field.set(obj, fieldValue);
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public static class PageNotSupportException extends RuntimeException {

		/** serialVersionUID*/
		private static final long serialVersionUID = 1L;

		public PageNotSupportException() {
			super();
		}

		public PageNotSupportException(String message, Throwable cause) {
			super(message, cause);
		}

		public PageNotSupportException(String message) {
			super(message);
		}

		public PageNotSupportException(Throwable cause) {
			super(cause);
		}
	}

}

分頁的Page物件:
package ssm.utils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Page<T> {

	public static final int DEFAULT_PAGE_SIZE = 10;

	protected int pageNo = 1; // 當前頁, 預設為第1頁
	protected int pageSize = DEFAULT_PAGE_SIZE; // 每頁記錄數
	protected long totalRecord = -1; // 總記錄數, 預設為-1, 表示需要查詢
	protected int totalPage = -1; // 總頁數, 預設為-1, 表示需要計算

	protected List<T> results; // 當前頁記錄List形式
	
	public Map<String, Object> params = new HashMap<String, Object>();//設定頁面傳遞的查詢引數

	public Map<String, Object> getParams() {
		return params;
	}

	public void setParams(Map<String, Object> params) {
		this.params = params;
	}

	public int getPageNo() {
		return pageNo;
	}

	public void setPageNo(int pageNo) {
		this.pageNo = pageNo;
	}

	public int getPageSize() {
		return pageSize;
	}

	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
		computeTotalPage();
	}

	public long getTotalRecord() {
		return totalRecord;
	}

	public int getTotalPage() {
		return totalPage;
	}

	public void setTotalRecord(long totalRecord) {
		this.totalRecord = totalRecord;
		computeTotalPage();
	}

	protected void computeTotalPage() {
		if (getPageSize() > 0 && getTotalRecord() > -1) {
			this.totalPage = (int) (getTotalRecord() % getPageSize() == 0 ? getTotalRecord() / getPageSize() : getTotalRecord() / getPageSize() + 1);
		}
	}

	public List<T> getResults() {
		return results;
	}

	public void setResults(List<T> results) {
		this.results = results;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder().append("Page [pageNo=").append(pageNo).append(", pageSize=").append(pageSize)
				.append(", totalRecord=").append(totalRecord < 0 ? "null" : totalRecord).append(", totalPage=")
				.append(totalPage < 0 ? "null" : totalPage).append(", curPageObjects=").append(results == null ? "null" : results.size()).append("]");
		return builder.toString();
	}

}

ServiceImpl呼叫過程:最後查詢出來的分頁的資訊
@Override
	public List<UserVo> selectSelective(UserVo userVo) {
		
		List<UserVo> listVo = new ArrayList<UserVo>();
//		UserPoExample example = new UserPoExample();
//		Criteria criteria = example.createCriteria();
//		criteria.andUsernameEqualTo(userVo.getUsername());
//		criteria.andRoleEqualTo(userVo.getRole());
//		example.setOrderByClause("userId desc");//設定排序方式
//		example.setStart(0);
//		example.setLimit(10);
		Page<UserVo> page = new Page<UserVo>();
		
		List<UserPo> listPo =null;
		try {
//			UserPo po1 = new UserPo();
//			po1.setUsername(userVo.getUsername());
//			po1.setRole(userVo.getRole());
			Map<String, Object> params = new HashMap<String, Object>();
			params.put("username", userVo.getUsername());
			params.put("role", userVo.getRole());
			params.put("orderByClause","userId desc");
			page.setParams(params);
			
			listPo = userPoMapper.selectBySelective(page);
			
			for(UserPo po:listPo){
				UserVo vo = new UserVo();
				BeanUtils.copyProperties(po, vo);
				listVo.add(vo);
			}
			page.setResults(listVo);
		} catch (Exception e) {
			logger.error(e);
		}
		
		return listVo;
	}

對應的xml
<select id="selectBySelective" parameterType="ssm.utils.Page" resultType="ssm.po.UserPo">
    select * from tb_user
    <where>
        <if test="params.username != null and params.username !=''">
            userName = #{params.username}
            <!-- userName like '%${usesrname}%' -->
        </if>
        <if test="params.password != null and params.password !=''">
            and password = #{params.password}
        </if>
        <if test="params.sex != null and params.sex !=''">
            and sex = #{params.sex}
        </if>
        <if test="params.age != null and params.age !=''">
            and age = #{params.age}
        </if>
        <if test="params.email != null and params.email !=''">
            and email = #{params.email}
        </if>
        <if test="params.courseid != null and params.courseid !=''">
            and courseId = #{params.courseid}
        </if>
        <if test="params.role != null and params.role !=''">
            and role = #{params.role}
        </if>
        <if test="params.state != null and params.state !=''">
            and state = #{params.state}
        </if>
    </where>
        <if test="params.orderByClause != null and params.orderByClause !=''"></if>
        	order by #{params.orderByClause}
      
  </select>