溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

怎樣解析MyBatis中的SqlSessionFactory和SqlSession

發(fā)布時(shí)間:2021-12-18 13:07:30 來(lái)源:億速云 閱讀:296 作者:柒染 欄目:開(kāi)發(fā)技術(shù)

怎樣解析MyBatis中的SqlSessionFactory和SqlSession,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

【1】SqlSessionFactoryBuilder

這個(gè)類(lèi)可以被初始化、使用和丟棄,如果你已經(jīng)創(chuàng)建好了一個(gè)SqlSessionFactory 后就不用再保留它。

因此,SqlSessionFactoryBuilder 的最好作用域是方法體內(nèi)

比如說(shuō)定義一個(gè)方法變量。你可以重復(fù)使用SqlSessionFactoryBuilder 生成多個(gè)SqlSessionFactory 實(shí)例,但是最好不要強(qiáng)行保留,因?yàn)閄ML 的解析資源要用來(lái)做其它更重要的事。

mybatis3.4.1版本中,SqlSessionFactoryBuilder中獲取SqlSessionFactory實(shí)例如下(每一種都允許你從不同的資源中創(chuàng)建一個(gè) SqlSessionFactory 實(shí)例):

SqlSessionFactory build(Reader reader)
SqlSessionFactory build(Reader reader, String environment) 
SqlSessionFactory build(Reader reader, Properties properties) 
SqlSessionFactory build(Reader reader, String environment, Properties properties)
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)
SqlSessionFactory build(Configuration config)

SqlSessionFactoryBuilder完整源碼

package org.apache.ibatis.session;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
/**
 * Builds {@link SqlSession} instances.
 *
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {
  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }
  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }
  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }
//核心方法一
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
    //這里非常非常重要,會(huì)解析mybatis的相關(guān)配置,以及XXXMapper.xml中的結(jié)點(diǎn)、xxxxMapper.java中的注解
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }
  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }
//核心方法二
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
//核心方法三,根據(jù)解析到的Configuration 創(chuàng)建一個(gè)默認(rèn)的DefaultSqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}

上面有兩種創(chuàng)建DefaultSqlSessionFactory的核心方法,提供了諸多重載方法,最后調(diào)用核心方法三得到一個(gè)默認(rèn)的DefaultSqlSessionFactory實(shí)例。

【2】SqlSessionFactory

SqlSessionFactory一旦創(chuàng)建,SqlSessionFactory 就會(huì)在整個(gè)應(yīng)用過(guò)程中始終存在。所以沒(méi)有理由去銷(xiāo)毀和再創(chuàng)建它,一個(gè)應(yīng)用運(yùn)行中也不建議多次創(chuàng)建SqlSessionFactory。

因此SqlSessionFactory最好的作用域是Application。

可以有多種方法實(shí)現(xiàn),最簡(jiǎn)單的方法是單例模式或者是靜態(tài)單例模式。然而這也不是廣泛贊成和好用的。反而,使用Google Guice 或Spring 來(lái)進(jìn)行依賴(lài)反射會(huì)更好。這些框架允許你生成管理器來(lái)管理SqlSessionFactory 的單例生命周期。

SqlSessionFactory 接口源碼

package org.apache.ibatis.session;
import java.sql.Connection;
/**
根據(jù)一個(gè)數(shù)據(jù)庫(kù)連接或者數(shù)據(jù)源創(chuàng)建一個(gè)sqlsession實(shí)例,create..out of 根據(jù)/由什么創(chuàng)建
 * Creates an {@link SqlSession} out of a connection or a DataSource
 * @author Clinton Begin
 */
public interface SqlSessionFactory {
  SqlSession openSession();
  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);
  Configuration getConfiguration();
}

其默認(rèn)實(shí)現(xiàn)類(lèi)是DefaultSqlSessionFactory。

SqlSessionFactory 有六個(gè)方法創(chuàng)建 SqlSession 實(shí)例

