複習寶典之MyBatis

查看更多寶典,請點擊《金三銀四,你的專屬面試寶典》html

第五章:MyBatis

MyBatis是一個能夠自定義SQL、存儲過程和高級映射的持久層框架。java

 

1)建立sqlsession的流程

mybatis進行持久化操做時重要的幾個類: SqlSessionFactoryBuilder:build方法建立SqlSessionFactory實例。 SqlSessionFactory:建立SqlSession實例的工廠。 SqlSession:用於執行持久化操做的對象,相似於jdbc中的Connection。 SqlSessionTemplate:MyBatis提供的持久層訪問模板化的工具,線程安全,可經過構造參數或依賴注入SqlSessionFactory實例。web

 

2)配置文件相關

2.1 在mybatis配置文件中支持兩種事務類型管理器,分別是:面試

  ①、type = "JDBC":這個配置就是直接使用了 JDBC 的提交和回滾設置,它依賴於從數據源獲得的鏈接來管理事務做用域。redis

  ②、type="MANAGED":這個配置幾乎沒作什麼。它歷來不提交或回滾一個鏈接,而是讓容器來管理事務的整個生命週期(好比 JEE 應用服務器的上下文)。 默認狀況下它會關閉鏈接,然而一些容器並不但願這樣,所以須要將 closeConnection 屬性設置爲 false 來阻止它默認的關閉行爲。算法

注意:和數據源配置同樣,一般項目中咱們不會單獨使用 mybatis 來管理事務。好比選擇框架 Spring +mybatis,這時候沒有必要配置事務管理器, 由於 Spring 模塊會使用自帶的管理器來覆蓋前面的配置。spring

 

2.2 mybatis配置文件中datasource的type類型:sql

POOLED:這是JDBC鏈接對象的數據源鏈接池的實現,用來避免建立新的鏈接實例時必要的初始鏈接和認證時間。這是一種當前web應用程序用來快速響應請求很流行的方法。數據庫

JNDI:這個數據源的實現是爲了使用如Spring或這類的容器,容器能夠集中或在外部配置數據源,而後放置一個JNDI上下文的引用。編程

UNPOOLED:這個數據源的實現是每次被請求時簡單打開和關閉鏈接。它有一點慢,這是對簡單應用程序的一個很好的選擇,由於他不須要及時的可用鏈接。不一樣的數據庫對這個的表現也是不同的,因此對某些數據庫來上配置數據源並不重要,這個配置也是閒置的。

 

3)SqlSessionFactory

SqlSessionFactoryBuilder:這個類能夠被實例化,使用和丟棄。一旦你建立了SqlSessionFactory後,這個類就不須要存在了。所以SqlSessionFactoryBuilder實例的最佳範圍是方法範圍(也就是本地方法變量)。你能夠重用SqlSessionFactoryBuilder來建立多個SqlSessionFactory實例,可是最好的方式是不須要保持它一直存在來保證全部XML解析資源,由於還有更重要的事情要作。

SqlSessionFactory線程安全,一旦被建立,SqlSessionFactory應該在你的應用執行期間都存在。沒有理由來處理或從新建立它。使用SqlSessionFactory的最佳實踐是在應用運行期間不要重複建立屢次。這樣的操做將被視爲是很是糟糕的。所以SqlSessionFactory的最佳範圍是應用範圍。有不少方法能夠作到,最簡單的就是使用單例模式或者靜態單例模式。然而這兩種方法都不認爲是最佳實踐。這樣的話,你能夠考慮依賴注入容器,好比Google Guice或spring。這樣的框架容許你建立支持程序來管理單例SqlSessionFactory的生命週期。

SqlSession:每一個線程都應該有它本身的SqlSession實例。SqlSession的實例不能被共享,也是線程不安全的。所以最佳的範圍是請求或方法範圍。絕對不能將SqlSession實例的引用放在一個類的靜態字段甚至是實例字段中。也毫不能將SqlSession實例的引用放在任何類型的管理範圍中,好比Serlvet架構中的HttpSession。若是你如今正用任意的Web框架,要考慮SqlSession放在一個和HTTP請求對象類似的範圍內。換句話說,基於收到的HTTP請求,你能夠打開了一個SqlSession,而後返回響應,就能夠關閉它了。關閉Session很重要,你應該確保使用finally塊來關閉它。

 

