Mybatis攔截器實現分頁

来源:http://www.cnblogs.com/taocong/archive/2017/01/24/6346663.html
-Advertisement-
Play Games

本文介紹使用Mybatis攔截器,實現分頁;並且在dao層,直接返回自定義的分頁對象。 最終dao層結果: 接下來一步一步來實現分頁。 一.創建Page對象: 可以發現,這裡繼承了一個PageList類;這個類也是自己創建的一個類,實現List介面。為什麼要PageList這個類,是因為Page需要 ...


本文介紹使用Mybatis攔截器,實現分頁;並且在dao層,直接返回自定義的分頁對象。

最終dao層結果:

public interface ModelMapper {
	Page<Model> pageByConditions(RowBounds rowBounds, Model record);      
}

接下來一步一步來實現分頁。

一.創建Page對象:

public class Page<T> extends PageList<T>  {

	private int pageNo = 1;// 頁碼,預設是第一頁
	private int pageSize = 15;// 每頁顯示的記錄數,預設是15
	private int totalRecord;// 總記錄數
	private int totalPage;// 總頁數
	
	public Page() {
		
	}
	
	public Page(int pageNo, int pageSize, int totalRecord,
			List<T> results) {
		this.pageNo = pageNo;
		this.pageSize = pageSize;
		this.totalRecord = totalRecord;
		this.setResult(results);
		int totalPage = totalRecord % pageSize == 0 ? totalRecord / pageSize : totalRecord / pageSize + 1;
		this.setTotalPage(totalPage);
	}

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

	public int getTotalRecord() {
		return totalRecord;
	}

	public void setTotalRecord(int totalRecord) {
		this.totalRecord = totalRecord;
		// 在設置總頁數的時候計算出對應的總頁數,在下麵的三目運算中加法擁有更高的優先順序,所以最後可以不加括弧。
		int totalPage = totalRecord % pageSize == 0 ? totalRecord / pageSize : totalRecord / pageSize + 1;
		this.setTotalPage(totalPage);
	}

	public int getTotalPage() {
		return totalPage;
	}

	public void setTotalPage(int totalPage) {
		this.totalPage = totalPage;
	}


	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("Page [pageNo=").append(pageNo).append(", pageSize=").append(pageSize).append(", results=")
				.append(getResult()).append(", totalPage=").append(totalPage).append(", totalRecord=").append(totalRecord)
				.append("]");
		return builder.toString();
	}
}

  可以發現,這裡繼承了一個PageList類;這個類也是自己創建的一個類,實現List介面。為什麼要PageList這個類,是因為Page需要實現List介面,而介面中的抽象方法,需要逐一實現,所以提供PageList在統一的地方寫實現List介面的方法。

  為什麼Page需要實現List介面,這個會在稍後的代碼中做解釋。

PageList類:

public class PageList<T> implements List<T> {

	private List<T> result;
	
	public List<T> getResult() {
		return result;
	}

	public void setResult(List<T> result) {
		this.result = result;
	}

	@Override
	public int size() {
		return result.size();
	}

	@Override
	public boolean isEmpty() {
		return result.isEmpty();
	}

	@Override
	public boolean contains(Object o) {
		return result.contains(o);
	}

	@Override
	public Iterator<T> iterator() {
		return result.iterator();
	}

	@Override
	public Object[] toArray() {
		return result.toArray();
	}

	@Override
	public <E> E[] toArray(E[] a) {
		return result.toArray(a);
	}

	@Override
	public boolean add(T e) {
		return result.add(e);
	}

	@Override
	public boolean remove(Object o) {
		return result.remove(o);
	}

	@Override
	public boolean containsAll(Collection<?> c) {
		return result.containsAll(c);
	}

	@Override
	public boolean addAll(Collection<? extends T> c) {
		return result.addAll(c);
	}

	@Override
	public boolean addAll(int index, Collection<? extends T> c) {
		return result.addAll(index, c);
	}

	@Override
	public boolean removeAll(Collection<?> c) {
		return result.removeAll(c);
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		return result.retainAll(c);
	}

