MyBatis之SqlSessions

使用 MyBatis 的主要 Java 接口就是 SqlSession。儘管你可以使用這個接口執行命令,獲 取映射器和管理事務。我們會討論 SqlSession 本身更多,但是首先我們還是要了解如何獲取 一個 SqlSession 實例。SqlSessions 是由 SqlSessionFactory 實例創建的。SqlSessionFactory 對 象 包 含 創 建 SqlSession 實 例 的 所 有 方 法 。 而 SqlSessionFactory 本 身 是 由 SqlSessionFactoryBuilder 創建的,它可以從 XML 配置,註解或手動配置 Java 來創建 SqlSessionFactory。 


當Mybatis與一些依賴注入框架(如Spring或者Guice)同時使用時,SqlSessions將被依賴注入框架所創建,所以你不需要使用SqlSessionFactoryBuilder或者SqlSessionFactory,可以直接看SqlSession這一節。請參考Mybatis-Spring或者Mybatis-Guice手冊瞭解更多信息。 


SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 有五個 build()方法,每一種都允許你從不同的資源中創建一個 SqlSession 實例。

SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)

第一種方法是最常用的,它使用了一個參照了 XML 文檔或上面討論過的更特定的 mybatis-config.xml 文件的 Reader 實例。 可選的參數是 environment 和 properties。 Environment 決定加載哪種環境,包括數據源和事務管理器。比如:

<environments default="development">
 <environment id="development">
   <transactionManager type="JDBC">
       ...
   <dataSource type="POOLED">
       ...
 </environment>
 <environment id="production">
   <transactionManager type="MANAGED">
       ...
   <dataSource type="JNDI">
       ...
 </environment>
</environments>

如果你調用了 一個使用 environment 參數 方 式的 build 方法, 那麼 MyBatis 將會使用 configuration 對象來配置這個 environment。 當然, 如果你指定了一個不合法的 environment, 你會得到錯誤提示。 如果你調用了其中之一沒有 environment 參數的 build 方法, 那麼就使用 默認的 environment(在上面的示例中就會指定爲 default=”development”)。 


如果你調用了使用 properties 實例的方法,那麼 MyBatis 就會加載那些 properties(屬性 配置文件) ,並你在你配置中可使用它們。那些屬性可以用${propName}語法形式多次用在 配置文件中。 


回想一下,屬性可以從 mybatis-config.xml 中被引用,或者直接指定它。因此理解優先 級是很重要的。我們在文檔前面已經提及它了,但是這裏要再次重申:

如果一個屬性存在於這些位置,那麼 MyBatis 將會按找下面的順序來加載它們:

  • 在 properties 元素體中指定的屬性首先被讀取,

  • 從 properties 元素的類路徑 resource 或 url 指定的屬性第二個被讀取, 可以覆蓋已經 指定的重複屬性,

  • 作爲方法參 數傳遞 的屬性最 後被讀 取,可以 覆蓋已 經從 properties 元 素體和 resource/url 屬性中加載的任意重複屬性。 

因此,最高優先級的屬性是通過方法參數傳遞的,之後是 resource/url 屬性指定的,最 後是在 properties 元素體中指定的屬性。

總結一下,前四個方法很大程度上是相同的,但是由於可以覆蓋,就允許你可選地指定 environment 和/或 properties。 這裏給出一個從 mybatis-config.xml 文件創建 SqlSessionFactory 的示例:

String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);

注意這裏我們使用了 Resources 工具類,這個類在 org.mybatis.io 包中。Resources 類正 如其名,會幫助你從類路徑下,文件系統或一個 web URL 加載資源文件。看一下這個類的 源代碼或者通過你的 IDE 來查看,就會看到一整套有用的方法。這裏給出一個簡表:

URL getResourceURL(String resource)
URL getResourceURL(ClassLoader loader, String resource)
InputStream getResourceAsStream(String resource)
InputStream getResourceAsStream(ClassLoader loader, String resource)
Properties getResourceAsProperties(String resource)
Properties getResourceAsProperties(ClassLoader loader, String resource)
Reader getResourceAsReader(String resource)
Reader getResourceAsReader(ClassLoader loader, String resource)
File getResourceAsFile(String resource)
File getResourceAsFile(ClassLoader loader, String resource)
InputStream getUrlAsStream(String urlString)
Reader getUrlAsReader(String urlString)
Properties getUrlAsProperties(String urlString)
Class classForName(String className)