SqlSessionFactory 有六個(gè)方法創(chuàng)建 SqlSession 實(shí)例。通常來(lái)說(shuō),當(dāng)你選擇其中一個(gè)方法時(shí),你需要考慮以下幾點(diǎn):

  • 事務(wù)處理:你希望在 session 作用域中使用事務(wù)作用域,還是使用自動(dòng)提交(auto-commit)?(對(duì)很多數(shù)據(jù)庫(kù)和/或 JDBC 驅(qū)動(dòng)來(lái)說(shuō),等同于關(guān)閉事務(wù)支持)

  • 數(shù)據(jù)庫(kù)連接:你希望 MyBatis 幫你從已配置的數(shù)據(jù)源獲取連接,還是使用自己提供的連接?

  • 語(yǔ)句執(zhí)行:你希望 MyBatis 復(fù)用 PreparedStatement 和/或批量更新語(yǔ)句(包括插入語(yǔ)句和刪除語(yǔ)句)嗎?

基于以上需求,有下列已重載的多個(gè) openSession() 方法供使用。

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

默認(rèn)的 openSession() 方法沒(méi)有參數(shù),它會(huì)創(chuàng)建具備如下特性的 SqlSession:

  • 事務(wù)作用域?qū)?huì)開(kāi)啟(也就是不自動(dòng)提交)。

  • 將由當(dāng)前環(huán)境配置的 DataSource 實(shí)例中獲取 Connection 對(duì)象。

  • 事務(wù)隔離級(jí)別將會(huì)使用驅(qū)動(dòng)或數(shù)據(jù)源的默認(rèn)設(shè)置。

  • 預(yù)處理語(yǔ)句不會(huì)被復(fù)用,也不會(huì)批量處理更新。

相信你已經(jīng)能從方法簽名中知道這些方法的區(qū)別。向 autoCommit 可選參數(shù)傳遞 true 值即可開(kāi)啟自動(dòng)提交功能。若要使用自己的 Connection 實(shí)例,傳遞一個(gè) Connection 實(shí)例給 connection 參數(shù)即可。注意,我們沒(méi)有提供同時(shí)設(shè)置 Connection 和 autoCommit 的方法,這是因?yàn)?MyBatis 會(huì)依據(jù)傳入的 Connection 來(lái)決定是否啟用 autoCommit。對(duì)于事務(wù)隔離級(jí)別,MyBatis 使用了一個(gè) Java 枚舉包裝器來(lái)表示,稱(chēng)為 TransactionIsolationLevel,事務(wù)隔離級(jí)別支持 JDBC 的五個(gè)隔離級(jí)別(NONE、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE),并且與預(yù)期的行為一致。

你可能對(duì) ExecutorType 參數(shù)感到陌生。這個(gè)枚舉類(lèi)型定義了三個(gè)值:

  • ExecutorType.SIMPLE:該類(lèi)型的執(zhí)行器沒(méi)有特別的行為。它為每個(gè)語(yǔ)句的執(zhí)行創(chuàng)建一個(gè)新的預(yù)處理語(yǔ)句。

  • ExecutorType.REUSE:該類(lèi)型的執(zhí)行器會(huì)復(fù)用預(yù)處理語(yǔ)句。

  • ExecutorType.BATCH:該類(lèi)型的執(zhí)行器會(huì)批量執(zhí)行所有更新語(yǔ)句,如果 SELECT 在多個(gè)更新中間執(zhí)行,將在必要時(shí)將多條更新語(yǔ)句分隔開(kāi)來(lái),以方便理解。

批量操作是在session.commit()以后才發(fā)送sql語(yǔ)句給數(shù)據(jù)庫(kù)進(jìn)行執(zhí)行的如果我們想讓其提前執(zhí)行,以方便后續(xù)可能的查詢(xún)操作獲取數(shù)據(jù),我們可以使用sqlSession.flushStatements()方法,讓其直接沖刷到數(shù)據(jù)庫(kù)進(jìn)行執(zhí)行。

提示 在 SqlSessionFactory 中還有一個(gè)方法我們沒(méi)有提及,就是 getConfiguration()。這個(gè)方法會(huì)返回一個(gè) Configuration 實(shí)例,你可以在運(yùn)行時(shí)使用它來(lái)檢查 MyBatis 的配置。

