《Mybatis 手擼專欄》第10章:使用策略模式,調用參數處理器

作者:小傅哥

部落格://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!😄

一、前言

你這程式碼寫的,咋這麼軸呢!

說到軸,讓我想起初中上學時老師說的話:「你那腦瓜子,咋跟手燜子似的!」 東北話手燜子就是那種冬天戴的大棉手套,棉手套里的棉花都被壓的又沉又硬的了,所以來比喻腦瓜子笨。

而寫軸程式碼的大部分都是剛畢業沒多久,或者剛開始工作的碼農,畢竟經驗不足經歷不多,寫出一些不太好維護的程式碼也情有可原。而那些絕對多數鍛鍊出來的老碼農,其實程式碼的穩定程度、設計經驗、縝密邏輯,都是相對來說要好很多的。當然一部分老碼農,只是老了而已,程式碼還是那個程式碼!

所以企業招聘些年輕人,需要年輕的思想。但沒必要嚯嚯只是頭髮沒多少的老碼農,否則誰來給你平穩落地你那些天馬行空的想法呢!難道體驗、穩定、流暢,不應該是更值得追求的,非得喜歡全是愣頭青似的程式碼,寫出幾百個bug,造成大量資損和客訴,讓老闆覺得很爽?

二、目標

上一章節,小傅哥帶著大家細化的 XML 語句構建器,解耦在解析 XML 中的所需要處理的 Mapper 資訊,包括;SQL、入參、出參、類型,並對這些資訊進行記錄到 ParameterMapping 參數映射處理類中。那麼這個一章節我們將結合這部分參數的提取,對執行的 SQL 進行參數的自動化設置,而不是像我們之前那樣把參數寫成固定的,如圖 10-1 所示

圖 10-1 硬編碼參數設置

  • 在流程上,通過 DefaultSqlSession#selectOne 方法調用執行器,並通過預處理語句處理器 PreparedStatementHandler 執行參數設置和結果查詢。
  • 那麼這個流程中我們所處理的參數資訊,也就是每個 SQL 執行時,那些?號 需要被替換的地方,目前是通過硬編碼的方式進行處理的。而這就是本章節需要解決的問題,如果只是硬編碼完成參數設置,那麼對於所有那些不同類型的參數就沒法進行操作了。
  • 所以本章節需要結合結合上一章節所完成的語句構建器對 SQL 參數資訊的拆解,本章將會按照這些參數的解析,處理這裡硬編碼為自動化類型設置。針對不同類型的參數設置,這部分使用了什麼設計模式呢?

三、設計

這裡可以思考🤔下,參數的處理也就是通常我們使用 JDBC 直接操作資料庫時,所使用 ps.setXxx(i, parameter); 設置的各類參數。那麼在自動化解析 XML 中 SQL 拆分出所有的參數類型後,則應該根據不同的參數進行不同的類型設置,也就;Long 調用 ps.setLongString 調用 ps.setString 所以這裡需要使用策略模式,在解析 SQL 時按照不同的執行策略,封裝進去類型處理器(也就是是實現 TypeHandler 介面的過程)。整體設計如圖 10-2 所示

圖 10-2 策略模式處理參數處理器

  • 其實關於參數的處理,因為有很多的類型(Long\String\Object\...),所以這裡最重要的體現則是策略模式的使用。
  • 這裡包括了構建參數時根據類型,選擇對應的策略類型處理器,填充到參數映射集合中。另外一方面是參數的使用,也就是在執行 DefaultSqlSession#selectOne 的鏈路中,包括了參數的設置,按照參數的不同類型,獲取出對應的處理器,以及入參值。注意:由於入參值可能是一個對象中的屬性,所以這裡我們用到了前面章節實現的反射類工具 MetaObject 進行值的獲取,避免由於動態的對象,沒法硬編碼獲取屬性值。

四、實現

1. 工程結構

