成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Mybatis超詳細插件機制解析,弄懂攔截器So easy

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

[[285992]]

概述

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超詳細插件機制解析,弄懂攔截器So easy

 

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

  1. Executor是 Mybatis的內部執行器,它負責調用StatementHandler操作數據庫,并把結果集通過 ResultSetHandler進行自動映射,另外,他還處理了二級緩存的操作。從這里可以看出,我們也是可以通過插件來實現自定義的二級緩存的。
  2. StatementHandler是Mybatis直接和數據庫執行sql腳本的對象。另外它也實現了Mybatis的一級緩存。這里,我們可以使用插件來實現對一級緩存的操作(禁用等等)。
  3. ParameterHandler是Mybatis實現Sql入參設置的對象。插件可以改變我們Sql的參數默認設置。
  4. ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口對象。我們可以定義插件對Mybatis的結果集自動映射進行修改。

插件Interceptor

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

  1. public interface Interceptor {   
  2.   Object intercept(Invocation invocation) throws Throwable;     
  3.   Object plugin(Object target);   
  4.   void setProperties(Properties properties); 

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

  • setProperties方法是在Mybatis進行配置插件的時候可以配置自定義相關屬性,即:接口實現對象的參數配置。
  • plugin方法是插件用于封裝目標對象的,通過該方法我們可以返回目標對象本身,也可以返回一個它的代理,可以決定是否要進行攔截進而決定要返回一個什么樣的目標對象,官方提供了示例:return Plugin.wrap(target, this)。
  • intercept方法就是要進行攔截的時候要執行的方法。

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

Mybatis四大接口對象創建方法

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

  1. public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 
  2.   //確保ExecutorType不為空(defaultExecutorType有可能為空) 
  3.   executorType = executorType == null ? defaultExecutorType : executorType; 
  4.   executorType = executorType == null ? ExecutorType.SIMPLE : executorType; 
  5.   Executor executor;  if (ExecutorType.BATCH == executorType) { 
  6.    executor = new BatchExecutor(this, transaction); 
  7.   } else if (ExecutorType.REUSE == executorType) { 
  8.    executor = new ReuseExecutor(this, transaction); 
  9.   } else { 
  10.    executor = new SimpleExecutor(this, transaction); 
  11.   }  if (cacheEnabled) { 
  12.    executor = new CachingExecutor(executor); 
  13.   } 
  14.   executor = (Executor) interceptorChain.pluginAll(executor); 
  15.   return executor; 
  16.  
  17. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { 
  18.   StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); 
  19.   statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); 
  20.   return statementHandler; 
  21.  
  22. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { 
  23.   ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); 
  24.   parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); 
  25.   return parameterHandler; 
  26.  
  27. public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { 
  28.   ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); 
  29.   resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); 
  30.   return resultSetHandler; 

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

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

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

Plugin對象

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

  1. public static Object wrap(Object target, Interceptor interceptor) { 
  2.   // 獲取插件的Intercepts注解 
  3.   Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); 
  4.   Class<?> type = target.getClass(); 
  5.   Class<?>[] interfaces = getAllInterfaces(type, signatureMap); 
  6.   if (interfaces.length > 0) { 
  7.    return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); 
  8.   } 
  9.   return target; 

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

插件配置注解@Intercepts

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

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  2.   try { 
  3.    Set<Method> methods = signatureMap.get(method.getDeclaringClass()); 
  4.    if (methods != null && methods.contains(method)) { 
  5.      return interceptor.intercept(new Invocation(target, method, args)); 
  6.    } 
  7.    return method.invoke(target, args); 
  8.   } catch (Exception e) { 
  9.    throw ExceptionUtil.unwrapThrowable(e); 
  10.   } 

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

官方插件開發方式

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

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

自定義開發方式

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

  1. public class TestInterceptor implements Interceptor { 
  2.   public Object intercept(Invocation invocation) throws Throwable { 
  3.     Object target = invocation.getTarget(); //被代理對象 
  4.     Method method = invocation.getMethod(); //代理方法 
  5.     Object[] args = invocation.getArgs(); //方法參數 
  6.     // do something ...... 方法攔截前執行代碼塊 
  7.     Object result = invocation.proceed(); 
  8.     // do something .......方法攔截后執行代碼塊 
  9.     return result; 
  10.   } 
  11.   public Object plugin(final Object target) { 
  12.     return Proxy.newProxyInstance(Interceptor.class.getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { 
  13.       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  14.         return intercept(new Invocation(target, method, args)); 
  15.       } 
  16.     }); 
  17.   } 
  18.   public void setProperties(Properties properties) { 
  19.   } 

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

