主頁 > 軟體設計 > 手把手教你開發 MyBatis 插件

手把手教你開發 MyBatis 插件

2021-03-03 16:55:54 軟體設計

小伙伴們元宵節快樂,記得吃元宵哦~

在日常開發中,小伙伴們多多少少都有用過 MyBatis 插件,松哥猜測大家用的最多的就是 MyBatis 的分頁插件!不知道小伙伴們有沒有想過有一天自己也來開發一個 MyBatis 插件?

其實自己動手擼一個 MyBatis 插件并不難,今天松哥就把手帶大家擼一個 MyBatis 插件!

1.MyBatis 插件介面

即使你沒開發過 MyBatis 插件,估計也能猜出來,MyBatis 插件是通過攔截器來起作用的,MyBatis 框架在設計的時候,就已經為插件的開發預留了相關介面,如下:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

這個介面中就三個方法,第一個方法必須實作,后面兩個方法都是可選的,三個方法作用分別如下:

  1. intercept:這個就是具體的攔截方法,我們自定義 MyBatis 插件時,一般都需要重寫該方法,我們插件所完成的作業也都是在該方法中完成的,

  2. plugin:這個方法的引數 target 就是攔截器要攔截的物件,一般來說我們不需要重寫該方法,Plugin.wrap 方法會自動判斷攔截器的簽名和被攔截物件的介面是否匹配,如果匹配,才會通過動態代理攔截目標物件,

  3. setProperties:這個方法用來傳遞插件的引數,可以通過引數來改變插件的行為,我們定義好插件之后,需要對插件進行配置,在配置的時候,可以給插件設定相關屬性,設定的屬性可以通過該方法獲取到,插件屬性設定像下面這樣:

<plugins>
    <plugin interceptor="org.javaboy.mybatis03.plugin.CamelInterceptor">
        <property name="xxx" value="xxx"/>
    </plugin>
</plugins>

2.MyBatis 攔截器簽名

攔截器定義好了后,攔截誰?

這個就需要攔截器簽名來完成了!

攔截器簽名是一個名為 @Intercepts 的注解,該注解中可以通過 @Signature 配置多個簽名,@Signature 注解中則包含三個屬性:

  • type: 攔截器需要攔截的介面,有 4 個可選項,分別是:Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler,
  • method: 攔截器所攔截介面中的方法名,也就是前面四個介面中的方法名,介面和方法要對應上,
  • args: 攔截器所攔截方法的引數型別,通過方法名和引數型別可以鎖定唯一一個方法,

一個簡單的簽名可能像下面這樣:

@Intercepts(@Signature(
        type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class}
))
public class CamelInterceptor implements Interceptor {
    //...
}

3.被攔截的物件

根據前面的介紹,被攔截的物件主要有如下四個:

Executor

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

各方法含義分別如下:

  • update:該方法會在所有的 INSERT、 UPDATE、 DELETE 執行時被呼叫,如果想要攔截這些操作,可以通過該方法實作,
  • query:該方法會在 SELECT 查詢方法執行時被呼叫,方法引數攜帶了很多有用的資訊,如果需要獲取,可以通過該方法實作,
  • queryCursor:當 SELECT 的回傳型別是 Cursor 時,該方法會被呼叫,
  • flushStatements:當 SqlSession 方法呼叫 flushStatements 方法或執行的介面方法中帶有 @Flush 注解時該方法會被觸發,
  • commit:當 SqlSession 方法呼叫 commit 方法時該方法會被觸發,
  • rollback:當 SqlSession 方法呼叫 rollback 方法時該方法會被觸發,
  • getTransaction:當 SqlSession 方法獲取資料庫連接時該方法會被觸發,
  • close:該方法在懶加載獲取新的 Executor 后會被觸發,
  • isClosed:該方法在懶加載執行查詢前會被觸發,

ParameterHandler

public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps) throws SQLException;

}

各方法含義分別如下:

  • getParameterObject:在執行存盤程序處理出參的時候該方法會被觸發,
  • setParameters:設定 SQL 引數時該方法會被觸發,

ResultSetHandler

public interface ResultSetHandler {

  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

各方法含義分別如下:

  • handleResultSets:該方法會在所有的查詢方法中被觸發(除去回傳值型別為 Cursor 的查詢方法),一般來說,如果我們想對查詢結果進行二次處理,可以通過攔截該方法實作,
  • handleCursorResultSets:當查詢方法的回傳值型別為 Cursor 時,該方法會被觸發,
  • handleOutputParameters:使用存盤程序處理出參的時候該方法會被呼叫,

StatementHandler

public interface StatementHandler {

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

各方法含義分別如下:

  • prepare:該方法在資料庫執行前被觸發,
  • parameterize:該方法在 prepare 方法之后執行,用來處理引數資訊,
  • batch:如果 MyBatis 的全劇配置中配置了 defaultExecutorType=”BATCH”,執行資料操作時該方法會被呼叫,
  • update:更新操作時該方法會被觸發,
  • query:該方法在 SELECT 方法執行時會被觸發,
  • queryCursor:該方法在 SELECT 方法執行時,并且回傳值為 Cursor 時會被觸發,

在開發一個具體的插件時,我們應當根據自己的需求來決定到底攔截哪個方法,

4.開發分頁插件

4.1 記憶體分頁

MyBatis 中提供了一個不太好用的記憶體分頁功能,就是一次性把所有資料都查詢出來,然后在記憶體中進行分頁處理,這種分頁方式效率很低,基本上沒啥用,但是如果我們想要自定義分頁插件,就需要對這種分頁方式有一個簡單了解,

記憶體分頁的使用方式如下,首先在 Mapper 中添加 RowBounds 引數,如下:

public interface UserMapper {
    List<User> getAllUsersByPage(RowBounds rowBounds);
}

然后在 XML 檔案中定義相關 SQL:

<select id="getAllUsersByPage" resultType="org.javaboy.mybatis03.model.User">
    select * from user
</select>

可以看到,在 SQL 定義時,壓根不用管分頁的事情,MyBatis 會查詢到所有的資料,然后在記憶體中進行分頁處理,

Mapper 中方法的呼叫方式如下:

@Test
public void test3() {
    UserMapper userMapper = sqlSessionFactory.openSession().getMapper(UserMapper.class);
    RowBounds rowBounds = new RowBounds(1,2);
    List<User> list = userMapper.getAllUsersByPage(rowBounds);
    for (User user : list) {
        System.out.println("user = " + user);
    }
}

構建 RowBounds 時傳入兩個引數,分別是 offset 和 limit,對應分頁 SQL 中的兩個引數,也可以通過 RowBounds.DEFAULT 的方式構建一個 RowBounds 實體,這種方式構建出來的 RowBounds 實體,offset 為 0,limit 則為 Integer.MAX_VALUE,也就相當于不分頁,

這就是 MyBatis 中提供的一個很不實用的記憶體分頁功能,

了解了 MyBatis 自帶的記憶體分頁之后,接下來我們就可以來看看如何自定義分頁插件了,

4.2 自定義分頁插件

首先要宣告一下,這里松哥帶大家自定義 MyBatis 分頁插件,主要是想通過這個東西讓小伙伴們了解自定義 MyBatis 插件的一些條條框框,了解整個自定義插件的流程,分頁插件并不是我們的目的,自定義分頁插件只是為了讓大家的學習程序變得有趣一些而已,

接下來我們就來開啟自定義分頁插件之旅,

首先我們需要自定義一個 RowBounds,因為 MyBatis 原生的 RowBounds 是記憶體分頁,并且沒有辦法獲取到總記錄數(一般分頁查詢的時候我們還需要獲取到總記錄數),所以我們自定義 PageRowBounds,對原生的 RowBounds 功能進行增強,如下:

public class PageRowBounds extends RowBounds {
    private Long total;

    public PageRowBounds(int offset, int limit) {
        super(offset, limit);
    }

    public PageRowBounds() {
    }

    public Long getTotal() {
        return total;
    }