mybatis-step-09
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.mybatis
    │           ├── binding
    │           │   ├── MapperMethod.java
    │           │   ├── MapperProxy.java
    │           │   ├── MapperProxyFactory.java
    │           │   └── MapperRegistry.java
    │           ├── builder
    │           │   ├── xml
    │           │   │   ├── XMLConfigBuilder.java
    │           │   │   ├── XMLMapperBuilder.java
    │           │   │   └── XMLStatementBuilder.java
    │           │   ├── BaseBuilder.java
    │           │   ├── ParameterExpression.java
    │           │   ├── SqlSourceBuilder.java
    │           │   └── StaticSqlSource.java
    │           ├── datasource
    │           ├── executor
    │           │   ├── resultset
    │           │   │   └── ParameterHandler.java
    │           │   ├── resultset
    │           │   │   ├── DefaultResultSetHandler.java
    │           │   │   └── ResultSetHandler.java
    │           │   ├── statement
    │           │   │   ├── BaseStatementHandler.java
    │           │   │   ├── PreparedStatementHandler.java
    │           │   │   ├── SimpleStatementHandler.java
    │           │   │   └── StatementHandler.java
    │           │   ├── BaseExecutor.java
    │           │   ├── Executor.java
    │           │   └── SimpleExecutor.java
    │           ├── io
    │           ├── mapping
    │           │   ├── BoundSql.java
    │           │   ├── Environment.java
    │           │   ├── MappedStatement.java
    │           │   ├── ParameterMapping.java
    │           │   ├── SqlCommandType.java
    │           │   └── SqlSource.java
    │           ├── parsing
    │           ├── reflection
    │           ├── scripting
    │           │   ├── defaults
    │           │   │   └── DefaultParameterHandler.java
    │           │   ├── xmltags
    │           │   │   ├── DynamicContext.java
    │           │   │   ├── MixedSqlNode.java
    │           │   │   ├── SqlNode.java
    │           │   │   ├── StaticTextSqlNode.java
    │           │   │   ├── XMLLanguageDriver.java
    │           │   │   └── XMLScriptBuilder.java
    │           │   ├── LanguageDriver.java
    │           │   └── LanguageDriverRegistry.java
    │           ├── session
    │           │   ├── defaults
    │           │   │   ├── DefaultSqlSession.java
    │           │   │   └── DefaultSqlSessionFactory.java
    │           │   ├── Configuration.java
    │           │   ├── ResultHandler.java
    │           │   ├── SqlSession.java
    │           │   ├── SqlSessionFactory.java
    │           │   ├── SqlSessionFactoryBuilder.java
    │           │   └── TransactionIsolationLevel.java
    │           ├── transaction
    │           └── type
    │               ├── BaseTypeHandler.java
    │               ├── JdbcType.java
    │               ├── LongTypeHandler.java
    │               ├── StringTypeHandler.java
    │               ├── TypeAliasRegistry.java
    │               ├── TypeHandler.java
    │               └── TypeHandlerRegistry.java
    └── test
        ├── java
        │   └── cn.bugstack.mybatis.test.dao
        │       ├── dao
        │       │   └── IUserDao.java
        │       ├── po
        │       │   └── User.java
        │       └── ApiTest.java
        └── resources
            ├── mapper
            │   └──User_Mapper.xml
            └── mybatis-config-datasource.xml

工程源碼//github.com/fuzhengwei/small-mybatis

使用策略模式,處理參數處理器核心類關係,如圖 10-3 所示

圖 10-3 使用策略模式,處理參數處理器核心類關係

核心處理主要分為三塊;類型處理、參數設置、參數使用;

  • 以定義 TypeHandler 類型處理器策略介面,實現不同的處理策略,包括;Long、String、Integer 等。這裡我們先只實現2種類型,讀者在學習過程中,可以按照這個結構來添加其他類型。
  • 類型策略處理器實現完成後,需要註冊到處理器註冊機中,後續其他模組參數的設置還是使用都是從 Configuration 中獲取到 TypeHandlerRegistry 進行使用。
  • 那麼有了這樣的策略處理器以後,在進行操作解析 SQL 的時候,就可以按照不同的類型把對應的策略處理器設置到 BoundSql#parameterMappings 參數里,後續使用也是從這裡進行獲取。

