中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Mybatis插件機制詳細解析

發布時間:2021-08-31 15:41:21 來源:億速云 閱讀:192 作者:chen 欄目:數據庫

本篇內容介紹了“Mybatis插件機制詳細解析”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

Mybatis插件又稱攔截器,本篇文章中出現的攔截器都表示插件。

Mybatis采用責任鏈模式,通過動態代理組織多個插件(攔截器),通過這些插件可以改變Mybatis的默認行為(諸如SQL重寫之類的),由于插件會深入到Mybatis的核心,因此在編寫自己的插件前最好了解下它的原理,以便寫出安全高效的插件。

MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction,  close, isClosed)

  • ParameterHandler (getParameterObject, setParameters)

  • ResultSetHandler (handleResultSets, handleOutputParameters)

  • StatementHandler (prepare, parameterize, batch, update, query)

總體概括為:

  • 攔截執行器的方法

  • 攔截參數的處理

  • 攔截結果集的處理

  • 攔截Sql語法構建的處理

Mybatis是通過動態代理的方式實現攔截的,閱讀此篇文章需要先對Java的動態代理機制有所了解。

Mybatis四大接口

既然Mybatis是對四大接口進行攔截的,那我們先要知道Mybatis的四大接口是哪些: Executor, StatementHandler,  ResultSetHandler, ParameterHandler。

Mybatis插件機制詳細解析

上圖Mybatis框架的整個執行過程。Mybatis插件能夠對這四大對象進行攔截,可以說包含到了Mybatis一次SQL執行的所有操作。可見Mybatis的的插件很強大。

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. Executor是 Mybatis的內部執行器,它負責調用StatementHandler操作數據庫,并把結果集通過  ResultSetHandler進行自動映射,另外,他還處理了二級緩存的操作。從這里可以看出,我們也是可以通過插件來實現自定義的二級緩存的。

  3. StatementHandler是Mybatis直接和數據庫執行sql腳本的對象。另外它也實現了Mybatis的一級緩存。這里,我們可以使用插件來實現對一級緩存的操作(禁用等等)。

  4. ParameterHandler是Mybatis實現Sql入參設置的對象。插件可以改變我們Sql的參數默認設置。

  5. ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口對象。我們可以定義插件對Mybatis的結果集自動映射進行修改。

插件Interceptor

Mybatis的插件實現要實現Interceptor接口,我們看下這個接口定義的方法。

public interface Interceptor {     Object intercept(Invocation invocation) throws Throwable;       Object plugin(Object target);     void setProperties(Properties properties); }

這個接口只聲明了三個方法:

  • setProperties方法是在Mybatis進行配置插件的時候可以配置自定義相關屬性,即:接口實現對象的參數配置。

  • plugin方法是插件用于封裝目標對象的,通過該方法我們可以返回目標對象本身,也可以返回一個它的代理,可以決定是否要進行攔截進而決定要返回一個什么樣的目標對象,官方提供了示例:return  Plugin.wrap(target, this)。

  • intercept方法就是要進行攔截的時候要執行的方法。

理解這個接口的定義,先要知道java動態代理機制。plugin接口即返回參數target對象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理對象。在調用對應對象的接口的時候,可以進行攔截并處理。

Mybatis四大接口對象創建方法

Mybatis的插件是采用對四大接口的對象生成動態代理對象的方法來實現的。那么現在我們看下Mybatis是怎么創建這四大接口對象的。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {   //確保ExecutorType不為空(defaultExecutorType有可能為空)   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; }  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; }  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {   ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);   parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);   return parameterHandler; }  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; }

查看源碼可以發現,  Mybatis框架在創建好這四大接口對象的實例后,都會調用InterceptorChain.pluginAll()方法。InterceptorChain對象是插件執行鏈對象,看源碼就知道里面維護了Mybatis配置的所有插件(Interceptor)對象。

// target --> Executor/ParameterHandler/ResultSetHander/StatementHandler public Object pluginAll(Object target) {   for (Interceptor interceptor : interceptors) {    target = interceptor.plugin(target);   }   return target; }

其實就是按順序執行我們插件的plugin方法,一層一層返回我們原對象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理對象。當我們調用四大接口的方法的時候,實際上是調用代理對象的相應方法,代理對象又會調用四大接口的實例。

Plugin對象

我們知道,官方推薦插件實現plugin方法為:Plugin.wrap(target, this);

public static Object wrap(Object target, Interceptor interceptor) {   // 獲取插件的Intercepts注解   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; }

這個方法其實是Mybatis簡化我們插件實現的工具方法。其實就是根據當前攔截的對象創建了一個動態代理對象。代理對象的InvocationHandler處理器為新建的Plugin對象。

插件配置注解@Intercepts

Mybatis的插件都要有Intercepts注解來指定要攔截哪個對象的哪個方法。我們知道,Plugin.warp方法會返回四大接口對象的代理對象(通過new  Plugin()創建的IvocationHandler處理器),會攔截所有的執行方法。在代理對象執行對應方法的時候,會調用InvocationHandler處理器的invoke方法。Mybatis中利用了注解的方式配置指定攔截哪些方法。具體如下:

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)) {      return interceptor.intercept(new Invocation(target, method, args));    }    return method.invoke(target, args);   } catch (Exception e) {    throw ExceptionUtil.unwrapThrowable(e);   } }