4)獲取添加時的主鍵值

<!-- 添加用戶 -->
<insert id="addUser" parameterType="com.pojo.User">
  <!-- 獲取最新的ID主鍵 -->
  <selectKey keyProperty="id" resultType="Integer" order="AFTER">
        select LAST_INSERT_ID()
  </selectKey>
  insert into user (username,birthday,address,sex)
  values (#{username},#{birthday},#{address},#{sex})
</insert>

 

5)statementType取值

在mapper文件中可使用statementType標記使用什麼對象操做SQL語句。

statementType:標記操做SQL的對象

取值說明:

一、STATEMENT:直接操做sql,不進行預編譯,獲取數據:$--Statement

二、PREPARED:預處理,參數,進行預編譯,獲取數據:#--PreparedStatement:默認

三、CALLABLE:執行存儲過程—CallableStatement

其中若是在文件中,取值不一樣,那麼獲取參數的方式也不一樣

<update id="update4" statementType="STATEMENT">

update tb_car set price={id}

</update>

<update id="update5" statementType="PREPARED">

update tb_car set xh=#{xh} where id=#{id}

</update>

注意:若是隻爲STATEMENT,那麼sql就是直接的進行字符串拼接,這樣若是爲字符串須要加上引號,若是爲PREARED,是使用的參數替換,也就是索引佔位符,咱們的#會轉換爲 ?再設置對應參數的值。

 

6)mybatis相關概念

'${}':表示字符串拼接;

'#{}':表示佔位符,能夠防止sql注入;

映射到另外一張表的字段,若是是一對一,則使用association;

映射到另外一張表的字段,若是是一對多,則使用collection;

 

mybatis中cache回收算法:

FIFO(First In First Out):先進先出算法,即先放入緩存的先被移除。

LRU(Least Recently Used):最近最少使用算法,使用時間距離如今最久的那個被移除。

LFU(Least Frequently Used):最不經常使用算法,必定時間段內使用【次數(頻率)】最少的那個被移除。

實際應用中基於LRU的緩存居多,如Guava Cache、Ehcache支持LRU。

 

mybatis中resulttype提供哪些值:

返回通常數據類型

<!-- 
  指定 resultType 返回值類型時 String 類型的,string 在這裏是一個別名,表明的是 java.lang.String
  對於引用數據類型,都是將大寫字母轉小寫,好比 HashMap 對應的別名是 'hashmap'
  基本數據類型考慮到重複的問題,會在其前面加上 '_',好比 byte 對應的別名是 '_byte'
-->
<select id="getEmpNameById" resultType="string">
  select username from t_employee where id = #{id}
</select>

返回 JavaBean 類型

<!-- 
  經過 resultType 指定查詢的結果是 Employee 類型的數據  
  只須要指定 resultType 的類型,MyBatis 會自動將查詢的結果映射成 JavaBean 中的屬性
-->
<select id="getEmpById" resultType="employee">
  select * from t_employee where id = #{id}
</select>

返回List類型

<!--
  注意這裏的 resultType 返回值類型是集合內存儲數據的類型,不是 'list'
-->
<select id="getAllEmps" resultType="employee">
  select * from t_employee
</select>

返回Map類型

<!-- 
  注意這裏的 resultType 返回值類型是 'map'
-->
<select id="getEmpAsMapById" resultType="map">
  select * from t_employee where id = #{id}
</select>

上面返回結果的形式都是基於查詢 (select) 的,其實對於增刪改的操做也能夠返回必定類型的數據,好比BooleanInteger等。

 

7)Java中的四種引用類型

Java中的四種引用類型:

強引用(Strong References):強引用類型是咱們平時寫代碼的時候最經常使用的引用,而大部分人每每都會忽略這個概念

