MyBatis 理論篇
什麼是MyBatis
MyBatis是支持普通SQL查詢,存儲過程和高級映射的優秀持久層框架.MyBitis消除了幾乎所有的JDBC代碼和參數的手工設置及對結果的檢索.MyBatis可以使用簡單的XML或註解用於配置和原始映射,將接口和Java的POJO(Plain Old Object ,普通的Java對象)映射成數據庫中的記錄.
入門
每個MyBatis的應用程序都以一個SQLSessionFactory對象的實例爲核心.SQLSessionFactory對象的實例可以通過SqlSessionFactory對象來獲得.SqlSessionFactory對象可以通過XML培訓文件,或從以往使用慣例中準備好的Configuration類實例中來構建SQLSessionFactory對象.
從XML中構建SQLSessionFactory
從XML文件中構建SQLSessionFactory的實例非常簡單.這裏建議你使用類路徑下的資源文件來配置,但是你可以使用任意的Reader實例,這個實例包括由文字形式的文件路徑或URL形式的文件路徑file://來創建.MyBatis包含了一個工具類,稱作爲資源,這些工具類包含一些方法,這些方法使得從類路徑或其他位置加載資源文件變得更簡單.
String resource = "org/mybatis/example/Configuration.xml";
Reader reader = Resources.getResourceAsReader(resource);
sqlMapper = new SqlSessionFactoryBuilder().build(reader);
XML配置文件包含對MyBatis系統的核心配置,包含獲取數據庫連接實例的數據源和決定事務範圍和控制事務管理器,關於XML配置文件的詳細內容可以在文檔後面找到,這裏給一個簡單的示例:
<?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>
<environments default="development">
<!-- environment 元素體中包含對事務管理和連接池的環境配置 -->
<environment id="development">
<transactionManager type="JDBC" />
<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>
<!-- mappers 元素是包含所有mapper(映射器)的列表,這些mapper的XML文件包含SQL代碼和映射定義信息 -->
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml" />
</mappers>
</configuration>
當然,在XML配置文件中還有很多可以配置的,上面的示例支出的則是最關鍵的部分.要注意XML頭部的聲明,需要用來驗證XML文檔正確性.environment元素體中包含對事務管理和連接池的環境配置,mappers元素是包含所有mapper(映射器)的列表,這些mapper的XML文件包含SQL代碼和映射定義信息
不使用XML構建SQLSessionFactory
如果你喜歡從java程序而不是從XML文件中直接創建配置實例,或創建你自己的配置構建器,MyBatis也提供完整的配置類,提供所有從XML文件中加載配置信息的選項.
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development",
transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(configuration);
注意,這種清空下配置是天價映射器類.映射器類是java類,這些類包含SQL映射語句的註解從而避免了XML文件的依賴,XML映射仍然是在大多數高級映射(比如:嵌套Join映射)時需要.處於這樣的原因,如果存在XML配置文件的話,MyBatis將會自動查找和加載一個對等的XML文件(這種情況下,基於類路徑下的BlogMapper.class類的類名,那麼BlogMapper.xml 將會被加載).
從SqlSessionFactory中獲取SqlSession
現在我們已經知道如何獲取SQLSessionFactory對象了,基於同樣的啓示,我們就可以獲得SqlSession的實例了.SqlSession對象完全包含以數據庫爲背景的所有執行SQL操作的方法,你可以用SqlSession.實例來直接執行已映射的SQL語句,例如:
SqlSession session = sqlMapper.openSession();
try {
Blog blog = (Blog) session.selectOne(
"org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}
這種方法起到的作用,和我們使用之前的MyBatis版本是相似的,現在有一種更簡潔的方法.使用合理描述參數和SQL語句返回值的接口(比如BlogMapper.class),這樣現在就可以執行更簡單,更安全的代碼.沒有容易發生的字符串文字和轉換的錯誤. 例如:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}
探究已映射的SQL語句
這裏你也許可以知道通過SqlSession和Mapper對象到底執行了什麼操作.已映射的SQL語句是一個很大的主題,而且這個主題會貫穿本文檔中的大部分內容,爲了宏觀的概念,這裏有一些示例.
上面提到的任何一個示例,語句是通過XML或註解定義的.我們先來看看XML.使用基於XML的映射語言,在過去的幾年中使得MyBatis非常流行,它爲MyBatis提供所有的特性設置.如果你以前用過MyBatis,這個概念你就應該很熟悉了,但是XML映射文件也有很多的改進,後面我們詳細再說.這裏給出一個基於XML映射語句的示例,這些語句應該可以滿足上述示例中的SqlSession對象的調用.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" parameterType="int" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
這個簡單的例子看起來很多額外的東西,但是也相當簡潔了.如果你喜好,你可以在一個單獨的XML映射文件中定義很多的映射語句,除XML頭部和文檔聲明之外,你可以得到很多方便之處.在文件中剩餘部分是很好的自我解釋.在命名空間”com.mybatis.example.BlogMapper”中,它定義了一個名爲”selsect Blog”映射的語句,這樣它允許你使用完全限定名“org.mybatis.example.BlogMapper.selectBlog”來調用映射語句,我們下面的實例中所有的寫法也是這樣的.
Blog blog = (Blog) session.selectOne(
"org.mybatis.example.BlogMapper.selectBlog", 101);
要注意這個使用完全是限定名調用Java對象的方法是相似的,這樣做是由原因的.這個命名可以直接映射相同命名空間下的映射器類,使用一個名稱,參數和返回值已映射的查詢語句都一樣的方法既可以.這就允許你非常容易地調用映射器接口中的方法,這和你前面看到的是i一樣的,下面這個示例中它有出現了.
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
第二種方式有很多的優點,首先它不是基於文字的,那就更安全了.第二,如果你的IDE有代碼補全功能,那麼你可以利用它來操作已映射的SQL語句.第三,不需要強制類型轉換,同時BlogMapper接口可以保持簡潔,返回值類型很安全(參數類型也很安全.)
命名空間的一點註釋
命名空間在之前版本MyBatis中是可選項,非常混亂在使用上沒有幫助,現在命名空間是必須的,而且有要給目的,它是用更長的完全限定名來隔離語句.
命名空間使得接口綁定稱爲肯,就像你看到的那樣,如果之前不瞭解,那麼現在你就會使用他們了,你應該按照下面給出的示例來練習,以免改變自己的想法.使用命名空間,並將它放在適合的Java包空間之下,這樣使你的代碼變得簡潔,在更長的時間內提供MyBatis的作用.
命名解析:爲了減少輸入量,MyBatis對所有的命名配置元素使用如下的命名解析規則,包括語句,結果映射,緩存等.
- 直接查詢完全限定名(比如:“com.mypackage.MyMapper.selectAllThings”),如果發現就使用.
- 短名稱(比如:selectAllThings”)可以用來引用在任意含糊的對象.而如果有兩個或兩個以上的(比如:“com.foo.selectAllThings”和“com.bar.selectAllThings”),那麼就會得到錯誤報告,說命名稱是含糊的,因此就必須使用完全限定名.
對BlogMapper這樣的映射器來說,還有一個妙招.它們中間映射的語句可以不需要再XML中來寫,而可以使用java註解來替換,比如,上面的XML實例可以如下來替換:
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
對於簡單語句來說,使用註解代碼會更加清晰,然而java註解對於複雜語句來說就會混亂,應該限制使用.因此,如果你佈局不做複雜的事情,那麼最好使用XML來映射語句.
當然這也屈居於你的項目團隊的決定,看哪種更適合你來使用,還有以長久以來使用映射語句的重要性.也就是說,不要將自己侷限在一種方式,你可以輕鬆地將註解轉換成XML映射語句,反之亦然.
範圍和生命週期
理解我們目前已經討論過的不同範圍和聲明週期類是很重要的,不正確的使用他們會導致嚴重的併發問題.
SqlSessionFactoryBuilder
這個類可以被實例化,但是已丟棄.一旦你創建了SqlSessionFactory後,這個類就不需要存在了.因此SqlSessionFactoryBuilder 實例的最佳範圍是方法範圍(也就是本地方法變量).你可以重用SqlSessionFactoryBuilder來創建多個SqlSessionFactory實例,但是最好的方式就是不需要保持它一直存在來保證所有XML解析資源,因爲還有更重要的事情要做.
SqlSessionFactory
一旦被創建,SqlSessionFactory實例應該在你的應用程序執行期間都存在.沒有理由來處理或重新創建它.使用SQLSessionFactory的最佳實踐是在應用程序運行期間不要重複創建多次.這樣的操作將被視爲非常糟糕的.因此SqlSessionFactory的最佳範圍是應用範圍.有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式.然而這兩種方法都不認爲是最佳實踐.這樣的話,你可以考慮依賴注入容器,比如Google Guice 或 Spring .這樣的框允許你創建支持程序來管理單例SqlSessionFactory的生命週期.
SqlSession
每個線程都應該有它自己的SqlSession實例.SqlSession的實例不能共享使用,它也是線程不安全的.因此最佳的範圍是請求或方法範圍.絕對不能將SqlSession實例的引用放在一個類的靜態字段甚至是實例字段中.也絕不能將SqlSession實例的引用放在任何類型的掛了你範圍中,比如Servlet框架中HttpSession.如果你現在正在用任意的web框架,要考慮SqlSession放在一個Http請求對象相似的範圍內,就可以關閉它,關閉Session很重要,你應該確保finally塊來關閉它.下面的示例就是一個確保SqlSession關閉的基本模式.
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
在你的代碼中一貫的使用這種模式,將會保證所有數據庫資源都正確的關閉(假設沒有通過你自己的連接關閉,這會給MyBatis造成一種機箱表明你自己管理連接資源).
映射器實例
&ems;映射器是你創建綁定語句的接口.映射器接口的實例可以從SqlSession中獲得,那麼從技術上來說,當被請求時,任意映射器實例的最寬範圍和SqlSession是相同的.然而,映射器實例的最佳範圍是方法範圍.也就是說,他們應該在使用他們的方法中被請求,然後就拋棄掉.他們不需要明確地關閉.那麼在請求對象中保留它們也就不是什麼問題了,這和SqlSession相似,你也許會發現,在這個水平上管理太多的資源的話會失控.保持簡單,將映射器放在方法範圍內,下面的示例展示了這個實例:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// do work
} finally {
session.close();
}
XML映射配置文件
MyBatis的XML配置文件包含了影響MyBatis行爲甚深的設置和屬性信息.XML文檔的高層級結構如下.
- configuration 配置
- properties :屬性
- setting 設置
- typeAliases 類型命名
- typeHandlers 類型處理器
- objectFactory 對象工廠
- plugins 插件
- environments 環境
- environment 環境變量
- transactionManager 事務管理器
- dataSource 數據源
- 映射器
properties
這些都是外部化的,可替代的屬性,這些屬性也可以配置在典型的Java屬性文件中,或者通過properties元素的子元素來傳遞.例如:
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="myuser" />
<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>
這個例子中的username和password將由properties元素中設置的值來替換.driver和url屬性將會從包含進來的cofig.properties文件中的值替換.這裏提供很多配置選項.
屬性也可以被傳遞到SqlSessionBuilder.builder()方法中.例如:
SqlSessionFactory factory =
sqlSessionFactoryBuilder.build(reader, props);
// ... or ...
SqlSessionFactory factory =
sqlSessionFactoryBuilder.build(reader, environment, props);
如果在這些地方,屬性多於一個的話,MyBatis安裝如下屬性加載他們:
- 在properties元素內指定的屬性首先被讀取
- 從類路徑下資源或properties元素的url屬性中加載的屬性第二被讀取,她會覆蓋已存在的完全一樣的屬性
- 作爲方法參數傳遞的屬性最後被讀取,它也會覆蓋任一已存在的完全一樣的屬性,這些屬性可能是從properties元素內和資源/url屬性中加載的.
因此,最高優先級屬性是那些作爲方法參數的,然後是資源/url屬性,最後是properties元素中指定的屬性.
settings
這些是極其重要的調整,它們會修改MyBatis在運行時的行爲方式.見如下表
參數設置 | 描述 | 有效值 | 默認值 |
---|---|---|---|
cacheEnabled | 這個配置使用全局的映射器啓用或禁用緩存. | true | false | true |
lazyLoadingEnabled | 全局啓用或禁用延遲加載.當禁用時,所有關聯對象都會即時加載. | true | false | true |
aggressiveLazyLoading | 當啓用時,有延遲加載屬性的對象在被調用時將會完成加載任意屬性.否則,每種屬性將會按需要加載. | true | false | true |
multpleResultSetsEnabled | 允許或不允許多種結果從一個單獨的語句中返回(需要適合的驅動) | true | false | true |
useColumnLable | 使用列標籤代替列名.不同的卻動在這方面表現不同.參考驅動文檔或充分測試兩種方法來決定所使用的驅動 | true | false | true |
useGenerateKeys | 允許JDBC支持生成的鍵.需要適合的驅動.如果設置爲true則這個設置強制生成的鍵被使用,儘管一些驅動拒絕兼容但仍有效. | true | false | false |
autoMappingBehavoir | 指定MyBatis如何自動映射列到字段/屬性.PARTIAL只會自動映射簡單,沒有嵌套的結果.FULL會自動映射任意複雜的結果(嵌套的或其他情況) | NONE , PARTIAL , FULL | PARTIAL |
defaultExecutorType | 配置默認的執行器,SIMPLE執行器沒有什麼特別支出.REUSE執行器重用預處理語句.BATCH執行器重用語句和批量更新. | SIMPLE , REUSE , BATCH | SIMPLE |
defaultStatementTimeout | 設置超時時間,它決定驅動等待一個數據庫的響應時間 | 任何正整數 | Not Set (null) |
一個設置信息元素的實例,如下:
<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="enhancementEnabled" value="false" />
<setting name="defaultExecutorType" value="SIMPLE" />
<setting name="defaultStatementTimeout" value="25000" />
</settings>
typeAliases
類型別名爲Java類型命名一個短的名字.它只和XML配置有關,用來減少類完全限定的多餘部分.例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author" />
<typeAlias alias="Blog" type="domain.blog.Blog" />
<typeAlias alias="Comment" type="domain.blog.Comment" />
<typeAlias alias="Post" type="domain.blog.Post" />
<typeAlias alias="Section" type="domain.blog.Section" />
<typeAlias alias="Tag" type="domain.blog.Tag" />
</typeAliases>
這個配置,”Blog”可以任意來代替”domain.blog.Blog”所使用的地方.
對於普通的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 |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
typeHandlers
無論是MyBatis在預處理語句中設置一個參數,還是從結果中取出一個值時,類型處理器被用來將獲取的值以合適的方式轉換成Java類型,如下表格所示
類型處理器 | Java類型 | JDBC類型 |
---|---|---|
BooleanTypeHandler | Boolean ,boolean | 任何兼容的布爾值 |
ByteTypeHandler | Byte , byte | 任何兼容的數字或短整型 |
ShortTypeHandler | Short , short | 任何兼容的數字或短整型 |
IntegerTypeHandler | Integer , int | 任何兼容的數字和整型 |
LongTypeHandler | Long,long | 任何兼容的數字或長整型 |
FloatTypeHandler | Float,float | 任何兼容的數字或單精度浮點型 |
DoubleTypeHandler | Double,double | 任何兼容的數字或雙精度浮點型 |
BigDecimalTypeHandler | BigDecimal | 任何兼容的數字或十進制小數類型 |
StringTypeHandler | String | CHAR 和 VARCHAR 類型 |
ClobTypeHandler | String | CLOB和 LONGVARCHAR 類型 |
NStringTypeHandler | String | NVARCHAR 和 NCHAR 類型 |
NClobTypeHandler | String | NCLOB類型 |
ByteArrayTypeHandler | byte[] | 任何兼容的字節流類型 |
BlobTypeHandler | byte[] | BLOB和 LONGVARBINARY 類型 |
DateTypeHandler | Date(java.util) | TIMESTAMP 類型 |
DateOnlyTypeHandler | Date(java.util) | DATE 類型 |
TimeOnlyTypeHandler | Date(java.util) | TIME 類型 |
SqlTimestampTypeHandler | Timestamp(java.sql) | TIMESTAMP 類型 |
SqlDateTypeHandler | Date(java.sql) | DATE 類型 |
SqlTimeTypeHandler | Time(java.sql) | TIME 類型 |
ObjectTypeHandler | 任意 | 其他或未指定類型 |
EnumTypeHandler | Enumeration 類型 | VARCHAR-任何兼容的字符串類型, |
你可以重寫類型處理器或創建你自己的累成處理器來處理,不支持的或非標準的類型.要這樣做的話,簡單實現TypeHandler接口(org.mybatis.type),然後映射新的類型處理器類到Java類型,還有可選的一個JDBC類型.例如:
// ExampleTypeHandler.java
public class ExampleTypeHandler implements TypeHandler {
public void setParameter(PreparedStatement ps, int i, Object parameter,JdbcType jdbcType) throws SQLException {
ps.setString(i, (String) parameter);
}
public Object getResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName);
}
public Object getResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex);
}
}
// MapperConfig.xml
<typeHandlers>
<typeHandler javaType="String" jdbcType="VARCHAR"
handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
使用這樣的類型處理器將會覆蓋已經存在的處理Java的String類型屬性和VARCHAR參數及結果的類型處理器.要注意MyBatis不會審視數據庫元信息來決定使用哪種類型,所以你必須在參數和結果映射中指定那是VARCHAR類型的字段,來綁定到正確的類型處理器上.這是因爲MyBatis直到語句被執行都不知道數據類型的這個實現導致的.
objectFactory
MyBatis每次創建結果對象新的實例時,它使用一個ObjectFactory實例來完成.如果參數映射存在,默認的ObjectFactory不比使用默認構造方法或帶參數的構造方法實例化模板類的工作多.如果你項重寫默認的ObjectFactory,你可以創建你自己的.比如.
// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
}
public Object create(Class type,List<Class> constructorArgTypes,
List<Object> constructorArgs) {
return super.create(type, constructorArgTypes,
constructorArgs);
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
}
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100" />
</objectFactory>
ObjectFactory接口非常簡單.它包含兩個用於創建的方法,一個默認構造方法,另外一個是處理帶參數的構造方法.最終.setProperties方法可以被用來配置ObjectFactory.在初始化你懂ObjectFactory實例後,objectFactory元素體中定義的屬性會被傳遞給setProperties方法.
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的核心.這是低層的類和方法.
實現插件的力量也比較簡單,例如:
// ExamplePlugin.java
@Intercepts({@Signature(type= Executor.class,method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable
{
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
// MapperConfig.xml
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
上面的插件將會攔截在Excutor實例中所有的”update”方法調用,它也是負責低層映射語句執行的內部對象
覆蓋配置類
除了用插件來修改MyBatis核心行爲之外,你也可以完全覆蓋配置類.簡單擴展它,然後覆蓋其中任意方法,之後傳遞它到sqlSessionBuilder.builder(myConfig)方法的調用.這可能會嚴重影響MyBatis的行爲,所以要小心.
environments
MyBatis可以撇嘴多種環境.這會幫助你將SQL映射應用與多種數據庫之中.例如,你也許爲開發要設置不同的配置,測試和生產環境.或者你可能有多種生產級數據庫卻相同共享模式,所以你會想對不同數據庫使用相同的SQL映射,這種用例是很多的.
要記得一個很重要的問題:你可以配置多種環境,但你只能爲每個SqlSessionFactory實例選擇一個.
所以,如果你想連接兩個數據庫,你需要創建兩個SqlSessionFactory實例,每個數據庫對應一個.而如果是三個數據庫你就需要三個實例,以此類推.記憶起來很簡單:
每個數據庫對應一個SqlSessionFactory
爲了明確創建哪種環境,你可以將它作爲可選的參數傳遞給SqlSessionFactoryBuilder.可以接受環境配置的兩個方法簽名是:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment); SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,properties);
如果環境被忽略,那麼默認環境將會被加載,如下進行:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader); SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,properties);
環境元素定義瞭如何配置環境.
<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是自我解釋的.你可以使用你喜好的名稱來命名,只要確定默認的要匹配其中之一.
transactionManager
在MyBatis中有兩種事務管理器類型(也是是type=”[JDBC | MANAGED]”);
JDBC – 這個配置直接簡單使用了JDBC的提交和回滾設置.它依賴於從數據源得到的連接來管理事務範圍.
MANAGED – 這個配置幾乎沒做什麼.它從來不提交或回滾一個連接.而它會讓容器來管理事務的整個聲明週期(比如Spring 或JEEP應用服務器的上下文).默認情況下它會關閉連接.然而一些容器並不希望這樣,因此如果你需要從連接中停止它,將closeConnection屬性設置爲false.例如:
<transactionManager type="MANAGED"> <property name="closeConnection" value="false" /> </transactionManager>
這兩種事務管理器都不需要任何屬性.然而它們都是類型別名,要替換使用它們,你需要放置將你自己的類的完全限定名或類型別名,它們引用了你對TransacFactory接口的實現類.
public interface TransactionFactory { void setProperties(Properties props); Transaction newTransaction(Connection conn, boolean autoCommit); }
任何在XML中配置的屬性在實例化後將會被傳遞給setProperties()方法,你的事先類需要創建一個事務接口的實現,這個接口也很簡單:
public interface Transaction { Connection getConnection(); void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
使用這兩個接口,你可以完全自定義MyBatis對事務的管理.
dataSource
dataSource元素使用基本的JDBC數據源接口來配置JDBC連接對象的資源.
許多MyBitis的應用程序將會按示例中的例子來配置數據源.然而它並不是必須的.
要知道爲了方便,使用延遲加載,數據源纔是必須的.
有三種內建的數據源類型(也就是type=”???”);
UNPOOLED - 這個數據源的實現是每次被請求時簡單打開和關閉的連接.它有一點慢,這是對簡單應用程序的一個很好的選擇,因爲它不需要即使的可用連接.不同的數據庫對這個表現也是不一樣的,所以對某些數據來說配置數據源並不重要,這個配置也是閒置的.
UNPOOLED類型的數據源僅僅用來配置一下5中屬性:
driver – 這是JDBC驅動的Java類的完全限定名(如果你的驅動包含的有,它也不是數據源類)
url – 這是戶口的JDBC URL 地址
username – 登陸數據庫的用戶名
password – 登陸數據庫的密碼
defaultTransactionIsoIationLevel – 默認的連接事務隔離級別
作爲可選項,你可以傳遞數據庫驅動的屬性.要這樣做,屬性的前綴是”driver”,開頭的.例如:
driver.encoding=UTF8
這樣就會傳遞以”UTF8“來傳遞,”encoding“屬性,它是通過DriverManager.getConnection(url,driverProperties)方法傳遞給數據庫驅動.
POOLED – 這是JDBC連接對象的數據源連接池的實現,用來米麪創建新的連接實例時必要的初始連接和認證事件.這是一種當前Web應用程序來快速響應請求很流行的方法.
除了上述(UNPOOLED)屬性之外,還有很多屬性可以用來配置POOLED數據源
poolMaximumActiveConnections – 在任意時間存在的空閒連接數.
poolMaximumIdeConnections – 任意時間存在的空閒連接數
poolMaximumCheckoutTime – 在被強制返回之前,池中連接被檢查的時間,默認值2000毫秒(20秒)
poolPingQuery – 發送到數的偵測查詢,用來驗證連接是否正常工作,並且準備接收請求.默認是NO PING QUERY SET,這會引起許多數據庫驅動連接由一個錯誤信息而導致失敗
poolPingEnabled – 這是開啓或禁用偵測查詢.如果開啓,你必須用一個合法的SQL語句(最好是很快速的),設置poolPingQuery屬性.默認值:false.
poolPingConnectionsNotUsedFor – 這是用來配置poolPingQuery 多次時間被用一次.這可以被設置匹配標準的數據連接超時時間,來避免不必要的偵測.默認值:0(也就是所有連接每一時刻都被偵測– 但僅僅當poolPingEnabled 爲 true 時適用).
JNDI – 這個數據源的實現是爲了使用如Spring或應用服務器這個類的容器,容器可以集中或外部配置數據源,然後放置一個JNDI上下文的引用.這個數據源配置只需要兩個屬性
initial_context – 這個屬性用來初始化上下文中尋找環境(也就是initialContext.lookup(initial-context)).這是個可選屬性,如果被忽略,那麼data_source屬性將會直接一initialContext爲背景在此尋找.
- data_source – 這是引用數據源實例位置上的上下文路徑.它會以由initial_context查詢返回的環境爲背景來查找,如果initial_context沒有返回結果時,直接以初始上下文爲環境來查找.
和其他數據源配置相似,它也可以通過”env.”的前綴直接項初始化上下文發送屬性.
env.encoding=UTF8
在初始化之後,這就會以值”UTF8”向初始化上下文的構造方法傳遞名爲”encoding”的屬性
mappers
既然MyBatis的行爲已經由上述元素配置完了,我們現在就要定義SQL映射語句了.但是,首先我們需要告訴MyBatis到哪裏去兆這個語句.Java在這方面沒有提供一個很好的方法,所以最佳的方式是告訴MyBatis到哪裏去兆映射文件.你可以使用相對與類路徑的資源引用,或者字符表示,或URL引用的完全限定名(包括:file:///urls) , 例如:
// 使用相對於類路徑的資源
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml" />
<mapper resource="org/mybatis/builder/BlogMapper.xml" />
<mapper resource="org/mybatis/builder/PostMapper.xml" />
</mappers>
// 使用完全限定路徑
<mappers>
<mapper url="file:///var/sqlmaps/AuthorMapper.xml" />
<mapper url="file:///var/sqlmaps/BlogMapper.xml" />
<mapper url="file:///var/sqlmaps/PostMapper.xml" />
</mappers>
這些語句簡單告訴了MyBitis去哪裏找映射文件.其餘的細節就是在每個SQL映射文件中了,下面的部分我們來討論SQL映射文件.
SQL映射文件的XML文件
&ems;MyBatis真正的力量是在映射語句中.這裏是奇蹟發生的地方.對於所有的力量,SQL映射的XML文件是相當簡單.當然如果你將它們和對等功能的JDBC代碼來比較,你會發現映射文件節省了大約95%的代碼量.MyBatis的構建是聚焦與SQL的,使其遠離於普通的方式.
SQL映射文件有很少的幾個頂級元素(按照它們應該被定義的書序):
名稱 | 釋義 |
---|---|
cache | 配置給定命名空間的緩存 |
cache-ref | 從其他命名空間引用緩存配置 |
resultMap | 最複雜,也是最有力量的元素,用來描述如何從數據庫結果集中來加載你的對象. |
sql | 可以重用的SQL塊,也可以被其他語句引用. |
insert | 映射插入語句 |
update | 映射更新語句 |
delete | 映射刪除語句 |
select | 映射查詢語句 |
下一部分將從語句本身開始來描述每個元素的細節.
select
查詢語句是使用MyBatis時最常用的元素之一.直到你從數據庫去除數據時纔會發現將數據存在數據庫中是多麼有價值,所以許多應用程序的查詢操作要比更改數據操作多得多.對於每次插入,更新或刪除,那也會有很多的查詢.這是MyBatis的一個基本原則,也是講中心和努力放到查詢和結果映射的原因,對於簡單類別的查詢元素是非常簡單的.比如:
<select id=”selectPerson” parameterType=”int” resultType=”hashmap”>
SELECT * FROM PERSON WHERE ID = #{id}
</select>
這個語句被稱作selectPerson,使用一個int(或Integer)類型的參數,並返回一個HashMap類型的對象,其中的鍵是列名,值是對應的值.
注意參數註釋:
#{id}
這就告訴MyBatis創建一個PreparedStatement(預處理語句)參數.使用JDBC,這樣的一個參數在SQL中會由一個”?”來標識,並被傳遞到一個新的預處理語句中,就像這樣:
// 相似的JDBC代碼,不是MyBatis的
String selectPerson = “SELECT * FROM PERSON WHERE ID=?”;
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
當然,這需要很多單獨的JDBC的代碼來提取結果並將它們映射到對象實例中.這就是MyBatis節省你時間的地方.我們需要深入瞭解參數和結果映射.哪些細節部分我們西門來倆句.
select元素有很多屬性允許你配置,來決定每條語句的作用細節.
<select
id=”selectPerson”
parameterType=”int”
parameterMap=”deprecated”
resultType=”hashmap”
resultMap=”personResultMap”
flushCache=”false”
useCache=”true”
timeout=”10000”
fetchSize=”256”
statementType=”PREPARED”
resultSetType=”FORWARD_ONLY”
>
屬性 | 描述 |
---|---|
id | 在命名空間中唯一的標識符,可以被用來引用這條語句. |
parameterType | 將會傳入這條語句的參數類的完全限定名或別名 |
resultType | 從這條語句中返回的期望類型的完全限定名或別名.注意集和情形.那應該是集合可以包含的類型,而不能是集合本身.使用resultType或resultMap,但不能同時使用. |
resultMap | 命名引用外部的resultMap.返回map是MyBatis最具力量的特性,對其一個很好的理解的話,許多複雜映射的情形就能被解決了.使用resultMap或resultType,但不能同時使用. |
flushCache | 將其設置爲true , 無論語句什麼時候被調用,都會導致緩存被清空.默認值爲false. |
userCache | 將其設置爲true , 將會導致本條語句的結果被緩存,默認值爲true. |
timeout | 這個設置驅動程序等等數據庫返回請求結果,並拋出異常時間的最大等待值.默認不設置(驅動自行處理) |
fetchSize | 這時暗示驅動程序每次批量返回的結果行數.默認不設置(驅動自行處理) |
statementType | STATEMENT,PREPARED或CALLABLE的一種.這會讓MyBatis使用選擇使用Statement,PreparedStatement或CallableStatement.. 默認值:PREPARED. |
resultSetType | FORWARD_ONLY SCROLL_SENSITIVE SCROLL_INSENSITIVE中的一種.默認是不設置(驅動自行處理) |
input , update , delete
數據修改語句insert update delete 在他們的實現中非常相似.
<insert id="insertAuthor" parameterType="domain.blog.Author"
flushCache="true" statementType="PREPARED" keyProperty=""
useGeneratedKeys="" timeout="20000" />
<update id="insertAuthor" parameterType="domain.blog.Author"
flushCache="true" statementType="PREPARED" timeout="20000" />
<delete id="insertAuthor" parameterType="domain.blog.Author"
flushCache="true" statementType="PREPARED" timeout="20000" />
屬性 | 描述 |
---|---|
id | 在命名空間中唯一的標識符,可以被用來引用這條語句 |
parameterType | 將會傳入這條語句的參數類的完全限定或別名 |
flushCache | 將其設置爲true,不論語句什麼時候被調用,都會導致緩存被清空,默認值爲:false. |
timeout | 這個設置驅動程序等待數據庫返回請求結果,並拋出異常時間的最大等待值.默認不設置(驅動自行處理.) |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一種。這會讓 MyBatis使用選擇使用 Statement,PreparedStatement 或 CallableStatement。默認值:PREPARED。 |
useGeneratedKeys | (進隊insert有用)這會告訴MyBatis使用JDBC的getGeneratedKeys方法來去除由數據(比如:像MySQL和SQLServer這樣的數據庫管理系統的自動遞增字段)內部生成的主鍵.默認值:false. |
keyProperty | (僅對insert有用)標記一個屬性,MyBatis會通過getGeneratedKeys或者通過insert語句的selectKey子元素設置它的值.默認:不設置 |
下面就是 insert ,update 和delete 語句的示例:
<insert id="insertAuthor" parameterType="domain.blog.Author">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor" parameterType="domain.blog.Author">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
<delete id="deleteAuthor” parameterType=" int ">
delete from Author where id = #{id}
</delete>
如前所屬,插入語句有一點多,它有一些屬性和子元素用來處理主鍵的生成.
首先,如果你的數據庫支持自動生成主鍵的字段(比如MySQL和SQLServer數據庫),那麼你可以設置userGeneratedKeys=”true”,而且設置keyProperty到你已經做好的模板屬性上.例如,如果上面的Author表已經對id使用了自動生成的列類型,那麼語句可以修改爲:
<insert id="insertAuthor" parameterType="domain.blog.Author"
useGeneratedKeys=”true” keyProperty=”id”>
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
MyBatis有靈媧一種方法來處理數據庫不支持自動生成類型,或者肯JDBC驅動不支持自動生成主鍵的主鍵生成問題.
這裏有一個簡單(甚至很傻)的示例,它可以生成一個隨機ID(可能你不會這麼做,但是這樣展示了MyBatis處理問題的靈活性,因爲它並不真的關心ID的生成);
<insert id="insertAuthor" parameterType="domain.blog.Author">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
insert into Author
(id, username, password, email,bio, favourite_section)
values
(#{id}, #{username}, #{password}, #{email}, #{bio},
#{favouriteSection,jdbcType=VARCHAR}
)
</insert>
在上面的示例中,selectKey元素將會首先運行,Author的id會被設置,然後插入語句會被調用.這給你一個簡單的行爲在你的數據庫中來處理自動生成的主鍵,而不需要使你的Java代碼變得複雜.
selectKey 元素描述如下:
<selectKey keyProperty="id" resultType="int" order="BEFORE"
statementType="PREPARED">
屬性 | 描述 |
---|---|
keyProperty | selectKey語句結果應該被設置的目標屬性 |
resultType | 結果的類型,MyBatis通常可以算出來,但是寫上也沒有問題.MyBatis允許任何簡單類型用作主鍵的類型,包括字符串. |
order | 這可以被設置爲BEFORE或AFTER.如果設置爲BEFORE,那麼它首先選擇主鍵,設置keyProperty然後執行插入語句.如果設置爲AFTER,那麼先執行插入語句,然後是selectKey元素.這和Oracle數據庫相似,可以在插入語句中嵌入序列調用. |
statementType | 和前面的相同,MyBatis支持STATEMENT,PREPARED和CALLABLE語句的映射類型,分別代表PreparedStatement和CallableStaement類型 |
sql
這個元素可以被用來定義可重用的SQL代碼段,可以包含在其他語句中.比如: id,username,password
這個SQL片段可以被包含在其他語句中,例如
<select id=”selectUsers” parameterType=”int” resultType=”hashmap”>
select
<include refid=”userColumns” />
from some_table
where id = #{id}
</select>
Parameters
在之前的語句中,你已經看到了一些簡單參數的示例,在MyBatis中參數是非常強大的元素.對於簡單的做法,大概90%的情況,是不用太多的,比如:
<select id=”selectUsers” parameterType=”int” resultType=”User”>
select id, username, password
from users
where id = #{id}
</select>
上面這個示例說明了一個非常簡單的命名參數映射.參數類型被設置爲”int”,因此這個參數可以被設置成任何內容.原生的類型或簡單數據類型,比如整型和沒有相關屬性的字符串,因此它會完全用參數來替代.然而,如果你傳遞了一個複雜的對象,那麼MyBatis的處理方式就會有一點不同.比如:
<insert id=”insertUser” parameterType=”User”>
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果User類型的參數對象傳遞到了語句中id,username,和password屬性將會被查到,然後他們的值就被傳遞到預處理語句的參數中.
這點對於傳遞參數到語句中非常好,但是對於參數映射也有一些其他的特性.
首先,像MyBatis的其他部分,參數可以指定一個確定的數據類型.
#{property,javaType=int,jdbcType=NUMERIC}
像MyBatis的剩餘部分,javaType通常可以從參數對象中來確定,除非對象是一個HashMap.那麼javaType應該被確定來保證使用正確類型處理器.
注意:如果null被當作值來傳遞,對於所有可能爲空的列,JDBCType是需要的.也可以通過閱讀PreparedStatement.setNull();方法的JavaDocs文檔來研究它.
爲了以後自定義類型處理器,你可以指定一個確定的類型處理器類(或別名),比如:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
儘管它看起來繁瑣,但是實際上是你很少設置他們其中之一.
對於數據類型,對於決定有多少數字是相關的,有一個數值範圍.
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
最後mode屬性允許你指定in,out或inout參數.如果參數爲out或inout.參數對象屬性的真實值將會被改變,就像你期望你需要輸出一個參數.如果mode爲OUT(或INOUT),而且jdbcType爲CURSOR(也就是Oracle的REFCURSOR),你必須指定一個resultMap來映射結果集到參數類型.要注意這裏的javaType屬性是可選的,如果左邊是空白是jdbcType的CURSOR類型,它會自動被設置爲結果集.
#{department,
mode=OUT,
jdbcType=CURSOR,
javaType=ResultSet,
resultMap=departmentResultMap
MyBatis也支持很多高級的數據類型,比如機構體,但是當註冊out參數時你必須告訴語句類型名稱.比如(在此提示,在實際中不要像這樣換行);
#{middleInitial,
mode=OUT,
jdbcType=STRUCT,
jdbcTypeName=MY_TYPE,
resultMap=departmentResultMap}
景觀所有這些請打的選項很多時候你值簡單指定屬性名,MyBatis會自己計算剩餘的.最多的清空是你爲jdbcType指定可能爲空的列名.
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
字符串替換
默認情況下,使用#{}格式的語法會導致MyBatis創建預處理語句屬性並以它爲背景設置安全的值(比如?).這樣做很安全,很迅速,也是首選的做法,有時你只是項直接在SQL語句中插入一個不改變的字符串.比如,項ORDER BY , 你可以這樣來使用:
ORDER BY ${columnName}
這裏MyBatis不會修改或轉義字符串
重要:接收用戶輸出的內容並提供給語句中不變的字符串,這樣做是不安全的.這會導致潛在的SQL注入攻擊,因此你不應該允許用戶輸入這些字段,或者通常自行轉義並檢查.
resultMap
resultMap元素是MyBatis中最重要最強大的元素.它就是讓你遠離90%的需要從結果集中去除數據的JDBC代碼的哪個東西,而且在一些情形下允許你做一些JDBC不支持的事情.事實上,編寫相似於對複雜語句聯合映射這些等同的代碼,也需可以跨過上千行的代碼.ResultMap的設計就是簡單語句不需要明確的結果映射,而很多複雜語句確實需要描述它們的關係.
你已經看到簡單的映射語句的示例了,但沒有明確的resultMap.比如:
<select id=”selectUsers” parameterType=”int” resultType=”hashmap”>
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
這樣一個語句簡單作用域所有列被自動映射到HashMap的鍵上,這由resultType屬性指定.這在很多情況下是由用的.但是HashMap 不能很好描述一個領域模型,那樣你的應用程序將會使用JavaBean或POJOs或POJOs來作爲領域模型,MyBatis對兩者都支持.看看下面的JavaBean:
package com.someapp.model;
public class User {
private int id;
private String username;
private String hashedPassword;
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 getHashedPassword() {
return hashedPassword;
}
public void setHashedPassword(String hashedPassword) {
this.hashedPassword = hashedPassword;
}
}
基於JavaBean的規範,上面這個類有三個屬性:id,username和hashedPassword.這些在select語句中會精確匹配到列名.
這樣的一個JavaBean可以被映射到結果集,就像映射到HashMap一樣簡單.
<select id=”selectUsers” parameterType=”int” resultType=”com.someapp.model.User”>
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
要記住類型別名是你懂 夥伴.使用他們你可以不用輸入類的全路徑.比如:
<!-- 在XML配置文件中 -->
<typeAlias type=”com.someapp.model.User” alias=”User” />
<!-- 在SQL映射的XML文件中 -->
<select id=”selectUsers” parameterType=”int” resultType=”User”>
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
這些情況下,MyBatis會在幕後自動創建一個ResultMap,基於屬性名來映射到JavaBean的屬性上.如果列名沒有精確匹配,你可以在列名上使用select子句別名(一個標準的SQL特性)來匹配標籤.比如:
<select id=”selectUsers” parameterType=”int” resultType=”User”>
select
user_id as “id”,
user_name as “userName”,
hashed_password as “hashedPassword”
from some_table
where id = #{id}
</select>
ResultMap最優秀的地方你已經瞭解了很多了,但是你還沒有真正的看到一個.這些簡單示例不需要比你看到的更多東西.只是處於示例的原因,讓我們來看看最後一個實例中外部的resultMap是什麼樣子的,這也是解決列名不匹配的另外一種方式.
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name" />
<result property="password" column="hashed_password" />
</resultMap>
引用它的語句使用resultMap屬性就行了(注意我們去掉resultType屬性)比如:
<select id=”selectUsers” parameterType=”int” resultMap=”userResultMap”>
select
user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
高級結果映射
MyBatis創建的一個想法:數據庫不用永遠是你想要的或需要他們是什麼樣的.而我們最喜歡的數據庫最好是第三範式或BCNF範式,但是它們有時不是.如果可能有一個單獨的數據庫映射,所有應用程序都可以使用它,這非常好,但有時也不是.結果映射就是MyBatis提供處理這個問題的答案:
比如,我們如何映射下面的這個語句?
<!-- 非常複雜的語句 -->
<select id="selectBlogDetails" parameterType="int"
resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
你可能項把它映射到一個智能的對象模型,包含一個作者寫的博客,有很多的博文,沒騙博文有零條或多條的評論和標籤.下面是一個完整的複雜結果映射例子(假設作者,博客,博文,評論和標籤都是類型的別名.).我們來看看,但是不用緊張,我們會一步異步來說明.
<!-- 非常複雜的結果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int" />
</constructor>
<result property="title" column="blog_title" />
<association property="author" column="blog_author_id"
javaType="Author">
<id property="id" column="author_id" />
<result property="username" column="author_username" />
<result property="password" column="author_password" />
<result property="email" column="author_email" />
<result property="bio" column="author_bio" />
<result property="favouriteSection" column="author_favourite_section" />
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id" />
<result property="subject" column="post_subject" />
<association property="author" column="post_author_id"
javaType="Author" />
<collection property="comments" column="post_id" ofType=" Comment">
<id property="id" column="comment_id" />
</collection>
<collection property="tags" column="post_id" ofType=" Tag">
<id property="id" column="tag_id" />
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost" />
</discriminator>
</collection>
</resultMap>
resultMap元素有很多子元素和一個質的討論的結構,下面是resultMap元素的概念圖.
- resultMap
- constructor:類在實例化時,用來注入結果到構造方法中.
- idArg :id參數,標記接軌哦作爲ID可以幫助提高整體效能
- arg:注入到構造方法的一個普通結果
- id: 一個ID結果:標記結果作爲ID可以幫助提高整體效能
- result: 注入到字段或JavaBean屬性的普通結果
- association:一個複雜的類型關聯:許多結果將包成這種類型
- 嵌入結果映射:結果映射自身的關聯,或參考一個
- collection : 複雜類型的集合
- 嵌入結果映射: 結果映射自身的 集合,或者參考一個.
- discriminator:使用結果值來決定使用哪個結果映射
- case:基於某些值的結果映射
- 嵌入結果映射,這種情形結果也映射它本身,因此可以包含很多相同的元素,或者它可以參照一個外部的結果映射.
最佳實踐:通常逐步建立結果映射.單元測試的真正幫助在這裏,如果你嘗試創建一次創建一個向上面示例示例那樣巨大的結果映射,那可能會有錯誤而且很那去控制它來工作.開始簡單一些,異步異步的發展.而且要進行單元測試!使用該框架的缺點是它們優勢是黑盒.你確定你顯示想要的行爲的最好選擇是編寫單元測試,它也可以你幫助得到提交的錯誤.
id,result
<id property="id" column="post_id" />
<result property="subject" column="post_subject" />
這些是結果映射最基本內容.id和result都映射一個單獨列的值到簡單數據類型(字符串,整列,雙精度浮點數,日期等)的單獨屬性或字段.
這兩者之間的唯一不同是id表示的結果將是比當較對象實例時用到的標識符屬性.這幫助來改進整體表現,特別是緩存和嵌入結果映射(也就是聯合映射).
每個映射屬性
屬性 | 描述 |
---|---|
property | 映射到列結果的字段或屬性.如果匹配的是存在的,和給定名稱相同的JavaBean的屬性,那麼就會使用.否則MyBatis將會尋找給定名稱的字段.這兩種情形你可以使用通常點式的複雜屬性導航.比如,你可以這樣映射一些東西:”username”,或者映射到一些複雜的東西:”address.street.number.” |
column | 從數據庫中得到的列名,或者是列名的重命名標籤.這也是通常會傳遞給resultSet.getString(columnName)方法參數中相同的字符串 |
javaType | 一個Java類的完全限定名,或一個類型別名(參數上面內建類型別名的列表).如果你映射到一個JavaBean , MyBatis 通常可以斷定類型.然而,如果你映射到的是HashMap , 那麼你應該明確地指定javaType來保證所需的行爲. |
jdbcType | 在這個表格之後的所有支持的JDBC類型列表中的類型.JDBC類型是僅僅需要對插入,更新和刪除操作可能爲空的列進行處理.這是JDBC的需要,而不是MyBatis的.如果你直接使用JDBC編程,你需要指定這個類型,但不僅僅對可能爲空的值. |
typeHandler | 我們在前面討論過默認的類型處理器.使用這個屬性,你可以覆蓋默認的類型處理器,這個屬性值是類的完全限定名或者是一個類型處理器的實現,或者是類別名. |
支持的JDBC類型
爲了參考,MyBatis通過包含的jdbcType的枚舉類型,支持下面的JDBC類型.
BIT | FLOAT | CHAR | TIMESTAMP | OTHER | UNDEFINED |
---|---|---|---|---|---|
TINYNT | REAL | VARCHER | BINARY | BLOB | NVARCHAR |
SMALLINT | DOUBLE | LONGVARCHAR | VARVINARY | CLOB | NCHAR |
INTEGER | NUMERIC | DATE | LONGVARBINARY | BOOLEAN | NCLOB |
BIGINT | DECIMAL | TIME | NULL | CURSOR |
構造方法
<constructor>
<idArg column="id" javaType="int" />
<arg column=”username” javaType=”String” />
</constructor>
對於大多數數據傳輸對象(Data Transfer Object ,DTO )類型,屬性可以起作用,而且像絕大多數的領域模型,指令也許是你想使用一成不變的類的地方.通常包含引用或查詢數據的表很少或基本不變的話對一成不變的類來說是合適的.構造方法注入允許你再初始化時爲類設置屬性的值,而不用暴露共有方法.MyBatis也支持私有屬性和私有JavaBean屬性來達到這個目的的,但是一些人更青睞構造方法注入.Constructor(構造方法),元素支持這個.
例子:
public class User {
//…
public User(int id, String username) {
//…
}
//…
}
爲了向這個構造方法中注入結果,MyBatis需要通過它的參數的類型來標識構造方法.java沒有自查(或反射)參數的方法.所以當創建一個構造方法元素時,保證參數是按順序排列的,而且數據類型也是確定的.
<constructor>
<idArg column="id" javaType="int" />
<arg column=”username” javaType=”String” />
</constructor>
剩餘的屬性和規則和固定的id和result元素是相同的
屬性 | 描述 |
---|---|
column | 來自數據庫的類名,或重命名的列標籤.這和通常傳遞給resultSet.getString(columnName);方法的字符串是相同的. |
javaType | 一個Java類的完全限定名稱,或一個類型別名(參見上面內建類型的別名的列表)如果你映射到一個JavaBean,MyBatis通常可以斷定類型.然而,如果你映射到的是HashMap,那麼你應該明確地指定javaType來保證所需的行爲 |
jdbcType | 在這個表格之前的所支持的JDBC類型列表中的類型.JDBC類型是僅僅需要對插入,更新和刪除操作可能爲空的列進行處理.這是JDBC的需要,而不是MyBatis的.如果你直接使用JDBC 編程,你需要指定這個類型,但僅僅對可能爲空的值. |
typeHandler | 我們在掐滅討論夠默認的類型處理器.使用這個屬性,你可以覆蓋默認的類型處理器.這個屬性值是類的完全限定名或者是一個類型處理器的實現,或者是類型別名. |
關聯
<association property="author" column="blog_author_id"
javaType=" Author">
<id property="id" column="author_id" />
<result property="username" column="author_username" />
</association>
關聯元素處理”有一個”類型的關係.比如,我們在示例中,一個博客有一個用戶.關聯映射就工作於這種結果之上.你指定了目標屬性,來獲取值的列.屬性的Java類型(很多情況下MyBatis可以自己算出來),如果需要的話還有JDBC類型,如果你項覆蓋或獲取的結果值還需要類型控制器.
關聯中不同的是你需要告訴MyBatis如何加載關聯.MyBatis在這方面會有兩種不同的方式:
- 嵌套查詢:通過執行另外一個SQL映射語句來返回預期的複雜類型.
- 嵌套結果:使用嵌套結果映射來處理重複的聯合結果的子集.
首先,讓我們來查看這個元素的屬性.所有的你都會看到,它和普通的只由select和resultMap屬性的結果映射不同.
屬性 | 描述 |
---|---|
property | 映射到列結果的字段或屬性.如果匹配的是存在的,和給定的名稱相同的JavaBean的屬性,那麼就會使用.否則MyBatis將會尋找給定名稱的字段.這兩中情形你可以使用通常點式的複雜屬性導航.比如,你可以這樣映射一些東西:”username”,或者映射到一些複雜的東西”address.street.number”. |
column | 來自數據庫的列名,或重命名的列標籤.者和通常牀底給resultSet.getString(columnName)方法的字符串是相同的. 注意:要處理符合主鍵,你可以指定多個列名通過 column=”{prop1=coll,prop2=col2}”這種語法來傳遞給嵌套查詢語句.這會引起prop1和prop2以參數對象形式來設置給目標嵌套查詢語句. |
javaType | 一個java類的完全限定名,或一個類型別名(參加上面內建類型別名的列表),如果你映射到一個javaBean,MyBatis通常可以斷定類型.然而,如果你映射到的是HashMap,那麼你應該明確地指定javaType來保證所需的行爲. |
jdbcType | 在這個表格之前的所支持的JDBC類型列表中的類型,JDBC類型是僅僅需要對插入,更新,和刪除操作可能爲空的列進行處理,這是JDBC的需要,而不是MyBatis的,如果你直接使用JDBC編程,你需要指定這個類型,但僅僅對可能爲空的值. |
typeHandler | 我們在前面討論過默認的類型處理器.使用這個屬性,你可以覆蓋默認的類型處理器,這個屬性值是類的完全限定名或者是一個類型處理器的實現,或者是類型別名. |
關聯嵌套查詢
屬性 | 描述 |
---|---|
select | 另外一個映射語句的ID,可以加載這個屬性映射需要的複雜類型.獲取的在列屬性中指定的列的值將被傳遞給目標select語句作爲參數,表格後面有一個詳細的示例.注意:要處理符合主鍵,你可以指定多個列名通過 column=”{prop1=col1,prop2=col2}”,這種語法來傳遞給嵌套查詢語句.這會引起prop1和prop2參數對象形式來設置目標喬濤查詢語句 |
示例:
<resultMap id=”blogResult” type=”Blog”>
<association property="author" column="blog_author_id"
javaType="Author" select=”selectAuthor” />
</resultMap>
<select id=”selectBlog” parameterType=”int” resultMap=”blogResult”>
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id=”selectAuthor” parameterType=”int” resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
我們有兩個查詢語句:一個加載博客,另外一個加載作者,而且博客的結果映射描述了”selectAuthor”語句應該被用來加載它的author屬性.
其他所有的屬性將會被自動加載,假設他們的列和屬性名相匹配.
這種這種方式很簡單,但是對於大型數據庫集合和列表將不會表現很好.問題就是我們熟知的”N+1”查詢問題.概括地將,N+1查詢問題可以是這樣引起的:
- 你執行了一個單獨的SQL語句來獲取結果列表(就是”+1”)
- 對返回的每條記錄,你執行了一個查詢語句來爲每個加載細節(就是”N”)
這個問題會導致陳白上千的SQL語句被執行.這通常是不期望的.
MyBatis能延遲加載這樣的查詢就是一個好處,因此你可以分散這些語句同時運行的消耗.然而,如果你加載一個列表,之後迅速迭代來訪問嵌套的數據,你會調用所有的延遲加載,這樣的行爲可能是很糟糕的.
所以還有另外一種辦法.
關聯的嵌套結果
屬性 | 描述 |
---|---|
resultMap | 這種結果映射的ID,可以映射關聯的嵌套結果到一個適合的對象圖中.這是一種替代方法來調用另外一個查詢語句.這允許你聯合多個表來合成到一個單獨的結果集.這樣的結果集可能包含重複,數據的重複組組員被分解,合理映射到一個嵌套的對象圖.爲了使它變得容易,MyBatis讓你”鏈接”結果映射,來處理嵌套結果.例子會很容易來仿照,示例如下: |
在上面你已經看到了一個非常複雜的嵌套關聯的示例.下面這個是一個非常簡單是示例來說明他們如何工作.代替了執行一個分離的語句,我們聯合博客表和作者表在一起,就像:
<select id="selectBlog" parameterType="int" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio
From Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
注意這個聯合查詢,以及採取保護來確定所有結果被唯一而且清晰的名字來重命名.這使得映射非常簡單.現在我們可以映射這個結果;
<resultMap id="blogResult" type="Blog">
<id property=”blog_id” column="id" />
<result property="title" column="blog_title" />
<association property="author" column="blog_author_id"
javaType="Author" resultMap=”authorResult” />
</resultMap>
<resultMap id="authorResult" type="Author">
<id property="id" column="author_id" />
<result property="username" column="author_username" />
<result property="password" column="author_password" />
<result property="email" column="author_email" />
<result property="bio" column="author_bio" />
</resultMap>
在上面的示例中你可以看到博客的作者關聯代表這”authorResult”,結果映射來加載作者實例.
非常重要:在嵌套結果映射中id元素扮演了非常重要的角色.應該通常指定一個或多個屬性.它們可以用來唯一標識結果.實際上就是如果你不使用它(id元素),但是會產生一個嚴重的性能問題,不過MyBatis仍然可以正常工作,選擇的屬性越少越好,它們可以唯一地標識結果.主鍵就是一個顯而易見的選擇(幾遍是聯合主鍵).
現在,上面的示例用了外部的結果映射元素來映射關聯.這使得Author結果映射可以重用.然而,如果你不需要重用它的話,或者你僅僅引用你所有的結果映射合到一個單獨描述的結果映射中.你可以嵌套結果映射.這裏給出使用這種方式的相同示例:
<resultMap id="blogResult" type="Blog">
<id property=”blog_id” column="id" />
<result property="title" column="blog_title" />
<association property="author" column="blog_author_id"
javaType="Author">
<id property="id" column="author_id" />
<result property="username" column="author_username" />
<result property="password" column="author_password" />
<result property="email" column="author_email" />
<result property="bio" column="author_bio" />
</association>
</resultMap>
上面你已經看到了如何處理”有一個”類型關聯,但是”有很多個”是怎樣的?下面這個部分就是來討論這個主題的.
集合
<collection property="posts" ofType="domain.blog.Post">
<id property="id" column="post_id" />
<result property="subject" column="post_subject" />
<result property="body" column="post_body" />
</collection>
集合元素的作用幾乎和關聯是相同的.實際上,他們也很相似,文檔的異同是多餘的.所以我們更關注於他們的不同.
我們來繼續上面的示例,一個博客只有一個作者,但是博客有很多文章.在博客類中,這個可以由下面這樣的寫法來表示:
private List<Post> posts;
要映射嵌套結果到集合List中,我們使用集合元素.就像關聯元素一樣,我們可以從連接中使用嵌套查詢,或者嵌套結果.
集合嵌套查詢
首先,讓我們看看使用嵌套查詢來爲博客加載文章.
<resultMap id=”blogResult” type=”Blog”>
<collection property="posts" javaType=”ArrayList” column="blog_id"
ofType="Post" select=”selectPostsForBlog” />
</resultMap>
<select id=”selectBlog” parameterType=”int” resultMap=”blogResult”>
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id=”selectPostsForBlog” parameterType=”int” resultType="Author">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
這裏你應該注意很多東西,但大部分代碼和上面的關聯是非常相似的.首先,你應該注意我們使用的是集合元素.然後要注意那個心底 ofType屬性.這個屬性用來區分JavaBean(或字段)屬性類型和集合包含的類型來說是很重要的.所以你可以讀出下面這個映射.
<collection property="posts" javaType=”ArrayList” column="blog_id"
ofType="Post" select=”selectPostsForBlog”/>
讀作:”在Post類型的ArrayList中的post的集合.”
javaType屬性是不需要的,因爲MyBatis在很多情況下會爲你算出來.所以你可以縮短寫法.
<collection property="posts" column="blog_id" ofType="Post" select=”selectPostsForBlog”/>
集合的嵌套結果
至此,你可以猜測集合的嵌套結果是如何來工作的,因爲它和關聯完全相同,除了它應用了一個”ofType”屬性.
<select id="selectBlog" parameterType="int" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body,
from Blog B
left outer join Post P on B.id = P.blog_id
where B.id = #{id}
</select>
我們又一次聯合了博客表和文章表,而且關注於保證特性,結果列標籤的簡單映射.現在用文章映射集合映射博客,可以簡單寫爲:
<resultMap id="blogResult" type="Blog">
<id property=”id” column="blog_id" />
<result property="title" column="blog_title" />
<collection property="posts" ofType="Post">
<id property="id" column="post_id" />
<result property="subject" column="post_subject" />
<result property="body" column="post_body" />
</collection>
</resultMap>
同樣,要記得id元素的重要性,如果你不記得了,請你閱讀上面的關聯部分.
同樣,如果你引用更長形式允許你的結果映射的更多重用,你可以使用下面這個代替的映射.
<resultMap id="blogResult" type="Blog">
<id property=”id” column="blog_id" />
<result property="title" column="blog_title" />
<collection property="posts" ofType="Post" resultMap=”blogPostResult” />
</resultMap>
<resultMap id="blogPostResult" type="Post">
<id property="id" column="post_id" />
<result property="subject" column="post_subject" />
<result property="body" column="post_body" />
</resultMap>
注意:這個對你所映射的內容沒有深度,廣度或關聯和集合相聯合的限制.當映射他們時你應該在大腦中保留它們的表現.你的應用在找到最佳方法前要一致進行的單元測試和性能測試.好在MyBatis讓你後來可以改變想法,而不是對你的代碼造成很小(或任何)影響.
鑑別器
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost" />
</discriminator>
有時一個單獨的數據庫查詢也許返回很多不同(但是希望有些關聯)數據類型的結果集.鑑別器元素就是被設計來處理這個情況的,還有包括類的繼承層次結構.鑑別器非常容易理解,因爲它的表現很像Java語言中的switch語句.
定義鑑別器指定了column和javaType屬性.列是MyBatis查找比較值的地方.JavaType是需要被用來保證等價測試的合適類型(景觀字符串在很多情形下都會有)比如:
<resultMap id="vehicleResult" type="Vehicle">
<id property=”id” column="id" />
<result property="vin" column="vin" />
<result property="year" column="year" />
<result property="make" column="make" />
<result property="model" column="model" />
<result property="color" column="color" />
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult" />
<case value="2" resultMap="truckResult" />
<case value="3" resultMap="vanResult" />
<case value="4" resultMap="suvResult" />
</discriminator>
</resultMap>
在這個示例中,MyBatis會從結果集中得到每條記錄,然後比較它的vehicle類型的值.如果它匹配任何一個鑑別器示例,那麼就使用這個實例指定的結果映射.換句話說,這樣做完全是剩餘的結果映射被忽略(除非它被擴展,這在第二個示例中討論).如果沒有任何一個實例相匹配,那麼MyBatis僅僅使用鑑別器塊外定義的結果映射.所以,如果carResult按如下聲明:
<resultMap id="carResult" type="Car">
<result property=”doorCount” column="door_count" />
</resultMap>
那麼只有doorCount屬性會被加載.這步完成後完整地允許鑑別器示例的獨立組,景觀和父結果映射可能沒有什麼關係,這種情況下,我們當然知道cars和vehicles之間有關係;如Car是Vehicle示例.因此,我們想要剩餘的屬性也被加載.我們設置的結果映射的簡單改變如下.
<resultMap id="carResult" type="Car" extends=”vehicleResult”>
<result property=”doorCount” column="door_count" />
</resultMap>
現在vehicleResult和carResult的屬性都會被加載了.
儘管層級有些人會發現這個外部映射定義會多少有一些令人厭煩之處.因此還有另外一種語法來做簡介的映射風格:例如:
<resultMap id="vehicleResult" type="Vehicle">
<id property=”id” column="id" />
<result property="vin" column="vin" />
<result property="year" column="year" />
<result property="make" column="make" />
<result property="model" column="model" />
<result property="color" column="color" />
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultType="carResult">
<result property=”doorCount” column="door_count" />
</case>
<case value="2" resultType="truckResult">
<result property=”boxSize” column="box_size" />
<result property=”extendedCab” column="extended_cab" />
</case>
<case value="3" resultType="vanResult">
<result property=”powerSlidingDoor” column="power_sliding_door" />
</case>
<case value="4" resultType="suvResult">
<result property=”allWheelDrive” column="all_wheel_drive" />
</case>
</discriminator>
</resultMap>
要記得這些都是結果映射,如果你不指定任何結果,那麼MyBatis將會爲你自動匹配列和屬性,所以這些例子中的大部分是很冗長的.而起始是不需要的.
緩存
MyBatis包含一個非常強大的查詢緩存特性,它可以非常方便地配置和定製.MyBatis3中的緩存實現的很多改進都已經實現了,使得它更加強大,而且易於配置.
默認情況下是沒有開啓緩存的,除了局部的session緩存,可以增強變現而且處理循環依賴也是必須的.要開啓二級緩存.
參數介紹
- 映射語句穩重所有select語句將會被緩存
- 映射語句文件中所有inert , update 和delete 語句會刷新緩存.
- 緩存會使用Least Recently Use算法來收回
- 根據時間表,緩存不會以任何時間順序來屬性
- 緩存會存儲到列表集合或對象的1024個引用.
- 緩存會被視爲一個read/write的緩存,意味着對象檢索不是共享的,而且可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改.
所有的這些屬性都可以通過緩存元素的屬性來修改.比如:
<cache eviction="FIFO" flushInterval="60000" size="512"
readOnly="true" />
這個更高級的配置穿甲了一個FIFO緩存,並每個60秒刷新,存數據結果對象或列表的512個引用,而且返回的對象被認爲是隻讀的,因此在不同線程中的調用者之間修改他們會導致衝突.
可用的收回策略有:
屬性 | 描述 |
---|---|
LRU | 最近最少使用的,移除最常事件不被使用的對象 |
FIFO | 先進先出,安裝對象進入緩存的順序來移除他們. |
SOFT | 軟引用,移除基於垃圾回收器狀態和軟引用規劃的對象. |
WEAK | 弱引用,更積極地移除基於垃圾收集器狀態和弱引用規則的對象.默認是LRU |
flushInterval(刷新間隔) | 可以被設置爲任意的正整數,而且他們代表一個合理的毫秒形式的時間段.默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新. |
size(引用數目) | 可以被設置爲任意正整數,要記住你緩存的悐數目和你運行環境的可用內存資源數目.默認是1024. |
readOnly(只讀) | 只讀屬性可以被設置爲true或false.只讀的緩存會給所有調用者返回緩存對象的相同實例.因此這些對象不能被修改,這提供了很重要的性能優勢.可讀寫的緩存會返回緩存對象的拷貝(通過序列號).這會慢一些,但是安全,因此默認是false. |
使用自定義緩存
除了這些自定義緩存的方式,你也可以通過實現你自己的緩存或非其他第三方緩存方案,穿甲適配器來完成全覆緩存行爲.
<cache type=”com.domain.something.MyCustomCache”/>
這個示例展示瞭如何使用一個自定義的緩存試下.type屬性指定的類必須實現org.mybatis.cache.Cache接口.這個接口是MyBatis框架中很多複雜的接口之一,但是簡單給定它做什麼就行了.
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
ReadWriteLock getReadWriteLock();
}
要配置你的緩存,簡單和共有的JavaBean屬性來配置你的緩存實現,而是通過cache元素來傳遞屬性,比如,下面代碼會在你的緩存是現在中調用一個稱爲”setCacheFile(String file)”的方法:
<cache type=”com.domain.something.MyCustomCache”>
<property name=”cacheFile” value=”/tmp/my-custom-cache.tmp”/>
</cache>
你可以使用所有簡單類型作爲JavaBean的屬性,MyBatis會進行轉換.
記得緩存配置和緩存實例是綁定在SQL映射文件的命名空間是很重要的.因此,所有在相同命名空間的語句正如綁定的緩存一樣.語句可以修改和緩存交互的方式,或在語句的語句基礎上使用兩種簡單的屬性來完成配出他們.默認情況下,語句可以這樣來配置.
<select . flushCache=”false” useCache=”true” />
<insert . flushCache=”true” />
<update . flushCache=”true” />
<delete . flushCache=”true” />
因爲哪些是默認的,你明顯不能明確地以這種方式來配置一條語句.相反,如果你項改變默認的行爲,只能設置flushCache和useCache屬性.比如:在一些情況下你也許項配出從緩存中查詢特定語句結果,或者你也許想要一個查詢語句來刷新緩存.相似的,你也許有一些更新語句一款執行二日不需要刷新緩存.
參照緩存
回想一下上一節內容,這個熱書命名空間的唯一緩存會被使用或者刷新相同命名空間內的語句.也許將來的某個時候,你會想在命名空間中共享相同的緩存配置和實例.在這樣的情況下你可以使用cache-ref元素來引用另外一個緩存.
<cache-ref namespace=”com.someone.application.data.SomeMapper”/>
動態SQL
MyBatis的一個強大的特徵之一通常是它的動態SQL能力.如果你又使用JDBC或其他相似框架經驗,你就明白條件的串聯SQL字符串在一起是多麼的痛苦,確保不能忘了空格或在列表的最後省略逗號.動態SQL可以徹底處理這種痛苦.
通常使用動態SQL不可能是獨立的一部分,MyBAT死當然使用一種強大的動態SQL語言來改進這種情形,這種語言可以被用在任意映射的SQL語句中.
動態SQL元素和使用JSTL或其他相似的基於xml的文件處理器相似.在MyBatis之前的版本中,有很多的元素需要來了解.MyBatis3大大提升了他們,現在用不到原先一般的呀US怒就能工作了,MyBatis採用功能強大的基於ONGL的表達式來消除其他元素.
- if
- choose(when,otherwise)
- trim(where)
- foreach
if
在動態SQL中做所做的最通用的事情是包含部分where字句的條件.比如:
<select id=”findActiveBlogWithTitleLike” parameterType=”Blog”
resultType=”Blog”>
SELECT * FROM BLOG
WHERE state = „ACTIVE‟
<if test=”title ! null ”>
AND title like #{title}
</if>
</select>
這條語句會提供一個可選的文本查找功能.如果你沒有傳遞title,那麼所有激活的博客都會被返回.但是如果你傳遞了title.那麼就會查找相近的title(對於敏銳的檢索,這種情況下你的參數值需要包含任意的遮掩或通配符)
假若我們項可選的搜索title和author呢?首先,要改變語句的名稱讓它有意義,然後簡單加入另外一個條件.
<select id=”findActiveBlogLike” parameterType=”Blog”
resultType=”Blog”>
SELECT * FROM BLOG WHERE state = „ACTIVE‟
<if test=”title ! null ”>
AND title like #{title}
</if>
<if test=”author ! null and author.name ! =null”>
AND title like #{author.name}
</if>
</select>
choose,whenotherwise
有時我們不想應用所有的條件,相反我們項選擇很多情況下的一種.和Java中switch語句相似,MyBatis提供choose元素.
我們使用上面的示例,但是現在我們來搜索當title提供時僅有title條件,當author提供時僅有author條件.如果二者都沒有提供,只返回featured blogs(也許是由管理員策略地選擇的結果列表,而不是返回大量沒有意義的,隨機的博客列表).
<select id=”findActiveBlogLike” parameterType=”Blog”
resultType=”Blog”>
SELECT * FROM BLOG WHERE state = „ACTIVE‟
<choose>
<when test=”title ! null ”>
AND title like #{title}
</when>
<when test=”author ! null and author.name ! =null”>
AND title like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim,where ,set
前面的例子已經方便處理了一個臭名昭著的動態SQL問題,要考慮我們回到”if“示例後會發生什麼?但這次我們將”ACTIVE=1“也設置成動態的條件.
<select id=”findActiveBlogLike” parameterType=”Blog”
resultType=”Blog”>
SELECT * FROM BLOG
WHERE
<if test=”state ! null ”>
state = #{state}
</if>
<if test=”title ! null ”>
AND title like #{title}
</if>
<if test=”author ! null and author.name ! =null”>
AND title like #{author.name}
</if>
</select>
如果這些條件都沒有匹配上將會發生什麼?這條SQL結束時就會成這樣
SELECT * FROM BLOG
WHERE
這會導致查詢失敗,如果僅僅第二個條件匹配時什麼樣?這條SQL結束時就會時這樣:
SELECT * FROM BLOG
WHERE
AND title like „someTitle‟
這個查詢也會失敗,這個問題不能簡單的用條件來解決,如果你從來沒有這樣寫過,那麼你以後也不會這樣寫.
MyBatis有一個簡單的處理方式,這個90%的情況下都會有用,而在不能使用的地方,你可以自定義處理方式.加上一個簡單的改變,所有事情都會順利進行:
<select id=”findActiveBlogLike” parameterType=”Blog”
resultType=”Blog”>
SELECT * FROM BLOG
<where>
<if test=”state ! null ”>
state = #{state}
</if>
<if test=”title ! null ”>
AND title like #{title}
</if>
<if test=”author ! null and author.name ! =null”>
AND title like #{author.name}
</if>
</where>
</select>
where元素知道如果由被包含的標記返回任意內容,就僅僅插入”WHERE”.而且,如果以”AND”或”OR”開頭的內容,那麼就會跳過WHERE不插入.
如果where元素沒有做出你想要的,你可以使用trim元素來定義.比如,和where元素相等的trim元素是:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
…
</trim>
overrides屬性採用管道文本分隔符來覆蓋,這裏的空白也是重要的.它的結果就是移除在overrides屬性中指定的內容,插入在with屬性中的內容.
和動態更新語句相似的解決方案是set.set元素可以被用於動態包含更新列,而不包含不需要更新的.比如:
<update id="updateAuthorIfNecessary" parameterType="domain.blog.Author">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
這裏,set元素會動態前置SET關鍵字,而且也會消除任意無關的逗號,那也許在應用條件之後來跟蹤定義的值.
如果你對和這相等的trim元素好奇,它看起來就是這樣的.
<trim prefix="SET" suffixOverrides=",">
…
</trim>
注意這種情況下我們覆蓋一個後綴,而同時也附加前綴.
foreache
另外一個動態SQL通用的必要操作是迭代一個集合,通常是構建在IN條件中的.比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list" open="("
separator="," close=")">
#{item}
</foreach>
</select>
foreach元素非常強大,它允許你指定一個集合,聲明結合項和索引變量,它們可以用在元素體內,它也允許你指定開放和關閉的字符串,在迭代之間放置分隔符,這個元素是很智能的,它不會偶然地附加多於的分隔符.
注意:你可以傳遞一個List實例或者數組作爲參數對象傳給MyBatis.當你這麼做的時候,MyBatis會自動將它們包裝在一個Map中,用名稱作爲鍵,List實例將會以:”list“作爲鍵,而數組是麗江會以”array“作爲鍵.