小結

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

  1. 所有可能被攔截的處理類都會生成一個代理
  2. 處理類代理在執行對應方法時,判斷要不要執行插件中的攔截方法
  3. 執行插接中的攔截方法后,推進目標的執行

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

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

  • 不編寫不必要的插件;
  • 實現plugin方法時判斷一下目標類型,是本插件要攔截的對象才執行Plugin.wrap方法,否者直接返回目標本身,這樣可以減少目標被代理的次數。
  1. // 假如我們只要攔截Executor對象,那么我們應該這么做 
  2. public Object plugin(final Object target) { 
  3.   if (target instanceof Executor) { 
  4.    return Plugin.wrap(target, this); 
  5.   } else { 
  6.    return target; 
  7.   } 

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

  1. /** 
  2.  * Mybatis - 通用分頁插件(如果開啟二級緩存需要注意) 
  3.  */ 
  4. @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}), 
  5.     @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) 
  6. @Log4j 
  7. public class PageHelper implements Interceptor { 
  8.  
  9.   public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>(); 
  10.  
  11.   /** 
  12.    * 開始分頁 
  13.    * 
  14.    * @param pageNum 
  15.    * @param pageSize 
  16.    */ 
  17.   public static void startPage(int pageNum, int pageSize) { 
  18.     localPage.set(new Page(pageNum, pageSize)); 
  19.   } 
  20.  
  21.   /** 
  22.    * 結束分頁并返回結果,該方法必須被調用,否則localPage會一直保存下去,直到下一次startPage 
  23.    * 
  24.    * @return 
  25.    */ 
  26.   public static Page endPage() { 
  27.     Page page = localPage.get(); 
  28.     localPage.remove(); 
  29.     return page; 
  30.   } 
  31.  
  32.   public Object intercept(Invocation invocation) throws Throwable { 
  33.     if (localPage.get() == null) { 
  34.       return invocation.proceed(); 
  35.     } 
  36.     if (invocation.getTarget() instanceof StatementHandler) { 
  37.       StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); 
  38.       MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler); 
  39.       // 分離代理對象鏈(由于目標類可能被多個插件攔截,從而形成多次代理,通過下面的兩次循環 
  40.       // 可以分離出最原始的的目標類) 
  41.       while (metaStatementHandler.hasGetter("h")) { 
  42.         Object object = metaStatementHandler.getValue("h"); 
  43.         metaStatementHandler = SystemMetaObject.forObject(object); 
  44.       } 
  45.       // 分離最后一個代理對象的目標類 
  46.       while (metaStatementHandler.hasGetter("target")) { 
  47.         Object object = metaStatementHandler.getValue("target"); 
  48.         metaStatementHandler = SystemMetaObject.forObject(object); 
  49.       } 
  50.       MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); 
  51.       //分頁信息if (localPage.get() != null) { 
  52.       Page page = localPage.get(); 
  53.       BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); 
  54.       // 分頁參數作為參數對象parameterObject的一個屬性 
  55.       String sql = boundSql.getSql(); 
  56.       // 重寫sql 
  57.       String pageSql = buildPageSql(sql, page); 
  58.       //重寫分頁sql 
  59.       metaStatementHandler.setValue("delegate.boundSql.sql", pageSql); 
  60.       Connection connection = (Connection) invocation.getArgs()[0]; 
  61.       // 重設分頁參數里的總頁數等 
  62.       setPageParameter(sql, connection, mappedStatement, boundSql, page); 
  63.       // 將執行權交給下一個插件 
  64.       return invocation.proceed(); 
  65.     } else if (invocation.getTarget() instanceof ResultSetHandler) { 
  66.       Object result = invocation.proceed(); 
  67.       Page page = localPage.get(); 
  68.       page.setResult((List) result); 
  69.       return result; 
  70.     } 
  71.     return null
  72.   } 
  73.  
  74.   /** 
  75.    * 只攔截這兩種類型的 
  76.    * <br>StatementHandler 
  77.    * <br>ResultSetHandler 
  78.    * 
  79.    * @param target 
  80.    * @return 
  81.    */ 
  82.   public Object plugin(Object target) { 
  83.     if (target instanceof StatementHandler || target instanceof ResultSetHandler) { 
  84.       return Plugin.wrap(target, this); 
  85.     } else { 
  86.       return target; 
  87.     } 
  88.   } 
  89.  
  90.   public void setProperties(Properties properties) { 
  91.  
  92.   } 
  93.  
  94.   /** 
  95.    * 修改原SQL為分頁SQL 
  96.    * 
  97.    * @param sql 
  98.    * @param page 
  99.    * @return 
  100.    */ 
  101.   private String buildPageSql(String sql, Page page) { 
  102.     StringBuilder pageSql = new StringBuilder(200); 
  103.     pageSql.append("select * from ("); 
  104.     pageSql.append(sql); 
  105.     pageSql.append(" ) temp limit ").append(page.getStartRow()); 
  106.     pageSql.append(" , ").append(page.getPageSize()); 
  107.     return pageSql.toString(); 
  108.   } 
  109.  
  110.   /** 
  111.    * 獲取總記錄數 
  112.    * 
  113.    * @param sql 
  114.    * @param connection 
  115.    * @param mappedStatement 
  116.    * @param boundSql 
  117.    * @param page 
  118.    */ 
  119.   private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement, 
  120.                  BoundSql boundSql, Page page) { 
  121.     // 記錄總記錄數 
  122.     String countSql = "select count(0) from (" + sql + ") temp"
  123.     PreparedStatement countStmt = null
  124.     ResultSet rs = null
  125.     try { 
  126.       countStmt = connection.prepareStatement(countSql); 
  127.       BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, 
  128.           boundSql.getParameterMappings(), boundSql.getParameterObject()); 
  129.       setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject()); 
  130.       rs = countStmt.executeQuery(); 
  131.       int totalCount = 0; 
  132.       if (rs.next()) { 
  133.         totalCount = rs.getInt(1); 
  134.       } 
  135.       page.setTotal(totalCount); 
  136.       int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1); 
  137.       page.setPages(totalPage); 
  138.     } catch (SQLException e) { 
  139.       log.error("Ignore this exception", e); 
  140.     } finally { 
  141.       try { 
  142.         rs.close(); 
  143.       } catch (SQLException e) { 
  144.         log.error("Ignore this exception", e); 
  145.       } 
  146.       try { 
  147.         countStmt.close(); 
  148.       } catch (SQLException e) { 
  149.         log.error("Ignore this exception", e); 
  150.       } 
  151.     } 
  152.   } 
  153.  
  154.   /** 
  155.    * 代入參數值 
  156.    * 
  157.    * @param ps 
  158.    * @param mappedStatement 
  159.    * @param boundSql 
  160.    * @param parameterObject 
  161.    * @throws SQLException 
  162.    */ 
  163.   private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, 
  164.                 Object parameterObject) throws SQLException { 
  165.     ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); 
  166.     parameterHandler.setParameters(ps); 
  167.   } 
  168.  
  169.   @Data //采用lombok插件編譯 
  170.   public static class Page<E> { 
  171.     private int pageNum; 
  172.     private int pageSize; 
  173.     private int startRow; 
  174.     private int endRow; 
  175.     private long total; 
  176.     private int pages; 
  177.     private List<E> result; 
  178.  
  179.     public Page(int pageNum, int pageSize) { 
  180.       this.pageNum = pageNum; 
  181.       this.pageSize = pageSize; 
  182.       this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0; 
  183.       this.endRow = pageNum * pageSize; 
  184.     } 
  185.  
  186.   } 

 

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2024-12-27 08:39:10

