企業(yè)網(wǎng)站建設(shè)公司選擇分析免費(fèi)大數(shù)據(jù)查詢
1、全局配置文件
前面我們看到的Mybatis全局文件并沒有全部列舉出來,所以這一章我們來詳細(xì)的介紹一遍,Mybatis的全局配置文件并不是很復(fù)雜,它的所有元素和代碼如下所示:
<?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> <!--配置--><properties/> <!--屬性--><settings/> <!--全局配置參數(shù)--><typeAliases/> <!--類型別名--><typeHandlers/> <!--類型處理器--><objectFactory/><!--對象工廠--><plugins/><!--創(chuàng)建--><environments default=""><!--環(huán)境配置--><environment id=""><!--環(huán)境變量--><transactionManager type=""/><!--事務(wù)管理器--><dataSource type=""/><!--數(shù)據(jù)源--></environment></environments><databaseIdProvider type=""/><!--數(shù)據(jù)庫廠商標(biāo)識--><mappers/><!--映射器-->
</configuration>
注意:Mybatis的配置文件的順序是嚴(yán)格按照從上至下的順序聲明,不顛倒順序,如果顛倒了它們的順序,那么Mybatis在啟動階段就會產(chǎn)生異常,導(dǎo)致程序無法運(yùn)行
2、properties屬性
properties 的作用是引用java屬性文件中的配置信息,比如:加載連接數(shù)據(jù)庫的各種屬性的配置文件。
mybatis提供了三種方式使用properties屬性:
- property子元素(不推薦):就是在properties屬性中增加子屬性property,從而設(shè)置一些配置的key-value。
- properties文件:就是直接使用properties引入外部配置文件,相當(dāng)于將子屬性抽取成一個(gè)獨(dú)立的外部文件引入,例如db.properties。
- 程序代碼傳遞參數(shù):就是通過代碼的方式設(shè)置該配置相關(guān)的信息,如數(shù)據(jù)庫配置文件中的用戶名和密碼一般是密文,但是連接數(shù)據(jù)庫時(shí)需要對配置進(jìn)行解密,此時(shí)就只能通過程序代碼的方式配置了。
2.1、property子元素(不推薦)
以上一章的例子為基礎(chǔ),使用property子元素將數(shù)據(jù)庫的連接配置信息進(jìn)行改寫,如下所示:
<?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><properties><!--property子元素定義--><property name="database.driver" value="com.mysql.cj.jdbc.Driver"/><property name="database.url" value="jdbc:mysql://localhost:3306/user?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8"/><property name="database.username" value="root"/><property name="database.password" value="root"/></properties><environments default="development"><environment id="development"><transactionManager type="JDBC"></transactionManager><dataSource type="POOLED"><!--配置連接數(shù)據(jù)庫的4個(gè)基本信息--><property name="driver" value="${database.driver}"/><property name="url" value="${database.url}"/><property name="username" value="${database.username}"/><property name="password" value="${database.password}"/></dataSource></environment></environments>
</configuration>
這種配置方式時(shí)有缺點(diǎn)的,雖然這樣定義一次可以到處引用,但是如果配置項(xiàng)很多,那么就會讓配置文件顯得很龐大,所以使用這種方式顯然不是一個(gè)很好的選擇,為了解決這個(gè)缺點(diǎn),我們可以使用下面的配置方式,也就是使用properties文件的方式。
2.2、properties文件
使用properties文件的方式在我們的開發(fā)中是比較常用,主要的這種方式簡單,方便日后的維護(hù)和修改。首先將上述配置中的所有property屬性提取到一個(gè)叫做 databse.properties 的配置文件中,
如下代碼所示:
#數(shù)據(jù)庫連接配置
database.driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/user?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
database.username=root
database.password=root
然后在Mybatis配置文件中使用元素的resource屬性來引入properties文件。
<properties resource="database.properties" />
這樣就相當(dāng)于將 database.properties 中的所有配置都加載到MyBatis的配置文件中了,然后再按照 ${database.username} 的方式引入properties文件的屬性參數(shù)即可。但是這種使用方式也存在它的缺點(diǎn),當(dāng)外部配置文件中的值需要加密時(shí),如連接數(shù)據(jù)庫的用戶名和密碼,無法在配置文件中進(jìn)行解密,所以只能通過程序代碼傳遞的方式,就是要介紹的第三種,如下。
2.3、程序代碼傳遞參數(shù)
在真實(shí)的開發(fā)環(huán)境中,數(shù)據(jù)庫的用戶名和密碼對開發(fā)人員和其他人員是保密的。而運(yùn)維人員為了數(shù)據(jù)保密,一般都會把數(shù)據(jù)庫的用戶名和密碼進(jìn)行加密處理,會把加密后的數(shù)據(jù)配置到properties文件中。所以開發(fā)人員就必須用解密后的用戶名和密碼連接數(shù)據(jù)庫,不可能用加密的數(shù)據(jù)進(jìn)行連接。此時(shí)就需要使用到此種方式來對配置文件進(jìn)行解密。其實(shí)這種方式一般會和第二種配合使用,作用對特殊配置進(jìn)行覆蓋或重寫,以上面的database.properties為例,在使用到數(shù)據(jù)庫配置信息時(shí)對配置中的用戶名和密碼進(jìn)行解密。這里舉個(gè)MyBatis中獲取SqlSessionFactory的例子,代碼如下:
public static SqlSessionFactory getSqlSessionFactoryByXml() {synchronized (Lock) {if (null != sqlSessionFactory) {return sqlSessionFactory;}String resource = "mybatis-config.xml";InputStream inputStream;InputStream is = null;try {// 加載數(shù)據(jù)庫配置文件is = Resources.getResourceAsStream("database.properties");Properties properties = new Properties();properties.load(is);// 獲取加密信息String username= properties.getProperty("database.username");String password= properties.getProperty("database.password");// 解密用戶名和密碼,并重置屬性properties.setProperty("database.username", CyperTool.decodeByBase64(username));properties.setProperty("database.password", CyperTool.decodeByBase64(password));// 讀取mybatis配置文件inputStream = Resources.getResourceAsStream(resource);// 通過SqlSessionFactoryBuilder類的builder方法進(jìn)行構(gòu)建,并使用程序傳遞的方式覆蓋原有屬性sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, properties);} catch (IOException e) {e.printStackTrace();return null;}return sqlSessionFactory;}}
我們?yōu)榱吮WC數(shù)據(jù)的準(zhǔn)確性,加了synchronized鎖。首先使用Resources對象讀取了database.properties配置文件,然后獲取了它原來配置的用戶和密碼,進(jìn)行解密操作,最后使用SqlSessionFactoryBuilder的build方法,傳遞多個(gè)properties參數(shù)來完成。這將覆蓋之前加密的配置,這樣就可以連接數(shù)據(jù)庫了,同時(shí)也能滿足因?yàn)槿藛T對數(shù)據(jù)庫的用戶名和密碼的安全要求。
3、settings屬性
settings是MyBatis中最復(fù)雜的配置,它能深刻影響MyBatis底層的運(yùn)行,但是大部分情況下使用默認(rèn)值便可以運(yùn)行,所以在大部分情況下不需要大量配置,只需要修改一些常用的規(guī)則即可。常用規(guī)則有自動映射、駝峰命名映射、級聯(lián)規(guī)則、是否啟動緩存、執(zhí)行器類型等。
所有配置可參考MyBatis官方文檔:https://mybatis.org/mybatis-3/zh/configuration.html#settings??梢钥吹絪ettings的配置項(xiàng)非常之多,但是我們真正使用的并不會太多,我們只需把常用的搞清楚就可以了。比如關(guān)于緩存的CacheEnabled,關(guān)于級聯(lián)的lazyLoadingEnabled和aggressiveLazyLoading,關(guān)于自動映射的autoMappingBehavior和mapUnderscoreToCamelCase,關(guān)于執(zhí)行器類型的defaultExecutorType等。本文列出重要的幾個(gè)配置項(xiàng)及意義,并挑幾個(gè)常用配置加以說明:
<settings><!--緩存配置的全局開關(guān):如果這里設(shè)置成false,那么即便在映射器中配置開啟也無濟(jì)于事 --><setting name="cacheEnabled" value="true" /><!--延時(shí)加載的全局開關(guān) --><setting name="lazyLoadingEnabled" value="false" /><!-- 是否允許單一語句返回多結(jié)果集 --><setting name="multipleResultSetsEnabled" value="true" /><!-- 使用列標(biāo)簽代替列名,需要兼容驅(qū)動 --><setting name="useColumnLabel" value="true" /><!-- 允許JDBC自動生成主鍵,需要驅(qū)動兼容。如果設(shè)置為true,則這個(gè)設(shè)置強(qiáng)制使用自動生成主鍵,盡管一些驅(qū)動不能兼容但仍能正常工作 --><setting name="useGeneratedKeys" value="false" /><!-- 指定MyBatis該如何自動映射列到字段或?qū)傩?#xff1a;NONE表示取消自動映射;PARTIAL表示只會自動映射,沒有定義嵌套結(jié)果集和映射結(jié)果集;FULL會自動映射任意復(fù)雜的結(jié)果集,無論是否嵌套 --><setting name="autoMappingBehavior" value="PARTIAL" /><!-- 指定發(fā)現(xiàn)自動映射目標(biāo)未知列(或未知屬性類型)的行為。NONE: 不做任何反應(yīng)WARNING: 輸出警告日志FAILING: 映射失敗 (拋出 SqlSessionException) --><setting name="autoMappingUnknownColumnBehavior" value="WARNING" /><!-- 配置默認(rèn)的執(zhí)行器:SIMPLE是普通的執(zhí)行器;REUSE會重用預(yù)處理語句;BATCH會重用語句并執(zhí)行批量更新 --><setting name="defaultExecutorType" value="SIMPLE" /><!--設(shè)置超時(shí)時(shí)間:它決定驅(qū)動等待數(shù)據(jù)庫響應(yīng)的秒數(shù),任何正整數(shù)--><setting name="defaultStatementTimeout" value="25"/><!--設(shè)置數(shù)據(jù)庫驅(qū)動程序默認(rèn)返回的條數(shù)限制,此參數(shù)可以重新設(shè)置,任何正整數(shù) --><setting name="defaultFetchSize" value="100" /><!-- 允許在嵌套語句中使用分頁(RowBounds) --><setting name="safeRowBoundsEnabled" value="false" /><!-- 是否開啟自動駝峰命名規(guī)則,即從a_example到aExample的映射 --><setting name="mapUnderscoreToCamelCase" value="true" /><!-- 本地緩存機(jī)制,防止循環(huán)引用和加速重復(fù)嵌套循環(huán) --><setting name="localCacheScope" value="SESSION" /><!-- 當(dāng)沒有為參數(shù)提供特定JDBC類型時(shí),為空值指定JDBC類型。某些驅(qū)動需要指定列的JDBC類型,多數(shù)情況直接用一般類型即可,如NULL/VARCHAR/OTHER --><setting name="jdbcTypeForNull" value="OTHER" /><!-- 指定觸發(fā)延遲加載的方法,如equals/clone/hashCode/toString --><setting name="lazyLoadTriggerMethods" value="equals" />
</settings>
4、typeAlianses屬性
typeAlianses屬性就是起個(gè)別名,是為了在映射文件中更方便的編寫輸入?yún)?shù)類型和輸出結(jié)果類型,因?yàn)槠綍r(shí)的輸入輸出映射的全限定名顯得很長,在使用過程中不是很方便,所以MyBatis中允許我們使用一種簡寫的方式來代替全限定名,這樣可以提高我們的開發(fā)效率。
別名分為系統(tǒng)別名和自定義別名,系統(tǒng)別名就是系統(tǒng)默認(rèn)給我們起的別名,例如我們在輸入一個(gè)數(shù)值型的參數(shù)是,可以直接寫parameterType=”int”,這是因?yàn)橄到y(tǒng)將Integer的Java類型起的別名為int。我們可以通過Mybatis的官方文檔來查看:https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases。而自定義別名是自己定義的名稱,后面會介紹如何使用。
4.1、系統(tǒng)定義的別名
Mybatis本身給我們定義了大量的別名,包括有基本數(shù)據(jù)類型,包裝類、對象型、集合和Map等等。系統(tǒng)定義的別名是通過TypeAliasRegistry類來定義的,所以我們既可以通過這個(gè)對象獲取系統(tǒng)中已經(jīng)定義好的別名,也能自定義別名,先通過一段代碼來獲取系統(tǒng)中都預(yù)定義了哪些別名。
/*** 獲取系統(tǒng)別名配置*/
public static void getTypeAlias() {try {InputStream stream = getResourceAsStream("mybatis-config.xml");SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);SqlSession sqlSession = factory.openSession();//獲取TypeAliasRegistry對象TypeAliasRegistry typeAliasRegistry = sqlSession.getConfiguration().getTypeAliasRegistry();Map<String, Class<?>> tarMap = typeAliasRegistry.getTypeAliases();int i =0;for (String key : tarMap.keySet()) {//這個(gè)++i統(tǒng)計(jì)數(shù)量System.out.println(++i+"*****"+tarMap.get(key).getSimpleName()+"*****"+key);}System.out.println("系統(tǒng)定義的別名個(gè)數(shù)為:"+i);} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {getTypeAlias();
}
輸出結(jié)果就不貼出來了,有點(diǎn)長,可以自行運(yùn)行。通過運(yùn)行結(jié)果可以發(fā)現(xiàn)系統(tǒng)自定義的別名一共有72個(gè)(這72個(gè)別名的使用不區(qū)分大小寫)。所以我們可以使用別名代替冗長的全限定名,比如在MyBatis的映射文件中,我們設(shè)置一個(gè)SQL語句的參數(shù)類型或返回類型的時(shí)候,如果這個(gè)類型是字符串,我們完全可以用string代替java.lang.String。但是這就會有一個(gè)問題,我怎么知道哪個(gè)類型的別名是什么呢?在不知道的情況下有兩種方式可以知道:
保險(xiǎn)的方法:將系統(tǒng)別名打印出來,或者找官方文檔查詢;
尋規(guī)律:其實(shí)從上面的結(jié)果可以發(fā)現(xiàn)一個(gè)規(guī)律,就是如果類名以大寫開頭,則只要將大寫變?yōu)樾懢褪窃擃惖膭e名;而如果類名本來就是小寫,只需要在小寫前面加上下劃線即可。
4.2、自定義別名
我們在平時(shí)的開發(fā)中,系統(tǒng)中會有大量的類,比如User類,需要對其進(jìn)行反復(fù)的使用,而這些類系統(tǒng)并沒有給我們?nèi)e名,難道我們要反復(fù)的編寫很長的全限定名嗎?NO,Mybatis給我們提供了用戶自定義別名的規(guī)則,我們可以通過配置文件、包掃描或者注解進(jìn)行注冊。下面來介紹一下如何使用:
①、使用配置文件的typeAliases屬性
<!--配置別名-->
<typeAliases><!--對類單獨(dú)進(jìn)行別名設(shè)置 --><typeAlias alias="user" type="com.thr.pojo.User"></typeAlias><typeAlias alias="student" type="com.thr.pojo.Student"></typeAlias>
</typeAliases>
這樣我們就為兩個(gè)類定義好了別名,但是這種方式有個(gè)缺點(diǎn),如果多個(gè)類需要配置別名時(shí)就顯得很麻煩,所以這種方式顯然不行。
②、通過package自動掃描
<!--配置別名-->
<typeAliases><!-- 對包進(jìn)行掃描,可以批量進(jìn)行別名設(shè)置,設(shè)置規(guī)則是:獲取類名稱,將其第一個(gè)字母變?yōu)樾?--><package name="com.thr.pojo1"/><package name="com.thr.pojo2"/><package name="com.thr.pojo3"/>
</typeAliases>
這種方式會為掃描到的包下的所有類起一個(gè)別名,別名的命名規(guī)則為,將類名的第一個(gè)字母變?yōu)樾懽鳛閯e名,比如com.thr.pojo.User變?yōu)閯e名為user。但是使用這種方式還有缺點(diǎn),就是如果兩個(gè)不同的包下出現(xiàn)了同名的類,那么在掃描的時(shí)候就會出現(xiàn)異常(通常不會出現(xiàn)這種情況)。這個(gè)時(shí)候可以通過注解@Alians(“user1”)來進(jìn)行區(qū)分。
③、通過注解
這種方式比較簡單,只要在對應(yīng)包下的對應(yīng)類上面使用注解@Alias("別名")即可,如下:
package com.thr.pojo;
import org.apache.ibatis.type.Alias;@Alias("user")
public class User {省略......
}
這樣就能夠避免因?yàn)楸苊庵貜?fù)而導(dǎo)致掃描失敗的問題。
5、typeHandlers屬性(了解)
typeHandlers叫類型處理器,在JDBC中,需要在PreparedStatement中設(shè)置預(yù)編譯SQL所需的參數(shù)。在執(zhí)行SQL后,會根據(jù)結(jié)果集ResultSet對象得到數(shù)據(jù)庫的數(shù)據(jù),需要將數(shù)據(jù)庫中的類型和java中字段的類型進(jìn)行轉(zhuǎn)換一樣,這些操作在MyBatis中通過typeHandler來實(shí)現(xiàn)。在typeHandler中,包含有javaType和jdbcType兩種類型,其中javaType用來定義Java類型,jdbcType用來定義數(shù)據(jù)庫類型,那么typeHandler的作用就是承擔(dān)javaType和jdbcType兩種類型的轉(zhuǎn)換,如下圖所示。
MyBatis中的typeHandlers存在系統(tǒng)定義的和自定義兩種,MyBatis會根據(jù)javaType和jdbcType來決定采用哪個(gè)typeHandler來處理這些轉(zhuǎn)換規(guī)則,而且系統(tǒng)定義的能滿足大部分需求,但是有些情況是不夠用的,比如我們的特殊轉(zhuǎn)換規(guī)則,枚舉類型,這時(shí)我們就需要自定義的typeHandlers了。下面分別介紹這兩種typeHandler的使用。
5.1、系統(tǒng)定義的typeHandler
Mybatis內(nèi)部定義了許多有用的typeHandler,我們可以參考Mybatis的官方文檔查看:https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers,也可以自己通過程序代碼進(jìn)行打印,代碼如下:
/*** 獲取類型處理器*/
public static void getTypeHandlers() {//SqlSession代碼省略......TypeHandlerRegistry typeHandlerRegistry = sqlSession.getConfiguration().getTypeHandlerRegistry();Collection<TypeHandler<?>> handlers = typeHandlerRegistry.getTypeHandlers();System.out.println(handlers.size());int i = 0;for (TypeHandler<?> typeHandler : handlers) {System.out.println(++i+"*****"+typeHandler.getClass().getName());}
}
執(zhí)行結(jié)果就不列出來了,Mybatis一共定義了39個(gè)類型處理器。在大部分情況下我們不需要顯示的聲明JavaType和jdbcType,因?yàn)镸ybatis會自動探測到。
在Mybatis中typeHandler都需要實(shí)現(xiàn)接口org.apache.ibatis.type.TypeHandler,所以我們來看看這個(gè)接口長啥樣。源代碼如下:
public interface TypeHandler<T> {void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;T getResult(ResultSet var1, String var2) throws SQLException;T getResult(ResultSet var1, int var2) throws SQLException;T getResult(CallableStatement var1, int var2) throws SQLException;
}
簡單介紹一下內(nèi)部定義了內(nèi)容:
其中T表示泛型,專指JavaType,比如我們需要String類型的參數(shù),那么實(shí)現(xiàn)類就是可以寫成implement TypeHandler。
setParameter方法,是使用typeHandler通過PreparedStatement對象進(jìn)行設(shè)置SQL參數(shù)的時(shí)候使用的具體方法,其中i是請查收在SQL的下標(biāo),parameter是參數(shù),jdbcType為數(shù)據(jù)庫類型。
其中三個(gè)getResult的方法,它的作用是從JDBC結(jié)果集中獲取數(shù)據(jù)進(jìn)行轉(zhuǎn)換,要么使用列名,要么使用下標(biāo)來獲取數(shù)據(jù)庫的數(shù)據(jù),其中最后一個(gè)方法是存儲過程專用的方法。
既然學(xué)習(xí)了TypeHandler接口,那么接著來學(xué)習(xí)它的實(shí)現(xiàn)了BaseTypeHandler類。源代碼如下(只貼出少量):
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {......public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {......}public T getResult(ResultSet rs, String columnName) throws SQLException {......}public T getResult(ResultSet rs, int columnIndex) throws SQLException {......}public T getResult(CallableStatement cs, int columnIndex) throws SQLException {......}public abstract void setNonNullParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException;public abstract T getNullableResult(ResultSet var1, int var2) throws SQLException;public abstract T getNullableResult(CallableStatement var1, int var2) throws SQLException;}
簡單分析一下:
- setParameter方法,當(dāng)參數(shù)parameter和jdbcType同時(shí)為空時(shí),Mybatis將拋出異常。如果能目前jdbcType,則會繼續(xù)空設(shè)置;如果參數(shù)不為空,那么他將曹勇setNonNullParameter方法設(shè)置參數(shù)。
- getResult方法,非空結(jié)果集是通過getNullableResult方法獲取的。如果判斷為空,則返回null。
- getNullableResult方法用于存儲過程。
在Mybatis中使用最多的typeHandler為StringTypeHandler。它用于字符串的轉(zhuǎn)換,所以我們來學(xué)習(xí)一下。源代碼如下:
public class StringTypeHandler extends BaseTypeHandler<String> {public StringTypeHandler() {}public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, parameter);}public String getNullableResult(ResultSet rs, String columnName) throws SQLException {return rs.getString(columnName);}public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return rs.getString(columnIndex);}public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return cs.getString(columnIndex);}
}
從上述代碼可以看出它繼承了BaseTypeHandler類,并且實(shí)現(xiàn)了BaseTypeHandler的4個(gè)抽象方法,方法如下:
- setNonNullParameter:這個(gè)方法是用來將javaType轉(zhuǎn)換成jdbcTpe。
- getNullableResult:這個(gè)方法用來將從結(jié)果集根據(jù)列名稱獲取到的數(shù)據(jù)的jdbcType轉(zhuǎn)換成javaType。
- getNullableResult:這個(gè)方法用來將從結(jié)果集根據(jù)列索引獲取到的數(shù)據(jù)的jdbcType轉(zhuǎn)換成javaType。
- getNullableResult:這個(gè)方法用在存儲過程中。
這里Mybatis把JavaType和jdbcType進(jìn)行互換,那么他們是怎么進(jìn)行注冊的呢?在Mybatis中采用TypeHandlerRegistry類對象的register方法來進(jìn)行則。
public TypeHandlerRegistry(Configuration configuration) {......this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));this.register((JdbcType)JdbcType.BOOLEAN, (TypeHandler)(new BooleanTypeHandler()));this.register((JdbcType)JdbcType.BIT, (TypeHandler)(new BooleanTypeHandler()));......}
這樣就是實(shí)現(xiàn)了用代碼的形式注冊typeHandler。但是注意,自定義的typeHandler一般不會使用代碼進(jìn)行注冊,而是通過配置或者掃描,使用下面我們來學(xué)習(xí)如何自定義typeHandler。
5.2、自定義typeHandler
我們知道在大部分場景下,Mybatis的typeHandler都能應(yīng)付,但是有時(shí)候也會不夠用,比如枚舉類型,這個(gè)時(shí)候就需要自定義typeHandler來進(jìn)行處理了。從系統(tǒng)定義的typeHandler可以知道,要實(shí)現(xiàn)typeHandler就需要去實(shí)現(xiàn)接口typeHandler或者實(shí)現(xiàn)baseTypeHandler。
下面我們使用實(shí)現(xiàn)TypeHandler接口的方式創(chuàng)建一個(gè)MyTypeHandler,用來完成javaType中的String類型與jdbcType中的類型之間的轉(zhuǎn)化。
public class MyTypeHandler implements TypeHandler<String> {Logger log = Logger.getLogger(MyTypeHandler.class);@Overridepublic void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {log.info("設(shè)置string參數(shù):"+parameter);ps.setString(i,parameter);}@Overridepublic String getResult(ResultSet rs, String columnName) throws SQLException {String result = rs.getString(columnName);log.info("讀取string參數(shù)1:"+result);return result;}@Overridepublic String getResult(ResultSet rs, int columnIndex) throws SQLException {String result = rs.getString(columnIndex);log.info("讀取string參數(shù)2:"+result);return result;}@Overridepublic String getResult(CallableStatement cs, int columnIndex) throws SQLException {String result = cs.getString(columnIndex);log.info("讀取string參數(shù)3:"+result);return result;}
}
我們定義的泛型為String則表示我們要把數(shù)據(jù)庫類型的數(shù)據(jù)轉(zhuǎn)化為String類型,然后實(shí)現(xiàn)設(shè)置參數(shù)和獲取結(jié)果集的方法。但是這個(gè)時(shí)候還沒有啟動typeHandler。還需要在配置文件中配置一下。
<typeHandlers><typeHandler jdbcType="VARCHAR" javaType="string"handler="com.typeHandler.MyTypeHandler"/>
</typeHandlers>
配置完成之后系統(tǒng)才會讀取它,這樣就注冊完畢了,當(dāng)JavaType和jdbcType能與MyTypeHandler對應(yīng)的時(shí)候,它就會啟動MyTypeHandler。我們有兩種方式來使用自定義的typeHandler。
<!-- 模糊查詢,根據(jù)username字段查詢用戶-->
<select id="selectUserByName" parameterType="string" resultType="user">select * from t_user where username like concat ('%',#{username,typeHandler=com.typeHandler.MyTypeHandler},'%');
</select>
或者:
<select id="selectUserByName" parameterType="string" resultType="user">select * from t_user where username like concat ('%',#{username,javaType=string,jdbcType=VARCHAR},'%');
</select>
注意,要么指定了與自定義typeHandler一致的jdbcType和JavaType,要么直接使用typeHandler指定的具體實(shí)現(xiàn)類。在一些因?yàn)閿?shù)據(jù)庫返回為空導(dǎo)致無法判定采用哪個(gè)typeHandler來處理,而又沒有注冊對應(yīng)的JavaType的typeHandler是,Mybatis無法找到使用哪個(gè)typeHandler來轉(zhuǎn)換數(shù)據(jù)。
有時(shí)候類很多的時(shí)候,我們還可以采用包掃描的方式。
<typeHandlers><package name="com.typeHandler"/>
</typeHandlers>
但是這樣會無法指定jdbcType和JavaType,不過我們可以通過注解來處理它們,我們把MyTypeHandler類修改一些即可。
@MappedTypes(String.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MyTypeHandler implements TypeHandler<String> {......
}
最后:在我們的日常開發(fā)中,一般都不需要定義,使用默認(rèn)的就可以,除非是像枚舉這種特殊類型就需要自己實(shí)現(xiàn)。
6、objectFacotry屬性(了解)
objectFacotry表示為對象工廠。對象工廠我們只需了解即可,因?yàn)榈綍r(shí)候與spring整合后,都會由spring來管理。
我們在使用MyBatis執(zhí)行查詢語句的時(shí)候,通常都會有一個(gè)返回類型,這個(gè)是在mapper文件中給sql增加一個(gè)resultType(或resultMap)屬性進(jìn)行控制。resultType和resultMap都能控制返回類型,只要定義了這個(gè)配置就能自動返回我想要的結(jié)果,于是我就很納悶這個(gè)自動過程的實(shí)現(xiàn)原理,想必大多數(shù)人剛開始的時(shí)候應(yīng)該也有和我一樣的困惑和好奇,那么今天我就把自己的研究分享一下。在JDBC中查詢的結(jié)果會保存在一個(gè)結(jié)果集中,其實(shí)MyBatis也是這個(gè)原理,只不過MyBatis在創(chuàng)建結(jié)果集的時(shí)候,會使用其定義的對象工廠DefaultObjectFactory來完成對應(yīng)的工作。
7、plugins屬性(了解)
插件是Mybatis中最強(qiáng)大和靈活的組件,同時(shí)也是最復(fù)雜、最難使用的組件,并且它十分的危險(xiǎn),因?yàn)樗鼘⒏采wMybatis底層對象的核心方法和屬性。如果操作不當(dāng)將產(chǎn)生非常嚴(yán)重的后果,甚至是摧毀Mybatis框架,所以我們在不了解Mybatis的底層結(jié)構(gòu)的情況下,千萬不要去碰這個(gè)插件屬性。如果你想研究一下插件,那么前提是要清楚掌握Mybatis底層的結(jié)構(gòu)和運(yùn)行原理,否則將難以安全高效的使用它。而我們平時(shí)使用Mybatis的常規(guī)功能完全滿足日常的開發(fā),所以這里就不介紹了,有興趣的可以自行去學(xué)習(xí)。
8、environments屬性
environments屬性表示的是運(yùn)行環(huán)境,主要的作用是配置數(shù)據(jù)庫的一些信息,我們可以配置多個(gè)數(shù)據(jù)庫,但只能選擇一個(gè)。它里面分為兩個(gè)可配置的元素:事務(wù)管理器(transactionManager)、數(shù)據(jù)源(DataSource)。而在我們的日常開發(fā)中,這些都會交給Spring來管理,不用在全局配置中編寫,這些會在后面Mybatis整合Spring中進(jìn)行講解。我們先來看看environments環(huán)境配置的配置代碼吧。
<configuration><!-- 配置環(huán)境.--><environments default="development"><!-- id屬性必須和上面的default一致 --><environment id="development"><!--配置事務(wù)的類型--><transactionManager type="JDBC"></transactionManager><!--dataSource 元素使用標(biāo)準(zhǔn)的 JDBC 數(shù)據(jù)源接口來配置 JDBC 連接對象源 --><dataSource type="POOLED"><!--配置連接數(shù)據(jù)庫的4個(gè)基本信息--><property name="driver" value="${database.driver}"/><property name="url" value="${database.url}"/><property name="username" value="${database.username}"/><property name="password" value="${database.password}"/></dataSource></environment></environments>
</configuration>
下面我們主要來詳細(xì)介紹 transactionManager 和 DataSource 這兩個(gè)元素。
8.1、transactionManager(事務(wù)管理)
在MyBatis中,transactionManager提供了兩個(gè)實(shí)現(xiàn)類,它們都需要實(shí)現(xiàn)接口Transaction,所以我們可以查看以下Transaction的源代碼:
public interface Transaction {Connection getConnection() throws SQLException;void commit() throws SQLException;void rollback() throws SQLException;void close() throws SQLException;Integer getTimeout() throws SQLException;
}
從上面的方法可知,它主要的工作就是提交(commit)、回滾(rollback)、關(guān)閉(close)數(shù)據(jù)庫的事務(wù)。MyBatis中為Transaction接口提供了兩個(gè)實(shí)現(xiàn)類,分別是 JdbcTransaction 和 ManagedTransaction。如下圖所示:
并且分別對應(yīng)著 JdbcTransactionFactory 和 ManagedTransactionFactory 兩個(gè)工廠,這兩個(gè)工廠實(shí)現(xiàn)了 TransactionFactory 這個(gè)接口,當(dāng)我們在配置文件中通過 transactionManager 的type屬性配置事務(wù)管理器類型的時(shí)候,Mybatis就會自動從對應(yīng)的工廠獲取實(shí)例。我們可以把事務(wù)管理器配置成為以下兩種方式:
<transactionManager type="JDBC"/>
<transactionManager type="MANAGED"/>
下面說一下這兩者的區(qū)別:
JDBC:使用JdbcTransactionFactory工廠生成的JdbcTransaction對象實(shí)現(xiàn),以JDBC的方式進(jìn)行數(shù)據(jù)庫的提交、回滾等操作。
MANAGED:使用ManagedTransactionFactory工廠生成的ManagedTransaction對象實(shí)現(xiàn),它的提交和回滾不需要任何操作,而是把事務(wù)交給容器進(jìn)行處理,默認(rèn)情況下會關(guān)閉連接,如果不希望默認(rèn)關(guān)閉,只要將其中的closeConnection屬性設(shè)置為false即可。
<transactionManager type="MANAGED"><property name="closeConnection" value="false"/>
</transactionManager>
在測試的過程中發(fā)現(xiàn)的最明顯的區(qū)別就是,如果我使用JDBC的事務(wù)處理方式,當(dāng)我向數(shù)據(jù)庫中插入一條數(shù)據(jù)時(shí),在調(diào)用完插入接口執(zhí)行SQL之后,必須 執(zhí)行sqlSession.commit();進(jìn)行提交,否則雖然插入成功但是數(shù)據(jù)庫中還是看不到剛才插入的數(shù)據(jù);而使用MANAGED方式就不一樣了,只需調(diào)用接口即可,無需手動提交。
當(dāng)然除了使用默認(rèn)的,我們還可以根據(jù)需要自定義一個(gè)事務(wù)管理器,需要以下三步:
第一步:創(chuàng)建一個(gè)自定義事務(wù)工廠MyTransactionFactory,需要實(shí)現(xiàn)TransactionFactory接口,代碼如下:
/*** 創(chuàng)建自定義事務(wù)工廠*/
public class MyTransactionFactory implements TransactionFactory {@Overridepublic void setProperties(Properties props) {}@Overridepublic Transaction newTransaction(Connection connection) {//后面我們會創(chuàng)建這個(gè)類,它自定義的事務(wù)類return new MyTransaction(connection);}@Overridepublic Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean b) {return new MyTransaction(dataSource,level,b);}
}
這里就實(shí)現(xiàn)了TransactionFactory所有定義的工廠方法了,這時(shí)還要一個(gè)自定義的事務(wù)類,下面我們來創(chuàng)建。
第二步:創(chuàng)建一個(gè)自定義事務(wù)類MyTransaction,用來實(shí)現(xiàn)Transaction接口,代碼如下。
MyTransaction
第三步:配置自定義事務(wù)管理器
<transactionManager type="com.transaction.MyTransactionFactory"/>
注意:這個(gè)地方配置的是自定義的工廠類,而不是事務(wù)管理類,因?yàn)閙ybatis是根據(jù)配置的工廠獲取具體實(shí)例對象的。
8.2、DataSource(數(shù)據(jù)源)
在Mybatis中,數(shù)據(jù)庫是通過PooledDataSourceFactory、UnpooledDataSourceFactory和JndiDataSourceFactory三個(gè)工廠類來提供,前兩者分別產(chǎn)生PooledDataSource和UnpooledDataSource類對象,第三個(gè)則會根據(jù)JNDI的信息拿到外部容器實(shí)現(xiàn)的數(shù)據(jù)庫連接對象,但是不管怎樣,它們最后都會生成一個(gè)實(shí)現(xiàn)了DataSource接口的數(shù)據(jù)庫連接對象。
因?yàn)橛腥N數(shù)據(jù)源,所以它們的配置信息如下:
<dataSource type="POOLED">
<dataSource type="UNPOOLED">
<dataSource type="JNDI">
下面介紹一下這三種數(shù)據(jù)源的意義:
①、UNPOOLED
UNPOOLED采用非數(shù)據(jù)庫池的管理方式,每次請求都會新建一個(gè)連接,所以性能不是很高,使用這種數(shù)據(jù)源的時(shí)候,UNPOOLED類型的數(shù)據(jù)源可以配置以下屬性:
- driver:數(shù)據(jù)庫驅(qū)動名
- url:數(shù)據(jù)庫連接URL
- username:用戶名
- password:密碼
- defaultTransactionIsolationLevel:默認(rèn)的事務(wù)隔離級別,如果要傳遞屬性給驅(qū)動,則屬性的前綴為driver
②、POOLED
POOLED采用連接池的概念將數(shù)據(jù)庫鏈接對象Connection組織起來,可以在初始化時(shí)創(chuàng)建多個(gè)連接,使用時(shí)直接從連接池獲取,避免了重復(fù)創(chuàng)建連接所需的初始化和認(rèn)證時(shí)間,從而提升了效率,所以這種方式比較適合對性能要求高的應(yīng)用中。除了UNPOOLED中的配置屬性之外,還有下面幾個(gè)針對池子的配置:
- poolMaximumActiveConnections:任意時(shí)間都會存在的連接數(shù),默認(rèn)值為10
- poolMaxmumIdleConnections:可以空閑存在的連接數(shù)
- poolMaxmumCheckoutTime:在被強(qiáng)制返回之前,檢查出存在空閑連接的等待時(shí)間。即如果有20個(gè)連接,只有一個(gè)空閑,在這個(gè)空閑連接被找到之前的等待時(shí)間就用這個(gè)屬性配置。
poolTimeToWait:等待一個(gè)數(shù)據(jù)庫連接成功所需的時(shí)間,如果超出這個(gè)時(shí)間則嘗試重新連接。
還有其他的一些配置,不詳述了。
③、JNDI
JNDI數(shù)據(jù)源JNDI的實(shí)現(xiàn)是為了能在如EJB或應(yīng)用服務(wù)器這類容器中使用,容器可以集中或在外部配置數(shù)據(jù)源,然后放置一個(gè)JNDI上下文的引用。這種數(shù)據(jù)源只需配置兩個(gè)屬性:
- initial_context:用來在InitialContext中尋找上下文。可選,如果忽略,data_source屬性將會直接從InitialContext中尋找;
- data_source:引用數(shù)據(jù)源實(shí)例位置上下文的路徑。當(dāng)提供initial_context配置時(shí),data_source會在其返回的上下文進(jìn)行查找,否則直接從InitialContext中查找。
除了上述三種數(shù)據(jù)源之外,Mybatis還提供第三方數(shù)據(jù)源,如DBCP,但是需要我們自定義數(shù)據(jù)源工廠并進(jìn)行配置,這一點(diǎn)暫時(shí)不做研究。
9、databaseIdProvider屬性(了解)
databaseIdProvider元素主要是為了支持不同廠商的數(shù)據(jù)庫,這個(gè)元素不常用。比如有的公司內(nèi)部開發(fā)使用的數(shù)據(jù)庫都是MySQL,但是客戶要求使用Oracle,那麻煩了,因?yàn)镸ybatis的移植性不如Hibernate,但是Mybatis也不會那么蠢,在Mybatis中我們可以使用databaseIdProvider這個(gè)元素實(shí)現(xiàn)數(shù)據(jù)庫兼容不同廠商,即配置多中數(shù)據(jù)庫。
下面以O(shè)racle和MySQL兩種數(shù)據(jù)庫來介紹它們,要配置的屬性如下:
<!--數(shù)據(jù)庫廠商標(biāo)示 -->
<databaseIdProvider type="DB_VENDOR"><property name="Oracle" value="oracle"/><property name="MySQL" value="mysql"/><property name="DB2" value="d2"/>
</databaseIdProvider>
databaseIdProvider的type屬性是必須的,不配置時(shí)會報(bào)錯(cuò)。上面這個(gè)屬性值使用的是VendorDatabaseIdProvider類的別名。
property子元素是配置一個(gè)數(shù)據(jù)庫,其中的name屬性是數(shù)據(jù)庫名稱,value是我們自定義的別名,通過別名我們可以在SQL語句中標(biāo)識適用于哪種數(shù)據(jù)庫運(yùn)行。如果不知道數(shù)據(jù)庫名稱,我們可以通過以下代碼獲取connection.getMetaData().getDatabaseProductName()來獲取,代碼如下:
/*** 獲取數(shù)據(jù)庫名稱*/
public static void getDbInfo() {SqlSession sqlSession = null;Connection connection = null;try {InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);sqlSession = sqlSessionFactory.openSession();connection = sqlSession.getConnection();String dbName = connection.getMetaData().getDatabaseProductName();String dbVersion = connection.getMetaData().getDatabaseProductVersion();System.out.println("數(shù)據(jù)庫名稱是:" + dbName + ";版本是:" + dbVersion);} catch (SQLException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}
}
我的輸出結(jié)果是:數(shù)據(jù)庫名稱是:MySQL;版本是:5.7.28-log
然后下面我們就可以在自己的sql語句中使用屬性databaseId來標(biāo)示數(shù)據(jù)庫類型了。配置如下:
<!-- 查詢所有用戶 -->
<select id="selectAllUser" resultType="com.thr.User" databaseId="oracle">select * from t_user
</select>
注意:在上面的SQL中,我配置的databaseId是oracle,但是我的實(shí)際的數(shù)據(jù)庫是mysql,結(jié)果最后肯定會報(bào)BindingException異常(但是我這個(gè)代碼不知道為什么報(bào)的另一種異常,就很奇怪,百度了好久無果)。如果我們把databaseId=”oracle”換成mysql的,就能獲取正確結(jié)果了。除上述方法之外,我們還可以不在SQL中配置databaseId,這樣mybatis會使用默認(rèn)的配置,也是可以成功運(yùn)行的。
過上面的實(shí)踐知道了:使用多數(shù)據(jù)庫SQL時(shí)需要配置databaseIdProvider 屬性。當(dāng)databaseId屬性被配置的時(shí)候,系統(tǒng)會優(yōu)先獲取和數(shù)據(jù)庫配置一致的SQL,否則取沒有配置databaseId的SQL,可以把它當(dāng)默認(rèn)值;如果還是取不到,就會拋出異常。
除了系統(tǒng)自定義的標(biāo)識外,我們也可以自定義一個(gè)規(guī)則,需要實(shí)現(xiàn)MyBatis提供的DatabaseIdProvider接口,如下:
MyDatabaseIdProvider
然后在databaseIdProvider中做如下配置:
<!--數(shù)據(jù)庫廠商標(biāo)示 -->
<databaseIdProvider type="com.databaseidprovider.MyDatabaseIdProvider" />
property屬性可以不做配置了,其它都一樣。
10、mappers屬性
mapper屬性是用來加載映射文件的,也就是加載我們配置的SQL映射文件。它有四種方式加載:
用文件路徑引入
使用URL方式引入
用類注冊引入
用包名引入(推薦)
10.1、用文件路徑引入
<mappers><mapper resource="com/thr/mapper/UserMapper.xml" /><mapper resource="com/thr/mapper/StudentMapper.xml" /><mapper resource="com/thr/mapper/TeacherMapper.xml" />
</mappers>
這種方式是相對路徑,相對于項(xiàng)目目錄下,所以得用 / 分開。
10.2、使用URL方式引入
<mappers><mapper url="D:/mappers/UserMapper.xml" /><mapper url="D:/mappers/StudentMapper.xml" />
</mappers>
這種方式是絕對路徑,就是從我們的磁盤讀取映射文件,一般不會使用這種方式。
10.3、用類注冊引入
<mappers><mapper class="com.thr.mapper.UserMapper" /><mapper class="com.thr.mapper.StudentMapper" /><mapper class="com.thr.mapper.TeacherMapper" />
</mappers>
這種方式使用Mapper接口的全限定名,不用管路徑問題,讓Mybatis自己通過全限定名去找映射文件。但是前提是Mapper接口的名稱必須與映射文件的名稱相同,并且要在同一個(gè)包名下,否則會找不到。比如:UserMapper.java(接口)—UserMapper.xml(映射文件)。關(guān)于Mapper接口對應(yīng)的Mapper映射文件后面會詳細(xì)介紹。
10.4、用包名引入(推薦)
<mappers><package name="com.thr.mapper"/>
</mappers>
推薦使用這種方式,表示引入該包下的所有mapper接口,這里引入了com.thr.mapper包下的所有接口文件,然后讓Mybatis自己通過全限定名去找映射文件。
注意:這種方式的要求同樣是Mapper接口和Mapper的映射文件的名稱要相同,并且要放在相同的包名下,否則會導(dǎo)致找不到。