2. 入參數校準

這裡我們要先解決一個小問題,不知道讀者在我們所實現的源碼中,是否注意到這樣一個參數的傳遞,如圖 10-4

圖 10-4 參數設置時入參獲取

  • 這裡的參數傳遞後,需要獲取第0個參數,而且是硬編碼固定的。這是為什麼呢?這個第0個參數是哪來的,我們介面裡面調用的方法,參數不是一個嗎?就像:User queryUserInfoById(Long id);
  • 其實這個參數來自於映射器代理類 MapperProxy#invoke 中,因為 invoke 反射調用的方法,入參中是 Object[] args,所以這個參數被傳遞到後續的參數設置中。而我們的 DAO 測試類是一個已知的固定參數,所以後面硬編碼了獲取了第0個參數。

    • JDK 反射調用方法操作固定方法入參
  • 那麼結合這樣的問題,我們則需要根據方法的資訊,給方法做簽名操作,以便於轉換入參資訊為方法的資訊。比如數組轉換為對應的對象。

源碼詳見cn.bugstack.mybatis.binding.MapperMethod

public class MapperMethod {

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result = null;
        switch (command.getType()) {
            case SELECT:
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                break;
            default:
                throw new RuntimeException("Unknown execution method for: " + command.getName());
        }
        return result;
    }

    /**
     * 方法簽名
     */
    public static class MethodSignature {

        public Object convertArgsToSqlCommandParam(Object[] args) {
            final int paramCount = params.size();
            if (args == null || paramCount == 0) {
                // 如果沒參數
                return null;
            } else if (paramCount == 1) {
                return args[params.keySet().iterator().next().intValue()];
            } else {
                // 否則,返回一個ParamMap,修改參數名,參數名就是其位置
                final Map<String, Object> param = new ParamMap<Object>();
                int i = 0;
                for (Map.Entry<Integer, String> entry : params.entrySet()) {
                    // 1.先加一個#{0},#{1},#{2}...參數
                    param.put(entry.getValue(), args[entry.getKey().intValue()]);
                    // ...
                }
                return param;
            }
        }

    }
}
  • 在映射器方法中 MapperMethod#execute 將原來的直接將參數 args 傳遞給 SqlSession#selectOne 方法,調整為轉換後再傳遞對象。
  • 其實這裡的轉換操作就是來自於 Method#getParameterTypes 對參數的獲取和處理,與 args 進行比對。如果是單個參數,則直接返回參數 Tree 樹結構下的對應節點值。非單個類型,則需要進行循環處理,這樣轉換後的參數才能被直接使用。

3. 參數策略處理器

在 Mybatis 的源碼包中,有一個 type 包,這個包下所提供的就是一套參數的處理策略集合。它通過定義類型處理器介面、由抽象模板實現並定義標準流程,定提取抽象方法交給子類實現,這些子類就是各個類型處理器的具體實現。

3.1 策略介面

源碼詳見cn.bugstack.mybatis.type.TypeHandler

public interface TypeHandler<T> {

    /**
     * 設置參數
     */
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

}
  • 首先定義一個類型處理器的介面,這和我們在日常的業務開發中是類似的,就像如果是發貨商品,則定義一個統一標準介面,之後根據這個介面實現出不同的發貨策略。
  • 這裡設置參數也是一樣,所有不同類型的參數,都可以被提取出來這些標準的參數欄位和異常,後續的子類按照這個標準實現即可。Mybatis 源碼中有30+個類型處理

3.2 模板模式

源碼詳見cn.bugstack.mybatis.type.BaseTypeHandler