    public void setTotal(Long total) {
        this.total = total;
    }
}

可以看到,我們自定義的 PageRowBounds 中增加了 total 欄位,用來保存查詢的總記錄數,

接下來我們自定義攔截器 PageInterceptor,如下:

@Intercepts(@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
))
public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        if (rowBounds != RowBounds.DEFAULT) {
            Executor executor = (Executor) invocation.getTarget();
            BoundSql boundSql = ms.getBoundSql(parameterObject);
            Field additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
            additionalParametersField.setAccessible(true);
            Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
            if (rowBounds instanceof PageRowBounds) {
                MappedStatement countMs = newMappedStatement(ms, Long.class);
                CacheKey countKey = executor.createCacheKey(countMs, parameterObject, RowBounds.DEFAULT, boundSql);
                String countSql = "select count(*) from (" + boundSql.getSql() + ") temp";
                BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
                Set<String> keySet = additionalParameters.keySet();
                for (String key : keySet) {
                    countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
                }
                List<Object> countQueryResult = executor.query(countMs, parameterObject, RowBounds.DEFAULT, (ResultHandler) args[3], countKey, countBoundSql);
                Long count = (Long) countQueryResult.get(0);
                ((PageRowBounds) rowBounds).setTotal(count);
            }
            CacheKey pageKey = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql);
            pageKey.update("RowBounds");
            String pageSql = boundSql.getSql() + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit();
            BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameterObject);
            Set<String> keySet = additionalParameters.keySet();
            for (String key : keySet) {
                pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
            }
            List list = executor.query(ms, parameterObject, RowBounds.DEFAULT, (ResultHandler) args[3], pageKey, pageBoundSql);
            return list;
        }
        //不需要分頁,直接回傳結果
        return invocation.proceed();
    }

    private MappedStatement newMappedStatement(MappedStatement ms, Class<Long> longClass) {
        MappedStatement.Builder builder = new MappedStatement.Builder(
                ms.getConfiguration(), ms.getId() + "_count", ms.getSqlSource(), ms.getSqlCommandType()
        );
        ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), longClass, new ArrayList<>(0)).build();
        builder.resource(ms.getResource())
                .fetchSize(ms.getFetchSize())
                .statementType(ms.getStatementType())
                .timeout(ms.getTimeout())
                .parameterMap(ms.getParameterMap())
                .resultSetType(ms.getResultSetType())
                .cache(ms.getCache())
                .flushCacheRequired(ms.isFlushCacheRequired())
                .useCache(ms.isUseCache())
                .resultMaps(Arrays.asList(resultMap));
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        return builder.build();
    }
}

這是我們今天定義的核心代碼,涉及到的知識點松哥來給大家一個一個剖析,

  1. 首先通過 @Intercepts 注解配置攔截器簽名,從 @Signature 的定義中我們可以看到,攔截的是 Executor#query 方法,該方法有一個多載方法,通過 args 指定了方法引數,進而鎖定了多載方法(實際上該方法的另一個多載方法我們沒法攔截,那個是 MyBatis 內部呼叫的,這里不做討論),
  2. 將查詢操作攔截下來之后,接下來我們的操作主要在 PageInterceptor#intercept 方法中完成,該方法的引數重包含了攔截物件的諸多資訊,
  3. 通過 invocation.getArgs() 獲取攔截方法的引數,獲取到的是一個陣列,正常來說這個陣列的長度為 4,陣列第一項是一個 MappedStatement,我們在 Mapper.xml 中定義的各種操作節點和 SQL,都被封裝成一個個的 MappedStatement 物件了;陣列第二項就是所攔截方法的具體引數,也就是你在 Mapper 介面中定義的方法引數;陣列的第三項是一個 RowBounds 物件,我們在 Mapper 介面中定義方法時不一定使用了 RowBounds 物件,如果我們沒有定義 RowBounds 物件,系統會給我們提供一個默認的 RowBounds.DEFAULT;陣列第四項則是一個處理回傳值的 ResultHandler,
  4. 接下來判斷上一步提取到的 rowBounds 物件是否不為 RowBounds.DEFAULT,如果為 RowBounds.DEFAULT,說明用戶不想分頁;如果不為 RowBounds.DEFAULT,則說明用戶想要分頁,如果用戶不想分頁,則直接執行最后的 return invocation.proceed();,讓方法繼續往下走就行了,
  5. 如果需要進行分頁,則先從 invocation 物件中取出執行器 Executor、BoundSql 以及通過反射拿出來 BoundSql 中保存的額外引數(如果我們使用了動態 SQL,可能會存在該引數),BoundSql 中封裝了我們執行的 Sql 以及相關的引數,
  6. 接下來判斷 rowBounds 是否是 PageRowBounds 的實體,如果是,說明除了分頁查詢,還想要查詢總記錄數,如果不是,則說明 rowBounds 可能是 RowBounds 實體,此時只要分頁即可,不用查詢總記錄數,
  7. 如果需要查詢總記錄數,則首先呼叫 newMappedStatement 方法構造出一個新的 MappedStatement 物件出來,這個新的 MappedStatement 物件的回傳值是 Long 型別的,然后分別創建查詢的 CacheKey、拼接查詢的 countSql,再根據 countSql 構建出 countBoundSql,再將額外引數添加進 countBoundSql 中,最后通過 executor.query 方法完成查詢操作,并將查詢結果賦值給 PageRowBounds 中的 total 屬性,
  8. 接下來進行分頁查詢,有了第七步的介紹之后,分頁查詢就很簡單了,這里就不細說了,唯一需要強調的是,當我們啟動了這個分頁插件之后,MyBatis 原生的 RowBounds 記憶體分頁會變成物理分頁,原因就在這里我們修改了查詢 SQL,
  9. 最后將查詢結果回傳,

