本文主要內容:mysql
大多數框架都支持插件,用戶可經過編寫插件來自行擴展功能,Mybatis也不例外。git
在Mybatis中最出名的就是PageHelper 分頁插件,下面咱們先來使用一下這個分頁插件。github
Spring-Boot+Mybatis+PageHelper 。spring
引入pom依賴sql
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
配置分頁插件配置項數據庫
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
service接口代碼中緩存
PageInfo selectUsersByName(int pageIndex, int pageSize);
service實現類代碼中app
@Override
public PageInfo selectUsersByName(int pageIndex, int pageSize) {
PageHelper.startPage(pageIndex, pageSize);
List<User> users = userMapper.selectUsersByName(null);
return new PageInfo(users);
}
Mapper代碼代碼框架
<select id="selectUsersByName" resultMap="User">
select * from m_user
<where>
<if test="userName != null and userName != ''">
`name` = #{userName}
</if>
</where>
</select>
List<User> selectUsersByName(@Param("userName") String userName);
controller中代碼ide
@GetMapping("/user/name")
public PageInfo selectUsersByName(int pageIndex, int pageSize) {
return userService.selectUsersByName(pageIndex, pageSize);
}
而後咱們訪問
http://localhost:9002/user/name?pageIndex=1&pageSize=10
輸出結果:
輸出重要項說明:
咱們在看看輸出SQL:
發現其實執行了兩條SQL
:count和limit。
1.這個分頁插件無非就是在咱們的查詢條件上拼接了個limit和作了一個count查詢。
2.咱們這裏使用的是Mysql做爲數據庫,若是是Oracle的話那就不是limit了,因此這裏有多重數據庫對應的方案。
3.在沒有此插件的前面攔截並作了sql和相關處理。
下面是來自官網的一段話:
MyBatis 容許你在映射語句執行過程當中的某一點進行攔截調用。默認狀況下,MyBatis 容許使用插件來攔截的方法調用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
這些類中方法的細節能夠經過查看每一個方法的簽名來發現,或者直接查看 MyBatis 發行包中的源代碼。若是你想作的不只僅是監控方法的調用,那麼你最好至關了解要重寫的方法的行爲。由於在試圖修改或重寫已有方法的行爲時,極可能會破壞 MyBatis 的核心模塊。這些都是更底層的類和方法,因此使用插件的時候要特別小心。
經過 MyBatis 提供的強大機制,使用插件是很是簡單的,只需實現 Interceptor 接口,並指定想要攔截的方法簽名便可。
那咱們就嘗試着按照官方來寫一個插件。
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class TianPlugin implements Interceptor {
private Properties properties = new Properties();
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("老田寫的一個Mybatis插件--start");
Object returnObject = invocation.proceed();
System.out.println("老田寫的一個Mybatis插件---end");
return returnObject;
}
}
而後把插件類注入到容器中。
這裏的自定義徹底是官網給出的案例。從自定義的插件類中看到有個update,咱們猜想確定是須要執行update纔會被攔截到。
訪問前面的代碼:http://localhost:9002/updateUser
成功了。
這是你們確定會聯想到咱們剛剛開始學動態代理的時候,不就是在要調用的方法的前面和後面作點小東東嗎?
Mybatis
的插件確實就是這樣的。
咱們來分析一下官方的那段話和咱們自定義的插件。
首先,咱們自定義的插件必須是針對下面這四個類以及方法。
其次,咱們必須實現Mybatis的Interceptor。
Interceptor中三個方法的做用:
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
默認實現方法,裏面調用了Plugin.wrap()方法。
public class Plugin implements InvocationHandler {
private Object target;
private Interceptor interceptor;
private Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 建立JDK動態代理對象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 判斷是不是須要攔截的方法(很重要)
if (methods != null && methods.contains(method)) {
// 回調intercept()方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
//...省略其餘不相關代碼
}
這不就是一個JDK動態代理嗎?
Map<Class<?>, Set> signatureMap:緩存需攔截對象的反射結果,避免屢次反射,即target的反射結果。
因此,咱們不要動不動就說反射性能不好,那是由於你沒有像Mybatis同樣去緩存一個對象的反射結果。
判斷是不是須要攔截的方法,這句註釋很重要,一旦忽略了,都不知道Mybatis是怎麼判斷是否執行攔截內容的,要記住。
使用JDK的動態代理,給target對象建立一個delegate代理對象,以此來實現方法攔截和加強功能,它會回調intercept()方法。
在咱們自定義的插件上有一堆註解,別懼怕。
Mybatis規定插件必須編寫Annotation註解,是必須,而不是可選。
@Intercepts({@Signature( type= Executor.class, method = "update",
args = {MappedStatement.class,Object.class})}
)
public class TianPlugin implements Interceptor {
@Intercepts註解:裝載一個@Signature列表,一個@Signature其實就是一個須要攔截的方法封裝。那麼,一個攔截器要攔截多個方法,天然就是一個@Signature列表。
type= Executor.class, method = "update",args = {MappedStatement.class,Object.class}
解釋:要攔截Executor接口內的query()方法,參數類型爲args列表。
那若是想攔截多個方法呢?
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value();
}
這就簡單了吧,咱們在@Intercepts註解中能夠存放多個@Signature註解。
好比說前面分頁插件中就是攔截多個方法的。
爲何攔截兩個都是query方法呢?由於在Executor中有兩個query方法。
總結下:
Mybatis規定必須使用@Intercepts註解。
@Intercepts註解內能夠添加多個類多個方法,注意方法名和參數類型個數必定要對應起來。