最後一個 build 方法使用了一個 Configuration 實例。configuration 類包含你可能需要了 解 SqlSessionFactory 實例的所有內容。Configuration 類對於配置的自查很有用,包含查找和 操作 SQL 映射(不推薦使用,因爲應用正接收請求) 。configuration 類有所有配置的開關, 這些你已經瞭解了,只在 Java API 中露出來。這裏有一個簡單的示例,如何手動配置 configuration 實例,然後將它傳遞給 build()方法來創建 SqlSessionFactory。

DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();

Environment environment = new Environment("development", transactionFactory, dataSource);

Configuration configuration = new Configuration(environment);
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
configuration.getTypeAliasRegistry().registerAlias(Post.class);
configuration.getTypeAliasRegistry().registerAlias(Author.class);
configuration.addMapper(BoundBlogMapper.class);
configuration.addMapper(BoundAuthorMapper.class);

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);

現在你有一個 SqlSessionFactory,可以用來創建 SqlSession 實例。 


SqlSessionFactory
SqlSessionFactory 有六個方法可以用來創建 SqlSession 實例。通常來說,如何決定是你 選擇下面這些方法時: 

  • Transaction (事務): 你想爲 session 使用事務或者使用自動提交(通常意味着很多 數據庫和/或 JDBC 驅動沒有事務)

  • Connection (連接): 你想 MyBatis 獲得來自配置的數據源的連接還是提供你自己

  • Execution (執行): 你想 MyBatis 複用預處理語句和/或批量更新語句(包括插入和 刪除)

重載的 openSession()方法簽名設置允許你選擇這些可選中的任何一個組合。

SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration()
;

默認的 openSession()方法沒有參數,它會創建有如下特性的 SqlSession: 

  • 會開啓一個事務(也就是不自動提交)

  • 連接對象會從由活動環境配置的數據源實例中得到。

  • 事務隔離級別將會使用驅動或數據源的默認設置。

  • 預處理語句不會被複用,也不會批量處理更新。

這些方法大都可以自我解釋的。 開啓自動提交, “true” 傳遞 給可選的 autoCommit 參數。 提供自定義的連接,傳遞一個 Connection 實例給 connection 參數。注意沒有覆蓋同時設置 Connection 和 autoCommit 兩者的方法,因爲 MyBatis 會使用當前 connection 對象提供的設 置。 

MyBatis 爲事務隔離級別調用使用一個 Java 枚舉包裝器, 稱爲 TransactionIsolationLevel, 否則它們按預期的方式來工作,並有 JDBC 支持的 5 級 ( NONE,READUNCOMMITTED,READCOMMITTED,REPEA TABLE_READ,SERIALIZA BLE) 

還有一個可能對你來說是新見到的參數,就是 ExecutorType。這個枚舉類型定義了 3 個 值: 

  • ExecutorType.SIMPLE: 這個執行器類型不做特殊的事情。它爲每個語句的執行創建一個新的預處理語句。

  • ExecutorType.REUSE: 這個執行器類型會複用預處理語句。

  • ExecutorType.BATCH: 這個執行器會批量執行所有更新語句,如果 SELECT 在它們中間執行還會標定它們是 必須的,來保證一個簡單並易於理解的行爲。

在 SqlSessionFactory 中還有一個方法我們沒有提及,就是 getConfiguration()。這 個方法會返回一個 Configuration 實例,在運行時你可以使用它來自檢 MyBatis 的配置。 

如果你已經使用之前版本 MyBatis,你要回憶那些 session,transaction 和 batch 都是分離的。現在和以往不同了,這些都包含在 session 的作用域內了。你需要處理分開處理 事務或批量操作來得到它們的效果。 

SqlSession
如上面所提到的,SqlSession 實例在 MyBatis 中是非常強大的一個類。在這裏你會發現 所有執行語句的方法,提交或回滾事務,還有獲取映射器實例。 
在 SqlSession 類中有超過 20 個方法,所以將它們分開成易於理解的組合。 


語句執行方法
這些方法被用來執行定義在 SQL 映射的 XML 文件中的 SELECT,INSERT,UPDA E T 和 DELETE 語句。它們都會自行解釋,每一句都使用語句的 ID 屬性和參數對象,參數可以 是原生類型(自動裝箱或包裝類) ,JavaBean,POJO 或 Map。

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

selectOne 和 selectList 的不同僅僅是 selectOne 必須返回一個對象。 如果多餘一個, 或者 沒有返回 (或返回了 null) 那麼就會拋出異常。 , 如果你不知道需要多少對象, 使用 selectList。 

如果你想檢查一個對象是否存在,那麼最好返回統計數(0 或 1) 。因爲並不是所有語句都需 要參數,這些方法都是有不同重載版本的,它們可以不需要參數對象。

<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)

