封面:洛小汐
作者:潘潘
做大事和做小事的難度是一樣的。兩者都會消耗你的時間和精力,所以如果決心做事,就要做大事,要確保你的夢想值得追求,未來的收獲可以配得上你的努力。
前言
上一篇文章 《Mybatis系列全解(三):Mybatis簡單CRUD使用介紹》 ,我們基本上手了 Mybatis 的增刪改查操作,也感受到 Mybatis 的簡單高效舒美,但是肯定有部分朋友對於 Mybatis 的配置文件只是了解基本組成和大致用法,尚無一套完整的結構記憶,所以本篇文章我們將詳細的介紹 Mybatis 的配置全貌,畢竟 Mybatis 的配置文件對於整個 Mybatis 體系的構建與支撐有着深遠的影響。
Mybatis系列全解腦圖分享,持續更新中
Mybaits系列全解 (持續更新)
- Mybatis系列全解(一):手寫一套持久層框架
- Mybatis系列全解(二):Mybatis簡介與環境搭建
- Mybatis系列全解(三):Mybatis簡單CRUD使用介紹
- Mybatis系列全解(四):全網最全!Mybatis配置文件XML全貌詳解
- Mybatis系列全解(五):全網最全!詳解Mybatis的Mapper映射文件
- Mybatis系列全解(六):Mybatis最硬核的API你知道幾個?
- Mybatis系列全解(七):Dao層兩種實現方式
- Mybatis系列全解(八):Mybatis的動態SQL
- Mybatis系列全解(九):Mybatis的復雜映射
- Mybatis系列全解(十):Mybatis注解開發
- Mybatis系列全解(十一):Mybatis緩存全解
- Mybatis系列全解(十二):Mybatis插件開發
- Mybatis系列全解(十三):Mybatis代碼生成器
- Mybatis系列全解(十四):Spring集成Mybatis
- Mybatis系列全解(十五):SpringBoot集成Mybatis
- Mybatis系列全解(十六):Mybatis源碼剖析
目錄
1、為什么要使用配置文件
2、Mybatis 配置全貌
3、XML 核心配置
4、XML 映射文件
5、總結
為什么要使用配置文件
試想,如果沒有配置文件,我們的應用程序將只能沿着固定的姿態運行,幾乎不能做任何動態的調整,那么這不是一套完美的設計,因為我們希望擁有更寬更靈活的操作空間和更多的兼容度,同時也能解決硬編碼等問題,所以我們需要有配置文件,對應用程序進行參數預設和設置初始化工作。
那我們為何鍾情XML?
首先,當然是 XML 配置文件本身就足夠優秀,格式規范,存儲小,跨平台,讀取快…等等,所謂窈窕淑女,誰人不愛。
其次,也是一個重要影響因素,就是各大領域大佬的支持,像微軟、像Java系…等等,世上本無路,只是走的人多了,也就成了路 ( 這句話是魯迅老先生說的)。
所以,Mybatis選擇搭配XML配置,實屬合理。
Mybatis 配置全貌
Mybatis框架本身,理論上就一個配置文件,其實也只需要一個配置文件,即mybatis-config.xml (當然文件名允許自由命名),只不過這個配置文件其中的一個屬性mappers(映射器),由於可能產生過多的SQL映射文件,於是我們物理上單獨拓展出來,允許使用者定義任意數量的 xxxMapper.xml 映射文件。
把SQL映射文件單獨配置,是有好處的,一是靈活度上允許任意拓展,二也避免了其它無需經常變動的屬性配置遭遇誤改。
我們看看Mybatis官網給出的配置文件層次結構:
- configuration(配置)
- properties(屬性)
- settings(設置)
- typeAliases(類型別名)
- 三種別名定義方式
- typeHandlers(類型處理器)
- 自定義類型處理器
- objectFactory(對象工廠)
- plugins(插件)
- environments(環境配置)
- environment(環境變量)
- transactionManager(事務管理器)
- dataSource(數據源)
- 三種支持數據源與自定義數據源
- environment(環境變量)
- databaseIdProvider(數據庫廠商標識)
- mappers(映射器)
實際配置文件XML內容如下,除了約束頭 <?xml> 與 <!DOCTYPE>,
其余標簽元素都是 Mybatis 的核心配置屬性 :
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 1、屬性:例如jdbc.properties -->
<properties resource="jdbc.properties"></properties>
<!-- 2、設置:定義全局性設置,例如開啟二級緩存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 3、類型名稱:為一些類定義別名 -->
<typeAliases>
<typeAlias type="com.panshenlian.pojo.User" alias="user"></typeAlias>
</typeAliases>
<!-- 4、類型處理器:定義Java類型與數據庫中的數據類型之間的轉換關系 -->
<typeHandlers></typeHandlers>
<!-- 5、對象工廠 -->
<objectFactory type=""></objectFactory>
<!-- 6、插件:mybatis的插件,支持自定義插件 -->
<plugins>
<plugin interceptor=""></plugin>
</plugins>
<!-- 7、環境:配置mybatis的環境 -->
<environments default="development">
<!-- 環境變量:支持多套環境變量,例如開發環境、生產環境 -->
<environment id="development">
<!-- 事務管理器:默認JDBC -->
<transactionManager type="JDBC" />
<!-- 數據源:使用連接池,並加載mysql驅動連接數據庫 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!-- 8、數據庫廠商標識 -->
<databaseIdProvider type=""></databaseIdProvider>
<!-- 9、映射器:指定映射文件或者映射類 -->
<mappers>
<mapper resource="UserMapper.xml" />
</mappers>
</configuration>
必須注意:Mybatis配置文件的屬性位置順序是 固定 的,不允許 顛倒順序,否則 Mybatis 在解析 XML 文件的時候就會拋出異常,這個與 Mybatis 框架啟動加載配置信息順序有關,后續我們源碼分析會講到。
以上基本能夠清晰看明白 Mybatis 配置文件的層次結構關系,我們簡單畫一張腦圖:
基本是需要我們掌握 9 大頂級元素配置,其中標記 橘紅色 的屬性配置,由於涉及 插件 和 動態SQL ,插件配置可以應用於分頁與功能增強等,動態SQL例如 if 標簽、where 標簽、foreach標簽等,初步理解為應用於SQL語句拼接。這兩塊屬於 Mybatis 的兩個特性,我們后續單獨詳細進行梳理討論。
XML 核心配置
我們的核心配置文件 configuration(配置)作為最頂級節點,其余 9 大屬性都必須嵌套在其內,對於內部 9 大節點,我們逐一講解:
1、properties(屬性)
屬性標簽,顯而易見就是提供屬性配置,可進行動態替換,一般可以在 Java 屬性文件中配置,例如 jdbc.properties 配置文件 ,或通過 properties 元素標簽中的子元素 property 來指定配置。
舉例我們需要配置數據源信息,采用 property 標簽可以這樣配置:
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/myDB"/>
<property name="username" value="user1"/>
<property name="password" value="123456"/>
</properties>
設置好的屬性可以在整個配置文件中用來替換需要動態配置的屬性值。比如:
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
或者我們使用 Java 中的屬性配置文件,把屬性配置元素具體化到一個屬性文件中,並且使用屬性文件的 key 名作為占位符。例如 jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc\:mysql\://127.0.0.1\:3306/myDB
username=root
password=123456
使用時我們把屬性文件引入,並使用文件中定義的占位符,例如 db.driver :
<!-- 引入屬性配置文件 -->
<properties resource="jdbc.properties"></properties>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
但是問題來了,當我們既使用 *.properties 配置文件,同時又設置了 property 元素值,Mybatis 會使用哪邊配置的屬性值呢? 例如這種情況 :
<properties resource="jdbc.properties">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/myDB"/>
<property name="username" value="user1"/>
<property name="password" value="123456"/>
</properties>
這里,如果在 property 標簽元素與 jdbc.properties 文件中同時存在相同屬性,那么屬性文件將會覆蓋 property 標簽元素的屬性,例如最終 username屬性值會使用 jdbc.properties 文件中設置的 root,而不會使用屬性元素設置的 user1 。這樣實際為配置提供了諸多靈活選擇。
另外,properties 元素允許配置 resource 屬性或 url 屬性,只能二選一,要么使用 resource 指定本地的配置文件,要么使用 url 指定遠程的配置文件,因為 Mybatis 在加載配置時,如果發現 url 與 resource 同時存在,會拋出異常禁止。
<!-- 配置resource-->
<properties resource="xxx.properties">
<property name="driver" value="com.mysql.jdbc.Driver"/>
</properties>
<!-- 配置url-->
<properties url="http://xxxx">
<property name="driver" value="com.mysql.jdbc.Driver"/>
</properties>
還有一種情況,像 Mybatis 在解析配置的時候,也可以在 Java 代碼中構建屬性 java.util.Properties 屬性對象並傳遞到 SqlSessionFactoryBuilder.build() 方法中,例如:
// 構建屬性對象
Properties props = new Properties();
props.setProperty("driver","com.mysql.jdbc.Driver");
props.setProperty("url","jdbc:mysql://127.0.0.1:3306/myDB");
props.setProperty("username","user1");
props.setProperty("password","123456");
// 傳遞屬性構建 SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
那么這三種方式都允許配置,那在屬性配置重復的情況下,優先級別是怎樣呢?
properties 優先級
1、第一優先級:在 Java 代碼中構建的 properties 屬性對象;
2、第二優先級:通過屬性 resource 或 url 讀取到的本地文件或遠程文件;
3、第三優先級:直接在 properties 內部子標簽元素 property 中設置的屬性。
注意,在實際開發中,為了避免給后期維護造成困擾,建議使用單一種配置方式。
2、settings(設置)
settings 標簽元素,是 MyBatis 中極為重要的調整設置,它們會動態改變 MyBatis 的運行時行為,這些配置就像 Mybatis 內置的許多功能,當你需要使用時可以根據需要靈活調整,並且 settings 能配置的東西特別多,我們先來一起看看,一個完整的屬性配置示例:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<... more .../>
</settings>
-
屬性cacheEnabled
- 全局性地開啟或關閉所有映射器配置文件中已配置的任何緩存
- 支持 true | false
- 默認 true
-
屬性lazyLoadingEnabled
- 延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 特定關聯關系中可通過設置 fetchType 屬性來覆蓋該項的開關狀態。
- 支持 true | false
- 默認 false
-
屬性 aggressiveLazyLoading
- 開啟時,任一方法的調用都會加載該對象的所有延遲加載屬性。 否則,每個延遲加載屬性會按需加載(參考 lazyLoadTriggerMethods)。
- 支持 true | false
- 默認 false (在 3.4.1 及之前的版本中默認為 true)
-
屬性 multipleResultSetsEnabled
- 是否允許單個語句返回多結果集(需要數據庫驅動支持)。
- 支持 true | false
- 默認 true
-
屬性 useColumnLabel
- 使用列標簽代替列名。實際表現依賴於數據庫驅動,具體可參考數據庫驅動的相關文檔,或通過對比測試來觀察。
- 支持 true | false
- 默認 true
-
屬性 useGeneratedKeys
- 允許 JDBC 支持自動生成主鍵,需要數據庫驅動支持。如果設置為 true,將強制使用自動生成主鍵。盡管一些數據庫驅動不支持此特性,但仍可正常工作(如 Derby)。
- 支持 true | false
- 默認 false
-
屬性 autoMappingBehavior
- 指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示關閉自動映射;PARTIAL 只會自動映射沒有定義嵌套結果映射的字段。 FULL 會自動映射任何復雜的結果集(無論是否嵌套)。
- 支持 NONE, PARTIAL, FULL
- 默認 PARTIAL
-
屬性 autoMappingUnknownColumnBehavior
- 指定發現自動映射目標未知列(或未知屬性類型)的行為。
- NONE: 不做任何反應
- WARNING: 輸出警告日志( org.apache.ibatis.session.AutoMappingUnknownColumnBehavior 的日志等級必須設置為 WARN)
- FAILING: 映射失敗 (拋出 SqlSessionException)
- 支持 NONE, WARNING, FAILING
- 默認 NONE
- 指定發現自動映射目標未知列(或未知屬性類型)的行為。
-
屬性 defaultExecutorType
- 配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(PreparedStatement); BATCH 執行器不僅重用語句還會執行批量更新。
- 支持 SIMPLE REUSE BATCH
- 默認 SIMPLE
-
屬性 defaultStatementTimeout
- 設置超時時間,它決定數據庫驅動等待數據庫響應的秒數。
- 支持 任意正整數
- 默認 未設置 (null)
-
屬性 defaultFetchSize
- 動的結果集獲取數量(fetchSize)設置一個建議值。此參數只可以在查詢設置中被覆蓋。
- 支持 任意正整數
- 默認 未設置 (null)
-
屬性 defaultResultSetType
- 指定語句默認的滾動策略。(新增於 3.5.2)
- 支持 FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同於未設置)
- 默認 未設置 (null)
-
屬性 safeRowBoundsEnabled
- 是否允許在嵌套語句中使用分頁(RowBounds)。如果允許使用則設置為 false。
- 支持 true | false
- 默認 false
-
屬性 safeResultHandlerEnabled
- 是否允許在嵌套語句中使用結果處理器(ResultHandler)。如果允許使用則設置為 false。
- 支持 true | false
- 默認 true
-
屬性 mapUnderscoreToCamelCase
- 是否開啟駝峰命名自動映射,即從經典數據庫列名 A_COLUMN 映射到經典 Java 屬性名 aColumn。
- 支持 true | false
- 默認 false
-
屬性 localCacheScope
- MyBatis 利用本地緩存機制(Local Cache)防止循環引用和加速重復的嵌套查詢。 默認值為 SESSION,會緩存一個會話中執行的所有查詢。 若設置值為 STATEMENT,本地緩存將僅用於執行語句,對相同 SqlSession 的不同查詢將不會進行緩存。
- 支持 SESSION | STATEMENT
- 默認 SESSION
-
屬性 jdbcTypeForNull
- 當沒有為參數指定特定的 JDBC 類型時,空值的默認 JDBC 類型。 某些數據庫驅動需要指定列的 JDBC 類型,多數情況直接用一般類型即可,比如 NULL、VARCHAR 或 OTHER。
- JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。
- 默認 OTHER
-
屬性 lazyLoadTriggerMethods
- 指定對象的哪些方法觸發一次延遲加載。
- 支持 用逗號分隔的方法列表。
- 默認 equals,clone,hashCode,toString
-
屬性 defaultScriptingLanguage
- 指定動態 SQL 生成使用的默認腳本語言。
- 支持 一個類型別名或全限定類名。
- 默認 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
-
屬性 defaultEnumTypeHandler
- 指定 Enum 使用的默認 TypeHandler 。(新增於 3.4.5)
- 支持 一個類型別名或全限定類名。
- 默認 org.apache.ibatis.type.EnumTypeHandler
-
屬性 callSettersOnNulls
- 指定當結果集中值為 null 的時候是否調用映射對象的 setter(map 對象時為 put)方法,這在依賴於 Map.keySet() 或 null 值進行初始化時比較有用。注意基本類型(int、boolean 等)是不能設置成 null 的。
- 支持 true | false
- 默認 false
-
屬性 returnInstanceForEmptyRow
- 當返回行的所有列都是空時,MyBatis默認返回 null。 當開啟這個設置時,MyBatis會返回一個空實例。 請注意,它也適用於嵌套的結果集(如集合或關聯)。(新增於 3.4.2)
- 支持 true | false
- 默認 false
-
屬性 logPrefix
- 指定 MyBatis 增加到日志名稱的前綴。
- 支持 任何字符串
- 默認 未設置
-
屬性 logImpl
- 指定 MyBatis 所用日志的具體實現,未指定時將自動查找。
- 支持 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
- 默認 未設置
-
屬性 proxyFactory
- 指定 Mybatis 創建可延遲加載對象所用到的代理工具。
- 支持 CGLIB | JAVASSIST
- 默認 JAVASSIST (MyBatis 3.3 以上)
-
屬性 vfsImpl
- 指定 VFS 的實現
- 支持 自定義 VFS 的實現的類全限定名,以逗號分隔。
- 默認 未設置
-
屬性 useActualParamName
- 允許使用方法簽名中的名稱作為語句參數名稱。 為了使用該特性,你的項目必須采用 Java 8 編譯,並且加上 -parameters 選項。(新增於 3.4.1)
- 支持 true | false
- 默認 true
-
屬性 configurationFactory
- 指定一個提供 Configuration 實例的類。 這個被返回的 Configuration 實例用來加載被反序列化對象的延遲加載屬性值。 這個類必須包含一個簽名為static Configuration getConfiguration() 的方法。(新增於 3.2.3)
- 支持 一個類型別名或完全限定類名。
- 默認 未設置
-
屬性 shrinkWhitespacesInSql
- 從SQL中刪除多余的空格字符。請注意,這也會影響SQL中的文字字符串。 (新增於 3.5.5)
- 支持 true | false
- 默認 false
-
屬性 defaultSqlProviderType
- 指定一個本身擁查詢方法的類( 從 3.5.6 開始 ),這個類可以配置在注解 @SelectProvider 的 type 屬性值上。
- 支持 一個類型別名或完全限定類名。
- 默認 未設置
settings 支持了特別多功能支持,其實常規開發中使用到的屬性項不會特別多,除非項目有特殊要求,所以建議大家把這些設置當做字典即可,不必詳記 每一個屬性使用,需要時翻閱研讀。
3、typeAliases(類型別名)
類型別名可以給 Java 類型設置一個簡稱。 它僅用於 XML 配置,意在降低冗余的全限定類名書寫,因為書寫類的全限定名太長了,我們希望有一個簡稱來指代它。類型別名在 Mybatis 中分為 系統內置 和 用戶自定義 兩類,Mybatis 會在解析配置文件時把 typeAliases 實例存儲進入 Configuration 對象中,需要使用時直接獲取。
一般我們可以自定義別名,例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>
像這樣配置時,我們就可以在任何需要使用 domain.blog.Author 的地方,直接使用別名 author 。
但是,如果遇到項目中特別多 Java 類需要配置別名,怎么更快的設置呢?
可以指定一個包名進行掃描,MyBatis 會在包名下面掃描需要的 Java Bean,比如:
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
每一個在包 domain.blog 中的 Java Bean,在沒有注解的情況下,會使用 Bean 的首字母小寫的非限定類名來作為它的別名。 比如 domain.blog.Author 的別名為 author;若有 注解 ,則別名為其自定義的注解值。見下面的例子:
@Alias("myAuthor")
public class Author {
...
}
Mybatis 已經為許多常見的 Java 類型內建了相應的類型別名。下面就是一些為常見的 Java 類型內建的類型別名。它們都是不區分大小寫的,注意,為了應對原始類型的命名重復,采取了特殊的命名風格,可以發現 基本類型 的別名前綴都有下划線 ‘_’,而基本類型的 包裝類 則沒有,這個需要注意:
- 別名 _byte,對應的類型是:byte
- 別名 _long,對應的類型是:long
- 別名 _short,對應的類型是:short
- 別名 _int,對應的類型是:int
- 別名 _integer,對應的類型是:int
- 別名 _double,對應的類型是:double
- 別名 _float,對應的類型是:float
- 別名 _boolean,對應的類型是:boolean
- 別名 string,對應的類型是:String
- 別名 byte,對應的類型是:Byte
- 別名 long,對應的類型是:Long
- 別名 short,對應的類型是:Short
- 別名 int,對應的類型是:Integer
- 別名 integer,對應的類型是:Integer
- 別名 double,對應的類型是:Double
- 別名 float,對應的類型是:Float
- 別名 boolean,對應的類型是:Boolean
- 別名 date,對應的類型是:Date
- 別名 decimal,對應的類型是:BigDecimal
- 別名 bigdecimal,對應的類型是:BigDecimal
- 別名 object,對應的類型是:Object
- 別名 map,對應的類型是:Map
- 別名 hashmap,對應的類型是:HashMap
- 別名 list,對應的類型是:List
- 別名 arraylist,對應的類型是:ArrayList
- 別名 collection,對應的類型是:Collection
- 別名 iterator,對應的類型是:Iterator
我們可以通過源碼查看內置的類型別名的注冊信息。
具體源碼路徑在 org.apache.ibatis.type.TypeAliasRegistry # TypeAliasRegistry() :
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
別名是不區分大小寫的,同時也支持數組類型,只需要加 “[]” 即可使用,比如 Long 數組別名我們可以用 long[] 直接代替,例如在實際開發中,int 、INT 、integer 、INTEGER 都是代表 Integer , 這里主要由於 MyBatis 在注冊別名的時候會全部轉為小寫字母進行存儲,另外以上列表 無需牢記,僅僅在需要使用的時候查閱即可,基本也都可以看得明白。
4、typeHandlers(類型處理器)
MyBatis 在設置預處理SQL語句(PreparedStatement)中所需要的 參數 或從 結果集 ResultSet 中獲取對象時, 都會用類型處理器將獲取到的值以合適的方式轉換成 Java 類型。
類型處理器,主要用於處理 Java 類型與 JDBC 類型的映射匹配關系處理,下表描述了一些默認的類型處理器。
- 類型處理器 BooleanTypeHandler
- Java 類型:java.lang.Boolean, boolean
- JDBC 類型:數據庫兼容的 BOOLEAN
- 類型處理器 ByteTypeHandler
- Java 類型:java.lang.Byte, byte
- JDBC 類型:數據庫兼容的 NUMERIC 或 BYTE
- 類型處理器 ShortTypeHandler
- Java 類型:java.lang.Short, short
- JDBC 類型:數據庫兼容的 NUMERIC 或 SMALLINT
- 類型處理器 IntegerTypeHandler
- Java 類型:java.lang.Integer, int
- JDBC 類型:數據庫兼容的 NUMERIC 或 INTEGER
- 類型處理器 LongTypeHandler
- Java 類型:java.lang.Long, long
- JDBC 類型:數據庫兼容的 NUMERIC 或 BIGINT
- 類型處理器 FloatTypeHandler
- Java 類型:java.lang.Float, float
- JDBC 類型:數據庫兼容的 NUMERIC 或 FLOAT
- 類型處理器 DoubleTypeHandler
- Java 類型:java.lang.Double, double
- JDBC 類型:數據庫兼容的 NUMERIC 或 DOUBLE
- 類型處理器 BigDecimalTypeHandler
- Java 類型:java.math.BigDecimal
- JDBC 類型:數據庫兼容的 NUMERIC 或 DECIMAL
- 類型處理器 StringTypeHandler
- Java 類型:java.lang.String
- JDBC 類型:CHAR, VARCHAR
- 類型處理器 ClobReaderTypeHandler
- Java 類型:java.io.Reader
- JDBC 類型:-
- 類型處理器 ClobTypeHandler
- Java 類型:java.lang.String
- JDBC 類型:CLOB, LONGVARCHAR
- 類型處理器 NStringTypeHandler
- Java 類型:java.lang.String
- JDBC 類型:NVARCHAR, NCHAR
- 類型處理器 NClobTypeHandler
- Java 類型:java.lang.String
- JDBC 類型:NCLOB
- 類型處理器 BlobInputStreamTypeHandler
- Java 類型:java.io.InputStream
- JDBC 類型:-
- 類型處理器 ByteArrayTypeHandler
- Java 類型:byte[]
- JDBC 類型:數據庫兼容的字節流類型
- 類型處理器 BlobTypeHandler
- Java 類型:byte[]
- JDBC 類型:BLOB, LONGVARBINARY
- 類型處理器 DateTypeHandler
- Java 類型:java.util.Date
- JDBC 類型:TIMESTAMP
- 類型處理器 DateOnlyTypeHandler
- Java 類型:java.util.Date
- JDBC 類型:DATE
- 類型處理器 TimeOnlyTypeHandler
- Java 類型:java.util.Date
- JDBC 類型:TIME
- 類型處理器 SqlTimestampTypeHandler
- Java 類型:java.sql.Timestamp
- JDBC 類型:TIMESTAMP
- 類型處理器 SqlDateTypeHandler
- Java 類型:java.sql.Date
- JDBC 類型:DATE
- 類型處理器 SqlTimeTypeHandler
- Java 類型:java.sql.Time
- JDBC 類型:TIME
- 類型處理器 ObjectTypeHandler
- Java 類型:Any
- JDBC 類型:OTHER 或未指定類型
- 類型處理器 EnumTypeHandler
- Java 類型:Enumeration Type
- JDBC 類型:VARCHAR 或任何兼容的字符串類型,用來存儲枚舉的名稱(而不是索引序數值)
- 類型處理器 EnumOrdinalTypeHandler
- Java 類型:Enumeration Type
- JDBC 類型:任何兼容的 NUMERIC 或 DOUBLE 類型,用來存儲枚舉的序數值(而不是名稱)。
- 類型處理器 SqlxmlTypeHandler
- Java 類型:java.lang.String
- JDBC 類型:SQLXML
- 類型處理器 InstantTypeHandler
- Java 類型:java.time.Instant
- JDBC 類型:TIMESTAMP
- 類型處理器 LocalDateTimeTypeHandler
- Java 類型:java.time.LocalDateTime
- JDBC 類型:TIMESTAMP
- 類型處理器 LocalDateTypeHandler
- Java 類型:java.time.LocalDate
- JDBC 類型:DATE
- 類型處理器 LocalTimeTypeHandler
- Java 類型:java.time.LocalTime
- JDBC 類型:TIME
- 類型處理器 OffsetDateTimeTypeHandler
- Java 類型:java.time.OffsetDateTime
- JDBC 類型:TIMESTAMP
- 類型處理器 OffsetTimeTypeHandler
- Java 類型:java.time.OffsetTime
- JDBC 類型:TIME
- 類型處理器 ZonedDateTimeTypeHandler
- Java 類型:java.time.ZonedDateTime
- JDBC 類型:TIMESTAMP
- 類型處理器 YearTypeHandler
- Java 類型:java.time.Year
- JDBC 類型:INTEGER
- 類型處理器 MonthTypeHandler
- Java 類型:java.time.Month
- JDBC 類型:INTEGER
- 類型處理器 YearMonthTypeHandler
- Java 類型:java.time.YearMonth
- JDBC 類型:VARCHAR 或 LONGVARCHAR
- 類型處理器 JapaneseDateTypeHandler
- Java 類型:java.time.chrono.JapaneseDate
- JDBC 類型:DATE
我們可以通過源碼查看內置的類型別名的注冊信息。
具體源碼路徑在 org.apache.ibatis.type.TypeHandlerRegistry # TypeHandlerRegistry() :
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler());
register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler());
register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler());
register(Object.class, UNKNOWN_TYPE_HANDLER);
register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler());
register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
// mybatis-typehandlers-jsr310
if (Jdk.dateAndTimeApiExists) {
Java8TypeHandlersRegistrar.registerDateAndTimeHandlers(this);
}
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
從 3.4.5 開始,MyBatis 默認支持 JSR-310(日期和時間 API) ,可以在以上源碼上看到新增支持。
一般,你可以重寫已有的類型處理器,
或根據業務需要創建你自己的類型處理器,
以處理不支持的類型或非標准的類型。
具體做法為:
1、實現 org.apache.ibatis.type.TypeHandler
接口;
2、繼承 org.apache.ibatis.type.BaseTypeHandler
類。
本身 BaseTypeHandler 類作為抽象類就已經實現了 TypeHandler 接口。
所以我們看到接口 TypeHandler 定義了四個方法:
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
從方法名 setParameter 和 getResult 我們就可以知道,是發生在預編譯時設置參數(增刪改查傳入參數)與查詢結果集后轉換為 Java 類型時,類型處理器發揮作用。
具體實現如下,先自定義類型處理器類 MyExampleTypeHandler :
// MyExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MyExampleTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
自定義類已設定:JdbcType.VARCHAR 與 String 類做映射轉換(注解和泛型已體現)。
其次,在核心配置文件中設置類型處理器:
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.mybatis.example.MyExampleTypeHandler"/>
</typeHandlers>
或者不使用注解方式的話,取消 @MappedJdbcTypes(JdbcType.VARCHAR) 注解,直接在 xml 配置中指定 jdbcType 與 javaType 映射 :
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="string" handler="org.mybatis.example.MyExampleTypeHandler"/>
</typeHandlers>
記住, typeHandler 的配置方式優先級高於注解配置方式。
這里,自定義類型處理器將會覆蓋已有的處理 Java String 類型的屬性以及 VARCHAR 類型的參數和結果的類型處理器,基本以上步驟就已經自定了 JdbcType.VARCHAR 與 String類做映射轉換。
其實到這里,我們基本也就完成了類型處理器的自定義轉換,但是有一種情況,就是我們希望我們自定義的類型處理器只處理某一個 Java 實體中的 JdbcType.VARCHAR 與 String 類映射轉換,其它實體的處理還是使用系統內置的轉換,很簡單,我們只需要把以上兩步都去掉,在自定義類型處理類的注解@javaType和@MappedJdbcTypes都移除,配置文件中把 typehandler 屬性配置移除,直接在映射文件中編寫:
<resultMap id="MyResultMap" type="com.panshenlian.pojo.User">
<!-- id為int類型,但是沒指定自定義類型處理器,不受影響-->
<id column="id" property="id" />
<!-- username為String類型,但是沒指定自定義類型處理器,不受影響-->
<id column="username" property="username" />
<!-- password為String類型,但是沒指定自定義類型處理器,不受影響-->
<id column="password" property="password" />
<!-- birthday為String類型,指定自定義類型處理器,受影響!-->
<id column="birthday" property="birthday" typeHandler="com.panshenlian.typeHandler.MyStringHandler"/>
</resultMap>
<select id="findAll" resultType="com.panshenlian.pojo.User" resultMap="MyResultMap">
select * from User
</select>
User 實體參考:
package com.panshenlian.pojo;
/** * @Author: panshenlian * @Description: 用戶實體 * @Date: Create in 2:08 2020/12/07 */
public class User {
private int id;
private String username;
private String password;
private String birthday;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
}
最終自定義類型處理器,只會對 birthday 字段產生影響,其余字段均不受影響。
自定義類型處理器很靈活,只有當指定對應的 Java 類型和 Jdbc 類型時,處理器才會具體生效,否則 Mybatis 會默認匹配系統內置的類型處理器。
另外,當我們自定義很多類型處理器時,系統支持配置包掃描的方式查找類型處理器:
<!-- mybatis-config.xml -->
<typeHandlers>
<package name="org.mybatis.example"/>
</typeHandlers>
注意在使用自動發現功能的時候,只能通過注解方式來指定 JDBC 的類型。
你可以創建能夠處理多個類的泛型類型處理器。為了使用泛型類型處理器, 需要增加一個接受該類的 class 作為參數的構造器,這樣 MyBatis 會在構造一個類型處理器實例的時候傳入一個具體的類。
//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {
private Class<E> type;
public GenericTypeHandler(Class<E> type) {
if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
this.type = type;
}
...
處理枚舉類型
若想映射枚舉類型 Enum
,則需要從 EnumTypeHandler
或者 EnumOrdinalTypeHandler
中選擇一個來使用。
比如說我們想存儲取近似值時用到的舍入模式。默認情況下,MyBatis 會利用 EnumTypeHandler
來把 Enum
值轉換成對應的名字。
注意
EnumTypeHandler
在某種意義上來說是比較特別的,其它的處理器只針對某個特定的類,而它不同,它會處理任意繼承了Enum
的類。不過,我們可能不想存儲名字,相反我們的 DBA 會堅持使用整形值代碼。那也一樣簡單:在配置文件中把
EnumOrdinalTypeHandler
加到typeHandlers
中即可, 這樣每個RoundingMode
將通過他們的序數值來映射成對應的整形數值。
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>
但要是你想在一個地方將 Enum
映射成字符串,在另外一個地方映射成整形值呢?
自動映射器(auto-mapper)會自動地選用 EnumOrdinalTypeHandler
來處理枚舉類型, 所以如果我們想用普通的 EnumTypeHandler
,就必須要顯式地為那些 SQL 語句設置要使用的類型處理器。
下一篇文章我們才開始介紹映射器 mapper.xml 文件,如果你首次閱讀映射器概念,可能需要先跳過這里先去了解 mapper.xml 文件配置,再回頭過來看。
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="funkyNumber" property="funkyNumber"/>
<result column="roundingMode" property="roundingMode"/>
</resultMap>
<select id="getUser" resultMap="usermap">
select * from users
</select>
<insert id="insert">
insert into users (id, name, funkyNumber, roundingMode) values (
#{id}, #{name}, #{funkyNumber}, #{roundingMode}
)
</insert>
<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="funkyNumber" property="funkyNumber"/>
<result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
</resultMap>
<select id="getUser2" resultMap="usermap2">
select * from users2
</select>
<insert id="insert2">
insert into users2 (id, name, funkyNumber, roundingMode) values (
#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
)
</insert>
</mapper>
注意,這里的 select 語句強制使用 resultMap
來代替 resultType
。
5、objectFactory(對象工廠)
每次 MyBatis 創建結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)實例來完成實例化工作。 默認的對象工廠需要做的僅僅是實例化目標類,要么通過默認無參構造方法,要么通過存在的參數映射來調用帶有參數的構造方法。 如果想覆蓋對象工廠的默認行為,可以通過創建自己的對象工廠來實現。比如:
// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
}
public Object create(Class type, List constructorArgTypes, List constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
public boolean isCollection(Class type) {
return Collection.class.isAssignableFrom(type);
}
}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
ObjectFactory 接口很簡單,它包含兩個創建用的方法,一個是處理默認構造方法的,另外一個是處理帶參數的構造方法的。 最后,setProperties 方法可以被用來配置 ObjectFactory,在初始化你的 ObjectFactory 實例后, objectFactory 元素體中定義的屬性會被傳遞給 setProperties 方法。
正常情況下我們不需要使用到,或者說不建議使用,除非業務上確實需要對一個特殊實體初始構造做一個默認屬性值配置等處理,其余情況不推薦使用,避免產生不可控風險。
6、plugins(插件)
MyBatis 允許你在映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
插件功能主要開放攔截的對象就是以上列舉的 Mybatis 四大組件,后續我們講 Mybatis 核心API 的時候或者單獨介紹自定義插件的時候會詳細說明,這里大家可以先大致了解,包括數據分頁、操作日志增強、sql 性能監控等都可以通過插件實現,不過會存儲改造的風險,畢竟這些都是核心的 API 。
這四大類中方法具體可以通過查看每個方法的簽名來發現,或者直接查看 MyBatis 發行包中的源代碼。 如果你想做的不僅僅是監控方法的調用,那么你最好相當了解要重寫的方法的行為。 因為在試圖修改或重寫已有方法的行為時,很可能會破壞 MyBatis 的核心模塊。 這些都是更底層的類和方法,所以使用插件的時候要特別當心。
通過 MyBatis 提供的強大機制,使用插件是非常簡單的,只需實現 Interceptor 接口,並指定想要攔截的類,方法,參數(由於有多態的情況)即可。
// ExamplePlugin.java
@Intercepts({
@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
上面的插件將會攔截在 Executor 實例中所有的 “update” 方法調用, 這里的 Executor 是負責執行底層映射語句的內部對象。
覆蓋配置類 「 謹慎使用,存在風險 」
除了用插件來修改 MyBatis 核心行為以外,還可以通過完全覆蓋配置類來達到目的。只需繼承配置類后覆蓋其中的某個方法,再把它傳遞到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,這可能會極大影響 MyBatis 的行為,務請慎之又慎。
7、environments(環境配置)
MyBatis 可以配置成適應多種環境,這種機制有助於將 SQL 映射應用於多種數據庫之中, 現實情況下有多種理由需要這么做。例如,開發、測試和生產環境需要有不同的配置;或者想在具有相同 Schema 的多個生產數據庫中使用相同的 SQL 映射。還有許多類似的使用場景。
不過要記住:盡管可以配置多個環境,但每個 SqlSessionFactory 實例只能選擇一種環境。
所以,如果你想連接兩個數據庫,就需要創建兩個 SqlSessionFactory 實例,每個數據庫對應一個。而如果是三個數據庫,就需要三個實例,依此類推,記起來很簡單:
每個數據庫對應一個 SqlSessionFactory 實例。
為了指定創建哪種環境,只要將它作為可選的參數傳遞給 SqlSessionFactoryBuilder 即可。可以接受環境配置的兩個方法簽名是:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
如果忽略了環境參數,那么將會加載默認環境,如下所示:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
environments 元素定義了如何配置環境。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
注意一些關鍵點:
- 默認使用的環境 ID(比如:default=“development”)。
- 每個 environment 元素定義的環境 ID(比如:id=“development”)。
- 事務管理器的配置(比如:type=“JDBC”)。
- 數據源的配置(比如:type=“POOLED”)。
默認環境和環境 ID 顧名思義。 環境可以隨意命名,但務必保證默認的環境 ID 要匹配其中一個環境 ID。
事務管理器(transactionManager)
在 MyBatis 中有兩種類型的事務管理器(也就是 type="[JDBC|MANAGED]"):
- JDBC – 這個配置直接使用了 JDBC 的提交和回滾設施,它依賴從數據源獲得的連接來管理事務作用域。
- MANAGED – 這個配置幾乎沒做什么。它從不提交或回滾一個連接,而是讓容器來管理事務的整個生命周期(比如 JEE 應用服務器的上下文)。 默認情況下它會關閉連接。然而一些容器並不希望連接被關閉,因此需要將 closeConnection 屬性設置為 false 來阻止默認的關閉行為。例如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
如果你正在使用 Spring + MyBatis,則沒有必要配置事務管理器,因為 Spring 模塊會使用自帶的管理器來覆蓋前面的配置。這兩種事務管理器類型都不需要設置任何屬性。它們其實是類型別名,換句話說,你可以用 TransactionFactory 接口實現類的全限定名或類型別名代替它們。
public interface TransactionFactory {
default void setProperties(Properties props) { // 從 3.5.2 開始,該方法為默認方法
// 空實現
}
Transaction newTransaction(Connection conn);
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
在事務管理器實例化后,所有在 XML 中配置的屬性將會被傳遞給 setProperties() 方法。你的實現還需要創建一個 Transaction 接口的實現類,這個接口也很簡單:
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
使用這兩個接口,你可以完全自定義 MyBatis 對事務的處理。
數據源(dataSource)
dataSource 元素使用標准的 JDBC 數據源接口來配置 JDBC 連接對象的資源。
大多數 MyBatis 應用程序會按示例中的例子來配置數據源。雖然數據源配置是可選的,但如果要啟用延遲加載特性,就必須配置數據源。
有三種內建的數據源類型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
UNPOOLED– 這個數據源的實現會每次請求時打開和關閉連接。雖然有點慢,但對那些數據庫連接可用性要求不高的簡單應用程序來說,是一個很好的選擇。 性能表現則依賴於使用的數據庫,對某些數據庫來說,使用連接池並不重要,這個配置就很適合這種情形。UNPOOLED 類型的數據源僅僅需要配置以下 5 種屬性:
driver
– 這是 JDBC 驅動的 Java 類全限定名(並不是 JDBC 驅動中可能包含的數據源類)。url
– 這是數據庫的 JDBC URL 地址。username
– 登錄數據庫的用戶名。password
– 登錄數據庫的密碼。defaultTransactionIsolationLevel
– 默認的連接事務隔離級別。defaultNetworkTimeout
– 等待數據庫操作完成的默認網絡超時時間(單位:毫秒)。查看java.sql.Connection#setNetworkTimeout()
的 API 文檔以獲取更多信息。
作為可選項,你也可以傳遞屬性給數據庫驅動。只需在屬性名加上“driver.”前綴即可,例如:
driver.encoding=UTF8
這將通過 DriverManager.getConnection(url, driverProperties) 方法傳遞值為
UTF8
的encoding
屬性給數據庫驅動。
POOLED– 這種數據源的實現利用“池”的概念將 JDBC 連接對象組織起來,避免了創建新的連接實例時所必需的初始化和認證時間。 這種處理方式很流行,能使並發 Web 應用快速響應請求。
除了上述提到 UNPOOLED 下的屬性外,還有更多屬性用來配置 POOLED 的數據源:
poolMaximumActiveConnections
– 在任意時間可存在的活動(正在使用)連接數量,默認值:10poolMaximumIdleConnections
– 任意時間可能存在的空閑連接數。poolMaximumCheckoutTime
– 在被強制返回之前,池中連接被檢出(checked out)時間,默認值:20000 毫秒(即 20 秒)poolTimeToWait
– 這是一個底層設置,如果獲取連接花費了相當長的時間,連接池會打印狀態日志並重新嘗試獲取一個連接(避免在誤配置的情況下一直失敗且不打印日志),默認值:20000 毫秒(即 20 秒)。poolMaximumLocalBadConnectionTolerance
– 這是一個關於壞連接容忍度的底層設置, 作用於每一個嘗試從緩存池獲取連接的線程。 如果這個線程獲取到的是一個壞的連接,那么這個數據源允許這個線程嘗試重新獲取一個新的連接,但是這個重新嘗試的次數不應該超過poolMaximumIdleConnections
與poolMaximumLocalBadConnectionTolerance
之和。 默認值:3(新增於 3.4.5)poolPingQuery
– 發送到數據庫的偵測查詢,用來檢驗連接是否正常工作並准備接受請求。默認是“NO PING QUERY SET”,這會導致多數數據庫驅動出錯時返回恰當的錯誤消息。poolPingEnabled
– 是否啟用偵測查詢。若開啟,需要設置poolPingQuery
屬性為一個可執行的 SQL 語句(最好是一個速度非常快的 SQL 語句),默認值:false。poolPingConnectionsNotUsedFor
– 配置 poolPingQuery 的頻率。可以被設置為和數據庫連接超時時間一樣,來避免不必要的偵測,默認值:0(即所有連接每一時刻都被偵測 — 當然僅當 poolPingEnabled 為 true 時適用)。
JNDI – 這個數據源實現是為了能在如 EJB 或應用服務器這類容器中使用,容器可以集中或在外部配置數據源,然后放置一個 JNDI 上下文的數據源引用。這種數據源配置只需要兩個屬性:
initial_context
– 這個屬性用來在 InitialContext 中尋找上下文(即,initialContext.lookup(initial_context))。這是個可選屬性,如果忽略,那么將會直接從 InitialContext 中尋找 data_source 屬性。data_source
– 這是引用數據源實例位置的上下文路徑。提供了 initial_context 配置時會在其返回的上下文中進行查找,沒有提供時則直接在 InitialContext 中查找。
JNDI 可理解是一種仿 windows 注冊表形式的數據源。
和其他數據源配置類似,可以通過添加前綴“env.”直接把屬性傳遞給 InitialContext。比如:
env.encoding=UTF8
這就會在 InitialContext 實例化時往它的構造方法傳遞值為 UTF8
的 encoding
屬性。
你可以通過實現接口 org.apache.ibatis.datasource.DataSourceFactory
來使用第三方數據源實現:
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
可被用作父類來構建新的數據源適配器,比如下面這段插入 C3P0 數據源所必需的代碼:
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
public C3P0DataSourceFactory() {
this.dataSource = new ComboPooledDataSource();
}
}
為了令其工作,記得在配置文件中為每個希望 MyBatis 調用的 setter 方法增加對應的屬性。 下面是一個可以連接至 PostgreSQL 數據庫的例子:
<dataSource type="org.myproject.C3P0DataSourceFactory">
<property name="driver" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql:mydb"/>
<property name="username" value="postgres"/>
<property name="password" value="root"/>
</dataSource>
8、databaseIdProvider(數據庫廠商標識)
MyBatis 可以根據不同的數據庫廠商執行不同的語句,這種多廠商的支持是基於映射語句中的 databaseId
屬性。 MyBatis 會加載帶有匹配當前數據庫 databaseId
屬性和所有不帶 databaseId
屬性的語句。 如果同時找到帶有 databaseId
和不帶 databaseId
的相同語句,則后者會被舍棄。 為支持多廠商特性,只要像下面這樣在 mybatis-config.xml 文件中加入 databaseIdProvider
即可:
<databaseIdProvider type="DB_VENDOR" />
databaseIdProvider 對應的 DB_VENDOR 實現會將 databaseId 設置為 DatabaseMetaData#getDatabaseProductName()
返回的字符串。 由於通常情況下這些字符串都非常長,而且相同產品的不同版本會返回不同的值,你可能想通過設置屬性別名來使其變短:
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
在提供了屬性別名時,databaseIdProvider 的 DB_VENDOR 實現會將 databaseId 設置為數據庫產品名與屬性中的名稱第一個相匹配的值,如果沒有匹配的屬性,將會設置為 “null”。 在這個例子中,如果 getDatabaseProductName()
返回“Oracle (DataDirect)”,databaseId 將被設置為“oracle”。
你可以通過實現接口 org.apache.ibatis.mapping.DatabaseIdProvider
並在 mybatis-config.xml 中注冊來構建自己的 DatabaseIdProvider:
public interface DatabaseIdProvider {
default void setProperties(Properties p) { // 從 3.5.2 開始,該方法為默認方法
// 空實現
}
String getDatabaseId(DataSource dataSource) throws SQLException;
}
9、mappers(映射器)
既然 MyBatis 的行為已經由上述元素配置完了,我們現在就要來定義 SQL 映射語句了。 但首先,我們需要告訴 MyBatis 到哪里去找到這些語句。 在自動查找資源方面,Java 並沒有提供一個很好的解決方案,所以最好的辦法是直接告訴 MyBatis 到哪里去找映射文件。 你可以使用相對於類路徑的資源引用,或完全限定資源定位符(包括 file:///
形式的 URL),或類名和包名等。例如:
<!-- 使用相對於類路徑的資源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定資源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口實現類的完全限定類名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 將包內的映射器接口實現全部注冊為映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
XML 映射文件
在 XML 核心配置文件介紹中,我們介紹了映射文件 mapper.xml 的引入。
對於 Mapper 具體的映射配置文件,是 Mybatis 最復雜、最核心的組件,其中的標簽內容體系也是特別詳實,包括它的參數類型、動態SQL、定義SQL、緩存信息等等,我們在下一篇文章中再進行梳理討論,這里我們簡單引出。
總結
原本我計划把核心配置文件和映射器 mapper 文件放一塊講,但是發現內容太多太多了,基本核心配置文件就已經講得有點拖堂了,雖然這幾大頂級標簽使用起來已經毫不費力。SQL 映射器配置文件,我們后續更新,這塊基本是和我們日常打交道最高頻的操作。
本篇完,本系列下一篇我們講《 Mybatis系列全解(五):全網最全!詳解Mybatis的Mapper映射文件 》。
BIU ~ 文章持續更新,微信搜索「潘潘和他的朋友們」第一時間閱讀,隨時有驚喜。本文會在 GitHub https://github.com/JavaWorld 收錄,熱騰騰的技術、框架、面經、解決方案,我們都會以最美的姿勢第一時間送達,歡迎 Star。