public static void main(String[] args) {

  //建立一個對象,new出來的對象都是分配在java堆中的
  Sample sample = new Sample();   //sample這個引用就是強引用

  sample = null;                 //將這個引用指向空指針,
                                  //那麼上面那個剛new來的對象就沒用任何其它有效的引用指向它了
                                  //也就說該對象對於垃圾收集器是符合條件的
                                  //所以在接下來某個時間點 GC進行收集動做的時候, 該對象將會被銷燬,內存被釋放

}

軟引用(Soft References):軟引用在java.lang.ref包中有與之對應的類java.lang.ref.SoftReference。 重點: 被弱引用指向的對象不會被垃圾收集器收集(即便該對象沒有強引用指向它),除非jvm使用內存不夠了,纔會對這類對象進行銷燬,釋放內存。

public static void main(String[] args) {

  //建立一個對象,new出來的對象都是分配在java堆中的
  Sample sample = new Sample();   //sample這個引用就是強引用

  //建立一個軟引用指向這個對象   那麼此時就有兩個引用指向Sample對象
  SoftReference<Sample> softRef = new SoftReference<Sample>(sample);

  //將強引用指向空指針 那麼此時只有一個軟引用指向Sample對象
  //注意:softRef這個引用也是強引用,它是指向SoftReference這個對象的
  //那麼這個軟引用在哪呢? 能夠跟一下java.lang.Reference的源碼
  //private T referent; 這個纔是軟引用, 只被jvm使用
  sample = null;

  //能夠從新得到Sample對象,並用一個強引用指向它
  sample = softRef.get();
}

弱引用(Weak References):弱引用會被jvm忽略,也就說在GC進行垃圾收集的時候,若是一個對象只有弱引用指向它,那麼和沒有引用指向它是同樣的效果,jvm都會對它就行果斷的銷燬,釋放內存。其實這個特性是頗有用的,jdk也提供了java.util.WeakHashMap這麼一個key爲弱引用的Map。好比某個資源對象你要釋放(好比 db connection), 可是若是被其它map做爲key強引用了,就沒法釋放,被jvm收集。

弱引用對象並不須要在jvm耗盡內存的狀況下才進行回收, 是能夠隨時回收的。

public class Main {
private static final List<Object> TEST_DATA = new LinkedList<>();

private static final ReferenceQueue<Sample> QUEUE = new ReferenceQueue<>();

public static void main(String[] args) {

  //建立一個對象,new出來的對象都是分配在java堆中的
  Sample sample = new Sample();   //sample這個引用就是強引用

  //建立一個弱引用指向這個對象   那麼此時就有兩個引用指向Sample對象
  //SoftReference<Sample> softRef = new SoftReference<Sample>(sample, QUEUE);
  WeakReference<Sample> weakRef = new WeakReference<Sample>(sample, QUEUE);
  //將強引用指向空指針 那麼此時只有一個弱引用指向Sample對象
  //注意:softRef這個引用也是強引用,它是指向SoftReference這個對象的
  //那麼這個弱引用在哪呢? 能夠跟一下java.lang.Reference的源碼
  //private T referent; 這個纔是弱引用, 只被jvm使用
  sample = null;

  //能夠從新得到Sample對象,並用一個強引用指向它
  //sample = softRef.get();

  new Thread(){
      @Override
      public void run() {
              while (true) {
              System.out.println(weakRef.get());
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
                  Thread.currentThread().interrupt();
              }
              TEST_DATA.add(new byte[1024 * 1024 * 5]);
          }
      }
  }.start();

  new Thread(){
      @Override
      public void run() {
          while (true) {
              Reference<? extends Sample> poll = QUEUE.poll();
              if (poll != null) {
                  System.out.println("--- 弱引用對象被jvm回收了 ---- " + poll);
                  System.out.println("--- 回收對象 ---- " + poll.get());
              }
          }
      }
  }.start();

  try {
      Thread.currentThread().join();
  } catch (InterruptedException e) {
      System.exit(1);
  }
}
}
class Sample {
  private final byte[] data;
public Sample() {
  data = new byte[1024 * 1024 * 10];
}
}

