掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
我們可以把 Spring、Mybatis、Dubbo 這樣的大型框架或者一些公司內(nèi)部的較核心項目,都可以稱為復(fù)雜的系統(tǒng)。這樣的工程也不在是初學(xué)編程手里的玩具項目,沒有所謂的CRUD,更多時候要面對的都是對系統(tǒng)分層的結(jié)構(gòu)設(shè)計和聚合邏輯功能的實現(xiàn),再通過層層轉(zhuǎn)換進行實現(xiàn)和調(diào)用。

創(chuàng)新互聯(lián)公司是一家以網(wǎng)絡(luò)技術(shù)公司,為中小企業(yè)提供網(wǎng)站維護、成都網(wǎng)站設(shè)計、成都網(wǎng)站制作、網(wǎng)站備案、服務(wù)器租用、域名申請、軟件開發(fā)、成都小程序開發(fā)等企業(yè)互聯(lián)網(wǎng)相關(guān)業(yè)務(wù),是一家有著豐富的互聯(lián)網(wǎng)運營推廣經(jīng)驗的科技公司,有著多年的網(wǎng)站建站經(jīng)驗,致力于幫助中小企業(yè)在互聯(lián)網(wǎng)讓打出自已的品牌和口碑,讓企業(yè)在互聯(lián)網(wǎng)上打開一個面向全國乃至全球的業(yè)務(wù)窗口:建站歡迎來電:13518219792
這對于很多剛上道的小碼農(nóng)來說,會感覺非常難受,不知道要從哪下手,但又想著可以一口吃個胖子。其實這是不現(xiàn)實的,因為這些復(fù)雜系統(tǒng)中的框架中有太多的內(nèi)容你還沒用了解和熟悉,越是硬搞越難受,信心越受打擊。
其實對于解決這類復(fù)雜的項目問題,核心在于要將主干問題點縮小,具體的手段包括:分治、抽象和知識。運用設(shè)計模式和設(shè)計原則等相關(guān)知識,把問題空間合理切割為若干子問題,問題越小也就越容易理解和處理。就像你可以把很多內(nèi)容做成單個獨立的案例一樣,最終在進行聚合使用。
在上一章節(jié)我們初步的了解了怎么給一個接口類生成對應(yīng)的映射器代理,并在代理中完成一些用戶對接口方法的調(diào)用處理。雖然我們已經(jīng)看到了一個核心邏輯的處理方式,但在使用上還是有些刀耕火種的,包括:需要編碼告知 MapperProxyFactory 要對哪個接口進行代理,以及自己編寫一個假的 SqlSession 處理實際調(diào)用接口時的返回結(jié)果。
那么結(jié)合這兩塊問題點,我們本章節(jié)要對映射器的注冊提供注冊機處理,滿足用戶可以在使用的時候提供一個包的路徑即可完成掃描和注冊。與此同時需要對 SqlSession 進行規(guī)范化處理,讓它可以把我們的映射器代理和方法調(diào)用進行包裝,建立一個生命周期模型結(jié)構(gòu),便于后續(xù)的內(nèi)容的添加。
鑒于我們希望把整個工程包下關(guān)于數(shù)據(jù)庫操作的 DAO 接口與 Mapper 映射器關(guān)聯(lián)起來,那么就需要包裝一個可以掃描包路徑的完成映射的注冊器類。
當(dāng)然我們還要把上一章節(jié)中簡化的 SqlSession 進行完善,由 SqlSession 定義數(shù)據(jù)庫處理接口和獲取 Mapper 對象的操作,并把它交給映射器代理類進行使用。這一部分是對上一章節(jié)內(nèi)容的完善。
有了 SqlSession 以后,你可以把它理解成一種功能服務(wù),有了功能服務(wù)以后還需要給這個功能服務(wù)提供一個工廠,來對外統(tǒng)一提供這類服務(wù)。比如我們在 Mybatis 中非常常見的操作,開啟一個 SqlSession。整個設(shè)計可以如圖 3-1:
圖 3-1 映射器的注冊和使用
mybatis-step-02
└── src
├── main
│ └── java
│ └── cn.bugstack.mybatis
│ ├── binding
│ │ ├── MapperProxy.java
│ │ ├── MapperProxyFactory.java
│ │ └── MapperRegistry.java
│ └── session
│ ├── defaults
│ │ ├── DefaultSqlSession.java
│ │ └── DefaultSqlSessionFactory.java
│ ├── SqlSession.java
│ └── SqlSessionFactory.java
└── test
└── java
└── cn.bugstack.mybatis.test.dao
├── dao
│ ├── ISchoolDao.java
│ └── IUserDao.java
└── ApiTest.java
工程源碼:https://t.zsxq.com/bmqNFQ7。
映射器標(biāo)準(zhǔn)定義實現(xiàn)關(guān)系,如圖 3-2:
圖 3-2 映射器標(biāo)準(zhǔn)定義實現(xiàn)關(guān)系
源碼詳見:cn.bugstack.mybatis.binding.MapperRegistry。
public class MapperRegistry {
/**
* 將已添加的映射器代理加入到 HashMap
*/
private final Map, MapperProxyFactory>> knownMappers = new HashMap<>();
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);
}
}
public void addMapper(Class type) {
/* Mapper 必須是接口才會注冊 */
if (type.isInterface()) {
if (hasMapper(type)) {
// 如果重復(fù)添加了,報錯
throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
}
// 注冊映射器代理工廠
knownMappers.put(type, new MapperProxyFactory<>(type));
}
}
public void addMappers(String packageName) {
Set> mapperSet = ClassScanner.scanPackage(packageName);
for (Class> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
}
源碼詳見:cn.bugstack.mybatis.session.SqlSession。
public interface SqlSession {
/**
* Retrieve a single row mapped from the statement key
* 根據(jù)指定的SqlID獲取一條記錄的封裝對象
*
* @param the returned object type 封裝之后的對象類型
* @param statement sqlID
* @return Mapped object 封裝之后的對象
*/
T selectOne(String statement);
/**
* Retrieve a single row mapped from the statement key and parameter.
* 根據(jù)指定的SqlID獲取一條記錄的封裝對象,只不過這個方法容許我們可以給sql傳遞一些參數(shù)
* 一般在實際使用中,這個參數(shù)傳遞的是pojo,或者Map或者ImmutableMap
*
* @param the returned object type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @return Mapped object
*/
T selectOne(String statement, Object parameter);
/**
* Retrieves a mapper.
* 得到映射器,這個巧妙的使用了泛型,使得類型安全
*
* @param the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
T getMapper(Class type);
}
在 SqlSession 中定義用來執(zhí)行 SQL、獲取映射器對象以及后續(xù)管理事務(wù)操作的標(biāo)準(zhǔn)接口。
目前這個接口中對于數(shù)據(jù)庫的操作僅僅只提供了 selectOne,后續(xù)還會有相應(yīng)其他方法的定義。
源碼詳見:cn.bugstack.mybatis.session.defaults。
public class DefaultSqlSession implements SqlSession {
/**
* 映射器注冊機
*/
private MapperRegistry mapperRegistry;
@Override
public T selectOne(String statement, Object parameter) {
return (T) ("你被代理了!" + "方法:" + statement + " 入?yún)ⅲ? + parameter);
}
@Override
public T getMapper(Class type) {
return mapperRegistry.getMapper(type, this);
}
}
源碼詳見:cn.bugstack.mybatis.session.SqlSessionFactory。
public interface SqlSessionFactory {
/**
* 打開一個 session
* @return SqlSession
*/
SqlSession openSession();
}
源碼詳見:cn.bugstack.mybatis.session.defaults.DefaultSqlSessionFactory
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final MapperRegistry mapperRegistry;
public DefaultSqlSessionFactory(MapperRegistry mapperRegistry) {
this.mapperRegistry = mapperRegistry;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(mapperRegistry);
}
}
在同一個包路徑下,提供2個以上的 Dao 接口:
public interface ISchoolDao {
String querySchoolName(String uId);
}
public interface IUserDao {
String queryUserName(String uId);
Integer queryUserAge(String uId);
}
@Test
public void test_MapperProxyFactory() {
// 1. 注冊 Mapper
MapperRegistry registry = new MapperRegistry();
registry.addMappers("cn.bugstack.mybatis.test.dao");
// 2. 從 SqlSession 工廠獲取 Session
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(registry);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 獲取映射器對象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 4. 測試驗證
String res = userDao.queryUserName("10001");
logger.info("測試結(jié)果:{}", res);
}
在單元測試中通過注冊機掃描包路徑注冊映射器代理對象,并把注冊機傳遞給 SqlSessionFactory 工廠,這樣完成一個鏈接過程。
之后通過 SqlSession 獲取對應(yīng) DAO 類型的實現(xiàn)類,并進行方法驗證。
測試結(jié)果
22:43:23.254 [main] INFO cn.bugstack.mybatis.test.ApiTest - 測試結(jié)果:你被代理了!方法:queryUserName 入?yún)ⅲ篬Ljava.lang.Object;@50cbc42f
Process finished with exit code 0
通過測試大家可以看到,目前我們已經(jīng)在一個有 Mybatis 影子的手寫 ORM 框架中,完成了代理類的注冊和使用過程。
首先要從設(shè)計結(jié)構(gòu)上了解工廠模式對具體功能結(jié)構(gòu)的封裝,屏蔽過程細(xì)節(jié),限定上下文關(guān)系,把對外的使用減少耦合。
從這個過程上讀者伙伴也能發(fā)現(xiàn),使用 SqlSessionFactory 的工廠實現(xiàn)類包裝了 SqlSession 的標(biāo)準(zhǔn)定義實現(xiàn)類,并由 SqlSession 完成對映射器對象的注冊和使用。
本章學(xué)習(xí)要注意幾個重要的知識點,包括:映射器、代理類、注冊機、接口標(biāo)準(zhǔn)、工廠模式、上下文。這些工程開發(fā)的技巧都是在手寫 Mybatis 的過程中非常重要的部分,了解和熟悉才能更好的在自己的業(yè)務(wù)中進行使用。

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