public abstract class BaseTypeHandler<T> implements TypeHandler<T> {

    @Override
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        // 定義抽象方法,由子類實現不同類型的屬性設置
        setNonNullParameter(ps, i, parameter, jdbcType);
    }

    protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

}
  • 通過抽象基類的流程模板定義,便於一些參數的判斷和處理。不過目前我們還不需要那麼多的流程校驗,所以這裡只是定義和調用了一個最基本的抽象方法 setNonNullParameter。
  • 不過有一個這樣的結構,可以讓大家更加清楚整個 Mybatis 源碼的框架,便於後續閱讀或者擴展此部分源碼的時候,有一個框架結構的認知。

3.3 子類實現

源碼詳見cn.bugstack.mybatis.type.*

/**
 * @description Long類型處理器
 */
public class LongTypeHandler extends BaseTypeHandler<Long> {

    @Override
    protected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
        ps.setLong(i, parameter);
    }

}

/**
 * @description String類型處理器
 */
public class StringTypeHandler extends BaseTypeHandler<String>{

    @Override
    protected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter);
    }

}
  • 這裡的介面實現舉了個例子,分別是;LongTypeHandler、StringTypeHandler,在 Mybatis 源碼中還有很多其他類型,這裡我們暫時不需要實現那麼多,只要清楚這個處理過程和編碼方式即可。大家在學習中,可以嘗試再添加幾個其他類型,用於學習驗證

3.4 類型註冊機

類型處理器註冊機 TypeHandlerRegistry 是我們前面章節實現的,這裡只需要在這個類結構下,註冊新的類型就可以了。

源碼詳見cn.bugstack.mybatis.type.TypeHandlerRegistry

public final class TypeHandlerRegistry {

    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<>();
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<>();

    public TypeHandlerRegistry() {
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());

        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    }
 
    //...
}    
  • 這裡在構造函數中,新增加了 LongTypeHandler、StringTypeHandler 兩種類型的註冊器。
  • 同時可以注意到,無論是對象類型,還是基本類型,都是一個類型處理器。只不過在註冊的時候多註冊了一個。這種操作方式和我們平常的業務開發中,也是一樣的。一種是多註冊,另外一種是判斷處理。

4. 參數構建

相對於前面章節所完成的內容,這個章節需要對 SqlSourceBuilder 源碼構建器中,創建參數映射 ParameterMapping 需要添加參數處理器的內容。因為只有這樣才能方便的從參數映射中獲取到對應類型的處理器進行使用。

那麼就需要完善 ParameterMapping 添加 TypeHandler 屬性資訊,以及在 ParameterMappingTokenHandler#buildParameterMapping 處理參數映射時,構建出參數的映射。這一部分是在上一章節的實現過程中,細化的完善部分,如圖 10-6

圖 10-6 構建參數映射

那麼結合上一章節,這裡我們開始擴展出類型的設置。同時注意 MetaClass 反射工具類的使用

源碼詳見cn.bugstack.mybatis.builder.SqlSourceBuilder

// 構建參數映射
private ParameterMapping buildParameterMapping(String content) {
    // 先解析參數映射,就是轉化成一個 HashMap | #{favouriteSection,jdbcType=VARCHAR}
    Map<String, String> propertiesMap = new ParameterExpression(content);
    String property = propertiesMap.get("property");
    Class<?> propertyType;
    if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
        propertyType = parameterType;
    } else if (property != null) {
        MetaClass metaClass = MetaClass.forClass(parameterType);
        if (metaClass.hasGetter(property)) {
            propertyType = metaClass.getGetterType(property);
        } else {
            propertyType = Object.class;
        }
    } else {
        propertyType = Object.class;
    }
    logger.info("構建參數映射 property:{} propertyType:{}", property, propertyType);
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
    return builder.build();
}
  • 這一部分就是對參數的細化處理,構建出參數的映射關係,首先是 if 判斷對應的參數類型是否在 TypeHandlerRegistry 註冊器中,如果不在則拆解對象,按屬性進行獲取 propertyType 的操作。
  • 這一塊也用到了 MetaClass 反射工具類的使用,它的存在可以讓我們更加方便的處理,否則還需要要再寫反射類進行獲取對象屬性操作。