提示 如果你使用過(guò) MyBatis 的舊版本,可能還記得 session、事務(wù)和批量操作是相互獨(dú)立的。在新版本中則不是這樣。上述三者都包含在 session 作用域內(nèi)。你不必分別處理事務(wù)或批量操作就能得到想要的全部效果。

默認(rèn)的openSession() 方法沒(méi)有參數(shù),它會(huì)創(chuàng)建有如下特性的

  • 會(huì)開(kāi)啟一個(gè)事務(wù)(也就是不自動(dòng)提交)

  • 連接對(duì)象會(huì)從由活動(dòng)環(huán)境配置的數(shù)據(jù)源實(shí)例得到。

  • 事務(wù)隔離級(jí)別將會(huì)使用驅(qū)動(dòng)或數(shù)據(jù)源的默認(rèn)設(shè)置。

  • 預(yù)處理語(yǔ)句不會(huì)被復(fù)用,也不會(huì)批量處理更新。

【3】非線程安全的SqlSession

使用 MyBatis 的主要 Java 接口就是 SqlSession。你可以通過(guò)這個(gè)接口來(lái)執(zhí)行命令,獲取映射器實(shí)例和管理事務(wù)。

每個(gè)線程都有自己的SqlSession 實(shí)例,SqlSession 實(shí)例是不能被共享,也不是線程安全的。因此最好使用request 作用域或者方法體作用域。

不要使用類(lèi)的靜態(tài)變量來(lái)引用一個(gè)SqlSession 實(shí)例,甚至不要使用類(lèi)的一個(gè)實(shí)例變量來(lái)引用。否則,會(huì)有線程安全問(wèn)題。

永遠(yuǎn)不要在一個(gè)被管理域中引用SqlSession

比如說(shuō)在Servlet 中的HttpSession 中。如果你正在使用WEB 框架,應(yīng)該讓SqlSession 跟隨HTTP 請(qǐng)求的相似作用域。也就是說(shuō),在收到一個(gè)HTTP 請(qǐng)求過(guò)后,打開(kāi)SqlSession,等返回一個(gè)回應(yīng)以后,立馬關(guān)掉這個(gè)SqlSession。關(guān)閉SqlSession 是非常重要的,你必須要確保SqlSession 在finally 方法體中正常關(guān)閉。

SqlSession可以直接調(diào)用方法的id進(jìn)行數(shù)據(jù)庫(kù)操作,但是我們一般還是推薦使用SqlSession獲取到Dao接口的代理類(lèi),執(zhí)行代理對(duì)象的方法,可以更安全的進(jìn)行類(lèi)型檢查操作。

如下代碼所示,是調(diào)用方法ID進(jìn)行數(shù)據(jù)庫(kù)操作:

SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try {
	Employee employee = openSession.selectOne(
			"com.mybatis.EmployeeMapper.selectEmp", 1);
	System.out.println(employee);
} finally {
	openSession.close();
}

正如之前所提到的,SqlSession 在 MyBatis 中是非常強(qiáng)大的一個(gè)類(lèi)。它包含了所有執(zhí)行語(yǔ)句、提交或回滾事務(wù)以及獲取映射器實(shí)例的方法。

SqlSession 類(lèi)的方法超過(guò)了 20 個(gè),為了方便理解,我們將它們分成幾種組別。

語(yǔ)句執(zhí)行方法