	@Override
	public void clear() {
		result.clear();
	}

	@Override
	public T get(int index) {
		return result.get(index);
	}

	@Override
	public T set(int index, T element) {
		return result.set(index, element);
	}

	@Override
	public void add(int index, T element) {
		result.add(index, element);
	}

	@Override
	public T remove(int index) {
		return result.remove(index);
	}

	@Override
	public int indexOf(Object o) {
		return result.indexOf(o);
	}

	@Override
	public int lastIndexOf(Object o) {
		return result.lastIndexOf(o);
	}

	@Override
	public ListIterator<T> listIterator() {
		return result.listIterator();
	}

	@Override
	public ListIterator<T> listIterator(int index) {
		return result.listIterator(index);
	}

	@Override
	public List<T> subList(int fromIndex, int toIndex) {
		return result.subList(fromIndex, toIndex);
	}
}

二.提供Dao以及mapper.xml

  dao的寫法:

Page<Model> pageByConditions(RowBounds rowBounds, Model record);

  mapper.xml:

  <!-- 表名 -->
  <sql id="tableName" >
    model
  </sql>
  
  <!-- 數據表所有列名 -->
  <sql id="Base_Column_List" >
    id,     
    name    
  </sql>
  
  <!-- 查詢欄位 -->
  <sql id="Base_Search_Param" >
    <if test="id != null" >
          and id = #{id,jdbcType=INTEGER}
    </if>
    <if test="name != null" >
          and name = #{name,jdbcType=VARCHAR}
    </if>
  </sql>
  
  <!-- 分頁查詢語句 -->
  <select id="pageByConditions" resultMap="BaseResultMap">
      SELECT 
          <include refid="Base_Column_List" />
      FROM 
          <include refid="tableName" />
      WHERE 1=1
          <include refid="Base_Search_Param" />
  </select>

  ok,以上都是mybatis的基本操作,就不做多餘解釋。

三.創建攔截器:

  攔截器原理以及執行順序,可參考:http://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html、http://blog.csdn.net/abcd898989/article/details/51261163

  我們需要做的是創建一個攔截器(PageInterceptor)、一個執行者(PageExecutor)。

  1.PageInteceptor:實現Inteceptor介面,將PageExecutor進行執行,攔截sql添加分頁sql(limit xx,xx)

  2.PageExecutor:實現Executor介面,在查詢時,添加查詢總數並修改返回值類型。因為要做的是分頁,是查詢操作,所以裡邊的非查詢方法都使用基本的實現,只修改兩個query方法。

PageInteceptor完整代碼:

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
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.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;


@Intercepts({
        @Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class }),
        @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class PageInterceptor implements Interceptor {
    
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    
    private String pattern = "^.*page.*$";    // 需要進行分頁操作的字元串正則表達式

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getTarget() instanceof StatementHandler) {
            return handleStatementHandler(invocation);
        }
        return invocation.proceed();
    }

    /**
     * @param invocation
     * @return
     * @throws IllegalAccessException 
     * @throws InvocationTargetException 
     */
    private Object handleStatementHandler(Invocation invocation)
            throws InvocationTargetException, IllegalAccessException {
        StatementHandler statementHandler = (StatementHandler) invocation
                .getTarget();
        MetaObject metaStatementHandler = MetaObject.forObject(
                statementHandler, DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY);
        RowBounds rowBounds = (RowBounds) metaStatementHandler
                .getValue("delegate.rowBounds");
        if (rowBounds == null || (rowBounds.getOffset() == RowBounds.NO_ROW_OFFSET && rowBounds
                .getLimit() == RowBounds.NO_ROW_LIMIT)) {
            return invocation.proceed();
        }
        
        // 分離代理對象鏈(由於目標類可能被多個攔截器攔截,從而形成多次代理,通過下麵的兩次迴圈可以分離出最原始的的目標類)
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = MetaObject.forObject(object,
                    DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        }
        // 分離最後一個代理對象的目標類
        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = MetaObject.forObject(object,
                    DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        }

        // 將mybatis的記憶體分頁,調整為物理分頁
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        String sql = boundSql.getSql();
        // 重寫sql
        String pageSql = sql + " LIMIT " + rowBounds.getOffset() + "," + rowBounds.getLimit();
        metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
        // 採用物理分頁後,就不需要mybatis的記憶體分頁了,所以重置下麵的兩個參數
        metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
        metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
        
        // 將執行權交給下一個攔截器
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        if (Executor.class.isAssignableFrom(o.getClass())) {
            PageExecutor executor = new PageExecutor((Executor)o, pattern);
            return Plugin.wrap(executor, this);
        } else if (o instanceof StatementHandler) {
            return Plugin.wrap(o, this);
        }
        return o;
    }

    @Override
    public void setProperties(Properties properties) {
    }

}

