原理:
mybatis提供了攔截器功能,我們可以對(duì)Executor,StatementHandler,ParameterHandler,ResultSetHandler進(jìn)行攔截,也就是代理。
實(shí)現(xiàn):
分頁(yè)需要提供兩個(gè)數(shù)據(jù),一是偏移量,行數(shù)。在mysql數(shù)據(jù)庫(kù)中我們通過(guò)limit offset,rows實(shí)現(xiàn)分頁(yè)。
返回的結(jié)果,我們需要總數(shù),總頁(yè)數(shù),當(dāng)前頁(yè)數(shù),結(jié)果集來(lái)映射我們的前端顯示。
我們通過(guò)攔截Executor來(lái)實(shí)現(xiàn),并且制定攔截的方法是query方法。
- 第一步:區(qū)分出分頁(yè)方法,怎么實(shí)現(xiàn)在不改變?nèi)魏未a的情況下將分頁(yè)區(qū)別出來(lái),首先,我們肯定需要知道哪些方法是分頁(yè),我們通過(guò)定義統(tǒng)一的方法后綴名來(lái)區(qū)分。
- 第二步:查詢(xún)出總數(shù),我們需要在原來(lái)sql的基礎(chǔ)上查詢(xún)出總數(shù)是多少,在這里我們通過(guò)映射對(duì)象,調(diào)用Executor的query方法來(lái)獲得。
- 第三步:改造sql,變成分頁(yè)的sql語(yǔ)句。
- 第四步:執(zhí)行語(yǔ)句。
在這里有些地方并沒(méi)有提供直接的修改方法,所以只能通過(guò)替換的形式,代碼冗余。
原理理解
在這其中有幾個(gè)關(guān)鍵的類(lèi)
- MappedStatement
- BoundSql
- Executor
MappedStatement
這個(gè)就是我們mapper文件中對(duì)應(yīng)的一個(gè)select/update/delete節(jié)點(diǎn)。
public final class MappedStatement {
private String resource;//mapper配置文件名,如:UserMapper.xml
private Configuration configuration;//全局配置
private String id;//節(jié)點(diǎn)的id屬性加命名空間,如:com.lucky.mybatis.dao.UserMapper.selectByExample
private Integer fetchSize;
private Integer timeout;//超時(shí)時(shí)間
private StatementType statementType;//操作SQL的對(duì)象的類(lèi)型
private ResultSetType resultSetType;//結(jié)果類(lèi)型
private SqlSource sqlSource;//sql語(yǔ)句
private Cache cache;//緩存
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;//是否使用緩存,默認(rèn)為true
private boolean resultOrdered;//結(jié)果是否排序
private SqlCommandType sqlCommandType;//sql語(yǔ)句的類(lèi)型,如select、update、delete、insert
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;//數(shù)據(jù)庫(kù)ID
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
通過(guò)mapperStatement我們可以獲得sql,方法名,參數(shù),結(jié)果類(lèi)型,這些都是我們后面需要用到的。
BoundSql
BoundSql保存了sql執(zhí)行sql語(yǔ)句所需要的所有參數(shù),sql語(yǔ)句,傳入的參數(shù)
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
Executor
執(zhí)行器,所有的語(yǔ)句都通過(guò)這個(gè)執(zhí)行器執(zhí)行。我們下面主要的攔截對(duì)象,下面是我們要攔截的方法。
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
思路
有了上面的對(duì)象,我們也可以構(gòu)建自己的查詢(xún),對(duì)于分頁(yè)查詢(xún),我們說(shuō)先需要獲得總數(shù)量,這里我們需要在原有的基礎(chǔ)上構(gòu)建一個(gè)新的MappedStatement,修改sql和返回結(jié)果類(lèi)型。對(duì)于分頁(yè)查詢(xún),我們只需要在原來(lái)的基礎(chǔ)上修改sql,但是MappedStatement并沒(méi)喲支持直接修改sql的方法,我們只能構(gòu)建新的MappedStatement替換原來(lái)的MappedStatement。
代碼實(shí)現(xiàn):
繼承Interceptor接口。添加@Interceptor注解。在@Signature 注解中指定攔截的類(lèi)型,方法,方法參數(shù)。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Intercepts {
Signature[] value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
Class<?> type();
String method();
Class<?>[] args();
}
實(shí)現(xiàn)接口的三個(gè)方法。
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return null;
}
@Override
public Object plugin(Object o) {
return null;
}
@Override
public void setProperties(Properties properties) {
}
}
setProperties方法,有我們自定義提供方法的后綴,和頁(yè)大小。
public void setProperties(Properties properties) {
Integer o = Integer.valueOf((String) properties.get("pager.pageSize"));
if(o==null){
try {
throw new PageSizeNullException("pageSize can not be null");
} catch (PageSizeNullException e) {
e.printStackTrace();
}
}
pageSize=o;
String o1 = (String) properties.get("pager.pageFlag");
if(o1==null){
try {
throw new PageFlagNullException("pageFlag can not be null.");
} catch (PageFlagNullException e) {
e.printStackTrace();
}
}
PAGE_FLAG=o1;
}
intercept方法。參數(shù)Invocation,我們通過(guò)斷點(diǎn)來(lái)看一下。target是Executor執(zhí)行器,我們?cè)赼rgs中可以獲得MappedStatement映射對(duì)象,UserPager參數(shù),RowBounds。

