原理:
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ū)分。
- 第二步:查詢出總數(shù)鲸睛,我們需要在原來(lái)sql的基礎(chǔ)上查詢出總數(shù)是多少娜饵,在這里我們通過(guò)映射對(duì)象,調(diào)用Executor的query方法來(lái)獲得官辈。
- 第三步:改造sql箱舞,變成分頁(yè)的sql語(yǔ)句。
- 第四步:執(zhí)行語(yǔ)句拳亿。
在這里有些地方并沒(méi)有提供直接的修改方法晴股,所以只能通過(guò)替換的形式,代碼冗余肺魁。
原理理解
在這其中有幾個(gè)關(guān)鍵的類
- 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ì)象的類型
private ResultSetType resultSetType;//結(jié)果類型
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ǔ)句的類型寂呛,如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é)果類型,這些都是我們后面需要用到的胖缤。
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)建自己的查詢肾筐,對(duì)于分頁(yè)查詢,我們說(shuō)先需要獲得總數(shù)量缸剪,這里我們需要在原有的基礎(chǔ)上構(gòu)建一個(gè)新的MappedStatement,修改sql和返回結(jié)果類型吗铐。對(duì)于分頁(yè)查詢,我們只需要在原來(lái)的基礎(chǔ)上修改sql杏节,但是MappedStatement并沒(méi)喲支持直接修改sql的方法唬渗,我們只能構(gòu)建新的MappedStatement替換原來(lái)的MappedStatement。
代碼實(shí)現(xiàn):
繼承Interceptor接口奋渔。添加@Interceptor注解镊逝。在@Signature 注解中指定攔截的類型,方法嫉鲸,方法參數(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查詢語(yǔ)句,創(chuàng)建新的boundSql囚霸,創(chuàng)建新的mappedStatement腰根,再通過(guò)executor執(zhí)行查詢
* @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è)查詢,我們只需要在原來(lái)的基礎(chǔ)之上修改sql就行了额嘿,但是并沒(méi)有可以直接修改sql的方法,所以我們只能通過(guò)替換mappedStatement的方式劣挫。mybatis提供了一個(gè)類似反射的工具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é)果。