可以看到,只有通過Intercepts注解指定的方法才會執行我們自定義插件的intercept方法。未通過Intercepts注解指定的將不會執行我們的intercept方法。

官方插件開發方式

@Intercepts({@Signature(type = Executor.class, method = "query",     args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class TestInterceptor implements Interceptor {   public Object intercept(Invocation invocation) throws Throwable {    Object target = invocation.getTarget(); //被代理對象    Method method = invocation.getMethod(); //代理方法    Object[] args = invocation.getArgs(); //方法參數    // do something ...... 方法攔截前執行代碼塊    Object result = invocation.proceed();    // do something .......方法攔截后執行代碼塊    return result;   }   public Object plugin(Object target) {    return Plugin.wrap(target, this);   } }

以上就是Mybatis官方推薦的插件實現的方法,通過Plugin對象創建被代理對象的動態代理對象。可以發現,Mybatis的插件開發還是很簡單的。

自定義開發方式

Mybatis的插件開發通過內部提供的Plugin對象可以很簡單的開發。只有理解了插件實現原理,對應不采用Plugin對象我們一樣可以自己實現插件的開發。下面是我個人理解之后的自己實現的一種方式。

public class TestInterceptor implements Interceptor {   public Object intercept(Invocation invocation) throws Throwable {     Object target = invocation.getTarget(); //被代理對象     Method method = invocation.getMethod(); //代理方法     Object[] args = invocation.getArgs(); //方法參數     // do something ...... 方法攔截前執行代碼塊     Object result = invocation.proceed();     // do something .......方法攔截后執行代碼塊     return result;   }   public Object plugin(final Object target) {     return Proxy.newProxyInstance(Interceptor.class.getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         return intercept(new Invocation(target, method, args));       }     });   }   public void setProperties(Properties properties) {   } }

當然,Mybatis插件的那這個時候Intercepts的注解起不到作用了。

小結

我們在MyBatis配置了一個插件,在運行發生了什么

  1. 所有可能被攔截的處理類都會生成一個代理

  2. 處理類代理在執行對應方法時,判斷要不要執行插件中的攔截方法

  3. 執行插接中的攔截方法后,推進目標的執行

如果有N個插件,就有N個代理,每個代理都要執行上面的邏輯。這里面的層層代理要多次生成動態代理,是比較影響性能的。雖然能指定插件攔截的位置,但這個是在執行方法時動態判斷,初始化的時候就是簡單的把插件包裝到了所有可以攔截的地方。

因此,在編寫插件時需注意以下幾個原則:

  • 不編寫不必要的插件;

  • 實現plugin方法時判斷一下目標類型,是本插件要攔截的對象才執行Plugin.wrap方法,否者直接返回目標本身,這樣可以減少目標被代理的次數。

// 假如我們只要攔截Executor對象,那么我們應該這么做 public Object plugin(final Object target) {   if (target instanceof Executor) {    return Plugin.wrap(target, this);   } else {    return target;   } }

Mybatis插件很強大,可以對Mybatis框架進行很大的擴展。當然,如果你不理解Mybatis插件的原理,開發起來只能是模擬兩可。在實際開發過程中,我們可以參考別人寫的插件。下面是一個Mybatis分頁的插件,可以為以后開發做參考。

/**  * Mybatis - 通用分頁插件(如果開啟二級緩存需要注意)  */ @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),     @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) @Log4j public class PageHelper implements Interceptor {    public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();    /**    * 開始分頁    *    * @param pageNum    * @param pageSize    */   public static void startPage(int pageNum, int pageSize) {     localPage.set(new Page(pageNum, pageSize));   }    /**    * 結束分頁并返回結果,該方法必須被調用,否則localPage會一直保存下去,直到下一次startPage    *    * @return    */   public static Page endPage() {     Page page = localPage.get();     localPage.remove();     return page;   }    public Object intercept(Invocation invocation) throws Throwable {     if (localPage.get() == null) {       return invocation.proceed();     }     if (invocation.getTarget() instanceof StatementHandler) {       StatementHandler statementHandler = (StatementHandler) invocation.getTarget();       MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);       // 分離代理對象鏈(由于目標類可能被多個插件攔截,從而形成多次代理,通過下面的兩次循環       // 可以分離出最原始的的目標類)       while (metaStatementHandler.hasGetter("h")) {         Object object = metaStatementHandler.getValue("h");         metaStatementHandler = SystemMetaObject.forObject(object);       }       // 分離最后一個代理對象的目標類       while (metaStatementHandler.hasGetter("target")) {         Object object = metaStatementHandler.getValue("target");         metaStatementHandler = SystemMetaObject.forObject(object);       }       MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");       //分頁信息if (localPage.get() != null) {       Page page = localPage.get();       BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");       // 分頁參數作為參數對象parameterObject的一個屬性       String sql = boundSql.getSql();       // 重寫sql       String pageSql = buildPageSql(sql, page);       //重寫分頁sql       metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);       Connection connection = (Connection) invocation.getArgs()[0];       // 重設分頁參數里的總頁數等       setPageParameter(sql, connection, mappedStatement, boundSql, page);       // 將執行權交給下一個插件       return invocation.proceed();     } else if (invocation.getTarget() instanceof ResultSetHandler) {       Object result = invocation.proceed();       Page page = localPage.get();       page.setResult((List) result);       return result;     }     return null;   }    /**    * 只攔截這兩種類型的    * <br>StatementHandler    * <br>ResultSetHandler    *    * @param target    * @return    */   public Object plugin(Object target) {     if (target instanceof StatementHandler || target instanceof ResultSetHandler) {       return Plugin.wrap(target, this);     } else {       return target;     }   }    public void setProperties(Properties properties) {    }    /**    * 修改原SQL為分頁SQL    *    * @param sql    * @param page    * @return    */   private String buildPageSql(String sql, Page page) {     StringBuilder pageSql = new StringBuilder(200);     pageSql.append("select * from (");     pageSql.append(sql);     pageSql.append(" ) temp limit ").append(page.getStartRow());     pageSql.append(" , ").append(page.getPageSize());     return pageSql.toString();   }    /**    * 獲取總記錄數    *    * @param sql    * @param connection    * @param mappedStatement    * @param boundSql    * @param page    */   private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,                  BoundSql boundSql, Page page) {     // 記錄總記錄數     String countSql = "select count(0) from (" + sql + ") temp";     PreparedStatement countStmt = null;     ResultSet rs = null;     try {       countStmt = connection.prepareStatement(countSql);       BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,           boundSql.getParameterMappings(), boundSql.getParameterObject());       setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());       rs = countStmt.executeQuery();       int totalCount = 0;       if (rs.next()) {         totalCount = rs.getInt(1);       }       page.setTotal(totalCount);       int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);       page.setPages(totalPage);     } catch (SQLException e) {       log.error("Ignore this exception", e);     } finally {       try {         rs.close();       } catch (SQLException e) {         log.error("Ignore this exception", e);       }       try {         countStmt.close();       } catch (SQLException e) {         log.error("Ignore this exception", e);       }     }   }    /**    * 代入參數值    *    * @param ps    * @param mappedStatement    * @param boundSql    * @param parameterObject    * @throws SQLException    */   private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,                 Object parameterObject) throws SQLException {     ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);     parameterHandler.setParameters(ps);   }    @Data //采用lombok插件編譯   public static class Page<E> {     private int pageNum;     private int pageSize;     private int startRow;     private int endRow;     private long total;     private int pages;     private List<E> result;      public Page(int pageNum, int pageSize) {       this.pageNum = pageNum;       this.pageSize = pageSize;       this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0;       this.endRow = pageNum * pageSize;     }    } }

“Mybatis插件機制詳細解析”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

湛江市| 同仁县| 右玉县| 永年县| 增城市| 筠连县| 东安县| 临朐县| 肇源县| 英超| 乡城县| 芦溪县| 巴林右旗| 玉溪市| 缙云县| 汽车| 通河县| 社旗县| 嘉善县| 怀柔区| 巨野县| 昌都县| 连南| 合水县| 赤城县| 苏州市| 安康市| 聂荣县| 沁源县| 高尔夫| 天峻县| 富顺县| 福建省| 富锦市| 遵义市| 合山市| 孝感市| 金平| 黄梅县| 崇信县| 准格尔旗|