封面:洛小汐
做者:潘潘 java
作大事和作小事的難度是同樣的。二者都會消耗你的時間和精力,因此若是決心作事,就要作大事,要確保你的夢想值得追求,將來的收穫能夠配得上你的努力。mysql
上一篇文章 《Mybatis系列全解(三):Mybatis簡單CRUD使用介紹》 ,咱們基本上手了 Mybatis 的增刪改查操做,也感覺到 Mybatis 的簡單高效舒美,可是確定有部分朋友對於 Mybatis 的配置文件只是瞭解基本組成和大體用法,尚無一套完整的結構記憶,因此本篇文章咱們將詳細的介紹 Mybatis 的配置全貌,畢竟 Mybatis 的配置文件對於整個 Mybatis 體系的構建與支撐有着深遠的影響。git
Mybatis系列全解腦圖分享,持續更新中github
一、爲何要使用配置文件sql
二、Mybatis 配置全貌數據庫
三、XML 核心配置apache
四、XML 映射文件windows
五、總結數組
試想,若是沒有配置文件,咱們的應用程序將只能沿着固定的姿態運行,幾乎不能作任何動態的調整,那麼這不是一套完美的設計,由於咱們但願擁有更寬更靈活的操做空間和更多的兼容度,同時也能解決硬編碼等問題,因此咱們須要有配置文件,對應用程序進行參數預設和設置初始化工做。緩存
那咱們爲什麼鍾情XML?
首先,固然是 XML 配置文件自己就足夠優秀,格式規範,存儲小,跨平臺,讀取快...等等,所謂窈窕淑女,誰人不愛。
其次,也是一個重要影響因素,就是各大領域大佬的支持,像微軟、像Java系...等等,世上本無路,只是走的人多了,也就成了路 ( 這句話是魯迅老先生說的)。
因此,Mybatis選擇搭配XML配置,實屬合理。
Mybatis框架自己,理論上就一個配置文件,其實也只須要一個配置文件,即mybatis-config.xml (固然文件名容許自由命名),只不過這個配置文件其中的一個屬性mappers(映射器),因爲可能產生過多的SQL映射文件,因而咱們物理上單獨拓展出來,容許使用者定義任意數量的 xxxMapper.xml 映射文件。
把SQL映射文件單獨配置,是有好處的,一是靈活度上容許任意拓展,二也避免了其它無需常常變更的屬性配置遭遇誤改。
咱們看看Mybatis官網給出的配置文件層次結構:
實際配置文件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> <!-- 一、屬性:例如jdbc.properties --> <properties resource="jdbc.properties"></properties> <!-- 二、設置:定義全局性設置,例如開啓二級緩存 --> <settings> <setting name="cacheEnabled" value="true"/> </settings> <!-- 三、類型名稱:爲一些類定義別名 --> <typeAliases> <typeAlias type="com.panshenlian.pojo.User" alias="user"></typeAlias> </typeAliases> <!-- 四、類型處理器:定義Java類型與數據庫中的數據類型之間的轉換關係 --> <typeHandlers></typeHandlers> <!-- 五、對象工廠 --> <objectFactory type=""></objectFactory> <!-- 六、插件:mybatis的插件,支持自定義插件 --> <plugins> <plugin interceptor=""></plugin> </plugins> <!-- 七、環境:配置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> <!-- 八、數據庫廠商標識 --> <databaseIdProvider type=""></databaseIdProvider> <!-- 九、映射器:指定映射文件或者映射類 --> <mappers> <mapper resource="UserMapper.xml" /> </mappers> </configuration>
必須注意:Mybatis配置文件的屬性位置順序是 固定 的,不容許 顛倒順序,不然 Mybatis 在解析 XML 文件的時候就會拋出異常,這個與 Mybatis 框架啓動加載配置信息順序有關,後續咱們源碼分析會講到。
以上基本可以清晰看明白 Mybatis 配置文件的層次結構關係,咱們簡單畫一張腦圖:
基本是須要咱們掌握 9 大頂級元素配置,其中標記 橘紅色 的屬性配置,因爲涉及 插件 和 動態SQL ,插件配置能夠應用於分頁與功能加強等,動態SQL例如 if 標籤、where 標籤、foreach標籤等,初步理解爲應用於SQL語句拼接。這兩塊屬於 Mybatis 的兩個特性,咱們後續單獨詳細進行梳理討論。
咱們的核心配置文件 configuration(配置)做爲最頂級節點,其他 9 大屬性都必須嵌套在其內,對於內部 9 大節點,咱們逐一講解:
屬性標籤,顯而易見就是提供屬性配置,可進行動態替換,通常能夠在 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 優先級
一、第一優先級:在 Java 代碼中構建的 properties 屬性對象;
二、第二優先級:經過屬性 resource 或 url 讀取到的本地文件或遠程文件;
三、第三優先級:直接在 properties 內部子標籤元素 property 中設置的屬性。
注意,在實際開發中,爲了不給後期維護形成困擾,建議使用單一種配置方式。
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
屬性lazyLoadingEnabled
屬性 aggressiveLazyLoading
屬性 multipleResultSetsEnabled
屬性 useColumnLabel
屬性 useGeneratedKeys
屬性 autoMappingBehavior
屬性 autoMappingUnknownColumnBehavior
屬性 defaultExecutorType
屬性 defaultStatementTimeout
屬性 defaultFetchSize
屬性 defaultResultSetType
屬性 safeRowBoundsEnabled
屬性 safeResultHandlerEnabled
屬性 mapUnderscoreToCamelCase
屬性 localCacheScope
屬性 jdbcTypeForNull
屬性 lazyLoadTriggerMethods
屬性 defaultScriptingLanguage
屬性 defaultEnumTypeHandler
屬性 callSettersOnNulls
屬性 returnInstanceForEmptyRow
屬性 logPrefix
屬性 logImpl
屬性 proxyFactory
屬性 vfsImpl
屬性 useActualParamName
屬性 configurationFactory
屬性 shrinkWhitespacesInSql
settings 支持了特別多功能支持,其實常規開發中使用到的屬性項不會特別多,除非項目有特殊要求,因此建議你們把這些設置當作字典便可,沒必要詳記 每個屬性使用,須要時翻閱研讀。
類型別名能夠給 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 類型內建的類型別名。它們都是不區分大小寫的,注意,爲了應對原始類型的命名重複,採起了特殊的命名風格,能夠發現 基本類型 的別名前綴都有下劃線 ‘_’,而基本類型的 包裝類 則沒有,這個須要注意:
咱們能夠經過源碼查看內置的類型別名的註冊信息。
具體源碼路徑在 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 在註冊別名的時候會所有轉爲小寫字母進行存儲,另外以上列表 無需牢記,僅僅在須要使用的時候查閱便可,基本也均可以看得明白。
MyBatis 在設置預處理SQL語句(PreparedStatement)中所須要的 參數 或從 結果集 ResultSet 中獲取對象時, 都會用類型處理器將獲取到的值以合適的方式轉換成 Java 類型。
類型處理器,主要用於處理 Java 類型與 JDBC 類型的映射匹配關係處理,下表描述了一些默認的類型處理器。
咱們能夠經過源碼查看內置的類型別名的註冊信息。
具體源碼路徑在 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) ,能夠在以上源碼上看到新增支持。
通常,你能夠重寫已有的類型處理器,
或根據業務須要建立你本身的類型處理器,
以處理不支持的類型或非標準的類型。
具體作法爲:
一、實現 org.apache.ibatis.type.TypeHandler
接口;
二、繼承 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
。
每次 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 方法。
正常狀況下咱們不須要使用到,或者說不建議使用,除非業務上確實須要對一個特殊實體初始構造作一個默認屬性值配置等處理,其他狀況不推薦使用,避免產生不可控風險。
MyBatis 容許你在映射語句執行過程當中的某一點進行攔截調用。默認狀況下,MyBatis 容許使用插件來攔截的方法調用包括:
插件功能主要開放攔截的對象就是以上列舉的 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 的行爲,務請慎之又慎。
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 顧名思義。 環境能夠隨意命名,但務必保證默認的環境 ID 要匹配其中一個環境 ID。
事務管理器(transactionManager)
在 MyBatis 中有兩種類型的事務管理器(也就是 type="[JDBC|MANAGED]"):
<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>
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; }
既然 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 核心配置文件介紹中,咱們介紹了映射文件 mapper.xml 的引入。
對於 Mapper 具體的映射配置文件,是 Mybatis 最複雜、最核心的組件,其中的標籤內容體系也是特別詳實,包括它的參數類型、動態SQL、定義SQL、緩存信息等等,咱們在下一篇文章中再進行梳理討論,這裏咱們簡單引出。
本來我計劃把核心配置文件和映射器 mapper 文件放一塊講,可是發現內容太多太多了,基本核心配置文件就已經講得有點拖堂了,雖然這幾大頂級標籤使用起來已經絕不費力。SQL 映射器配置文件,咱們後續更新,這塊基本是和咱們平常打交道最高頻的操做。
本篇完,本系列下一篇咱們講《 Mybatis系列全解(五):全網最全!詳解Mybatis的Mapper映射文件 》。
BIU ~ 文章持續更新,微信搜索「潘潘和他的朋友們」第一時間閱讀,隨時有驚喜。本文會在 GitHub https://github.com/JavaWorld 收錄,熱騰騰的技術、框架、面經、解決方案,咱們都會以最美的姿式第一時間送達,歡迎 Star。