這些方法被用來(lái)執(zhí)行定義在 SQL 映射 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 語(yǔ)句。你可以通過(guò)名字快速了解它們的作用,每一方法都接受語(yǔ)句的 ID 以及參數(shù)對(duì)象,參數(shù)可以是原始類(lèi)型(支持自動(dòng)裝箱或包裝類(lèi))、JavaBean、POJO 或 Map。

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(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 必須返回一個(gè)對(duì)象或 null 值。如果返回值多于一個(gè),就會(huì)拋出異常。如果你不知道返回對(duì)象會(huì)有多少,請(qǐng)使用 selectList。如果需要查看某個(gè)對(duì)象是否存在,最好的辦法是查詢(xún)一個(gè) count 值(0 或 1)。selectMap 稍微特殊一點(diǎn),它會(huì)將返回對(duì)象的其中一個(gè)屬性作為 key 值,將對(duì)象作為 value 值,從而將多個(gè)結(jié)果集轉(zhuǎn)為 Map 類(lèi)型值。由于并不是所有語(yǔ)句都需要參數(shù),所以這些方法都具有一個(gè)不需要參數(shù)的重載形式。

游標(biāo)(Cursor)與列表(List)返回的結(jié)果相同,不同的是,游標(biāo)借助迭代器實(shí)現(xiàn)了數(shù)據(jù)的惰性加載。

try (Cursor<MyEntity> entities = session.selectCursor(statement, param)) {
   for (MyEntity entity:entities) {
      // 處理單個(gè)實(shí)體
   }
}
insert、update 以及 delete 方法返回的值表示受該語(yǔ)句影響的行數(shù)。
<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<T> Cursor<T> selectCursor(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)

最后,還有 select 方法的三個(gè)高級(jí)版本,它們?cè)试S你限制返回行數(shù)的范圍,或是提供自定義結(jié)果處理邏輯,通常在數(shù)據(jù)集非常龐大的情形下使用。

<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(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 參數(shù)會(huì)告訴 MyBatis 略過(guò)指定數(shù)量的記錄,并限制返回結(jié)果的數(shù)量。RowBounds 類(lèi)的 offset 和 limit 值只有在構(gòu)造函數(shù)時(shí)才能傳入,其它時(shí)候是不能修改的。

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

數(shù)據(jù)庫(kù)驅(qū)動(dòng)決定了略過(guò)記錄時(shí)的查詢(xún)效率。為了獲得最佳的性能,建議將 ResultSet 類(lèi)型設(shè)置為 SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE(換句話(huà)說(shuō):不要使用 FORWARD_ONLY)。

ResultHandler 參數(shù)允許自定義每行結(jié)果的處理過(guò)程。你可以將它添加到 List 中、創(chuàng)建 Map 和 Set,甚至丟棄每個(gè)返回值,只保留計(jì)算后的統(tǒng)計(jì)結(jié)果。你可以使用 ResultHandler 做很多事,這其實(shí)就是 MyBatis 構(gòu)建 結(jié)果列表的內(nèi)部實(shí)現(xiàn)辦法。

從版本 3.4.6 開(kāi)始,ResultHandler 會(huì)在存儲(chǔ)過(guò)程的 REFCURSOR 輸出參數(shù)中傳遞使用的 CALLABLE 語(yǔ)句。

它的接口很簡(jiǎn)單:

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

ResultContext 參數(shù)允許你訪問(wèn)結(jié)果對(duì)象和當(dāng)前已被創(chuàng)建的對(duì)象數(shù)目,另外還提供了一個(gè)返回值為 Boolean 的 stop 方法,你可以使用此 stop 方法來(lái)停止 MyBatis 加載更多的結(jié)果。

使用 ResultHandler 的時(shí)候需要注意以下兩個(gè)限制:

  • 使用帶 ResultHandler 參數(shù)的方法時(shí),收到的數(shù)據(jù)不會(huì)被緩存。

  • 當(dāng)使用高級(jí)的結(jié)果映射集(resultMap)時(shí),MyBatis 很可能需要數(shù)行結(jié)果來(lái)構(gòu)造一個(gè)對(duì)象。如果你使用了 ResultHandler,你可能會(huì)接收到關(guān)聯(lián)(association)或者集合(collection)中尚未被完整填充的對(duì)象。

立即批量更新方法

當(dāng)你將 ExecutorType 設(shè)置為 ExecutorType.BATCH 時(shí),可以使用這個(gè)方法清除(執(zhí)行)緩存在 JDBC 驅(qū)動(dòng)類(lèi)中的批量更新語(yǔ)句。

List<BatchResult> flushStatements()

事務(wù)控制方法

有四個(gè)方法用來(lái)控制事務(wù)作用域。當(dāng)然,如果你已經(jīng)設(shè)置了自動(dòng)提交或你使用了外部事務(wù)管理器,這些方法就沒(méi)什么作用了。然而,如果你正在使用由 Connection 實(shí)例控制的 JDBC 事務(wù)管理器,那么這四個(gè)方法就會(huì)派上用場(chǎng):

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

默認(rèn)情況下 MyBatis 不會(huì)自動(dòng)提交事務(wù),除非它偵測(cè)到調(diào)用了插入、更新或刪除方法改變了數(shù)據(jù)庫(kù)。如果你沒(méi)有使用這些方法提交修改,那么你可以在 commit 和 rollback 方法參數(shù)中傳入 true 值,來(lái)保證事務(wù)被正常提交(注意,在自動(dòng)提交模式或者使用了外部事務(wù)管理器的情況下,設(shè)置 force 值對(duì) session 無(wú)效)。大部分情況下你無(wú)需調(diào)用 rollback(),因?yàn)?MyBatis 會(huì)在你沒(méi)有調(diào)用 commit 時(shí)替你完成回滾操作。不過(guò),當(dāng)你要在一個(gè)可能多次提交或回滾的 session 中詳細(xì)控制事務(wù),回滾操作就派上用場(chǎng)了。