在前面的代碼中,我們一共在兩個地方重新組織了 SQL,一個是查詢總記錄數的時候,另一個則是分頁的時候,都是通過 boundSql.getSql() 獲取到 Mapper.xml 中的 SQL 然后進行改裝,有的小伙伴在 Mapper.xml 中寫 SQL 的時候不注意,結尾可能加上了 ;,這會導致分頁插件重新組裝的 SQL 運行出錯,這點需要注意,松哥在 GitHub 上看到的其他 MyBatis 分頁插件也是一樣的,Mapper.xml 中 SQL 結尾不能有 ;

如此之后,我們的分頁插件就算是定義成功了,

5.測驗

接下來我們對我們的分頁插件進行一個簡單測驗,

首先我們需要在全域配置中配置分頁插件,配置方式如下:

<plugins>
    <plugin interceptor="org.javaboy.mybatis03.plugin.PageInterceptor"></plugin>
</plugins>

接下來我們在 Mapper 中定義查詢介面:

public interface UserMapper {
    List<User> getAllUsersByPage(RowBounds rowBounds);
}

接下來定義 UserMapper.xml,如下:

<select id="getAllUsersByPage" resultType="org.javaboy.mybatis03.model.User">
    select * from user
</select>

最后我們進行測驗:

@Test
public void test3() {
    UserMapper userMapper = sqlSessionFactory.openSession().getMapper(UserMapper.class);
    List<User> list = userMapper.getAllUsersByPage(new RowBounds(1,2));
    for (User user : list) {
        System.out.println("user = " + user);
    }
}

這里在查詢時,我們使用了 RowBounds 物件,就只會進行分頁,而不會統計總記錄數,需要注意的時,此時的分頁已經不是記憶體分頁,而是物理分頁了,這點我們從列印出來的 SQL 中也能看到,如下:

可以看到,查詢的時候就已經進行了分頁了,

當然,我們也可以使用 PageRowBounds 進行測驗,如下:

@Test
public void test4() {
    UserMapper userMapper = sqlSessionFactory.openSession().getMapper(UserMapper.class);
    PageRowBounds pageRowBounds = new PageRowBounds(1, 2);
    List<User> list = userMapper.getAllUsersByPage(pageRowBounds);
    for (User user : list) {
        System.out.println("user = " + user);
    }
    System.out.println("pageRowBounds.getTotal() = " + pageRowBounds.getTotal());
}

此時通過 pageRowBounds.getTotal() 方法我們就可以獲取到總記錄數,

6.小結

好啦,今天主要和小伙伴們分享了我們如何自己開發一個 MyBatis 插件,插件功能其實都是次要的,最主要是希望小伙伴們能夠理解 MyBatis 的作業流程,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/265439.html

標籤:其他

上一篇:Codeforces Global Round 13 D. Zookeeper and The Infinite Zoo(思維,位運算)

下一篇:帶你認識清華標桿課教師 | 卓晴:自帶BGM的硬核“技術流”教師

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more