PageExecutor完整代碼:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

public class PageExecutor implements Executor {
    
    private Executor executor;
    
    private String pattern;
    
    public PageExecutor(Executor executor, String pattern) {
        this.executor = executor;
        this.pattern = pattern;
    }

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        return executor.update(ms, parameter);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
            CacheKey cacheKey, BoundSql boundSql) throws SQLException {
        RowBounds rb = new RowBounds(rowBounds.getOffset(), rowBounds.getLimit());
        List<E> rows = executor.query(ms, parameter, rowBounds, resultHandler,
                cacheKey, boundSql);
        return pageResolver(rows, ms, parameter, rb);
    }
    
    /**
     * 修改返回值類型
     * @param rows
     * @param ms
     * @param parameter
     * @param rowBounds
     * @return
     */
    private <E> List<E> pageResolver(List<E> rows, MappedStatement ms,
            Object parameter, RowBounds rowBounds) {
        String msid = ms.getId();
        // 如果需要分頁查詢,修改返回類型為Page對象
        if (msid.matches(pattern)) {
            int count = getCount(ms, parameter);
            int offset = rowBounds.getOffset();
            int pagesize = rowBounds.getLimit();
            return new Page<E>(offset/pagesize + 1, pagesize, count, rows);
        }
        return rows;
    }
    
    /**
     * 獲取總數
     * @param ms
     * @param parameter
     * @return
     */
    private int getCount(MappedStatement ms, Object parameter) {
        BoundSql bsql = ms.getBoundSql(parameter);
        String sql = bsql.getSql();
        String countSql = getCountSql(sql);
        Connection connection = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            connection = ms.getConfiguration().getEnvironment().getDataSource()
                    .getConnection();
            stmt = connection.prepareStatement(countSql);
            rs = stmt.executeQuery();
            if (rs.next())
                return rs.getInt(1);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (connection != null && !connection.isClosed()) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return 0;
    }
    
    private String getCountSql(String sql) {
        String countHql = " SELECT count(*) "
                + removeSelect(removeOrders(sql));

        return countHql;
    }
    
    protected String removeOrders(String sql) {
        Pattern p = Pattern.compile("ORDER\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(sql);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }
    
    // 去除sql語句中select子句
    private static String removeSelect(String hql) {
        int beginPos = hql.toLowerCase().indexOf("from");
        if (beginPos < 0) {
            throw new IllegalArgumentException(" hql : " + hql + " must has a keyword 'from'");
        }
        return hql.substring(beginPos);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
            throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        return query(ms, parameter, rowBounds, resultHandler,
                executor.createCacheKey(ms, parameter, rowBounds, boundSql),
                boundSql);
    }

    @Override
    public List<BatchResult> flushStatements() throws SQLException {
        return executor.flushStatements();
    }

    @Override
    public void commit(boolean required) throws SQLException {
        executor.commit(required);
    }

    @Override
    public void rollback(boolean required) throws SQLException {
        executor.rollback(required);
    }

    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject,
            RowBounds rowBounds, BoundSql boundSql) {
        return executor
                .createCacheKey(ms, parameterObject, rowBounds, boundSql);
    }

    @Override
    public boolean isCached(MappedStatement ms, CacheKey key) {
        return executor.isCached(ms, key);
    }

    @Override
    public void clearLocalCache() {
        executor.clearLocalCache();
    }

    @Override
    public void deferLoad(MappedStatement ms, MetaObject resultObject,
            String property, CacheKey key, Class<?> targetType) {
        executor.deferLoad(ms, resultObject, property, key, targetType);
    }

    @Override
    public Transaction getTransaction() {
        return executor.getTransaction();
    }

    @Override
    public void close(boolean forceRollback) {
        executor.close(forceRollback);
    }

    @Override
    public boolean isClosed() {
        return executor.isClosed();
    }
    
}

  關於Page需要實現List介面的原因:可以看到,query方法返回值是List<E>,而我們現在要在dao中使用Page<E>對象來接收mybatis返回的結果,所以需要讓Page實現List介面。

 

  分頁查詢執行順序:進入PageInterceptor的plugin方法,攔截到執行者,進入PageExecutor的query方法,執行executor.query()時,又再次回到PageInterceptor的plugin方法,這次會執行

  進入intercept方法,將執行的sql拼接上分頁限制語句,然後查詢出數據結果集合。executor.query()執行完成後,繼續執行pageResolver,如果方法名稱和配置的需要執行分頁操作的字元串匹配時,查詢數據總量,並返回Page對象;如果不匹配,直接返回List對象。