提示 MyBatis-Spring 和 MyBatis-Guice 提供了聲明式事務(wù)處理,所以如果你在使用 Mybatis 的同時(shí)使用了 Spring 或者 Guice,請(qǐng)參考它們的手冊(cè)以獲取更多的內(nèi)容。

本地緩存

Mybatis 使用到了兩種緩存:本地緩存(local cache)和二級(jí)緩存(second level cache)。

每當(dāng)一個(gè)新 session 被創(chuàng)建,MyBatis 就會(huì)創(chuàng)建一個(gè)與之相關(guān)聯(lián)的本地緩存。任何在 session 執(zhí)行過(guò)的查詢(xún)結(jié)果都會(huì)被保存在本地緩存中,所以,當(dāng)再次執(zhí)行參數(shù)相同的相同查詢(xún)時(shí),就不需要實(shí)際查詢(xún)數(shù)據(jù)庫(kù)了。本地緩存將會(huì)在做出修改、事務(wù)提交或回滾,以及關(guān)閉 session 時(shí)清空。

默認(rèn)情況下,本地緩存數(shù)據(jù)的生命周期等同于整個(gè) session 的周期。由于緩存會(huì)被用來(lái)解決循環(huán)引用問(wèn)題和加快重復(fù)嵌套查詢(xún)的速度,所以無(wú)法將其完全禁用。但是你可以通過(guò)設(shè)置 localCacheScope=STATEMENT 來(lái)只在語(yǔ)句執(zhí)行時(shí)使用緩存。

注意,如果 localCacheScope 被設(shè)置為 SESSION,對(duì)于某個(gè)對(duì)象,MyBatis 將返回在本地緩存中唯一對(duì)象的引用。對(duì)返回的對(duì)象(例如 list)做出的任何修改將會(huì)影響本地緩存的內(nèi)容,進(jìn)而將會(huì)影響到在本次 session 中從緩存返回的值。因此,不要對(duì) MyBatis 所返回的對(duì)象作出更改,以防后患。

你可以隨時(shí)調(diào)用以下方法來(lái)清空本地緩存:

void clearCache()

確保 SqlSession 被關(guān)閉

void close()

對(duì)于你打開(kāi)的任何 session,你都要保證它們被妥善關(guān)閉,這很重要。保證妥善關(guān)閉的最佳代碼模式是這樣的:

SqlSession session = sqlSessionFactory.openSession();
try (SqlSession session = sqlSessionFactory.openSession()) {
    // 假設(shè)下面三行代碼是你的業(yè)務(wù)邏輯
    session.insert(...);
    session.update(...);
    session.delete(...);
    session.commit();
}