虛幻引用(Phantom References):虛幻引用和弱引用的回收機制差很少,都是能夠被隨時回收的。可是不一樣的地方是,它的構造方法必須強制傳入ReferenceQueue,由於在jvm回收前(重點: 對,就是回收前,軟引用和弱引用都是回收後),會將PhantomReference對象加入ReferenceQueue中; 還有一點就是PhantomReference.get()方法永遠返回空,無論對象有沒有被回收。

public class Main {
  private static final List<Object> TEST_DATA = new LinkedList<>();

  private static final ReferenceQueue<Sample> QUEUE = new ReferenceQueue<>();
   
  public static void main(String[] args) {

      Sample sample = new Sample();

      PhantomReference<Sample> phantomRef = new PhantomReference<>(sample, QUEUE);
      sample = null;

      new Thread(){
          @Override
          public void run() {
              while (true) {
                  System.out.println(phantomRef.get());
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                      Thread.currentThread().interrupt();
                  }
                  TEST_DATA.add(new byte[1024 * 1024 * 5]);
              }
          }
      }.start();

      new Thread(){
          @Override
          public void run() {
              while (true) {
                  Reference<? extends Sample> poll = QUEUE.poll();
                  if (poll != null) {
                      System.out.println("--- 虛幻引用對象被jvm回收了 ---- " + poll);
                      System.out.println(poll.isEnqueued());
                      System.out.println("--- 回收對象 ---- " + poll.get());
                  }
              }
          }
      }.start();

      try {
          Thread.currentThread().join();
      } catch (InterruptedException e) {
          System.exit(1);
      }
  }
}
class Sample {
  private final byte[] data;

  public Sample() {
      data = new byte[1024 * 1024 * 10];
  }
}

 

擴展:mybatis的緩存與redis結合使用

http://www.javashuo.com/article/p-sintyuyy-ht.html

 

8)mybatis註解

@Options(useCache = true, flushCache = false, timeout = 10000) : 一些查詢的選項開關,好比useCache = true表示本次查詢結果被緩存以提升下次查詢速度,flushCache = false表示下次查詢時不刷新緩存,timeout = 10000表示查詢結果緩存10000秒。

@Results(value = { @Result(id = true, property = "id", column = "test_id", javaType = String.class, jdbcType = JdbcType.VARCHAR), @Result(property = "testText", column = "test_text", javaType = String.class, jdbcType = JdbcType.VARCHAR) }) : 表示sql查詢返回的結果集,@Results是以@Result爲元素的數組,@Result表示單條屬性-字段的映射關係,如:@Result(id = true, property = "id", column = "test_id", javaType = String.class, jdbcType = JdbcType.VARCHAR)能夠簡寫爲:@Result(id = true, property = "id", column = "test_id"),id = true表示這個test_id字段是個PK,查詢時mybatis會給予必要的優化,應該說數組中全部的@Result組成了單個記錄的映射關係,而@Results則單個記錄的集合。

@Param("id") :全侷限定別名,定義查詢參數在sql語句中的位置再也不是順序下標0,1,2,3....的形式,而是對應名稱,該名稱就在這裏定義。

@ResultMap(value = "getByTestText") :重要的註解,能夠解決複雜的映射關係,包括resultMap嵌套,鑑別器discriminator等等。注意一旦你啓用該註解,你將不得不在你的映射文件中配置你的resultMap,而value = "getByTestText"即爲映射文件中的resultMap ID(注意此處的value = "getByTestText",必須是在映射文件中指定命名空間路徑)。@ResultMap在某些簡單場合能夠用@Results代替,可是複雜查詢,好比聯合、嵌套查詢@ResultMap就會顯得解耦方便更容易管理。

 

9)mybatis中的OGNL

OGNL表達式(對象視圖導航語言),支持比EL表達式更豐富的語法。

MyBatis中可使用OGNL的地方有兩處:

動態SQL表達式中

