作者:小傅哥
部落格:https://bugstack.cn
沉澱、分享、成長,讓自己和他人都能有所收穫!?
一、前言
著急和快,是最大的障礙!
慢下來,慢下來,只有慢下來,你才能看到更全的資訊,才能學到更紮實的技術。而那些滿足你快的短篇內容雖然有時候更抓眼球,但也容易把人在技術學習上帶偏,總想著越快越好。
在小傅哥編寫技術文章的過程中,也會遇到這樣的情況,不少讀者更喜歡看;一個系列內容的開頭、一段成長故事的分享、一天成為架構的祕籍。當然我也能理解這種喜歡,畢竟大多數人都喜歡走捷徑,就像冬天買了運動健身裝備,夏天過去了還沒有拆封。
好了,接下來我們們幹正事!
二、目標
在你能閱讀這篇文章之時,我相信你已經是一個 Mybatis ORM 框架工具使用的熟練工了,那你是否清楚這個 ORM 框架是怎麼遮蔽我們對資料庫操作的細節的?
比如我們使用 JDBC 的時候,需要手動建立資料庫連結、編碼 SQL 語句、執行資料庫操作、自己封裝返回結果等。但在使用 ORM 框架後,只需要通過簡單配置即可對定義的 DAO 介面進行資料庫的操作了。
那麼本章節我們就來解決 ORM 框架第一個關聯物件介面和對映類的問題,把 DAO 介面使用代理類,包裝對映操作。
三、設計
通常如果能找到大家所在事情的共性內容,具有統一的流程處理,那麼它就是可以被凝聚和提煉的,做成通用的元件或者服務,被所有人進行使用,減少重複的人力投入。
而參考我們最開始使用 JDBC 的方式,從連線、查詢、封裝、返回,其實都一個固定的流程,那麼這個過程就可以被提煉以及封裝和補全大家所需要的功能。
當我們來設計一個 ORM 框架的過程中,首先要考慮怎麼把使用者定義的資料庫操作介面、xml配置的SQL語句、資料庫三者聯絡起來。其實最適合的操作就是使用代理的方式進行處理,因為代理可以封裝一個複雜的流程為介面物件的實現類,設計如圖 2-1:
- 首先提供一個對映器的代理實現類
MapperProxy
,通過代理類包裝對資料庫的操作,目前我們本章節會先提供一個簡單的包裝,模擬對資料庫的呼叫。 - 之後對
MapperProxy
代理類,提供工廠例項化操作 MapperProxyFactory#newInstance,為每個 IDAO 介面生成代理類。這塊其實用到的就是一個簡單工廠模式
接下來我們就按照這個設計實現一個簡單的對映器代理操作,編碼過程比較簡單。如果對代理知識不熟悉可以先補充下。
四、實現
1. 工程結構
mybatis-step-01
└── src
├── main
│ └── java
│ └── cn.bugstack.mybatis.binding
│ ├── MapperProxy.java
│ └── MapperProxyFactory.java
└── test
└── java
└── cn.bugstack.mybatis.test.dao
├── dao
│ └── IUserDao.java
└── ApiTest.java
工程原始碼:https://t.zsxq.com/bmqNFQ7
Mybatis 對映器代理類關係,如圖 2-2
目前這個 Mybatis 框架的代理操作實現的還只是最核心的功能,相當於是光屁股的娃娃,還沒有新增衣服。不過這樣漸進式的實現可以讓大家先了解到最核心的內容,後續我們在陸續的完善。
- MapperProxy 負責實現 InvocationHandler 介面的 invoke 方法,最終所有的實際呼叫都會呼叫到這個方法包裝的邏輯。
- MapperProxyFactory 是對 MapperProxy 的包裝,對外提供例項化物件的操作。當我們後面開始給每個運算元據庫的介面對映器註冊代理的時候,就需要使用到這個工廠類了。
2. 對映器代理類
原始碼詳見:cn.bugstack.mybatis.binding.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private Map<String, String> sqlSession;
private final Class<T> mapperInterface;
public MapperProxy(Map<String, String> sqlSession, Class<T> mapperInterface) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return "你的被代理了!" + sqlSession.get(mapperInterface.getName() + "." + method.getName());
}
}
}
- 通過實現 InvocationHandler#invoke 代理類介面,封裝操作邏輯的方式,對外介面提供資料庫操作物件。
- 目前我們這裡只是簡單的封裝了一個 sqlSession 的 Map 物件,你可以想象成所有的資料庫語句操作,都是通過
介面名稱+方法名稱作為key
,操作作為邏輯的方式進行使用的。那麼在反射呼叫中則獲取對應的操作直接執行並返回結果即可。當然這還只是最核心的簡化流程,後續不斷補充內容後,會看到對資料庫的操作 - 另外這裡要注意如果是 Object 提供的 toString、hashCode 等方法是不需要代理執行的,所以新增
Object.class.equals(method.getDeclaringClass())
判斷。
3. 代理類工廠
原始碼詳見:cn.bugstack.mybatis.binding.MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public T newInstance(Map<String, String> sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
}
- 工廠操作相當於把代理的建立給封裝起來了,如果不做這層封裝,那麼每一個建立代理類的操作,都需要自己使用
Proxy.newProxyInstance
進行處理,那麼這樣的操作方式就顯得比較麻煩了。 - 另外如果你對代理不是太熟悉,可以著重把 JDK Proxy 的內容做幾個案例補充下這塊的內容。
五、測試
1. 事先準備
cn.bugstack.mybatis.test.dao.IUserDao
public interface IUserDao {
String queryUserName(String uId);
Integer queryUserAge(String uId);
}
- 首先提供一個 DAO 介面,並定義2個介面方法。
2. 測試用例
@Test
public void test_MapperProxyFactory() {
MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>(IUserDao.class);
Map<String, String> sqlSession = new HashMap<>();
sqlSession.put("cn.bugstack.mybatis.test.dao.IUserDao.queryUserName", "模擬執行 Mapper.xml 中 SQL 語句的操作:查詢使用者姓名");
sqlSession.put("cn.bugstack.mybatis.test.dao.IUserDao.queryUserAge", "模擬執行 Mapper.xml 中 SQL 語句的操作:查詢使用者年齡");
IUserDao userDao = factory.newInstance(sqlSession);
String res = userDao.queryUserName("10001");
logger.info("測試結果:{}", res);
}
- 在單測中建立 MapperProxyFactory 工廠,並手動給 sqlSession Map 賦值,這裡的賦值相當於模擬資料庫中的操作。
- 接下來再把賦值資訊傳遞給代理物件例項化操作,這樣就可以在我們呼叫具體的 DAO 方法時從 sqlSession 中取值了。
測試結果
17:03:41.817 [main] INFO cn.bugstack.mybatis.test.ApiTest - 測試結果:你的被代理了!模擬執行 Mapper.xml 中 SQL 語句的操作:查詢使用者姓名
Process finished with exit code 0
- 從測試結果可以看到的,我們的介面已經被代理類實現了,同時我們可以在代理類中進行自己的操作封裝。那麼在我們後續實現的資料庫操作中,就可以對這部分內容進行擴充套件了。
六、總結
- 本章節我們初步對 Mybatis 框架中的資料庫 DAO 操作介面和對映器通過代理類的方式進行連結,這一步也是 ORM 框架裡非常核心的部分。有了這塊的內容,就可以在代理類中進行自己邏輯的擴充套件了。
- 在框架實現方面引入簡單工廠模式包裝代理類,遮蔽建立細節,這些也是大家在學習過程中需要注意的設計模式的點。
- 目前內容還比較簡單的,可以手動操作練習,隨著我們內容的增加,會有越來越多的包和類引入,完善 ORM 框架功能。