提示 和 SqlSessionFactory 一樣,你可以調(diào)用當(dāng)前使用的 SqlSession 的 getConfiguration 方法來(lái)獲得 Configuration 實(shí)例。

Configuration getConfiguration()

使用映射器

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

上述的各個(gè) insert、update、delete 和 select 方法都很強(qiáng)大,但也有些繁瑣,它們并不符合類(lèi)型安全,對(duì)你的 IDE 和單元測(cè)試也不是那么友好。因此,使用映射器類(lèi)來(lái)執(zhí)行映射語(yǔ)句是更常見(jiàn)的做法。

我們已經(jīng)在之前的入門(mén)章節(jié)中見(jiàn)到過(guò)一個(gè)使用映射器的示例。一個(gè)映射器類(lèi)就是一個(gè)僅需聲明與 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);
}

總之,每個(gè)映射器方法簽名應(yīng)該匹配相關(guān)聯(lián)的 SqlSession 方法,字符串參數(shù) ID 無(wú)需匹配。而是由方法名匹配映射語(yǔ)句的 ID。

此外,返回類(lèi)型必須匹配期望的結(jié)果類(lèi)型,返回單個(gè)值時(shí),返回類(lèi)型應(yīng)該是返回值的類(lèi),返回多個(gè)值時(shí),則為數(shù)組或集合類(lèi),另外也可以是游標(biāo)(Cursor)。所有常用的類(lèi)型都是支持的,包括:原始類(lèi)型、Map、POJO 和 JavaBean。

提示 映射器接口不需要去實(shí)現(xiàn)任何接口或繼承自任何類(lèi)。只要方法簽名可以被用來(lái)唯一識(shí)別對(duì)應(yīng)的映射語(yǔ)句就可以了。

提示 映射器接口可以繼承自其他接口。在使用 XML 來(lái)綁定映射器接口時(shí),保證語(yǔ)句處于合適的命名空間中即可。唯一的限制是,不能在兩個(gè)具有繼承關(guān)系的接口中擁有相同的方法簽名(這是潛在的危險(xiǎn)做法,不可取)。

你可以傳遞多個(gè)參數(shù)給一個(gè)映射器方法。在多個(gè)參數(shù)的情況下,默認(rèn)它們將會(huì)以 param 加上它們?cè)趨?shù)列表中的位置來(lái)命名,比如:#{param1}、#{param2}等。如果你想(在有多個(gè)參數(shù)時(shí))自定義參數(shù)的名稱(chēng),那么你可以在參數(shù)上使用 @Param("paramName") 注解。

你也可以給方法傳遞一個(gè) RowBounds 實(shí)例來(lái)限制查詢(xún)結(jié)果。

映射器注解

設(shè)計(jì)初期的 MyBatis 是一個(gè) XML 驅(qū)動(dòng)的框架。配置信息是基于 XML 的,映射語(yǔ)句也是定義在 XML 中的。而在 MyBatis 3 中,我們提供了其它的配置方式。MyBatis 3 構(gòu)建在全面且強(qiáng)大的基于 Java 語(yǔ)言的配置 API 之上。它是 XML 和注解配置的基礎(chǔ)。注解提供了一種簡(jiǎn)單且低成本的方式來(lái)實(shí)現(xiàn)簡(jiǎn)單的映射語(yǔ)句。

提示 不幸的是,Java 注解的表達(dá)能力和靈活性十分有限。盡管我們花了很多時(shí)間在調(diào)查、設(shè)計(jì)和試驗(yàn)上,但最強(qiáng)大的 MyBatis 映射并不能用注解來(lái)構(gòu)建——我們真沒(méi)開(kāi)玩笑。而 C# 屬性就沒(méi)有這些限制,因此 MyBatis.NET 的配置會(huì)比 XML 有更大的選擇余地。雖說(shuō)如此,基于 Java 注解的配置還是有它的好處的。

映射注解示例

這個(gè)例子展示了如何使用 @SelectKey 注解來(lái)在插入前讀取數(shù)據(jù)庫(kù)序列的值:

@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);

