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

小傅哥發表於2022-05-30

作者:小傅哥
部落格:https://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<T> 介面的過程)。整體設計如圖 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

工程原始碼https://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 這樣的反射器工具類進行處理。這些細節的功能點,讀者需要在學習的過程中,進行除錯驗證才能更好的吸收此類編碼設計的技巧和經驗。

相關文章