Java資料持久層
阿新 • • 發佈:2021-03-09
# 一、前言
## 1.持久層
Java資料持久層,其本身是為了實現與資料來源進行資料互動的存在,其目的是通過分層架構風格,進行應用&資料的解耦。
我從整體角度,依次闡述JDBC、Mybatis、MybatisPlus。
前者總是後者的依賴。只有在瞭解前者,才可以更好地學習後者。
## 2.技術選型
ciwai ,還有Hibernate、SpringData、JPA等。
至於Hibernate作為知名框架,其最大的特點,是支援面向物件的資料管理。但成也蕭何,敗也蕭何。Hibernate的該功能,導致其太重了。而大多數場景下,我們是不需要這個功能的。另外,Hibernate的該功能,使用起來過於複雜。其設計關聯關係&對映關係,帶來了太多複雜度。
SpringData,則是我看好的另一個Spring原生支援。但是目前主流還是Mybatis,其發展&主流的切換,還需要時間。就讓子彈飛一會兒吧。
至於MybatisPlus,是我在工業物聯網公司時所採用的一個技術方案。其符合“約定大於配置”的技術趨勢,減少了Mybatis那樣的配置成本,但是比JPA更加靈活。更棒的是,它支援stream這樣的編碼方式進行Sql支援(錯誤可以在編譯期透出)。但如果是大型公司,個人的建議是,謹慎考慮,再進行使用。拋開技術方面的考量,MybatisPlus雖然是優秀的開源軟體,但其開源社群&軟體管理確實相對過於薄弱。對於大公司的技術生態而言,這是一個不得不重視的風險點。
## 3.文章脈絡
不過,Mybatis作為現在最流行的ORM框架,還是值得大家相信的。所以經過考慮,這邊文章雖然包含三塊內容,但是JDBC更多作為一個依賴,進行了解。而MybatisPlus主要側重於其核心功能-BaseMapper的實現,以及其擴充套件Mybatis得到的擴充套件實現方式。整篇文章的重點,還是落在Mybatis,對其投入較大的精力進行描述。
## 4.文章優勢
又到了王婆賣瓜的階段。
文章最大的兩個優點:圖&結構。
本篇文章採用了數十張圖,用於展現對應關係。畢竟一圖勝千言嘛。
而結構方面,文章採用MECE原則。文章分為JDBC、Mybatis、MybatisPlus。核心的Mybatis分為靜態結構&執行流程。靜態結構對Mybatis的架構,以及模組進行了展開。執行流程則是針對Mybatis的初始化&執行兩個重要生命週期節點,進行展開。最後,通過Mybatis的核心Configuration的核心欄位解析(作用、來源、去向)進行總結收納。
## 5.文章遺憾
遺憾主要集中在兩個方面:
* 由於是一個長文(接近6W字),最近事情又多(財年底,大家懂的),所以難免有一些疏漏。歡迎大家指出來哈。
* 戰線拖得太長(寫了快兩個月)。雖然還有很多地方可以展開&深入,但是經過考慮後,還是放棄了。
> 文章中有很多補充部分,大家可以自行查閱,擴充套件知識面。雖然我查詢了一些資料,但是有點整理不動(又不知大家是否感興趣)。當然,如果大家對某部分感興趣,可以提出來,我出個單章。
# 二、JDBC
## 1.簡介
JDBC是一個規範,其分為兩個部分:
* 廠商:完成資料庫驅動
* Java開發者:呼叫統一介面
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210104151549557.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center)
## 2.整體結構
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210105174946627.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center)
對應元件:
* DriverManager:資料庫驅動管理器
* Driver:資料庫驅動的抽象介面,用於與資料庫服務進行通訊
* Connection:與資料庫的連線
* Statement:用於提交SQL語句
* Statement:通用介面,繼承自Wrapper。普通的不帶參的查詢SQL;支援批量更新,批量刪除;
* PreparedStatement:預編譯介面,繼承自Statement。可變引數的SQL,編譯一次,執行多次,效率高; 安全性好,有效防止Sql注入等問題;
* CallableStatement:繼承自PreparedStatement。支援呼叫儲存過程,提供了對輸出和輸入/輸出引數(INOUT)的支援;
* ResultSet:用於儲存資料庫結果
* SQLException:資料庫異常
## 3.生命週期
### a.初始化過程
驅動註冊&配置注入
### b.執行過程
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210104160340338.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center)
## 4.程式碼示例
原生JDBC較為原始,架構上的設計也是非常薄的。
所以,說得太多,還不如看看應用程式碼。
```java
// 1. 註冊驅動
// 使用java.sql.DriverManager類的靜態方法registerDriver(Driver driver)
// Driver是一個介面,引數傳遞:MySQL驅動程式的實現類
// DriverManager.registerDriver(new Driver());
// 檢視驅動類原始碼,註冊兩次驅動,浪費資源
Class.forName("com.mysql.jdbc.Driver");
// 2. 獲得連線
// uri:資料庫地址 jdbc:mysql://連線主機ip:埠號//資料庫名字
String url = "jdbc:mysql://localhost:3306/TEST";
// static Connection getConnection(String url, String user, String password)
// 返回值是java.sql.Connection介面的實現類,在MySQL驅動程式中
Connection conn = DriverManager.getConnection(url, "root", "123456");
// conn.setAutoCommit(false); // 用於事務提交conn.commit(),conn.rollback()
System.out.println(conn);// com.mysql.jdbc.JDBC4Connection@10d1f30
// 3. 獲得語句執行平臺,通過資料庫連線物件,獲取到SQL語句的執行者物件
//conn物件,呼叫方法 Statement createStatement() 獲取Statement物件,將SQL語句傳送到資料庫
//返回的是Statement介面的實現類物件,在MySQL驅動程式中
Statement statement = conn.createStatement();
System.out.println(statement);//com.mysql.jdbc.StatementImpl@137bc9
// 4. 執行sql語句
//通過執行者物件呼叫方法執行SQL語句,獲取結果
//int executeUpdate(String sql) 執行資料庫中的SQL語句,僅限於insert,update,delete
//返回值int,操作成功資料庫的行數
ResultSet resultSet = statement.executeQuery("SELECT * from user where id = 1");
System.out.println(resultSet);
// 5. 釋放資源
statement.close();
conn.close();
```
## 5.總結
關鍵詞:簡單、原始、看不到
現在基本沒有人直接使用了。大多使用框架。我在生產級的使用,還是剛工作的時候,在前端使用了類似的東東。
# 三、Mybatis
## 1.整體框架
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210104160457688.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center)
對應模組:
* 介面層
* SqlSession:應用程式與Mybatis的互動介面
* 核心處理層
* 配置解析:對Mybatis配置檔案、對映檔案,dao介面註解等進行配置解析,生成Configuration物件
* SQL解析:MyBatis 實現動態SQL 語句的功能,並提供了諸如where等SQL語句節點
* 引數對映:根據實參,解析動態SQLL節點,生成可執行SQL語句,處理佔位符,繫結實參
* SQL執行:負責快取,事務,JDBC等的排程。詳見執行過程圖
* 結果集對映:通過ResultSetHandler等,完成結果集的對映,得到結果物件並返回
* 外掛:提供外掛介面,便於使用者擴充套件,甚至修改Mybatis預設行為
* 基礎支援層
* 資料來源模組:通過配置生成(可委託第三方資料來源框架),包含目標資料庫資訊,向上支援連線生成等
* 事務管理模組:對資料庫事務進行抽象,並提供簡單實現。可與Spring整合,由Spring實現事務管理
* 快取模組:為Mybatis的一二級快取提供支援,從而優化資料庫效能
* Binding模組:實現DAO介面檔案與對應對映檔案的關聯
* 反射模組:對Java原生反射進行了封裝與優化
* 型別轉換:一方面實現JavaType與JDBCType的轉換,另一方面支撐Mybatis的別名機制
* 日誌模組:提供詳細日誌輸出資訊,並能夠整合第三方日誌框架(log4j,sel4j等)
* 資源載入:封裝Java原生類載入器,提供類與其他資原始檔的有序載入能力
* 解析器模組:一方面封裝XPath,提供xml配置檔案解析能力,另一方面為動態Sql佔位符的處理,提供支援
> 資料來源模組補充:即常用元件-DataSource。MyBatis 自身提供了相應的資料來源實現(Pooled,UnPooled,Jndi),MyBatis 也提供第三方資料來源整合的介面()。現在開源的資料來源都提供了比較豐富的功能,如連線池功能、檢測連線狀態等
> @Select註解,就可以省略對應的對映檔案節點
> DAO介面的實現類,是由Mybatis自動建立的動態代理物件(依賴於對應的對映檔案節點)
> Mybatis初始化階段:載入Mybatis配置檔案、對映檔案,dao介面註解->儲存到configuration物件中->建立SqlSessionFactory物件。Mybatis初始化階段後,開發者可以通過SqlSessionFactory,獲取對應的SqlSession。wdk-som的資料庫配置就是直接配置生成DataSource與SqlSessionFactory。
### a.解析模組
Mybatis的配置,有三種途徑:
* XML:如Mybatis-config.xml
* 註解:如DAO介面方法上的@Select
* 注入:如MybatisConfiguration類
其中,XML是Mybatis配置的主要方式。XML配置則涉及XML檔案解析。
XML常見解析方式,有一下三種:
* DOM:前端小夥伴,不要太熟悉。DOM 是基於樹形結構的XML 解析方式,它會將整個XML 文件讀入記憶體並構建一個DOM樹,基於這棵樹形結構對各個節點( Node )進行操作。DOM 解析方式的優點是易於程式設計,可以根據需求在樹形結構的各節點之間導航。DOM 解析方式的缺點是在XML檔案較大時,會造成較大的資源佔用(因為需要構建DOM樹)。
* SAX:SAX 是基於事件模型的XML 解析方式。當SAX 解析器解析到某型別節點時,會觸發註冊在該型別節點上的回撥函式,開發人員可以根據自己感興趣的事件註冊相應的回撥函式。由於在處理過程中井不會在記憶體中記錄XML 中的資料,所以佔用的資源比較小,這是其優點。其缺點是因為不儲存XML 文擋的結構,所以需要開發人員自己負責維護業務邏輯涉及的多層節點之間的關係。
* StAX:StAX將XML 文件作為一個事件流進行處理。不同於SAX,在StAX 解析方式中,應用程式控制著整個解析過程的推進,可以簡化應用處理XML 文件的程式碼,並且決定何時停止解析,而且StAX 可以同時處理多個XML 文件。
而Mybatis則是採用DOM解析方式,並結合XPath進行XML解析。
> DOM 會將整個XML 文件載入到記憶體中並形成樹狀資料結構,而XPath 是一種為查詢XML 文件而設計的語言,可以與DOM 解析方式配合使用,實現對XML 文件的解析。XPath 之於XML 就好比SQL 語言之於資料庫。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210114104822237.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70)
org.apache.ibatis.parsing.XPathParser#variables:mybatis-config.xml 中<propteries>標籤定義的鍵位對集合。
XPathParser中提供了一系列的eval*方法用於解析boolean、short、long、int、String、Node等型別的資訊,它通過呼叫XPath.evaluate方法查詢指定路徑的節點或屬性,並進行相應的型別裝換。
剩餘部分,此處不再詳解。
### b.反射模組
Mybatis執行過程中,大量使用了反射(如生成DAO對應代理實現類)。Mybatis對Java原生的反射操作進行了進一步的封裝,從而提供更加簡潔的API。
Reflector 是MyBatis 中反射模組的基礎,每個Reflector 物件都對應一個類,在Reflector 中快取了反射操作需要使用的類的元資訊。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210114104934817.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center)
從上圖中,可以看出
* 核心類:
* Reflector:對於每個類,都有一個對應的Reflector,用於儲存其類元資訊。可以類比Spring中的Bean。但是其內部沒有類之間的關聯&依賴關係
* MetaClass:MetaClass 是MyBatis 對類級別的元資訊的封裝和處理。MetaClass 通過Reflector 和PropertyTokenizer 組合使用, 實現了對複雜的屬性表示式的解析,並實現了獲取指定屬性描述資訊的功能。
* MetaObject:ObjectWrapper實現的屬性表示式解析功能,是委託給MetaObject實現的。
* 包:
* invoker:包含MethodInvoker、SetFieldInvoker等,用於實現目標方法反射呼叫,屬性讀取與設定等
* factory:包含ObjectFactory&DefaultObjectFactory,物件建立工廠。ObjectFactory提供例項建立介面,其預設實現為DefaultObjectFactory。在Mybatis原始碼的測試類中,存在對應測試。
* property:包含PropertyCopier、PropertyNamer、PropertyTokenizer,是類欄位工具,提供如欄位複製、欄位是否為屬性、欄位與index轉化(屬性表示式&Sql佔位符應用)等功能。
* wrapper:包含ObjectWrapperFactory、ObjectWrapper、BaseWrapper等。ObjectWrapper介面是對物件的包裝,抽象了物件的屬性資訊,定義了一系列查詢物件屬性資訊的方法,以及更新屬性的方法。
> TypeParameterResolver:進行型別解析。如TypeParameterResolver#resolveReturnType會返回對應類&方法的返回型別。在Mybatis原始碼的測試類中,存在對應測試。
#### Reflector
每個類,都有其對應等Reflector,用於儲存其對應的類元資訊(屬性,欄位等)
```java
public class Reflector {
// 對應的Class 型別
private final Class> type;
// 可讀屬性的名稱集合
private final String[] readablePropertyNames;
// 可寫屬性的名稱集合
private final String[] writablePropertyNames;
// 屬性相應的setter方法,key是屬性名稱,value是Invoker物件
private final Map setMethods = new HashMap<>();
// 屬性相應的getter方法集合,key是屬性名稱,value是Invoker物件
private final Map getMethods = new HashMap<>();
// 屬性相應的setter方法的引數值型別,key是屬性名稱,value是setter方法的引數型別
private final Map> setTypes = new HashMap<>();
// 屬性相應的getter方法的返回位型別,key是屬性名稱,value是getter方法的返回位型別
private final Map> getTypes = new HashMap<>();
// 預設構造方法
private Constructor> defaultConstructor;
// 所有屬性名稱的集合,key是屬性名稱的大寫形式,value是屬性名稱
private Map caseInsensitivePropertyMap = new HashMap<>();
// 構造方法
public Reflector(Class> clazz) {
type = clazz;
addDefaultConstructor(clazz);
addGetMethods(clazz);
addSetMethods(clazz);
addFields(clazz);
// 學習一下:Collection.toArray()返回的是Object[],而Collection.toArray(T[] a)返回的是T[]
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
// 其他方法
}
```
> 上述提到的都是”屬性“,而不是欄位。按照JavaBean的規範,類中定義的成員變數稱為“ 宇段” ,屬性則是通過ge陽r/setter 方法得到的,屬性只與類中的方法有關,與是否存在對應成員變數沒有關係。
> 所以,Mybatis與對應DO進行互動的依據是getter/setter方法。所以,可以通過自定義getter/setter方法進行欄位轉換。另外,DO中有欄位,但沒有對應getter/setter方法,則無法在對應mapper進行對映,最終導致報錯。
#### MetaClass
MetaClass 是MyBatis 對類級別的元資訊的封裝和處理。
MetaClass 通過Reflector 和PropertyTokenizer 組合使用, 實現了對複雜的屬性表示式的解析,並實現了獲取指定屬性描述資訊的功能。
```java
/**
* @author Clinton Begin
*/
public class MetaClass {
private final ReflectorFactory reflectorFactory;
// class對應等Reflector
private final Reflector reflector;
private MetaClass(Class> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
this.reflector = reflectorFactory.findForClass(type);
}
// 核心方法:解析屬性表示式。委託給buildProperty方法實現
public String findProperty(String name) {
StringBuilder prop = buildProperty(name, new StringBuilder());
return prop.length() > 0 ? prop.toString() : null;
}
private StringBuilder buildProperty(String name, StringBuilder builder) {
// name即是屬性表示式。如
// PropertyTokenizer包含name、indexName、index、children
PropertyTokenizer prop = new PropertyTokenizer(name);
// 判斷是否還有子表示式
if (prop.hasNext()) {
String propertyName = reflector.findPropertyName(prop.getName());
if (propertyName != null) {
// 返回結果,追加屬性名(.name形式)
builder.append(propertyName);
builder.append(".");
// 為該屬性,建立對應的MetaClass
MetaClass metaProp = metaClassForProperty(propertyName);
// 深度優先遞迴。建立所有MetaClass,並通過builder形成一個深度優先遍歷的關係鏈
metaProp.buildProperty(prop.getChildren(), builder);
}
} else {
String propertyName = reflector.findPropertyName(name);
if (propertyName != null) {
builder.append(propertyName);
}
}
return builder;
}
}
```
#### ObjectWrapper
ObjectWrapper介面是對物件的包裝,抽象了物件的屬性資訊,定義了一系列查詢物件屬性資訊的方法,以及更新屬性的方法。
其功能實現,是通過實現基礎類-BaseObjectWrapper,委託給MetaObject實現。
```java
/**
* @author Clinton Begin
*/
public interface ObjectWrapper {
// 如採Object Wrapper 中封裝的是普通的Bean物件,則呼叫相應屬性的相應getter 方法
// 如採封裝的是集合類,則獲取指定key或下標對應的value值
Object get(PropertyTokenizer prop);
void set(PropertyTokenizer prop, Object value);
// 查詢屬性表示式指定的屬性,第二個引數表示是否忽略屬性表示式中的下畫線
String findProperty(String name, boolean useCamelCaseMapping);
String[] getGetterNames();
String[] getSetterNames();
// 解析屬性表示式指定屬性的setter 方法的引數型別。name為請求的屬性表示式
Class> getSetterType(String name);
Class> getGetterType(String name);
boolean hasSetter(String name);
boolean hasGetter(String name);
// 為屬性表示式指定的屬性建立相應的MetaObject物件
MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);
boolean isCollection();
void add(Object element);
void addAll(List element);
}
```
#### MetaObject
ObjectWrapper實現的屬性表示式解析功能,是委託給MetaObject實現的。
```java
/**
* @author Clinton Begin
*/
public class MetaObject {
// 原生物件,即MetaObject所表示的物件
private final Object originalObject;
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
// 根據物件型別,使用不同的wrapper方法
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
if (object == null) {
return SystemMetaObject.NULL_META_OBJECT;
} else {
return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
}
// 從MetaObject中,獲取某個欄位的屬性值
public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
return metaValue.getValue(prop.getChildren());
}
} else {
return objectWrapper.get(prop);
}
}
// 對MetaObject中某個欄位進行賦值
public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null) {
// don't instantiate child path if value is null
return;
} else {
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
}
}
metaValue.setValue(prop.getChildren(), value);
} else {
objectWrapper.set(prop, value);
}
}
// 其他方法
```
### c.型別轉換
JDBC 資料型別與Java 語言中的資料型別井不是完全對應的,所以在PreparedStatement 為SQL 語句繫結引數時,需要從Java 型別轉換成JDBC 型別,而從結果集中獲取資料時,則需要從JDBC 型別轉換成Java 型別。My Batis 使用型別模組完成上述兩種轉換。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/202101151146268.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center)
#### TypeHandler
```java
/**
* @author Clinton Begin
*/
public interface TypeHandler {
// 設定引數。在通過PreparedStatement 為SQL 語句繫結引數時,會將資料由Java 型別轉換成JdbcType 型別
// 《Mybatis技術內幕》這部分的解釋反了,詳見入參與功能實現程式碼
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
// 獲取結果。
// 從ResultSet 中獲取資料時會呼叫此方法,會將資料由Java 型別轉換成JdbcType 型別
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
```
#### BaseTypeHandler
```java
public abstract class BaseTypeHandler extends TypeReference implements TypeHandler {
@Deprecated
protected Configuration configuration;
@Deprecated
public void setConfiguration(Configuration c) {
this.configuration = c;
}
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
// 設定引數,該方法具體有子類實現
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
try {
return getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
}
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
try {
return getNullableResult(rs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e, e);
}
}
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
try {
return getNullableResult(cs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e);
}
}
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
}
```
實現子類的型別轉換,最終還是會落到JDBC的PreparedStatement/ResultSet中對應的型別轉換方法。
而PreparedStatement/ResultSet,是由入參帶入的。
TypeHandlerRegistry&TypeAliasRegistry,主要是進行型別處理器&類型別名的管理(類似IOC容器對Bean的管理)。
### d.資料來源模組
Mybatis的資料來源模組,採用了工廠方法設計模式。
如其中DataSourceFactory是工廠介面,而PooledDataSourceFactory等則是其工廠實現類。
Mybatis提供了三個工廠類實現方式:
* PooledDataSourceFactory
* UnpooledDataSourceFactory
* JndiDataSourceFactory
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210201163027804.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center)
呼叫方舉例:org.apache.ibatis.builder.xml.XMLConfigBuilder#dataSourceElement
#### DataSourceFactory
```java
public interface DataSourceFactory {
void setProperties(Properties var1);
DataSource getDataSource();
}
```
#### UnpooledDataSourceFactory
```java
public class UnpooledDataSourceFactory implements DataSourceFactory {
private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
protected DataSource dataSource;
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@Override
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
// 利用基礎層的配置解析模組,建立DataSource 相應的MetaObject
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
for (Object key : properties.keySet()) {
// 遍歷Properties,從而獲取DataSource所需的配置資訊
String propertyName = (String) key;
// 以”driver.”開頭的配置項,是對DataSource的配置,記錄到driverProperties中儲存
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}
@Override
public DataSource getDataSource() {
return dataSource;
}
private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
Object convertedValue = value;
Class> targetType = metaDataSource.getSetterType(propertyName);
if (targetType == Integer.class || targetType == int.class) {
convertedValue = Integer.valueOf(value);
} else if (targetType == Long.class || targetType == long.class) {
convertedValue = Long.valueOf(value);
} else if (targetType == Boolean.class || targetType == boolean.class) {
convertedValue = Boolean.valueOf(value);
}
return convertedValue;
}
}
```
#### UnpooledDataSource
```java
public class UnpooledDataSource implements DataSource {
// 進行驅動載入的classLoader,可參照JDBC相關處理
private ClassLoader driverClassLoader;
// 驅動配置
private Properties driverProperties;
// 驅動登錄檔(全量)
private static Map registeredDrivers = new ConcurrentHashMap<>();
// 當前DataSource所採用的驅動,如mysqlDriver
private String driver;
// 資料來源地址
private String url;
// 使用者名稱
private String username;
// 密碼
private String password;
// 是否自動提交(有關於事務),預設自動提交
private Boolean autoCommit;
// 預設事務隔離級別
private Integer defaultTransactionIsolationLevel;
// 預設網路超時時間
private Integer defaultNetworkTimeout;
// 驅動註冊
static {
Enumeration drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
// 方法略
}
```
### e.事務管理模組
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210201180042632.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center)
#### TransactionFactory
```java
public interface TransactionFactory {
default void setProperties(Properties props) {
// NOP
}
Transaction newTransaction(Connection conn);
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
```
#### Transaction
```java
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
// 獲取事務超時時間(Spring的SpringManagedTransaction,存在對應實現)
Integer getTimeout() throws SQLException;
}
```
#### SpringManagedTransaction
```java
public class SpringManagedTransaction implements Transaction {
private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class);
private final DataSource dataSource;
private Connection connection;
// 當前連線是否為事務連線
private boolean isConnectionTransactional;
// 是否自動提交。如果是自動提交,也就不需要手動commit()了
private boolean autoCommit;
public SpringManagedTransaction(DataSource dataSource) {
Assert.notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
}
public Connection getConnection() throws SQLException {
if (this.connection == null) {
this.openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
// DataSourceUtils獲取對應的事務性ConnectionHolder,然後比對當前連線與ConnectionHolder
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
}
}
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
}
// 事務提交
this.connection.commit();
}
}
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
}
// 事務回滾
this.connection.rollback();
}
}
public void close() throws SQLException {
// 通過DataSourceUtils,釋放當前連線。依舊涉及ConnectionHolder
DataSourceUtils.releaseConnection(this.connection, this.dataSource);
}
public Integer getTimeout() throws SQLException {
// Connection沒有對應的事務超時時間,這裡直接呼叫底層實現,獲取事務超時時間
ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);
return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null;
}
}
```
這裡的實現,涉及Connection的事務實現、DataSourceUtils、TransactionSynchronizationManager.getResource三個點。
### f.快取模組
Cache:多種實現。如FIFO、LRU
CacheKey:應對SQL的可變引數
TransactionalCacheManager&TransactionalCache:事務快取
快取模組,是直接關聯執行模組-Executor模組
* Mybatis的快取:
* 一級快取:預設開啟。屬於SqlSession級別的快取。利用BaseExecute -> PerpetualCache -> HashMap實現。
* 二級快取:預設關閉。屬於全域性級別的快取。利用CacheExecute -> TransactionalCacheManager -> HashMap -> TransactionalCache
快取實現,採用裝飾器模式
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210202170729700.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center)
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210202182132203.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70)
#### Cache
```java
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}
```
#### PerpetualCache
```java
public class PerpetualCache implements Cache {
private final String id;
private final Map