5. 參數使用

參數構建完成後,就可以在 DefaultSqlSession#selectOne 調用時設置參數使用了。那麼這裡的鏈路關係;Executor#query - > SimpleExecutor#doQuery -> StatementHandler#parameterize -> PreparedStatementHandler#parameterize -> ParameterHandler#setParameters 到了 ParameterHandler#setParameters 就可以看到了根據參數的不同處理器循環設置參數。

源碼詳見cn.bugstack.mybatis.scripting.defaults.DefaultParameterHandler

public class DefaultParameterHandler implements ParameterHandler {

    @Override
    public void setParameters(PreparedStatement ps) throws SQLException {
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (null != parameterMappings) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                String propertyName = parameterMapping.getProperty();
                Object value;
                if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    // 通過 MetaObject.getValue 反射取得值設進去
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                JdbcType jdbcType = parameterMapping.getJdbcType();

                // 設置參數
                logger.info("根據每個ParameterMapping中的TypeHandler設置對應的參數資訊 value:{}", JSON.toJSONString(value));
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
            }
        }
    }

}
  • 每一個循環的參數設置,都是從 BoundSql 中獲取 ParameterMapping 集合進行循環操作,而這個集合參數就是我們前面 ParameterMappingTokenHandler#buildParameterMapping 構建參數映射時處理的。
  • 設置參數時根據參數的 parameterObject 入參的資訊,判斷是否基本類型,如果不是則從對象中進行拆解獲取(也就是一個對象A中包括屬性b),處理完成後就可以準確拿到對應的入參值了。因為在映射器方法 MapperMethod 中已經處理了一遍方法簽名,所以這裡的入參就更方便使用了
  • 基本資訊獲取完成後,則根據參數類型獲取到對應的 TypeHandler 類型處理器,也就是找到 LongTypeHandler、StringTypeHandler 等,確定找到以後,則可以進行對應的參數設置了 typeHandler.setParameter(ps, i + 1, value, jdbcType) 通過這樣的方式把我們之前硬編碼的操作進行解耦。

五、測試

1. 事先準備

1.1 創建庫表

創建一個資料庫名稱為 mybatis 並在庫中創建表 user 以及添加測試數據,如下:

CREATE TABLE
    USER
    (
        id bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',
        userId VARCHAR(9) COMMENT '用戶ID',
        userHead VARCHAR(16) COMMENT '用戶頭像',
        createTime TIMESTAMP NULL COMMENT '創建時間',
        updateTime TIMESTAMP NULL COMMENT '更新時間',
        userName VARCHAR(64),
        PRIMARY KEY (id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
insert into user (id, userId, userHead, createTime, updateTime, userName) values (1, '10001', '1_04', '2022-04-13 00:00:00', '2022-04-13 00:00:00', '小傅哥');    

1.2 配置數據源

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </dataSource>
    </environment>
</environments>
  • 通過 mybatis-config-datasource.xml 配置數據源資訊,包括:driver、url、username、password
  • 在這裡 dataSource 可以按需配置成 DRUID、UNPOOLED 和 POOLED 進行測試驗證。

1.3 配置Mapper

<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.bugstack.mybatis.test.po.User">
    SELECT id, userId, userName, userHead
    FROM user
    where id = #{id}
</select>

<select id="queryUserInfo" parameterType="cn.bugstack.mybatis.test.po.User" resultType="cn.bugstack.mybatis.test.po.User">
    SELECT id, userId, userName, userHead
    FROM user
    where id = #{id} and userId = #{userId}
</select>
  • 這部分暫時不需要調整,目前還只是一個入參的類型的參數,後續我們全部完善這部分內容以後,則再提供更多的其他參數進行驗證。

2. 單元測試

源碼詳見cn.bugstack.mybatis.test.ApiTest

@Before
public void init() throws IOException {
    // 1. 從SqlSessionFactory中獲取SqlSession
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
    sqlSession = sqlSessionFactory.openSession();
}
  • 因為接下來我們需要驗證兩種不同入參的單元測試,分別來測試基本類型參數和對象類型參數。

2.1 基本類型參數

@Test
public void test_queryUserInfoById() {
    // 1. 獲取映射器對象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    // 2. 測試驗證:基本參數
    User user = userDao.queryUserInfoById(1L);
    logger.info("測試結果:{}", JSON.toJSONString(user));
}

07:40:08.531 [main] INFO  c.b.mybatis.builder.SqlSourceBuilder - 構建參數映射 property:id propertyType:class java.lang.Long
07:40:08.598 [main] INFO  c.b.m.s.defaults.DefaultSqlSession - 執行查詢 statement:cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfoById parameter:1
07:40:08.875 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 183284570.
07:40:08.894 [main] INFO  c.b.m.s.d.DefaultParameterHandler - 根據每個ParameterMapping中的TypeHandler設置對應的參數資訊 value:1
07:40:08.961 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 測試結果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
  • 測試過程中可以在 DefaultParameterHandler#setParameters 中打斷點,驗證方法參數以及獲得到的類型處理器,這裡測試驗證通過,可以滿足基本類型對象的入參資訊。

2.2 對象類型參數

@Test
public void test_queryUserInfo() {
    // 1. 獲取映射器對象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    // 2. 測試驗證:對象參數
    User user = userDao.queryUserInfo(new User(1L, "10001"));
    logger.info("測試結果:{}", JSON.toJSONString(user));
}

07:41:11.025 [main] INFO  c.b.mybatis.builder.SqlSourceBuilder - 構建參數映射 property:userId propertyType:class java.lang.String
07:41:11.232 [main] INFO  c.b.m.s.defaults.DefaultSqlSession - 執行查詢 statement:cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfo parameter:{"id":1,"userId":"10001"}
07:41:11.638 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 402405659.
07:41:11.661 [main] INFO  c.b.m.s.d.DefaultParameterHandler - 根據每個ParameterMapping中的TypeHandler設置對應的參數資訊 value:1
07:43:28.516 [main] INFO  c.b.m.s.d.DefaultParameterHandler - 根據每個ParameterMapping中的TypeHandler設置對應的參數資訊 value:"10001"
07:43:30.820 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 測試結果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
  • 此案例主要驗證對象參數 User 中包含兩個屬性時,檢查我們的程式碼處理過程,驗證是否可以正確獲取到兩個類型處理器,分別設置參數的過程。
  • 從測試結果中,可以看到測試通過,並列印了相關參數的構建和使用。

六、總結

  • 到本章節,我們算是把一個 ORM 框架的基本流程串聯起來了,不要硬編碼也能完成簡單 SQL 的處理。讀者夥伴可以仔細閱讀下當前框架中,所包含的分包結構。比如:構建、綁定、映射、反射、執行、類型、事務、數據源等等,嘗試畫一畫他們的鏈接關係,這樣會讓你更加清晰現在的程式碼解耦結構。
  • 此章節中比較重要的體現是關於參數類型的策略化設計,通過策略解耦,模板定義流程,讓我們整個參數設置變得更加清晰,也就不需要硬編碼了。
  • 除此之外也有一些細節的功能點,如;MapperMethod 中添加方法簽名、類型處理器創建和使用時候,都使用了 MetaObject 這樣的反射器工具類進行處理。這些細節的功能點,讀者需要在學習的過程中,進行調試驗證才能更好的吸收此類編碼設計的技巧和經驗。