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

溫馨提示×

溫馨提示×

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

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

MyBatis插件的原理是什么

發布時間:2021-06-18 14:41:44 來源:億速云 閱讀:170 作者:Leah 欄目:web開發

本篇文章為大家展示了MyBatis插件的原理是什么,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

 插件原理分析

mybatis插件涉及到的幾個類:

MyBatis插件的原理是什么

我將以 Executor 為例,分析 MyBatis 是如何為 Executor 實例植入插件的。Executor 實例是在開啟 SqlSession  時被創建的,因此,我們從源頭進行分析。先來看一下 SqlSession 開啟的過程。

public SqlSession openSession() {     return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {     Transaction tx = null;     try {         // 省略部分邏輯                  // 創建 Executor         final Executor executor = configuration.newExecutor(tx, execType);         return new DefaultSqlSession(configuration, executor, autoCommit);     }      catch (Exception e) {...}      finally {...} }

Executor 的創建過程封裝在 Configuration 中,我們跟進去看看看。

// Configuration類中 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {     executorType = executorType == null ? defaultExecutorType : executorType;     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;     Executor executor;          // 根據 executorType 創建相應的 Executor 實例     if (ExecutorType.BATCH == executorType) {...}      else if (ExecutorType.REUSE == executorType) {...}      else {         executor = new SimpleExecutor(this, transaction);     }     if (cacheEnabled) {         executor = new CachingExecutor(executor);     }          // 植入插件     executor = (Executor) interceptorChain.pluginAll(executor);     return executor; }

如上,newExecutor 方法在創建好 Executor 實例后,緊接著通過攔截器鏈 interceptorChain 為 Executor  實例植入代理邏輯。那下面我們看一下 InterceptorChain 的代碼是怎樣的。

public class InterceptorChain {     private final List<Interceptor> interceptors = new ArrayList<Interceptor>();     public Object pluginAll(Object target) {         // 遍歷攔截器集合         for (Interceptor interceptor : interceptors) {             // 調用攔截器的 plugin 方法植入相應的插件邏輯             target = interceptor.plugin(target);         }         return target;     }     /** 添加插件實例到 interceptors 集合中 */     public void addInterceptor(Interceptor interceptor) {         interceptors.add(interceptor);     }     /** 獲取插件列表 */     public List<Interceptor> getInterceptors() {         return Collections.unmodifiableList(interceptors);     } }

上面的for循環代表了只要是插件,都會以責任鏈的方式逐一執行(別指望它能跳過某個節點),所謂插件,其實就類似于攔截器。

這里就用到了責任鏈設計模式,責任鏈設計模式就相當于我們在OA系統里發起審批,領導們一層一層進行審批。

以上是 InterceptorChain 的全部代碼,比較簡單。它的 pluginAll 方法會調用具體插件的 plugin  方法植入相應的插件邏輯。如果有多個插件,則會多次調用 plugin 方法,最終生成一個層層嵌套的代理類。形如下面:

MyBatis插件的原理是什么

當 Executor 的某個方法被調用的時候,插件邏輯會先行執行。執行順序由外而內,比如上圖的執行順序為 plugin3 &rarr; plugin2 &rarr;  Plugin1 &rarr; Executor。

plugin 方法是由具體的插件類實現,不過該方法代碼一般比較固定,所以下面找個示例分析一下。

// TianPlugin類 public Object plugin(Object target) {     return Plugin.wrap(target, this); }  //Plugin public static Object wrap(Object target, Interceptor interceptor) {     /*      * 獲取插件類 @Signature 注解內容,并生成相應的映射結構。形如下面:      * {      *     Executor.class : [query, update, commit],      *     ParameterHandler.class : [getParameterObject, setParameters]      * }      */     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; }

如上,plugin 方法在內部調用了 Plugin 類的 wrap 方法,用于為目標對象生成代理。Plugin 類實現了  InvocationHandler 接口,因此它可以作為參數傳給 Proxy 的 newProxyInstance 方法。

到這里,關于插件植入的邏輯就分析完了。接下來,我們來看看插件邏輯是怎樣執行的。

執行插件邏輯

Plugin 實現了 InvocationHandler 接口,因此它的 invoke 方法會攔截所有的方法調用。invoke  方法會對所攔截的方法進行檢測,以決定是否執行插件邏輯。該方法的邏輯如下:

//在Plugin類中 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {     try {         /*          * 獲取被攔截方法列表,比如:          *    signatureMap.get(Executor.class),可能返回 [query, update, commit]          */         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);     } }

invoke 方法的代碼比較少,邏輯不難理解。首先,invoke 方法會檢測被攔截方法是否配置在插件的 @Signature  注解中,若是,則執行插件邏輯,否則執行被攔截方法。插件邏輯封裝在 intercept 中,該方法的參數類型為 Invocation。Invocation  主要用于存儲目標類,方法以及方法參數列表。下面簡單看一下該類的定義。

public class Invocation {      private final Object target;     private final Method method;     private final Object[] args;      public Invocation(Object target, Method method, Object[] args) {         this.target = target;         this.method = method;         this.args = args;     }     // 省略部分代碼     public Object proceed() throws InvocationTargetException, IllegalAccessException {         //反射調用被攔截的方法         return method.invoke(target, args);     } }

關于插件的執行邏輯就分析到這,整個過程不難理解,大家簡單看看即可。

自定義插件

下面為了讓大家更好的理解Mybatis的插件機制,我們來模擬一個慢sql監控的插件。

/**  * 慢查詢sql 插件  */ @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class SlowSqlPlugin implements Interceptor {      private long slowTime;      //攔截后需要處理的業務     @Override     public Object intercept(Invocation invocation) throws Throwable {         //通過StatementHandler獲取執行的sql         StatementHandler statementHandler = (StatementHandler) invocation.getTarget();         BoundSql boundSql = statementHandler.getBoundSql();         String sql = boundSql.getSql();          long start = System.currentTimeMillis();         //結束攔截         Object proceed = invocation.proceed();         long end = System.currentTimeMillis();         long f = end - start;         System.out.println(sql);         System.out.println("耗時=" + f);         if (f > slowTime) {             System.out.println("本次數據庫操作是慢查詢,sql是:");             System.out.println(sql);         }         return proceed;     }      //獲取到攔截的對象,底層也是通過代理實現的,實際上是拿到一個目標代理對象     @Override     public Object plugin(Object target) {         //觸發intercept方法         return Plugin.wrap(target, this);     }      //設置屬性     @Override     public void setProperties(Properties properties) {         //獲取我們定義的慢sql的時間閾值slowTime         this.slowTime = Long.parseLong(properties.getProperty("slowTime"));     } }

然后把這個插件類注入到容器中。

MyBatis插件的原理是什么

然后我們來執行查詢的方法。

MyBatis插件的原理是什么

耗時28秒的,大于我們定義的10毫秒,那這條SQL就是我們認為的慢SQL。

通過這個插件,我們就能很輕松的理解setProperties()方法是做什么的了。

回顧分頁插件

也是實現mybatis接口Interceptor。

@SuppressWarnings({"rawtypes", "unchecked"}) @Intercepts(     {         @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),         @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),     } ) public class PageInterceptor implements Interceptor {         @Override     public Object intercept(Invocation invocation) throws Throwable {         ...     }

intercept方法中

MyBatis插件的原理是什么

//AbstractHelperDialect類中 @Override public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {         String sql = boundSql.getSql();         Page page = getLocalPage();         //支持 order by         String orderBy = page.getOrderBy();         if (StringUtil.isNotEmpty(orderBy)) {             pageKey.update(orderBy);             sql = OrderByParser.converToOrderBySql(sql, orderBy);         }         if (page.isOrderByOnly()) {             return sql;         }         //獲取分頁sql         return getPageSql(sql, page, pageKey);  } //模板方法模式中的鉤子方法  public abstract String getPageSql(String sql, Page page, CacheKey pageKey);

AbstractHelperDialect類的實現類有如下(也就是此分頁插件支持的數據庫就以下幾種):

MyBatis插件的原理是什么

我們用的是MySQL。這里也有與之對應的。

@Override    public String getPageSql(String sql, Page page, CacheKey pageKey) {        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);        sqlBuilder.append(sql);        if (page.getStartRow() == 0) {            sqlBuilder.append(" LIMIT ? ");        } else {            sqlBuilder.append(" LIMIT ?, ? ");        }        pageKey.update(page.getPageSize());        return sqlBuilder.toString();    }

到這里我們就知道了,它無非就是在我們執行的SQL上再拼接了Limit罷了。同理,Oracle也就是使用rownum來處理分頁了。下面是Oracle處理分頁

@Override     public String getPageSql(String sql, Page page, CacheKey pageKey) {         StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120);         if (page.getStartRow() > 0) {             sqlBuilder.append("SELECT * FROM ( ");         }         if (page.getEndRow() > 0) {             sqlBuilder.append(" SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM ( ");         }         sqlBuilder.append(sql);         if (page.getEndRow() > 0) {             sqlBuilder.append(" ) TMP_PAGE WHERE ROWNUM <= ? ");         }         if (page.getStartRow() > 0) {             sqlBuilder.append(" ) WHERE ROW_ID > ? ");         }         return sqlBuilder.toString();     }

其他數據庫分頁操作類似。關于具體原理分析,這里就沒必要贅述了,因為分頁插件源代碼里注釋基本上全是中文。

Mybatis插件應用場景

  • 水平分表

  • 權限控制

  • 數據的加解密

總結

Spring-Boot+Mybatis繼承了分頁插件,以及使用案例、插件的原理分析、源碼分析、如何自定義插件。

涉及到技術點:JDK動態代理、責任鏈設計模式、模板方法模式。

Mybatis插件關鍵對象總結:

  • Inteceptor接口:自定義攔截必須實現的類。

  • InterceptorChain:存放插件的容器。

  • Plugin:h對象,提供創建代理類的方法。

  • Invocation:對被代理對象的封裝。

上述內容就是MyBatis插件的原理是什么,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

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

AI

深水埗区| 洛阳市| 贡山| 金乡县| 布拖县| 昭觉县| 盐边县| 香港| 德兴市| 容城县| 沙坪坝区| 尼木县| 珲春市| 稻城县| 玛纳斯县| 准格尔旗| 堆龙德庆县| 清苑县| 苗栗市| 肇源县| 广灵县| 辽宁省| 友谊县| 梧州市| 乃东县| 通山县| 康平县| 邯郸县| 久治县| 大田县| 河北省| 江门市| 南京市| 罗甸县| 灵丘县| 南川市| 天津市| 平顶山市| 宁蒗| 临邑县| 岐山县|