2011-06-09 17:26:17

Qt 插件 API

2009-12-11 10:29:03

PHP插件機制

2010-09-08 14:39:35

2021-06-22 06:52:46

Vite 插件機制Rollup

2025-01-02 10:10:51

2022-07-11 10:37:41

MapPart集合

2013-11-04 09:35:38

Firefox插件攔截FLASH

2024-07-17 09:23:58

Vite插件機制

2023-11-07 10:19:08

2020-12-10 08:21:27

XML映射Mybatis

2009-06-24 16:00:00

2024-05-06 00:00:00

C#工具代碼

2009-09-27 17:37:32

Hibernate攔截

2025-02-28 08:14:53

2024-02-28 09:35:52

2011-01-21 15:02:14

jQuerywebJavaScript

2023-09-05 08:58:07

2011-05-16 10:14:11

Hibernate

2011-04-07 17:54:22

Policing
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 黄视频免费 | 夜夜草av| 九九九久久国产免费 | 91高清在线观看 | 色综合成人网 | 日韩精品av | 国产资源网 | 在线成人一区 | 日韩美女一区二区三区在线观看 | 色五月激情五月 | 精品国产一级 | 亚洲手机视频在线 | 欧美日韩在线播放 | 日本中文在线 | 日韩毛片在线视频 | 国产成人综合久久 | 国产成人福利 | 久久婷婷色 | 欧美一区二区在线看 | 亚洲一区二区免费电影 | 亚洲精品乱码久久久久v最新版 | 国产精品久久久久久久7777 | 性天堂网 | 黑人巨大精品欧美一区二区免费 | 日本网站免费在线观看 | 欧美日韩亚洲成人 | 91av在线电影| 国产精品美女久久久久久免费 | 亚洲婷婷六月天 | 亚洲视频免费观看 | 一区二区三区视频免费观看 | 久久久男人的天堂 | 欧美日韩亚洲国产综合 | 56pao在线| 亚洲黄色av网站 | 国产精品欧美一区二区 | 欧美另类视频 | 一区二区三区不卡视频 | 国产精品v| 欧美一区二区三区 | 99精品欧美一区二区三区综合在线 |