<select id="xxx" ...>
  select id,name,... from country
  <where>
      <if test="name != null and name != ''">
          name like concat('%', #{name}, '%')
      </if>
  </where>
</select>

上面代碼中test的值會使用OGNL計算結果。

<select id="xxx" ...>
  select id,name,... from country
  <bind name="nameLike" value="'%' + name + '%'"/>
  <where>
      <if test="name != null and name != ''">
          name like #{nameLike}
      </if>
  </where>
</select>

這裏<bind>value值會使用OGNL計算。

${param}參數中

<select id="xxx" ...>
  select id,name,... from country
  <where>
      <if test="name != null and name != ''">
          name like '${'%' + name + '%'}'
      </if>
  </where>
</select>

這裏注意寫的是${'%' + name + '%'},而不是%${name}%,這兩種方式的結果同樣,可是處理過程不同。

在MyBatis中處理${}的時候,只是使用OGNL計算這個結果值,而後替換SQL中對應的${xxx},OGNL處理的只是${這裏的表達式}

這裏表達式能夠是OGNL支持的全部表達式,能夠寫的很複雜,能夠調用靜態方法返回值,也能夠調用靜態的屬性值。

 

10)java代碼塊

10.1 靜態代碼塊

在java類中(方法中不能存在靜態代碼塊)使用static關鍵字和{}聲明的代碼塊:

public` `class` `CodeBlock {
  ``static``{
      ``System.out.println(``"靜態代碼塊"``);
  ``}
}

執行時機:靜態代碼塊在類被加載的時候就運行了,並且只運行一次,而且優先於各類代碼塊以及構造函數。若是一個類中有多個靜態代碼塊,會按照書寫順序依次執行。

靜態代碼塊的做用:通常狀況下,若是有些代碼須要在項目啓動的時候就執行,這時候就須要靜態代碼塊。好比一個項目啓動須要加載的不少配置文件等資源,咱們就能夠都放入靜態代碼塊中。

靜態代碼塊不能存在任何方法體中:這個應該很好理解,首先咱們要明確靜態代碼塊是在類加載的時候就要運行了。咱們分狀況討論:

  對於普通方法,因爲普通方法是經過加載類,而後new出實例化對象,經過對象才能運行這個方法,而靜態代碼塊只須要加載類以後就能運行了。

  對於靜態方法,在類加載的時候,靜態方法也已經加載了,可是咱們必需要經過類名或者對象名才能訪問,也就是說相比於靜態代碼塊,靜態代碼塊是主動運行的,而靜態方法是被動運行的。

  無論是哪一種方法,咱們須要明確靜態代碼塊的存在在類加載的時候就自動運行了,而放在無論是普通方法仍是靜態方法中,都是不能自動運行的。

靜態代碼塊不能訪問普通變量:這個理解思惟同上,普通變量只能經過對象來調用,是不能放在靜態代碼塊中的。

 

10.2 構造代碼塊

在java類中使用{}聲明的代碼塊(和靜態代碼塊的區別是少了static關鍵字):

public` `class` `CodeBlock {
  ``static``{
      ``System.out.println(``"靜態代碼塊"``);
  ``}
  ``{
      ``System.out.println(``"構造代碼塊"``);
  ``}
}

執行時機:構造代碼塊在建立對象時被調用,每次建立對象都會調用一次,可是優先於構造函數執行。須要注意的是,聽名字咱們就知道,構造代碼塊不是優先於構造函數執行,而是依託於構造函數,也就是說,若是你不實例化對象,構造代碼塊是不會執行的。

構造代碼塊的做用:和構造函數的做用相似,都能對對象進行初始化,而且只要建立一個對象,構造代碼塊都會執行一次。可是反過來,構造函數則不必定每一個對象創建時都執行(多個構造函數狀況下,創建對象時傳入的參數不一樣則初始化使用對應的構造函數)。利用每次建立對象的時候都會提早調用一次構造代碼塊特性,咱們能夠作諸如統計建立對象的次數等功能。

 

10.3 構造函數