第一步:獲得總數(shù)。
/**
* 構(gòu)造count查詢(xún)語(yǔ)句,創(chuàng)建新的boundSql,創(chuàng)建新的mappedStatement,再通過(guò)executor執(zhí)行查詢(xún)
* @param executor
* @param mappedStatement
* @param parameter
* @param boundSql
* @param resultHandler
* @return
* @throws SQLException
*/
public Long getTotalCount(Executor executor,MappedStatement mappedStatement,Object parameter,BoundSql boundSql,ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("select count(1) from (").append(sql).append(") as temp");
BoundSql newboundSql = new BoundSql(mappedStatement.getConfiguration(),stringBuffer.toString(),boundSql.getParameterMappings(),parameter);
MappedStatement mappedStatement1 = buildCountMappedStatement(mappedStatement, mappedStatement.getId()+COUNT_FLAG);
CacheKey cacheKey = executor.createCacheKey(mappedStatement1, parameter, RowBounds.DEFAULT, boundSql);
List query = executor.query(mappedStatement1, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, newboundSql);
return (Long)query.get(0);
}
/**
* 創(chuàng)建新的mappedStatement,添加結(jié)果映射
* @param mappedStatement
* @param id
* @return
*/
public MappedStatement buildCountMappedStatement(MappedStatement mappedStatement,String id){
MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(),id,mappedStatement.getSqlSource(),mappedStatement.getSqlCommandType());
List<ResultMap> resultMapList = new ArrayList<>();
ResultMap.Builder resultMap = new ResultMap.Builder(mappedStatement.getConfiguration(),id,Long.class,DEFAULT_LIST_RESULTMAPPING);
resultMapList.add(resultMap.build());
builder.resultMaps(resultMapList);
return builder.build();
}
獲得總數(shù)之后,開(kāi)始構(gòu)建分頁(yè)查詢(xún),我們只需要在原來(lái)的基礎(chǔ)之上修改sql就行了,但是并沒(méi)有可以直接修改sql的方法,所以我們只能通過(guò)替換mappedStatement的方式。mybatis提供了一個(gè)類(lèi)似反射的工具M(jìn)etaObject。
/**
* 利用mybatis提供的反射工具,將sql注入到mappedStatement中去
* @param invocation
* @param sql
*/
public void resetSql2Invocation(Invocation invocation,String sql){
Object[] args = invocation.getArgs();
MappedStatement mappedStatements = (MappedStatement) args[0];
Object parameter= args[1];
MappedStatement mappedStatement = buildMappedStatement(mappedStatements, mappedStatements.getBoundSql(parameter));
MetaObject metaObject = MetaObject.forObject(mappedStatement, OBJECT_FACTORY, OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
metaObject.setValue("sqlSource.boundSql.sql",sql);
args[0]=mappedStatement;
}
/**
* 構(gòu)建新的mappedStatement
* @param mappedStatement
* @param boundSql
* @return
*/
public MappedStatement buildMappedStatement(MappedStatement mappedStatement,BoundSql boundSql){
MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(),mappedStatement.getId(),new BoundSqlSource(boundSql),mappedStatement.getSqlCommandType());
setProperties(builder,mappedStatement);
return builder.build();
}
至此,完成了分頁(yè)的實(shí)現(xiàn),我們看一下結(jié)果。
