-
MyBatis外掛外掛機制簡介
MyBatis外掛其實就是為使用者提供的自行擴充攔截器,主要是為了可以更好的滿足業務需要。
在MyBatis中提供了四大核心元件對資料庫進行處理,分別是Executor、Statement Handler、ParameterHandler及ResultSetHandler,同時也支援對這四大元件進行自定義擴充套件攔截,用來增強核心物件的功能。其本質上是使用底層的動態代理來實現的,即程式執行時執行的都是代理後的物件。
MyBatis允許攔截的方法如下:
-
執行器Executor (update、query、commit、rollback等方法);
-
SQL語法構建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
-
引數處理器ParameterHandler (getParameterObject、setParameters方法);
-
結果集處理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);
-
-
攔截器引數簡介
-
攔截器註解
@Intercepts(//可配置多個@Signature,均使用此類進行攔截增強 { @Signature(//指定需要攔截的類、方法及方法引數 type = Executor.class,//需要攔截介面 method = "update",//需要攔截方法名稱 args = {MappedStatement.class, Object.class}//攔截方法的請求引數 ) } )
-
實現Interceptor介面,並實現方法
public Object intercept(Invocation invocation)//每次攔截到都會執行此方法,方法內寫增強邏輯 invocation//代理物件,可以獲取目標方法、請求引數、執行結果等 invocation.proceed() //執行目標方法
public Object plugin(Object target) Plugin.wrap(target,this)//包裝目標物件,為目標物件建立代理物件,將當前生成的代理物件放入攔截器鏈中
public void setProperties(Properties properties)//獲取配置檔案中的外掛引數,外掛初始化時呼叫一次
-
-
自定義外掛
-
新建MyExecuter類,實現Interceptor介面
package com.rangers.plugin; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.lang.reflect.Method; import java.util.Properties; /** * @Author Rangers * @Description 自定義Executor update方法 * @Date 2021-03-11 **/ @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class} ) }) public class MyExecuter implements Interceptor { // 接收外掛的配置引數 private Properties properties = new Properties(); // 增強邏輯寫在此方法中 @Override public Object intercept(Invocation invocation) throws Throwable { // 列印外掛的配置引數 System.out.println("外掛的配置引數:"+properties.toString()); // 獲取目標方法Method物件 Method method = invocation.getMethod(); // 獲取目標方法的請求引數 與args列表一一對應 String reqParam = invocation.getArgs()[1].toString(); System.out.println("方法名稱:"+method.getName()+" 請求引數:"+ reqParam); // 執行目標方法 Object result = invocation.proceed(); System.out.println("方法名稱:"+method.getName()+" 執行結果:"+result); return result; } /** * @Author Rangers * @Description **/ @Override public Object plugin(Object target) { //System.out.println("需要包裝的目標物件:"+target+" 目標物件型別"+ target.getClass()); // 主要是將當前生成的代理物件放入攔截器鏈中,包裝目標物件,為目標物件建立代理物件 return Plugin.wrap(target,this); } /** * @Author Rangers * @Description 獲取配置檔案中的外掛屬性引數,外掛初始化時呼叫一次 **/ @Override public void setProperties(Properties properties) { // 將配置引數進行接收 this.properties = properties; } }
-
主配置檔案新增標籤
<plugins> <!--指定攔截器類--> <plugin interceptor="com.rangers.plugin.MyExecuter"> <!--配置攔截器 屬性引數--> <property name="param1" value="value1"/> <property name="param2" value="value2"/> <property name="param3" value="value3"/> </plugin> </plugins>
-
-
外掛原理
a、在Executor、StatementHandler、ParameterHandler及ResultSetHandler四大物件建立時,並不是直接返回的,而是中間多了一步interceptorChain.pluginAll()(均在Configuration類中進行建立)。
- Executor—interceptorChain.pluginAll(executor);
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
- StatementHandler—interceptorChain.pluginAll(statementHandler);
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
- ParameterHandler—interceptorChain.pluginAll(parameterHandler);
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }
- ResultSetHandler—interceptorChain.pluginAll(resultSetHandler)
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; }
b、interceptorChain.pluginAll()呼叫的就是實現了Interceptor介面的plugin()方法,plugin()方法又通過Plugin.wrap(target,this)為目標物件建立一個Plugin的代理物件,新增到攔截鏈interceptorChain中。具體程式碼如下:
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { // 呼叫實現Interceptor介面的plugin方法 target = interceptor.plugin(target); } return target; }
@Override public Object plugin(Object target) { // 呼叫Plugin的wrap()方法,建立代理物件 return Plugin.wrap(target,this); }
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) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } // 否則返回目標物件 return target; }
c、Plugin實現了 InvocationHandler介面,因此它的invoke方法會攔截所有的方法呼叫。invoke()方法會 對所攔截的方法進行檢測,以決定是否執行外掛邏輯。
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 根據元件物件Class從signatureMap中獲取到需要攔截的方法set集合 Set<Method> methods = signatureMap.get(method.getDeclaringClass()); // 若包含當前方法則進行攔截增強 if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { // 獲取到所有攔截器的類 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // issue #251 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } // 獲取到@Signature註解陣列 Signature[] sigs = interceptsAnnotation.value(); // signatureMap存放所有攔截到方法,key為四大元件的Class,value為元件對應的方法set集合 Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); for (Signature sig : sigs) { Set<Method> methods = signatureMap.get(sig.type()); if (methods == null) { methods = new HashSet<Method>(); signatureMap.put(sig.type(), methods); } try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; }
-
PageHelper外掛
PageHelper是MyBaits框架使用最廣泛的第三方物理分頁外掛,分頁助手PageHelper是將分頁的複雜操作進行封裝,使用簡單的方式即可獲得分頁的相關資料。
使用步驟:
-
新增依賴
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.8</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>1.2</version> </dependency>
-
配置外掛
<plugin interceptor="com.github.pagehelper.PageInterceptor"> </plugin>
-
使用分頁
@org.junit.Test public void testPagehealper() { PageHelper.startPage(1, 2); List<User> users = userDao.findAll(); if (users != null && users.size() > 0) { for (User user : users) { System.out.println(user.toString()); } PageInfo<User> pageInfo = new PageInfo<>(users); System.out.println("總條數:" + pageInfo.getTotal()); System.out.println("總頁數:" + pageInfo.getPages()); System.out.println("當前頁:" + pageInfo.getPageNum()); System.out.println("每頁顯萬長度:" + pageInfo.getPageSize()); System.out.println("是否第一頁:" + pageInfo.isIsFirstPage()); System.out.println("是否最後一頁:" + pageInfo.isIsLastPage()); } }
-
MyBatis(八):MyBatis外掛機制詳解
相關文章
- 精盡MyBatis原始碼分析 - 外掛機制MyBatis原始碼
- MyBatis外掛MyBatis
- mybatis plus 啟用 mybatis外掛MyBatis
- mybatis的外掛:mybatis-generator(MBG)MyBatis
- MyBatis加強(4)~mybatis 外掛開發MyBatis
- Mybatis外掛開發MyBatis
- mybatis 自定義外掛MyBatis
- 自己動手編寫一個Mybatis外掛:Mybatis脫敏外掛MyBatis
- mybatis generator外掛系列--分頁外掛MyBatis
- MyBatis外掛 - 通用mapperMyBatisAPP
- myBatis分頁外掛配置MyBatis
- mybatis的外掛:mapperMyBatisAPP
- MyBatis-07-外掛原理MyBatis
- Mybatis-plus外掛功能MyBatis
- SOFATracer 外掛埋點機制詳解
- Mybatis詳解MyBatis
- 淺析MyBatis(三):聊一聊MyBatis的實用外掛與自定義外掛MyBatis
- 深入淺出MyBatis:MyBatis外掛及開發過程MyBatis
- Mybatis快取機制MyBatis快取
- Maven外掛生成myBatis逆向工程MavenMyBatis
- MyBatis外掛使用–通用MapperMyBatisAPP
- SpringMvc+Mybatis常用外掛SpringMVCMyBatis
- mybatis plus 新增分頁外掛MyBatis
- MyBatis詳解(一)MyBatis
- MyBatis 配置詳解MyBatis
- Idea - 關於mybatis的外掛IdeaMyBatis
- mybatis的快取機制MyBatis快取
- MyBatis 的快取機制MyBatis快取
- Mybatis快取詳解MyBatis快取
- MyBatis Generator 用法詳解MyBatis
- Mybatis的使用詳解MyBatis
- 談談 MyBatis 的外掛化設計MyBatis
- 更便捷的Mybatis增強外掛——EasyMybatisMyBatis
- 5. MyBatis-Plus效能分析外掛MyBatis
- mybatis的三發外掛:分頁pagehelpMyBatis
- 【MyBatis原始碼分析】外掛實現原理MyBatis原始碼
- MyBatis(九):MyBatis型別處理器(TypeHandler)詳解MyBatis型別
- 使用Mybatis外掛列印SQL詳細內容及執行時間MyBatisSQL