1.構造函數的命名必須和類名徹底相同。在java中普通函數能夠和構造函數同名,可是必須帶有返回值;

  2.構造函數的功能主要用於在類的對象建立時定義初始化的狀態。它沒有返回值,也不能用void來修飾。這就保證了它不只什麼也不用自動返回,並且根本不能有任何選擇。而其餘方法都有返回值,即便是void返回值。儘管方法體自己不會自動返回什麼,但仍然可讓它返回一些東西,而這些東西多是不安全的;

  3.構造函數不能被直接調用,必須經過new運算符在建立對象時纔會自動調用;而通常的方法是在程序執行到它的時候被調用的;

  4.當定義一個類的時候,一般狀況下都會顯示該類的構造函數,並在函數中指定初始化的工做也可省略,不過Java編譯器會提供一個默認的構造函數.此默認構造函數是不帶參數的。而通常的方法不存在這一特色;

 

10.4 普通代碼塊

普通代碼塊和構造代碼塊的區別是,構造代碼塊是在類中定義的,而普通代碼塊是在方法體中定義的。且普通代碼塊的執行順序和書寫順序一致。

public` `void` `sayHello(){
  ``{
      ``System.out.println(``"普通代碼塊"``);
  ``}
}

 

10.5 執行順序

靜態代碼塊>構造代碼塊>構造函數>普通代碼塊 

父類與子類:

首先執行父類靜態的內容,父類靜態的內容執行完畢後,接着去執行子類的靜態的內容,當子類的靜態內容執行完畢以後,再去看父類有沒有構造代碼塊,若是有就執行父類的構造代碼塊,父類的構造代碼塊執行完畢,接着執行父類的構造方法;父類的構造方法執行完畢以後,它接着去看子類有沒有構造代碼塊,若是有就執行子類的構造代碼塊。子類的構造代碼塊執行完畢再去執行子類的構造方法。

  總之一句話,靜態代碼塊內容先執行,接着執行父類構造代碼塊和構造方法,而後執行子類構造代碼塊和構造方法。

 

11)mybatis與hibernate的區別

相同點:

都是java中orm框架、屏蔽jdbc api的底層訪問細節,使用咱們不用與jdbc api打交道,就能夠完成對數據庫的持久化操做。jdbc api編程流程固定,還將sql語句與java代碼混雜在了一塊兒,常常須要拼湊sql語句,細節很繁瑣。

mybatis的好處:屏蔽jdbc api的底層訪問細節;將sql語句與java代碼進行分離;提供了將結果集自動封裝稱爲實體對象和對象的集合的功能.queryForList返回對象集合,用queryForObject返回單個對象;提供了自動將實體對象的屬性傳遞給sql語句的參數。

Hibername的好處:Hibernate是一個全自動的orm映射工具,它能夠自動生成sql語句,並執行並返回java結果。

不一樣點:

一、hibernate要比ibatis功能強大不少。由於hibernate自動生成sql語句。

二、ibatis須要咱們本身在xml配置文件中寫sql語句,hibernate咱們沒法直接控制該語句,咱們就沒法去寫特定的高效率的sql。對於一些不太複雜的sql查詢,hibernate能夠很好幫咱們完成,可是,對於特別複雜的查詢,hibernate就很難適應了,這時候用ibatis就是不錯的選擇,由於ibatis仍是由咱們本身寫sql語句。

ibatis能夠出來複雜語句,而hibernate不能。

三、ibatis要比hibernate簡單的多。ibatis是面向sql的,不一樣考慮對象間一些複雜的映射關係。

 

12)hibernate的緩存機制

Hibernate中的緩存分一級緩存和二級緩存。

一級緩存就是Session級別的緩存,在事務範圍內有效是,內置的不能被卸載。二級緩存是SesionFactory級別的緩存,從應用啓動到應用結束有效。是可選的,默認沒有二級緩存,須要手動開啓。

保存數據庫後,在內存中保存一份,若是更新了數據庫就要同步更新。

什麼樣的數據適合存放到第二級緩存中? 

1) 不多被修改的數據  帖子的最後回覆時間 

2) 常常被查詢的數據 電商的地點

2) 不是很重要的數據,容許出現偶爾併發的數據   

3) 不會被併發訪問的數據   

4) 常量數據

擴展:hibernate的二級緩存默認是不支持分佈式緩存的。使用memcahe,redis等中央緩存來代替二級緩存。

相關文章
相關標籤/搜索