最後,還有查詢方法的三個高級版本,它們允許你限制返回行數的範圍,或者提供自定 義結果控制邏輯,這通常用於大量的數據集合。

<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)

RowBounds 參數會告訴 MyBatis 略過指定數量的記錄,還有限制返回結果的數量。 RowBounds 類有一個構造方法來接收 offset 和 limit,否則是不可改變的。

int offset = 100;
int limit = 25;
RowBounds rowBounds = new RowBounds(offset, limit);

不同的驅動會實現這方面的不同級別的效率。對於最佳的表現,使用結果集類型的 SCROLLSENSITIVE 或 SCROLLINSENSITIVE(或句話說:不是 FORWARD_ONLY)。 
ResultHandler 參數允許你按你喜歡的方式處理每一行。你可以將它添加到 List 中,創 建 Map, 或拋出每個結果而不是隻保留總計。 Set 你可以使用 ResultHandler 做很多漂亮的事, 那就是 MyBatis 內部創建結果集列表。 
它的接口很簡單。

package org.apache.ibatis.session;
public interface ResultHandler<T> {
 void handleResult(ResultContext<? extends T> context);
}

ResultContext 參數給你訪問結果對象本身的方法, 大量結果對象被創建, 你可以使用布 爾返回值的 stop()方法來停止 MyBatis 加載更多的結果。 

批量立即更新方法(Flush Method)
有一個方法可以刷新(執行)存儲在JDBC驅動類中的批量更新語句。當你將ExecutorType.BATCH作爲ExecutorType使用時可以採用此方法。

List<BatchResult> flushStatements()

事務控制方法
控制事務作用域有四個方法。 當然, 如果你已經選擇了自動提交或你正在使用外部事務管 理器,這就沒有任何效果了。然而,如果你正在使用 JDBC 事務管理員,由 Connection 實 例來控制,那麼這四個方法就會派上用場:

void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)

默認情況下 MyBatis 不會自動提交事務, 除非它偵測到有插入, 更新或刪除操作改變了 數據庫。如果你已經做出了一些改變而沒有使用這些方法,那麼你可以傳遞 true 到 commit 和 rollback 方法來保證它會被提交(注意,你不能在自動提交模式下強制 session,或者使用 了外部事務管理器時) 。很多時候你不用調用 rollback(),因爲如果你沒有調用 commit 時 MyBatis 會替你完成。然而,如果你需要更多對多提交和回滾都可能的 session 的細粒度控 制,你可以使用回滾選擇來使它成爲可能。 

MyBatis-Spring和MyBatis-Guice提供了聲明事務處理,所以如果你在使用Mybatis的同時使用了Spring或者Guice,那麼請參考它們的手冊以獲取更多的內容。

清理 Session 級的緩存

void clearCache()

SqlSession 實例有一個本地緩存在執行 update,commit,rollback 和 close 時被清理。要 明確地關閉它(獲取打算做更多的工作) ,你可以調用 clearCache()。 
確保 SqlSession 被關閉

void close()

你必須保證的最重要的事情是你要關閉所打開的任何 session。保證做到這點的最佳方 式是下面的工作模式:

SqlSession session = sqlSessionFactory.openSession();
try {
   // following 3 lines pseudocod for "doing some work"
   session.insert(...);
   session.update(...);
   session.delete(...);
   session.commit();
} finally {
   session.close();
}

還有,如果你正在使用jdk 1.7以上的版本還有MyBatis 3.2以上的版本,你可以使用try-with-resources語句:

try (SqlSession session = sqlSessionFactory.openSession()) {
   // following 3 lines pseudocode for "doing some work"
   session.insert(...);
   session.update(...);
   session.delete(...);
   session.commit();
}

就像 SqlSessionFactory,你可以通過調用 getConfiguration()方法獲得 SqlSession 使用的 Configuration 實例

Configuration getConfiguration()

使用映射器

<T> T getMapper(Class<T> type)

上述的各個 insert,update,delete 和 select 方法都很強大,但也有些繁瑣,沒有類型安 全,對於你的 IDE 也沒有幫助,還有可能的單元測試。在上面的入門章節中我們已經看到 了一個使用映射器的示例。 

因此, 一個更通用的方式來執行映射語句是使用映射器類。 一個映射器類就是一個簡單 的接口,其中的方法定義匹配於 SqlSession 方法。下面的示例展示了一些方法簽名和它們是 如何映射到 SqlSession 的。

