掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
上班就像打怪升級,拿著一把西瓜刀,從南天門砍到北天門。但時間長了,怪越來越兇了,西瓜刀也不得手了。咋辦,在游戲里大家肯定是想辦法換裝備了、買武器了、學(xué)技能了,這樣才能有機(jī)會打通更多的關(guān)卡。

其實我們作為程序員上班也是一樣的,如果一直都以為這點技術(shù)夠?qū)憣慍RUD就夠了,反正現(xiàn)在還能應(yīng)付的了。但3年后呢、5年后呢,總有一天你的技術(shù)根本沒法滿足公司對你現(xiàn)階段的要求,最簡單的CRUD也早已交給了曾經(jīng)年輕的另外的你。
有人說:“程序員不是技術(shù)牛就能一直行!” 但其實技術(shù)牛就是行,當(dāng)你牛到一定的階段,解決別人解決不了的問題,處理別人處理的不了的方案,蝎子粑粑獨(dú)一份,誰又能攔得住你呢。在哪里工作都是你自己來定的,你只管技術(shù)牛,就能橫著走。
延續(xù)著上一章節(jié),我們對參數(shù)的封裝和調(diào)用,使用了策略模式進(jìn)行解耦處理,本章節(jié)將對執(zhí)行完查詢的結(jié)果進(jìn)行封裝處理。而不是像我們前面章節(jié)那樣粗魯?shù)呐袛喾庋b,因為這樣的方式既不能滿足不同類型的優(yōu)雅擴(kuò)展,也不以為維護(hù)迭代。如圖 11-1 所示
圖 11-1 簡單的結(jié)果集處理
在我們使用 JDBC 獲取到查詢結(jié)果 ResultSet#getObject 可以獲取返回屬性值,但其實 ResultSet 是可以按照不同的屬性類型進(jìn)行返回結(jié)果的,而不是都返回 Object 對象(如圖11-2 所示)。那么其實我們在上一章節(jié)中處理屬性信息時候,所開發(fā)的 TypeHandler 接口的實現(xiàn)類,就可以擴(kuò)充返回結(jié)果的方法,例如:LongTypeHandler#getResult、StringTypeHandler#getResult 等,這樣我們就可以使用策略模式非常明確的定位到返回的結(jié)果,而不需要進(jìn)行if判斷處理。
圖 11-2 返回類型
再有了這個目標(biāo)的前提下,就可以通過解析 XML 信息時封裝返回類型到映射器語句類中,MappedStatement#resultMaps 直到執(zhí)行完 SQL 語句,按照我們的返回結(jié)果參數(shù)類型,創(chuàng)建對象和使用 MetaObject 反射工具類填充屬性信息。詳細(xì)設(shè)計如圖 11-3 所示
圖 11-3 封裝結(jié)果集處理器
mybatis-step-10
└── src
├── main
│ └── java
│ └── cn.bugstack.mybatis
│ ├── binding
│ ├── builder
│ │ ├── xml
│ │ │ ├── XMLConfigBuilder.java
│ │ │ ├── XMLMapperBuilder.java
│ │ │ └── XMLStatementBuilder.java
│ │ ├── BaseBuilder.java
│ │ ├── MapperBuilderAssistant.java
│ │ ├── ParameterExpression.java
│ │ ├── SqlSourceBuilder.java
│ │ └── StaticSqlSource.java
│ ├── datasource
│ ├── executor
│ │ ├── resultset
│ │ │ └── ParameterHandler.java
│ │ ├── resultset
│ │ │ ├── DefaultResultContext.java
│ │ │ └── DefaultResultHandler.java
│ │ ├── resultset
│ │ │ ├── DefaultResultSetHandler.java
│ │ │ └── ResultSetHandler.java
│ │ │ └── ResultSetWrapper.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
│ │ ├── ResultMap.java
│ │ ├── ResultMapping.java
│ │ ├── SqlCommandType.java
│ │ └── SqlSource.java
│ ├── parsing
│ ├── reflection
│ ├── scripting
│ ├── session
│ │ ├── defaults
│ │ │ ├── DefaultSqlSession.java
│ │ │ └── DefaultSqlSessionFactory.java
│ │ ├── Configuration.java
│ │ ├── ResultContext.java
│ │ ├── ResultHandler.java
│ │ ├── RowBounds.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
流程解耦,封裝結(jié)果集處理器核心類關(guān)系,如圖 11-4 所示
圖 11-4 封裝結(jié)果集處理器核心類關(guān)系
鑒于對 XML 語句構(gòu)建器中解析語句后的信息封裝會逐步增多,所以這里需要引入映射構(gòu)建器助手對類中方法的職責(zé)進(jìn)行劃分,降低一個方法塊內(nèi)的邏輯復(fù)雜度。這樣的方式也更加利于代碼的維護(hù)和擴(kuò)展。
熟悉使用 Mybatis 的讀者都清楚的知道,在一條語句配置中需要有包括一個返回類型的配置,這個返回類型可以是通過 resultType 配置,也可以使用 resultMap 進(jìn)行處理,而無論使用哪種方式其實最終都會被封裝成統(tǒng)一的 ResultMap 結(jié)果映射類。
那么一般我們配置 ResultMap 都是配置了字段的映射,所以實際的代碼開發(fā)中 ResultMap 還會包含 ResultMapping 也就是每一個字段的映射信息,包括:colum、javaType、jdbcType 等。由于本章節(jié)暫時還不涉及到 ResultMap 的使用,所以這里我們先只是建好基本的地基結(jié)構(gòu)就可以。
源碼詳見:cn.bugstack.mybatis.mapping.ResultMap
public class ResultMap {
private String id;
private Class> type;
private List resultMappings;
private Set mappedColumns;
//...
} ResultMap 就是一個簡單的返回結(jié)果信息映射類,并提供了建造者方法,方便外部使用。沒有太多的邏輯行為,具體可以參照源碼。
MapperBuilderAssistant 構(gòu)建器助手專門為創(chuàng)建 MappedStatement 映射語句類而服務(wù)的,在這個類中封裝了入?yún)⒑统鰠⒌挠成?、以及把這些配置信息寫入到 Configuration 配置項中。
源碼詳見:cn.bugstack.mybatis.builder.MapperBuilderAssistant
public class MapperBuilderAssistant extends BaseBuilder {
/**
* 添加映射器語句
*/
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
SqlCommandType sqlCommandType,
Class> parameterType,
String resultMap,
Class> resultType,
LanguageDriver lang
) {
// 給id加上namespace前綴:cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfoById
id = applyCurrentNamespace(id, false);
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);
// 結(jié)果映射,給 MappedStatement#resultMaps
setStatementResultMap(resultMap, resultType, statementBuilder);
MappedStatement statement = statementBuilder.build();
// 映射語句信息,建造完存放到配置項中
configuration.addMappedStatement(statement);
return statement;
}
private void setStatementResultMap(
String resultMap,
Class> resultType,
MappedStatement.Builder statementBuilder) {
List resultMaps = new ArrayList<>();
/*
* 通常使用 resultType 即可滿足大部分場景
* 接下來我們就可以清理 XMLStatementBuilder 語句構(gòu)建器中解析后,映射語句類的構(gòu)建和存放處理流程。通過使用助手類,統(tǒng)一封裝參數(shù)信息。
源碼詳見:cn.bugstack.mybatis.builder.xml.XMLStatementBuilder
從 DefaultSqlSession 調(diào)用 Executor 語句執(zhí)行器,一直到 PreparedStatementHandler 預(yù)處理語句處理,最后就是 DefaultResultSetHandler 結(jié)果信息的封裝。
前面章節(jié)對此處的封裝處理,并沒有解耦的操作,只是簡單的 JDBC 使用通過查詢結(jié)果,反射處理返回信息就結(jié)束了。如果是使用這樣的一個簡單的 if···else 面向過程方式進(jìn)行開發(fā),那么后續(xù)所需要滿足 Mybatis 的全部封裝對象功能,就會變得特別吃力,一個方法塊也會越來越大。
所以這一部分的內(nèi)容處理是需要被解耦,分為;對象的實例化、結(jié)果信息的封裝、策略模式的處理、寫入上下文返回等操作,只有通過這樣的解耦流程,才能更加方便的擴(kuò)展流程不同節(jié)點中的各類需求。
源碼詳見:cn.bugstack.mybatis.executor.resultset.DefaultResultSetHandler#handleResultSet
這是一套結(jié)果封裝的核心處理流程,包括創(chuàng)建處理器、封裝數(shù)據(jù)和保存結(jié)果,接下來就分別介紹下這塊代碼的具體實現(xiàn)。
源碼詳見:cn.bugstack.mybatis.executor.result.DefaultResultHandler
public class DefaultResultHandler implements ResultHandler {
private final List這里封裝了一個非常簡單的結(jié)果集對象,默認(rèn)情況下都會寫入到這個對象的 list 集合中。
在處理封裝數(shù)據(jù)的過程中,包括根據(jù) resultType 使用反射工具類 ObjectFactory#create 方法創(chuàng)建出 Bean 對象。這個過程會根據(jù)不同的類型進(jìn)行創(chuàng)建,不過暫時我們這里只是普通對象,所以不會填充太多的代碼,避免擾亂讀者的重點核心內(nèi)容的學(xué)習(xí)
調(diào)用鏈路:handleResultSet->handleRowValuesForSimpleResultMap->getRowValue->createResultObject
源碼詳見:cn.bugstack.mybatis.executor.resultset.DefaultResultSetHandler#createResultObject
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List> constructorArgTypes, List
對象實例化完成后,就是根據(jù) ResultSet 獲取出對應(yīng)的值填充到對象的屬性中,但這里需要注意,這個結(jié)果的獲取來自于 TypeHandler#getResult 接口新增的方法,由不同的類型處理器實現(xiàn),通過這樣的策略模式設(shè)計方式就可以巧妙的避免 if···else 的判斷處理。
圖 11-7 使用策略模式,獲取返回結(jié)果
源碼詳見:cn.bugstack.mybatis.executor.resultset.DefaultResultSetHandler#applyAutomaticMappings
創(chuàng)建一個數(shù)據(jù)庫名稱為 mybatis 并在庫中創(chuàng)建表 user 以及添加測試數(shù)據(jù),如下:
CREATE TABLE
USER
(
id bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',
userId VARCHAR(9) COMMENT '用戶ID',
userHead VARCHAR(16) COMMENT '用戶頭像',
createTime TIMESTAMP NULL COMMENT '創(chuàng)建時間',
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', '小傅哥');
@Before
public void init() throws IOException {
// 1. 從SqlSessionFactory中獲取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
sqlSession = sqlSessionFactory.openSession();
}
@Test
public void test_queryUserInfoById() {
// 1. 獲取映射器對象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 測試驗證:基本參數(shù)
User user = userDao.queryUserInfoById(1L);
logger.info("測試結(jié)果:{}", JSON.toJSONString(user));
}
測試結(jié)果
12:39:17.321 [main] INFO c.b.mybatis.builder.SqlSourceBuilder - 構(gòu)建參數(shù)映射 property:id propertyType:class java.lang.Long
12:39:17.321 [main] INFO c.b.mybatis.builder.SqlSourceBuilder - 構(gòu)建參數(shù)映射 property:userId propertyType:class java.lang.String
12:39:17.382 [main] INFO c.b.m.s.defaults.DefaultSqlSession - 執(zhí)行查詢 statement:cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfoById parameter:1
12:39:17.684 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根據(jù)每個ParameterMapping中的TypeHandler設(shè)置對應(yīng)的參數(shù)信息 value:1
12:39:17.728 [main] INFO cn.bugstack.mybatis.test.ApiTest - 測試結(jié)果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
Process finished with exit code 0

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流