首先Mybatis 默認(rèn)是內(nèi)存分頁(yè),就是將所有數(shù)據(jù)從磁盤(pán)讀入到內(nèi)存中后,再去分頁(yè)。這樣會(huì)大幅度降低應(yīng)用性能,至于為什么,自己看代碼。
-
Mybatis 的物理分頁(yè)可以Mybatis的攔截器來(lái)做
- 攔截StatementHandler的prepare 方法,然后在攔截器中把Sql語(yǔ)句更換成對(duì)應(yīng)的分頁(yè)查找查詢(xún)語(yǔ)句,然后再調(diào)用StatementHandler對(duì)象的prepare方法,即調(diào)用
invocation.proceed()
方法
- 攔截StatementHandler的prepare 方法,然后在攔截器中把Sql語(yǔ)句更換成對(duì)應(yīng)的分頁(yè)查找查詢(xún)語(yǔ)句,然后再調(diào)用StatementHandler對(duì)象的prepare方法,即調(diào)用
Mybatis攔截器只能攔截四種類(lèi)型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。
一個(gè)簡(jiǎn)單的攔截器例子
@Intercept 注解中的@Signature中標(biāo)示的屬性,標(biāo)示當(dāng)前攔截器要攔截的那個(gè)類(lèi)的那個(gè)方法,攔截方法的傳入的參數(shù)
//攔截StatementHandler.class的prepare 方法!,這個(gè)方法的參數(shù)是一個(gè)Connecttion對(duì)象和Interger對(duì)象
@Intercepts( {
@Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class ,Integer.class}) })
public class TestInterceptor implements Interceptor {
Object intercept(Invocation invocation) throws Throwable{....};
Object plugin(Object target){....};
void setProperties(Properties properties){....};
}
---------------------注意在Mybatis 的XML中注冊(cè)攔截器!--------------------------------
現(xiàn)在已經(jīng)掌握一個(gè)攔截器的寫(xiě)法,現(xiàn)在的問(wèn)題就在于,從什么地方著手去攔截?
- 首先要明白,Mybatis 是對(duì)JDBC的一個(gè)高層次的封裝。而JDBC 在完成數(shù)據(jù)操作的時(shí)候必須要有一個(gè)Statement對(duì)象。而Statement對(duì)應(yīng)的SQL語(yǔ)句是在是在Statement之前產(chǎn)生的。所以我們的思路就是在生成Statement之前對(duì)sql進(jìn)行下手。更改sql語(yǔ)句成我們需要的!
- 對(duì)于Mybatis,其Statement生成是在
RouteStatementHandler
中。所以我們要做的就是攔截這個(gè)handler的prepare
方法!然后修改Sql語(yǔ)句?。。?!
//攔截到Sql 并輸出到控制臺(tái)
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 其實(shí)就是代理模式!
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");
System.out.println(delegate.getBoundSql().getSql());
//Sy).getClass().getName());stem.out.println(invocation.getTarget(
return invocation.proceed();
}
我們知道利用Mybatis查詢(xún)一個(gè)集合時(shí)傳入Rowbounds對(duì)象即可指定其Offset和Limit,只不過(guò)其沒(méi)有利用原生sql去查詢(xún)罷了,我們現(xiàn)在做的,就是通過(guò)攔截器 拿到這個(gè)參數(shù),然后織入到SQL語(yǔ)句中,這樣我們就可以完成一個(gè)物理分頁(yè)!
package person.walker.interceptor;
import org.apache.ibatis.executor.Executor;
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.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import sun.reflect.Reflection;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;
/**
* Created by Administrator on 2017/3/1 0001.
*/
@Intercepts({
@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class, Integer.class})})
public class TestInterceptor implements Interceptor {
// select語(yǔ)句正則表達(dá)式匹配:
private final static String REGEX = "^\\s*[Ss][Ee][Ll][Ee][Cc][Tt].*$";
@Override
public Object intercept(Invocation invocation) throws Throwable {
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
// BoundSql類(lèi)中有一個(gè)sql屬性,即為待執(zhí)行的sql語(yǔ)句
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (sql.matches(REGEX)) {
// delegate是RoutingStatementHandler通過(guò)mapper映射文件中設(shè)置的statementType來(lái)指定具體的StatementHandler
Object delegate = getFieldValue(handler, "delegate");
// rowBounds,即為Mybais 原生的Sql 分頁(yè)參數(shù),由于Rowbounds 在BaseStateHandler中所以我們需要去找父類(lèi)
RowBounds rowBounds = (RowBounds) getFieldValue(delegate, "rowBounds");
// 如果rowBound不為空,且rowBounds的起始位置不為0,則代表我們需要進(jìn)行分頁(yè)處理
if (rowBounds != null) {
// assemSql(...)完成對(duì)sql語(yǔ)句的裝配及rowBounds的重置操作
setFieldValue(boundSql, "sql", assemSql(sql, rowBounds));
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
String prop1 = properties.getProperty("prop1");
String prop2 = properties.getProperty("prop2");
System.out.println(prop1 + "------" + prop2);
}
private Object getFieldValue(Object object, String fieldName) {
Field field = null;
for (Class<?> clazz=object.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
if (field != null){
field.setAccessible(true);
break;
}
} catch (NoSuchFieldException e) {
//這里不用做處理,子類(lèi)沒(méi)有該字段可能對(duì)應(yīng)的父類(lèi)有,都沒(méi)有就返回null。
}
}
try {
return field.get(object);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
private void setFieldValue(Object object, String fieldName, Object value) {
Field field = null;
for (Class<?> clazz=object.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
if (field != null){
field.setAccessible(true);
try {
field.set(object,value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
break;
}
} catch (NoSuchFieldException e) {
//這里不用做處理,子類(lèi)沒(méi)有該字段可能對(duì)應(yīng)的父類(lèi)有,都沒(méi)有就返回null。
}
}
}
public String assemSql(String oldSql, RowBounds rowBounds) throws Exception {
String sql = oldSql + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit();
// 這兩步是必須的,因?yàn)樵谇懊嬷脫Q好sql語(yǔ)句以后,實(shí)際的結(jié)果集就是我們想要的所以offset和limit必須重置為初始值
setFieldValue(rowBounds, "offset", RowBounds.NO_ROW_OFFSET);
setFieldValue(rowBounds, "limit", RowBounds.NO_ROW_LIMIT);
return sql;
}
}