public interface AuthorMapper {
 // (Author) selectOne("selectAuthor",5);
 Author selectAuthor(int id);
 // (List<Author>) selectList(“selectAuthors”)
 List<Author> selectAuthors();
 // (Map<Integer,Author>) selectMap("selectAuthors", "id")
 @MapKey("id")
 Map<Integer, Author> selectAuthors();
 // insert("insertAuthor", author)
 int insertAuthor(Author author);
 // updateAuthor("updateAuthor", author)
 int updateAuthor(Author author);
 // delete("deleteAuthor",5)
 int deleteAuthor(int id);
}

總之, 每個映射器方法簽名應該匹配相關聯的 SqlSession 方法, 而沒有字符串參數 ID。 相反,方法名必須匹配映射語句的 ID。 

此外,返回類型必須匹配期望的結果類型。所有常用的類型都是支持的,包括:原生類 型,Map,POJO 和 JavaBean。 
映射器接口不需要去實現任何接口或擴展任何類。 只要方法前面可以被用來唯一標識對 應的映射語句就可以了。 

映射器接口可以擴展其他接口。當使用 XML 來構建映射器接口時要保證在合適的命名 空間中有語句。 而且, 唯一的限制就是你不能在兩個繼承關係的接口中有相同的方法簽名 (這 也是不好的想法)。 

你可以傳遞多個參數給一個映射器方法。 如果你這樣做了, 默認情況下它們將會以它們 在參數列表中的位置來命名,比如:#{param1},#{param2}等。如果你想改變參數的名稱(只在多參數 情況下) ,那麼你可以在參數上使用@Param(“paramName”)註解。 
你也可以給方法傳遞一個 RowBounds 實例來限制查詢結果。 

映射器註解
因爲最初設計時,MyBatis 是一個 XML 驅動的框架。配置信息是基於 XML 的,而且 映射語句也是定義在 XML 中的。而到了 MyBatis 3,有新的可用的選擇了。MyBatis 3 構建 在基於全面而且強大的 Java 配置 API 之上。這個配置 API 是基於 XML 的 MyBatis 配置的 基礎,也是新的基於註解配置的基礎。註解提供了一種簡單的方式來實現簡單映射語句,而 不會引入大量的開銷。 

不幸的是,Java 註解限制了它們的表現和靈活。儘管很多時間都花調查,設計和 實驗上,最強大的 MyBatis 映射不能用註解來構建,那並不可笑。C#屬性(做示例)就沒 有這些限制,因此 MyBatis.NET 將會比 XML 有更豐富的選擇。也就是說,基於 Java 註解 的配置離不開它的特性。

 
映射申明樣例
這個例子展示瞭如何使用 @SelectKey 註解來在插入前讀取數據庫序列的值:

@Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
@SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
int insertTable3(Name name);

這個例子展示瞭如何使用 @SelectKey 註解來在插入後讀取數據庫識別列的值:

@Insert("insert into table2 (name) values(#{name})")
@SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
int insertTable2(Name name);

這個例子展示瞭如何使用@Flush註解去調用SqlSession#flushStatements():

@Flush
List<BatchResult> flush();

這些例子展示瞭如何通過指定@Result的id屬性來命名結果集:

@Results(id = "userResult", value = {
 @Result(property = "id", column = "uid", id = true),
 @Result(property = "firstName", column = "first_name"),
 @Result(property = "lastName", column = "last_name")
})
@Select("select * from users where id = #{id}")
User getUserById(Integer id);

@Results(id = "companyResults")
@ConstructorArgs({
 @Arg(property = "id", column = "cid", id = true),
 @Arg(property = "name", column = "name")
})
@Select("select * from company where id = #{id}")
Company getCompanyById(Integer id);

這個例子展示了單一參數使用@SqlProvider:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);

class UserSqlBuilder {
 public String buildGetUsersByName(final String name) {
   return new SQL(){{
     SELECT("*");
     FROM("users");
     if (name != null) {
       WHERE("name like #{value} || '%'");
     }
     ORDER_BY("id");
   }}.toString();
 }
}

這個列子展示了多參數使用@SqlProvider:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(
   @Param("name")
String name, @Param("orderByColumn") String orderByColumn)
;

class UserSqlBuilder {

 // If not use @Param, you should be define same arguments with mapper method
 public String buildGetUsersByName(
     final String name, final String orderByColumn)
{
   return new SQL(){{
     SELECT("*");
     FROM("users");
     WHERE("name like #{name} || '%'");
     ORDER_BY(orderByColumn);
   }}.toString();
 }

 // If use @Param, you can define only arguments to be used
 public String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
   return new SQL(){{
     SELECT("*");
     FROM("users");
     WHERE("name like #{name} || '%'");
     ORDER_BY(orderByColumn);
   }}.toString();
 }
}



關注微信公衆號:IT哈哈。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章