Mybatis攔截器實(shí)現(xiàn)分頁(yè)
最終dao層結(jié)果:
public interface ModelMapper {
Page<Model> pageByConditions(RowBounds rowBounds, Model record);
}
接下來(lái)一步一步來(lái)實(shí)現(xiàn)分頁(yè)。
一.創(chuàng)建Page對(duì)象:
public class Page<T> extends PageList<T> {
private int pageNo = 1;// 頁(yè)碼,默認(rèn)是第一頁(yè)
private int pageSize = 15;// 每頁(yè)顯示的記錄數(shù),默認(rèn)是15
private int totalRecord;// 總記錄數(shù)
private int totalPage;// 總頁(yè)數(shù)
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;
// 在設(shè)置總頁(yè)數(shù)的時(shí)候計(jì)算出對(duì)應(yīng)的總頁(yè)數(shù),在下面的三目運(yùn)算中加法擁有更高的優(yōu)先級(jí),所以最后可以不加括號(hào)。
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();
}
}
可以發(fā)現(xiàn),這里繼承了一個(gè)PageList類(lèi);這個(gè)類(lèi)也是自己創(chuàng)建的一個(gè)類(lèi),實(shí)現(xiàn)List接口。為什么要PageList這個(gè)類(lèi),是因?yàn)镻age需要實(shí)現(xiàn)List接口,而接口中的抽象方法,需要逐一實(shí)現(xiàn),所以提供PageList在統(tǒng)一的地方寫(xiě)實(shí)現(xiàn)List接口的方法。
為什么Page需要實(shí)現(xiàn)List接口,這個(gè)會(huì)在稍后的代碼中做解釋。
PageList類(lèi):
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的寫(xiě)法:
Page<Model> pageByConditions(RowBounds rowBounds, Model record);
mapper.xml:
<!-- 表名 -->
<sql id="tableName" >
model
</sql>
<!-- 數(shù)據(jù)表所有列名 -->
<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>
<!-- 分頁(yè)查詢語(yǔ)句 -->
<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的基本操作,就不做多余解釋。
三.創(chuàng)建攔截器:
我們需要做的是創(chuàng)建一個(gè)攔截器(PageInterceptor)、一個(gè)執(zhí)行者(PageExecutor)。
1.PageInteceptor:實(shí)現(xiàn)Inteceptor接口,將PageExecutor進(jìn)行執(zhí)行,攔截sql添加分頁(yè)sql(limit xx,xx)
2.PageExecutor:實(shí)現(xiàn)Executor接口,在查詢時(shí),添加查詢總數(shù)并修改返回值類(lèi)型。因?yàn)橐龅氖欠猪?yè),是查詢操作,所以里邊的非查詢方法都使用基本的實(shí)現(xiàn),只修改兩個(gè)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.*$"; // 需要進(jìn)行分頁(yè)操作的字符串正則表達(dá)式
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();
}
// 分離代理對(duì)象鏈(由于目標(biāo)類(lèi)可能被多個(gè)攔截器攔截,從而形成多次代理,通過(guò)下面的兩次循環(huán)可以分離出最原始的的目標(biāo)類(lèi))
while (metaStatementHandler.hasGetter("h")) {
Object object = metaStatementHandler.getValue("h");
metaStatementHandler = MetaObject.forObject(object,
DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
}
// 分離最后一個(gè)代理對(duì)象的目標(biāo)類(lèi)
while (metaStatementHandler.hasGetter("target")) {
Object object = metaStatementHandler.getValue("target");
metaStatementHandler = MetaObject.forObject(object,
DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
}
// 將mybatis的內(nèi)存分頁(yè),調(diào)整為物理分頁(yè)
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
String sql = boundSql.getSql();
// 重寫(xiě)sql
String pageSql = sql + " LIMIT " + rowBounds.getOffset() + "," + rowBounds.getLimit();
metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
// 采用物理分頁(yè)后,就不需要mybatis的內(nèi)存分頁(yè)了,所以重置下面的兩個(gè)參數(shù)
metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
// 將執(zhí)行權(quán)交給下一個(gè)攔截器
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);
}
/**
* 修改返回值類(lèi)型
* @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();
// 如果需要分頁(yè)查詢,修改返回類(lèi)型為Page對(duì)象
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;
}
/**
* 獲取總數(shù)
* @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語(yǔ)句中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();
}
}
關(guān)于Page需要實(shí)現(xiàn)List接口的原因:可以看到,query方法返回值是List<E>,而我們現(xiàn)在要在dao中使用Page<E>對(duì)象來(lái)接收mybatis返回的結(jié)果,所以需要讓Page實(shí)現(xiàn)List接口。
分頁(yè)查詢執(zhí)行順序:進(jìn)入PageInterceptor的plugin方法,攔截到執(zhí)行者,進(jìn)入PageExecutor的query方法,執(zhí)行executor.query()時(shí),又再次回到PageInterceptor的plugin方法,這次會(huì)執(zhí)行

進(jìn)入intercept方法,將執(zhí)行的sql拼接上分頁(yè)限制語(yǔ)句,然后查詢出數(shù)據(jù)結(jié)果集合。executor.query()執(zhí)行完成后,繼續(xù)執(zhí)行pageResolver,如果方法名稱和配置的需要執(zhí)行分頁(yè)操作的字符串匹配時(shí),查詢數(shù)據(jù)總量,并返回Page對(duì)象;如果不匹配,直接返回List對(duì)象。
四.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>
五.測(cè)試代碼:
@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);
}
本文主要介紹了Mybatis攔截器實(shí)現(xiàn)分頁(yè)的步驟與方法。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
相關(guān)文章
Spring的IOC解決程序耦合的實(shí)現(xiàn)
本文主要介紹了Spring的IOC解決程序耦合的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07
如何將Object類(lèi)轉(zhuǎn)換為實(shí)體類(lèi)
這篇文章主要介紹了如何將Object類(lèi)轉(zhuǎn)換為實(shí)體類(lèi),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
關(guān)于SpringCloud?Ribbon替換輪詢算法問(wèn)題
Spring?Cloud?Ribbon是基于Netlix?Ribbon實(shí)現(xiàn)的一套客戶端負(fù)載均衡的工具。接下來(lái)通過(guò)本文給大家介紹SpringCloud?Ribbon替換輪詢算法問(wèn)題,需要的朋友可以參考下2022-01-01
Java中String的intern()方法詳細(xì)說(shuō)明
這篇文章主要介紹了Java中String的intern()方法詳細(xì)說(shuō)明,String::intern()是一個(gè)本地方法,他的作用就是如果字符串常量池中已經(jīng)包含了一個(gè)等于此String對(duì)象的字符串,則返回代表池中的這個(gè)字符串額String對(duì)象的引用,需要的朋友可以參考下2023-11-11
SpringBoot整合TKMyBatis實(shí)現(xiàn)單表增刪改查操作
據(jù)說(shuō)tk.mybatis能夠讓我不寫(xiě)sql代碼就可以所有單表操作問(wèn)題,作為熱愛(ài)偷懶的我,怎么能放過(guò)這種機(jī)會(huì)。talk is cheap, show me the code。趕緊搞個(gè)例子爽一把先2023-01-01
Java超詳細(xì)講解WebMvcConfigurer攔截器
這篇文章將用實(shí)例來(lái)和大家介紹一下WebMvcConfigurer攔截器。文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下2022-06-06
Java 8 Stream Api 中的 map和 flatMap 操作方法
Java 8提供了非常好用的 Stream API ,可以很方便的操作集合。今天通過(guò)這篇文章給大家分享Java 8 Stream Api 中的 map和 flatMap 操作方法,需要的朋友可以參考下2019-11-11
如何將char類(lèi)型的數(shù)字字符轉(zhuǎn)換成int類(lèi)型問(wèn)題
這篇文章主要介紹了如何將char類(lèi)型的數(shù)字字符轉(zhuǎn)換成int類(lèi)型問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
SpringSecurity實(shí)現(xiàn)動(dòng)態(tài)url攔截(基于rbac模型)
本文主要介紹了SpringSecurity動(dòng)態(tài)url攔截,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08