四.xml配置:

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:/conf/mybatis/mybaties-config.xml"></property>
        <property name="mapperLocations">
            <list>
                <value>classpath:/conf/mybatis/**/*-mapper.xml</value>
            </list>
        </property>
        <property name="plugins">
            <list>
                <ref bean="pageInterceptor"/>
            </list>        
        </property>
    </bean>
    
     <bean id="pageInterceptor" class="cn.com.common.PageInterceptor">
        <property name="pattern" value="^.*page.*$"></property>
    </bean>

 

五.測試代碼:

    @Test
    public void testPage() {
        int pageNo = 1;
        int pageSize = 10;
        RowBounds bounds = new RowBounds((pageNo - 1) * pageSize, pageSize);
        Model record = new Model();
        
        Page<Model> list = modelMapper.pageByConditions(bounds, record);
    }

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 魔方 是一個基於 ASP.NET MVC 的 用戶許可權管理平臺,可作為各種信息管理系統的基礎框架。 演示:http://cube.newlifex.com 源碼 演示賬號:admin/admin 源碼: https://git.newlifex.com/NewLife/X/Tree/master/N ...
  • 先上圖: 操作方法: 1、先設置一下gridview中屬性:IndicatorWidth,一般為:40。如下圖;(一般可以顯示5位數字。如要更長顯示,自己測試一下。) 2、找到gridview中的:CustomDrawRowIndicator事件,輸入如下麵代碼: 1 private void gv ...
  • 參考頁面: http://www.yuanjiaocheng.net/CSharp/csharp-class.html http://www.yuanjiaocheng.net/CSharp/csharp-variable.html http://www.yuanjiaocheng.net/CSha ...
  • 參考頁面: http://www.yuanjiaocheng.net/webapi/test-webapi.html http://www.yuanjiaocheng.net/webapi/web-api-controller.html http://www.yuanjiaocheng.net/we ...
  • 參考頁面: http://www.yuanjiaocheng.net/entity/Persistence-in-EF.html http://www.yuanjiaocheng.net/entity/crud-in-connected.html http://www.yuanjiaocheng.n ...
  • 參考頁面: http://www.yuanjiaocheng.net/webapi/media-formatter.html http://www.yuanjiaocheng.net/webapi/webapi-filters.html http://www.yuanjiaocheng.net/we ...
  • 參考頁面: http://www.yuanjiaocheng.net/ASPNET-CORE/projectjson.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-configuration.html http://www.yuanjiaoch ...
  • js 中用$('#addUserForm').serialize(),//獲取表單中所有數據 傳送到前臺 (controller) $.ajax({ type : "POST", url : $.el.Register.AppUrl + "path", data :$('#addUserForm') ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...