這個(gè)例子展示了如何使用 @SelectKey 注解來(lái)在插入后讀取數(shù)據(jù)庫(kù)自增列的值:

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

這個(gè)例子展示了如何使用 @Flush 注解來(lái)調(diào)用 SqlSession#flushStatements():

@Flush
List<BatchResult> flush();

這些例子展示了如何通過(guò)指定 @Result 的 id 屬性來(lái)命名結(jié)果集:

@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(column = "cid", javaType = Integer.class, id = true),
  @Arg(column = "name", javaType = String.class)
})
@Select("select * from company where id = #{id}")
Company getCompanyById(Integer id);

這個(gè)例子展示了如何使用單個(gè)參數(shù)的 @SqlProvider 注解:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);
class UserSqlBuilder {
  public static String buildGetUsersByName(final String name) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      if (name != null) {
        WHERE("name like #{value} || '%'");
      }
      ORDER_BY("id");
    }}.toString();
  }
}

這個(gè)例子展示了如何使用多個(gè)參數(shù)的 @SqlProvider 注解:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(
    @Param("name") String name, @Param("orderByColumn") String orderByColumn);
class UserSqlBuilder {
  // 如果不使用 @Param,就應(yīng)該定義與 mapper 方法相同的參數(shù)
  public static String buildGetUsersByName(
      final String name, final String orderByColumn) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      WHERE("name like #{name} || '%'");
      ORDER_BY(orderByColumn);
    }}.toString();
  }
  // 如果使用 @Param,就可以只定義需要使用的參數(shù)
  public static String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      WHERE("name like #{name} || '%'");
      ORDER_BY(orderByColumn);
    }}.toString();
  }
}

以下例子展示了通過(guò)使用全局配置Configuration 來(lái)實(shí)現(xiàn)所有mapper方法共享一個(gè) sql provider 類(lèi)(3.5.6后可用):

Configuration configuration = new Configuration();
configuration.setDefaultSqlProviderType(TemplateFilePathProvider.class);
 // Specify an sql provider class for sharing on all mapper methods
// ...
// Can omit the type/value attribute on sql provider annotation
// If omit it, the MyBatis apply the class that specified on defaultSqlProviderType.
public interface UserMapper {
  @SelectProvider // Same with @SelectProvider(TemplateFilePathProvider.class)
  User findUser(int id);
  @InsertProvider // Same with @InsertProvider(TemplateFilePathProvider.class)
  void createUser(User user);
  @UpdateProvider // Same with @UpdateProvider(TemplateFilePathProvider.class)
  void updateUser(User user);
  @DeleteProvider // Same with @DeleteProvider(TemplateFilePathProvider.class)
  void deleteUser(int id);
}

以下例子展示了 ProviderMethodResolver(3.5.1 后可用)的默認(rèn)實(shí)現(xiàn)使用方法:

@SelectProvider(UserSqlProvider.class)
List<User> getUsersByName(String name);
// 在你的 provider 類(lèi)中實(shí)現(xiàn) ProviderMethodResolver 接口
class UserSqlProvider implements ProviderMethodResolver {
  // 默認(rèn)實(shí)現(xiàn)中,會(huì)將映射器方法的調(diào)用解析到實(shí)現(xiàn)的同名方法上
  public static String getUsersByName(final String name) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      if (name != null) {
        WHERE("name like #{value} || '%'");
      }
      ORDER_BY("id");
    }}.toString();
  }
}

以下例子展示了如何在注解上使用databaseId (3.5.5后可用):

@Select(value = "SELECT SYS_GUID() FROM dual", databaseId = "oracle") // Use this statement if DatabaseIdProvider provide "oracle"
@Select(value = "SELECT uuid_generate_v4()", databaseId = "postgres") // Use this statement if DatabaseIdProvider provide "postgres"
@Select("SELECT RANDOM_UUID()") // Use this statement if the DatabaseIdProvider not configured or not matches databaseId
String generateId();

看完上述內(nèi)容,你們掌握怎樣解析MyBatis中的SqlSessionFactory和SqlSession的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI