day62_Mybatis學習筆記_02

  • 今天內容安排:
    • 一、高級結果映射(一對1、一對多、多對多)(重點)
    • 二、延遲加載
    • 三、查詢緩存
    • 四、Spring 和 mybatis 的整合(重點)
    • 五、逆向工程

一、高級結果映射(即:關聯查詢映射)(重點)

1.一、分析數據模型

1.1.一、思路

  • 一、每張表記錄的數據內容(即:明確每張表存儲的信息)
    • `分模塊`對每張表記錄的內容進行熟悉,至關於你`學習系統需求(功能)`的過程。
  • 二、每張表重要的字段(即:明確每張表中關鍵字段(主鍵、外鍵、非空))
    • `主鍵`、`外鍵`、非空字段
  • 三、數據庫級別表與表的關係(即:明確數據庫中表與表之間的外鍵關係)
    • 外鍵關係
  • 四、表與表之間的業務關係(即:明確業務中表與表的關係(創建在具體的業務上))
    • 在分析表與表之間的業務關係時必定要創建`在某個業務意義基礎上去分析`。

1.1.二、圖解分析

1.1.三、數據庫表之間有外鍵關係的業務關係

  • user和orders:
    • user --> orders:一個用戶能夠建立多個訂單,一對多
    • orders --> user:一個訂單隻由一個用戶建立,一對一
  • orders和orderdetail:
    • orders --> orderdetail:一個訂單能夠包括多個訂單明細,由於一個訂單能夠購買多個商品,每一個商品的購買信息在orderdetail記錄,一對多
    • orderdetail --> orders:一個訂單明細只能包括在一個訂單中,一對一
  • orderdetail和ites:
    • orderdetail --> itesms:一個訂單明細只對應一個商品信息,一對一
    • items --> orderdetail:一個商品能夠包括在多個訂單明細中 ,一對多

1.1.四、數據庫表之間沒有外鍵關係的業務關係

  • orders和items:
    • 這兩張表沒有直接的外鍵關係,經過業務及數據庫的間接關係分析出它們是多對多的關係。
    • orders -–> orderdetail -–> items:一個訂單能夠有多個訂單明細,一個訂單明細對應一個商品,因此一個訂單對應多個商品
    • items -–> orderdetail -–> orders:一個商品能夠對應多個訂單明細,一個訂單明細對應一個訂單,因此一個商品對應多個訂單
  • user和items:
    • 這兩張表沒有直接的外鍵關係,經過業務及數據庫的間接關係分析出它們是多對多的關係。
    • user -–> orders -–> orderdetail -–> items:一個用戶有多個訂單,一個訂單有多個訂單明細、一個訂單明細對應一個商品,因此一個用戶對應多個商品
    • items -–> orderdetail -–> orders -–> user:一個商品對應多個訂單明細,一個訂單明細對應一個訂單,一個訂單對應一個用戶,因此一個商品對應多個用戶

1.二、一對一查詢

1.2.一、需求

  • 查詢訂單信息,關聯查詢建立訂單的用戶信息(用戶名稱和性別)

1.2.二、SQL語句

  • 肯定查詢的主表:訂單表 orders
  • 肯定查詢的關聯表:用戶表 user
  • 關聯查詢使用內鏈接呢?仍是外鏈接呢?到了企業裏面,咱們寫sql時大多數的時候咱們須要考慮,不少時候,咱們會使用外鏈接,要先把主表信息查詢出來,而後須要考慮是使用左外鏈接仍是右外鏈接,記錄爲空的要不要,不考慮好的話,查詢到的結果集數量會和咱們預想的有很大出入。
  • 本例中咱們使用的是等值鏈接,因此暫時咱們不用考慮那麼多。

sql語句以下:php

SELECT
  orders.id,
  orders.user_id,
  orders.number,
  user.username,
  user.sex
FROM
  orders,
  user
WHERE orders.user_id = user.id;

1.2.三、resultType

  • 複雜查詢時,單表對應的po類已不能知足輸出結果集的映射。
  • 因此要根據需求創建一個擴展類來做爲resultType的類型。

(1)建立擴展PO類
  通常User.java類要和數據表表字段一致,最好不要在這裏面添加其餘字段,今天學習mybatis的逆向工程時,會根據表結構,生成po類,若是在po類中擴展字段,此時會被覆蓋掉。
  因此針對要擴展的po類,咱們須要建立一個擴展類,來繼承它。html

/**
 * 經過此類映射訂單和用戶查詢的結果,讓此類繼承包括字段較多的pojo類
 * @author Bruce
 *
 */

public class OrdersExt extends Orders {

    // 添加用戶屬性
    // user.username
    // user.sex
    private String username;
    private String sex;

    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

}

(2)編寫mapper接口
  建立OrdersMapper接口類,在類中添加如下內容:前端

public interface OrdersMapper {
    // 一對一映射之 resultType
    // 查詢訂單信息,關聯查詢建立訂單的用戶信息(用戶名稱和性別)
    public List<OrdersExt> findOrdersAndUser();
}

(3)編寫映射文件
  建立OrdersMapper.xml映射文件,在映射文件中添加如下內容:java

<mapper namespace="com.itheima.mybatis.mapper.OrdersMapper">
    <!-- 一對一映射之 resultType -->
    <!-- 查詢訂單信息,關聯查詢建立訂單的用戶信息(用戶名稱和性別) -->
    <select id="findOrdersAndUser" resultType="com.itheima.mybatis.po.OrdersExt">
        SELECT
          orders.id,
          orders.user_id,
          orders.number,
          user.username,
          user.sex
        FROM
          orders,
          user
        WHERE orders.user_id = user.id
    </select>
</mapper>

(4)加載映射文件
  在config/SqlMapConfig.xml中,添加如下內容:mysql

    <!-- 加載mapper,即加載映射文件 -->
    <mappers>
        <!-- 使用相對於類路徑的資源,加載配置文件 -->
        <!--  
        <mapper resource="sqlmap/User.xml"/>
        <mapper resource="mapper/UserMapper.xml"/>
        <mapper resource="mapper/OrdersMapper.xml"/>
        -->

        <!-- 推薦使用:批量加載mapper文件,須要mapper接口文件和mapper映射文件名稱相同且在同一個包下 -->
        <package name="com.itheima.mybatis.mapper"/>
    </mappers>

(5)編寫測試代碼git

    @Test
    public void testFindUserById() 
{
        // 根據SqlSessionFactory建立SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 建立OrdersMapper對象
        // 由Mybatis經過sqlSession來建立動態代理對象
        OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);

        List<OrdersExt> list = mapper.findOrdersAndUser();
        System.out.println(list);

        sqlSession.close();
    }

(6)小結github

  • 使用resultType來進行一對一結果映射,查詢出的列的個數和映射的屬性的個數要一致。並且映射的屬性要存在與一個大的對象中,它是一種平鋪式的映射,即:數據庫查詢出多少條記錄,則映射成多少個對象。

1.2.四、resultMap

  • 使用resultMap來進行一對一結果映射,它是將關聯對象添加到主信息的對象中,具體說是對象嵌套對象的一種映射方式。

(1)修改擴展PO類
  在OrdersExt類中,添加User對象web

/**
 * 經過此類映射訂單和用戶查詢的結果,讓此類繼承包括字段較多的pojo類
 * 在OrdersExt類中,添加User對象
 * @author Bruce
 *
 */

public class OrdersExt extends Orders {

    // 添加用戶屬性
    // user.username
    // user.sex
    private String username;

    private String sex;

    // 添加用戶對象(用戶信息)
    private User user;

    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }

    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

}

(2)編寫mapper接口redis

    // 一對一映射之 resultMap
    // 查詢訂單信息,關聯查詢建立訂單的用戶信息(用戶名稱和性別)
    public List<OrdersExt> findOrdersAndUserResultMap();

(3)編寫映射文件spring

    <!-- 聲明/定義一個resultMap -->
    <resultMap type="com.itheima.mybatis.po.OrdersExt" id="OrdersAndUserResultMap">
        <!-- 訂單信息映射 -->
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <!-- 用戶信息映射(一對一) -->
        <!-- 
            association標籤:定義一個一對一關係
                property:指定關聯對象要映射到OrdersExt的哪一個屬性上 
                javaType:指定關聯對象所要映射的java類型

                id標籤:指定關聯對象結果集的惟一標識,很重要,建議在關聯查詢時必須寫上,不寫不會報錯,可是會影響性能
        -->

        <association property="user" javaType="com.itheima.mybatis.po.User">
            <id column="user_id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
        </association>
    </resultMap>
    <!-- 一對一映射之 resultMap -->
    <!-- 查詢訂單信息,關聯查詢建立訂單的用戶信息(用戶名稱和性別) -->
    <select id="findOrdersAndUserResultMap" resultMap="OrdersAndUserResultMap">
        SELECT
          orders.id,
          orders.user_id,
          orders.number,
          user.username,
          user.sex
        FROM
          orders,
          user
        WHERE orders.user_id = user.id
    </select>

(4)加載映射文件
  已配置,此處無需再次配置。
(5)編寫測試代碼

    @Test
    public void testFindOrdersAndUserResultMap() 
{
        // 根據SqlSessionFactory建立SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 建立OrdersMapper對象
        // 由Mybatis經過sqlSession來建立動態代理對象
        OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);

        List<OrdersExt> list = mapper.findOrdersAndUserResultMap();
        System.out.println(list);

        sqlSession.close();
    }

(6)小結

  • 在一對一結果映射時,使用resultType更加簡單方便,若是有特殊要求(對象嵌套對象)時,須要使用resultMap進行映射,好比:查詢訂單列表,而後在點擊列表中的查看訂單明細按鈕,這個時候就須要使用resultMap進行結果映射。而resultType更適應於查詢訂單明細信息,好比,查詢訂單明細列表。

1.2.五、一對一查詢小結

實現一對一查詢:

  • resultType:使用resultType實現較爲簡單,若是pojo中沒有包括查詢出來的列名,須要增長列名對應的屬性,建立擴展PO類,便可完成映射。
    若是沒有查詢結果的特殊要求建議使用resultType。
  • resultMap:須要單獨定義resultMap,實現上有點麻煩,若是對查詢結果有特殊的要求,使用resultMap能夠完成將關聯查詢映射到pojo的對象屬性中。
  • resultMap能夠實現延遲加載,resultType沒法實現延遲加載。

1.三、一對多查詢

  • 一對多查詢和一對一查詢的配置基本相似。只是若是使用resultMap的話,映射一對多關聯關係要使用collection標籤

1.3.一、需求

  • 查詢訂單信息,關聯查詢訂單明細信息及用戶信息。

1.3.二、SQL語句

  • 肯定主查詢表:訂單表 orders
  • 肯定關聯查詢表:訂單明細表、用戶表 orderdetail、user
  • 關聯查詢使用內鏈接呢?仍是外鏈接呢?到了企業裏面,咱們寫sql時大多數的時候咱們須要考慮,不少時候,咱們會使用外鏈接,要先把主表信息查詢出來,而後須要考慮是使用左外鏈接仍是右外鏈接,記錄爲空的要不要,不考慮好的話,查詢到的結果集數量會和咱們預想的有很大出入。
  • 本例中咱們使用的是等值鏈接,因此暫時咱們不用考慮那麼多。

sql語句以下:

SELECT
  orders.id,
  orders.user_id,
  orders.number,
  user.username,
  user.sex,
  orderdetail.id detail_id,
  orderdetail.items_id,
  orderdetail.items_num
FROM
  orders,
  user,
  orderdetail
WHERE orders.user_id = user.id
  AND orders.id = orderdetail.orders_id;

1.3.三、分析

  • 使用resultType將上邊的查詢結果映射到擴展的pojo中,訂單信息將會重複。
  • 要求:對OrdersExt映射不能出現重複記錄。
    • 在OrdersExt.java類中添加`List detailList`屬性。
    • 最終會將訂單信息映射到OrdersExt中,訂單所對應的訂單明細映射到OrdersExt中的detailList屬性中。
    • 映射成的OrdersExt記錄數爲兩條(OrdersExt信息不重複)
    • 每一個OrdersExt中的detailList屬性存儲了該訂單所對應的訂單明細集合。

(1)修改擴展PO類
  在OrdersExt類中添加如下屬性,並提供get/set方法:

    // 訂單明細信息
    private List<Orderdetail> detailList;

    public List<Orderdetail> getDetailList() {
        return detailList;
    }

    public void setDetailList(List<Orderdetail> detailList{
        this.detailList = detailList;
    }

(2)編寫mapper接口

    // 一對多映射之 resultMap
    // 查詢訂單信息,關聯查詢訂單明細信息及用戶信息
    public List<OrdersExt> findOrdersAndOrderdetailResultMap();

(3)編寫映射文件

    <!-- 聲明/定義一個resultMap -->
    <!-- extends:繼承已有的ResultMap,值爲繼承的ResultMap的惟一標示 -->
    <resultMap type="com.itheima.mybatis.po.OrdersExt" id="OrdersAndOrderdetailResultMap" extends="OrdersAndUserResultMap">
        <!-- 訂單信息映射 -->
        <!-- 用戶信息映射(一對一) -->
        <!-- 訂單明細信息映射(一對多) -->
        <!-- collection標籤:定義一個一對多關係
                ofType:指定該集合參數所映射的類型
        -->

        <collection property="detailList" ofType="com.itheima.mybatis.po.Orderdetail">
            <id column="detail_id" property="id"/>
            <result column="items_id" property="itemsId"/>
            <result column="items_num" property="itemsNum"/>
        </collection>
    </resultMap>
    <!-- 一對多映射之 resultMap -->
    <!-- 查詢訂單信息,關聯查詢訂單明細信息及用戶信息 -->
    <select id="findOrdersAndOrderdetailResultMap" resultMap="OrdersAndOrderdetailResultMap">
        SELECT
          orders.id,
          orders.user_id,
          orders.number,
          user.username,
          user.sex,
          orderdetail.id detail_id,
          orderdetail.items_id,
          orderdetail.items_num
        FROM
          orders,
          user,
          orderdetail
        WHERE orders.user_id = user.id
          AND orders.id = orderdetail.orders_id
    </select>

  resultMap的extends屬性:能夠用此屬性來繼承一個已有的resultmap。可是它繼承的resultMap的type和它自己的type要保持一致。
(4)編寫測試代碼

    @Test
    public void testFindOrdersAndOrderdetailResultMap() 
{
        // 根據SqlSessionFactory建立SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 建立OrdersMapper對象
        // 由Mybatis經過sqlSession來建立動態代理對象
        OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);

        List<OrdersExt> list = mapper.findOrdersAndOrderdetailResultMap();
        System.out.println(list);

        sqlSession.close();
    }

1.3.四、一對多查詢小結

  • mybatis使用resultMap的collection對關聯查詢的多條記錄映射到一個list集合屬性中。
  • 若是使用resultType實現:
    • 須要對結果集進行二次處理。
    • 將訂單明細映射到OrdersExt中的detailList中,須要本身處理,使用雙重循環遍歷,去掉重複記錄,將訂單明細放在detailList中。很麻煩,很差。

1.四、多對多查詢

  • 多對多映射是一對多映射的特例。

1.4.一、需求

  • 查詢用戶信息,關聯查詢該用戶購買的商品信息,要求將關聯信息映射到主pojo的pojo屬性中。

1.4.二、SQL語句

  • 查詢主表:user
  • 查詢關聯表:orders、orderdetail、items
  • 關聯查詢使用內鏈接呢?仍是外鏈接呢?到了企業裏面,咱們寫sql時大多數的時候咱們須要考慮,不少時候,咱們會使用外鏈接,要先把主表信息查詢出來,而後須要考慮是使用左外鏈接仍是右外鏈接,記錄爲空的要不要,不考慮好的話,查詢到的結果集數量會和咱們預想的有很大出入。
  • 本例中咱們使用的是等值鏈接,因此暫時咱們不用考慮那麼多。

sql語句以下:

SELECT
  orders.id orders_id,
  orders.user_id,
  orders.number orders_number,
  user.username user_username,
  user.sex user_sex,
  orderdetail.id detail_id,
  orderdetail.items_id,
  orderdetail.items_num,
  items.name items_name,
  items.price items_price
FROM
  orders,
  user,
  orderdetail,
  items
WHERE user.id = orders.user_id
  AND orders.id = orderdetail.orders_id
  AND orderdetail.items_id = items.id;

1.4.三、映射思路

  • 將用戶信息映射到UserExt中。
    • 在UserExt類中添加訂單列表屬性List ordersList,將用戶建立的訂單映射到ordersList
    • 在OrdersExt中添加訂單明細列表屬性List detailList,將訂單的明細映射到detailList
    • 在OrderdetailExt中添加Items屬性,將訂單明細所對應的商品映射到Items

(1)修改擴展PO類
  在UserExt類中添加List<Orders>屬性

    // 添加訂單列表
    private List<Orders> ordersList;

  在OrdersExt類中添加List<Orderdetail>屬性

    // 訂單明細信息
    private List<Orderdetail> detailList;

  在OrderdetailExt類中添加Items屬性

    // 添加商品信息
    private Items items;

(2)編寫mapper接口
  在UserMapper.java中,添加如下內容:

    // 多對多映射之 resultMap
    // 查詢用戶信息,關聯查詢該用戶購買的商品信息
    public List<UserExt> findUserAndItemsResultMap();

(3)編寫映射文件
  在UserMapper.xml中,添加如下內容:

    <!-- 聲明/定義一個resultMap -->
    <resultMap type="com.itheima.mybatis.po.UserExt" id="UserAndItemsResultMap">
        <!-- 用戶信息映射-->
        <id column="user_id" property="id"/>
        <result column="user_username" property="username"/>
        <result column="user_sex" property="sex"/>
        <!-- 訂單信息映射(一對多) -->
        <collection property="ordersList" ofType="com.itheima.mybatis.po.OrdersExt">
            <id column="orders_id" property="id"/>
            <result column="user_id" property="userId"/>
            <result column="orders_number" property="number"/>
            <!-- 訂單明細信息映射(一對多) -->
            <collection property="detailList" ofType="com.itheima.mybatis.po.OrderdetailExt">
                <id column="detail_id" property="id"/>
                <result column="items_id" property="itemsId"/>
                <result column="items_num" property="itemsNum"/>
                <!-- 商品信息映射(一對一) -->
                <association property="items" javaType="com.itheima.mybatis.po.Items">
                    <id column="items_id" property="id"/>
                    <result column="items_name" property="name"/>
                    <result column="items_price" property="price"/>
                </association>
            </collection>       
        </collection>
    </resultMap>
    <!-- 多對多映射之 resultMap -->
    <!-- 查詢用戶信息,關聯查詢該用戶購買的商品信息 -->
    <select id="findUserAndItemsResultMap" resultMap="UserAndItemsResultMap">
        SELECT
          orders.id orders_id,
          orders.user_id,
          orders.number orders_number,
          user.username user_username,
          user.sex user_sex,
          orderdetail.id detail_id,
          orderdetail.items_id,
          orderdetail.items_num,
          items.name items_name,
          items.price items_price
        FROM
          orders,
          user,
          orderdetail,
          items
        WHERE user.id = orders.user_id
          AND orders.id = orderdetail.orders_id
          AND orderdetail.items_id = items.id   
    </select>

(4)編寫測試代碼
  在UserMapperTest.java中,添加如下內容:

    @Test
    public void testFindUserAndItemsResultMap() 
{
        // 根據SqlSessionFactory建立SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 建立UserMapper對象
        // 由Mybatis經過sqlSession來建立動態代理對象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<UserExt> list = mapper.findUserAndItemsResultMap();
        System.out.println(list);

        sqlSession.close();
    }

1.4.四、多對多查詢小結

  • 將查詢用戶購買的商品信息明細清單,(用戶名、用戶地址、購買商品名稱、購買商品時間、購買商品數量)
  • 針對上邊的需求就使用resultType將查詢到的記錄映射到一個擴展的pojo中,很簡單實現明細清單的功能。
  • 一對可能是多對多的特例,以下需求:
  • 查詢用戶購買的商品信息,用戶和商品的關係是多對多關係。
  • 需求1:
    • 查詢字段:用戶帳號、用戶名稱、用戶性別、商品名稱、商品價格(最多見)
    • 企業開發中常見明細列表,用戶購買商品明細列表,
    • 使用resultType將上邊查詢列映射到pojo輸出。
  • 需求2:
    • 查詢字段:用戶帳號、用戶名稱、購買商品數量、商品明細(鼠標移上顯示明細)
    • 使用resultMap將用戶購買的商品明細列表映射到user對象中。
  • 總結:
    • 使用resultMap是針對那些對查詢結果映射有特殊要求的功能,好比特殊要求映射成list中包括多個list。

1.五、高級映射總結

resultType:
做用:
    將查詢結果按照sql列名pojo屬性名一致性映射到pojo中。
場合:
    常見一些明細記錄的展現,好比用戶購買商品明細,將關聯查詢信息所有展現在頁面時,此時可直接使用resultType將每一條記錄映射到pojo中,在前端頁面遍歷listlist中是pojo)便可。
------------------------------------------------------------
resultMap:
    使用association和collection完成一對一和一對多高級映射(對結果有特殊的映射要求)。
------------------------------------------------------------
association:
做用:
    將關聯查詢信息映射到一個pojo對象中。
場合:
    爲了方便查詢關聯信息可使用association將關聯訂單信息映射爲用戶對象的pojo屬性中,好比:查詢訂單及關聯用戶信息。
    使用resultType沒法將查詢結果映射到pojo對象的pojo屬性中,根據對結果集查詢遍歷的須要選擇使用resultType仍是resultMap。
------------------------------------------------------------    
collection:
做用:
    將關聯查詢信息映射到一個list集合中。
場合:
    爲了方便查詢遍歷關聯信息可使用collection將關聯信息映射到list集合中,好比:查詢用戶權限範圍模塊及模塊下的菜單,可以使用collection將模塊映射到模塊list中,將菜單列表映射到模塊對象的菜單list屬性中,這樣的作的目的也是方便對查詢結果集進行遍歷查詢。
    若是使用resultType沒法將查詢結果映射到list集合中。

二、延遲加載

2.一、什麼是延遲加載

  • 在mybatis中,resultMap中的association和collection標籤具備延遲加載的功能。
  • 延遲加載的意思是說,在關聯查詢時,利用延遲加載,先加載主信息。須要關聯信息時再去按需加載關聯信息。這樣會大大提升數據庫性能,由於查詢單表要比關聯查詢多張錶速度要快。
  • 延遲加載又叫懶加載,也叫按需加載。也就是說先加載主信息,在須要的時候,再去加載從信息。

2.二、設置延遲加載

  • Mybatis默認是不開啓延遲加載功能的,咱們須要手動開啓。
  • 須要在SqlMapConfig.xml文件中,在標籤中開啓延遲加載功能。
  • lazyLoadingEnabled、aggressiveLazyLoading

配置圖以下:

2.三、使用association進行延遲加載

2.3.一、需求

  查詢訂單而且關聯查詢用戶信息(對用戶信息的加載要求是按需加載)

  • 一、建立一個statement來查詢訂單信息
  • 二、建立一個statement來查詢用戶信息

(1)編寫mapper接口
  在OrdersMapper.xml文件中,添加如下內容:

    // 延時加載
    public List<OrdersExt> findOrderAndUserLazyLoading();

(2)編寫映射文件
  編寫查詢訂單信息的映射文件

    <!-- 定義一個 resultMap-->
    <resultMap type="com.itheima.mybatis.po.OrdersExt" id="OrderAndUserLazyLoading">
        <!-- 訂單信息映射 -->
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <!-- 用戶信息映射(一對一) -->
        <!-- select:指定關聯查詢的查詢statement(即查詢用戶的statement的id),而後將查詢結果,封裝到property屬性指定的變量中 -->
        <!-- column:經過column指定的列所查詢出的結果,做爲select指的statement的入參
                注意:若是select指定的statement,入參須要多個值,須要在column中{col1=prop1,col2=prop2} 
        -->

        <association property="com.itheima.mybatis.po.User" 
            select="com.itheima.mybatis.mapper.UserMapper.findUserById"
            column="user_id">

        </association>
    </resultMap>
    <!-- 延時加載 -->
    <select id="findOrderAndUserLazyLoading" resultMap="OrderAndUserLazyLoading">
        SELECT * FROM orders
    </select>

  編寫查詢用戶信息的映射文件

    <!-- 根據用戶ID,查詢用戶信息 -->
    <select id="findUserById" parameterType="int" resultType="com.itheima.mybatis.po.User">
        SELECT * FROM USER WHERE id = #{id}
    </select>

(3)編寫測試代碼

    @Test
    public void testFindOrdersAndUserLazyLoading() 
{
        // 根據SqlSessionFactory建立SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 建立OrdersMapper對象
        // 由Mybatis經過sqlSession來建立動態代理對象
        OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);

        List<OrdersExt> list = mapper.findOrdersAndUserLazyLoading(); // n+1現象,是因爲沒有使用延時加載而致使的

        for (OrdersExt order : list) {
            System.out.println(order.getUser()); // 按需加載時,須要的時候再去查詢數據庫
        }

        sqlSession.close();
    }

(4)設置延遲加載
  在SqlMapConfig.xml中,配置settings標籤,注意該標籤的位置

    <settings>
        <!-- 開啓延遲加載,默認值爲true,即默認是當即加載 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 設置積極的懶加載,默認值是true,false的是按需加載 -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

2.四、延遲加載思考

  • 不使用mybatis提供的association及collection中的延遲加載功能,如何實現延遲加載??
  • 實現方法以下:
  • 定義兩個mapper方法:
    • 一、查詢訂單列表
    • 二、根據用戶id查詢用戶信息
  • 實現思路:
    • 先去查詢第一個mapper方法,獲取訂單信息列表。
    • 在程序中(service),按需去調用第二個mapper方法去查詢用戶信息。
  • 總之:
    • 使用延遲加載方法,先去查詢簡單的sql(最好單表,也能夠關聯查詢),再去按須要加載關聯查詢的其它信息。

三、查詢緩存

3.一、mybatis緩存分析

  • mybatis提供查詢緩存,若是緩存中有數據就不用從數據庫中獲取,用於減輕數據庫壓力,提升系統性能。
  • Mybatis的緩存,包括一級緩存和二級緩存。
  • 一級緩存指的就是Sqlsession,在Sqlsession中有一個數據區域,是Map結構,這個區域就是一級緩存區域。一級緩存中的key是由sql語句、條件、statement等信息組成的一個惟一值。一級緩存中的value,就是查詢出的結果對象。
  • 二級緩存指的就是同一個namespace下的mapper,在二級緩存中,也有一個map結構,這個區域就是一級緩存區域。一級緩存中的key是由sql語句、條件、statement等信息組成的一個惟一值。一級緩存中的value,就是查詢出的結果對象。
  • 一級緩存是默認使用的。
  • 二級緩存須要手動開啓。
  • 一級緩存是Sqlsession級別的緩存。在操做數據庫時須要構造Sqlsession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據。不一樣的Sqlsession之間的緩存數據區域(HashMap)是互相不影響的。
  • 二級緩存是mapper級別的緩存,多個Sqlsession去操做同一個mapper的sql語句,多個Sqlsession能夠共用二級緩存,二級緩存是跨Sqlsession的。

3.二、一級緩存

3.2.一、原理


上圖詳解以下:
    第一次發起查詢用戶id1的用戶信息,先去找緩存中是否有id1的用戶信息,若是沒有,從數據庫查詢用戶信息。
    獲得用戶信息,將用戶信息存儲到一級緩存中。

    若是SqlSession去執行commit操做(執行插入、更新、刪除),會清空SqlSession中的一級緩存,
    這樣作的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。

    第二次發起查詢用戶id1的用戶信息,先去找緩存中是否有id1的用戶信息,緩存中有,直接從緩存中獲取用戶信息。

    Mybatis默認支持一級緩存。

3.2.二、測試1

    @Test
    public void testOneLevelCache() 
{
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 第一次查詢ID爲1的用戶,去緩存找,找不到就去查找數據庫
        User user1 = mapper.findUserById(1);
        System.out.println(user1);

        // 第二次查詢ID爲1的用戶
        User user2 = mapper.findUserById(1);
        System.out.println(user2);

        sqlSession.close();
    }

  只輸出一次SQL,以下圖所示:

3.2.三、測試2

    @Test
    public void testOneLevelCache() 
{
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 第一次查詢ID爲1的用戶,去緩存找,找不到就去查找數據庫
        User user1 = mapper.findUserById(1);
        System.out.println(user1);

        User user = new User();
        user.setUsername("曉藝");
        user.setAddress("物資學院");
        // 執行增刪改操做,清空緩存
        mapper.insertUser(user);
        sqlSession.commit();

        // 第二次查詢ID爲1的用戶
        User user2 = mapper.findUserById(1);
        System.out.println(user2);

        sqlSession.close();
    }

  中間執行了commit操做,一樣的查詢SQL輸出兩次,以下圖所示:

3.2.四、應用

  • 正式開發,是將mybatis和spring進行整合開發,事務控制在service中。
  • 一個service方法中包括 不少mapper方法調用。

例如:

    service {
        // 開始執行時,開啓事務,建立SqlSession對象
        // 第一次調用mapper的方法findUserById(1)

        // 第二次調用mapper的方法findUserById(1),從一級緩存中取數據
        // 方法結束,sqlSession關閉
    }

    若是是執行兩次service調用查詢相同的用戶信息,不走一級緩存,由於session方法結束,SqlSession就關閉,一級緩存就清空了。

3.三、二級緩存

3.3.一、原理

  • 下圖是多個SqlSession請求UserMapper的二級緩存圖解:

上圖詳解以下:

    二級緩存是mapper級別的。
    第一次調用mapper下的SQL去查詢用戶信息。查詢到的信息會存到該mapper對應的二級緩存區域內。
    第二次調用相同namespace下的mapper映射文件中相同的SQL去查詢用戶信息。會去對應的二級緩存內取結果。
    若是調用相同namespace下的mapper映射文件中的增刪改SQL,並執行了commit操做。此時會清空該namespace下的二級緩存。

3.3.二、開啓二級緩存

  • Mybatis默認是沒有開啓二級緩存的。

一、在覈心配置文件SqlMapConfig.xml中加入如下內容(開啓二級緩存總開關):
在settings標籤中添加如下內容:

    <!-- 開啓二級緩存總開關 -->
    <setting name="cacheEnabled" value="true"/>

二、在UserMapper映射文件中,加入如下內容,開啓二級緩存:

    <!-- 開啓本mapper下的namespace的二級緩存,默認使用的是mybatis提供的PerpetualCache -->
    <cache></cache> 

3.3.三、實現序列化

  • 因爲二級緩存的數據不必定都是存儲到內存中,它的存儲介質多種多樣,因此須要給緩存的對象執行序列化。
  • 若是該類存在父類,那麼父類也要實現序列化。

3.3.四、測試1

    @Test
    public void testTwoLevelCache() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        SqlSession sqlSession3 = sqlSessionFactory.openSession();

        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);

        // 第一次查詢ID爲1的用戶,去緩存找,找不到就去查找數據庫
        User user1 = mapper1.findUserById(1);
        System.out.println(user1);
        // 關閉SqlSession1,在close的時候,纔會將數據寫入到二級緩存中
        sqlSession1.close();

        // 第二次查詢ID爲1的用戶
        User user2 = mapper2.findUserById(1);
        System.out.println(user2);
        // 關閉SqlSession2
        sqlSession2.close();

        // 關閉SqlSession3
        sqlSession3.close();
    }

  SQL輸出結果,以下圖所示:


  Cache Hit Radio:緩存命中率
  第一次緩存中沒有記錄,則命中率0.0;
  第二次緩存中有記錄,則命中率0.5(訪問兩次,有一次命中)

3.3.五、測試2

    @Test
    public void testTwoLevelCache() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        SqlSession sqlSession3 = sqlSessionFactory.openSession();

        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);

        // 第一次查詢ID爲1的用戶,去緩存找,找不到就去查找數據庫
        User user1 = mapper1.findUserById(1);
        System.out.println(user1);
        // 關閉SqlSession1,在close的時候,纔會將數據寫入到二級緩存中
        sqlSession1.close();

        User user = new User();
        user.setUsername("曉藝");
        user.setAddress("物資學院");
        // 執行增刪改操做,清空緩存
        mapper3.insertUser(user);
        sqlSession3.commit();

        // 第二次查詢ID爲1的用戶
        User user2 = mapper2.findUserById(1);
        System.out.println(user2);
        // 關閉SqlSession2
        sqlSession2.close();

        // 關閉SqlSession3
        sqlSession3.close();
    }

  SQL輸出結果,以下圖所示:
  根據SQL分析,確實是清空了二級緩存了。

3.3.六、禁用二級緩存

  • 該statement中設置userCache=false,能夠禁用當前select語句的二級緩存,即每次查詢都是去數據庫中查詢,默認狀況下是true,即該statement使用二級緩存。以下代碼所示:
    <select id="findUserById" parameterType="int" resultType="com.itheima.mybatis.po.User" useCache="true">
        SELECT * FROM USER WHERE id = #{id}
    </select>

3.3.七、刷新二級緩存

  • 該statement中設置flushCache=true能夠刷新當前的二級緩存,默認狀況下:
  • 若是是select語句,那麼flushCache是false
  • 若是是insert、update、delete語句,那麼flushCache是true
  • 若是查詢語句設置成true,那麼每次查詢都是去數據庫查詢,即意味着該查詢的二級緩存失效。
  • 若是查詢語句設置成false,即便用二級緩存,那麼若是在數據庫中修改了數據,而緩存數據仍是原來的,這個時候就會出現髒讀。

flushCache設置以下:

    <select id="findUserById" parameterType="int" resultType="com.itheima.mybatis.po.User" useCache="true" flushCache="false">
        SELECT * FROM USER WHERE id = #{id}
    </select>

3.3.八、整合ehcache(瞭解)

  • ehcache是一個分佈式緩存。Mybatis自己是一個持久層框架,它不是專門的緩存框架,因此它對緩存的實現不夠好,不能支持分佈式緩存。

(1)什麼是分佈式緩存?

  • 系統爲了提升性能,一般會對系統採用分佈式部署(即:集羣部署方式)
  • 不使用分佈式緩存,緩存的數據在各個服務單獨存儲,不方便開發。因此要使用分佈式緩存對緩存數據進行集中式管理。
  • Mybatis自身沒法實現分佈式緩存,須要和其它分佈式緩存框架進行整合。

(2)整合思路(重點)

  • Mybatis提供了一個Cache接口,同時它本身有一個默認的實現類PerpetualCache
  • 經過實現Cache接口能夠實現mybatis緩存數據經過其餘緩存數據庫整合,mybatis的特長是sql,緩存數據管理不是mybatis的特長,爲了提升mybatis的性能,因此須要mybatis和第三方緩存數據庫整合,好比ehcache、memcache、redis等。
  • Mybatis提供接口以下:
  • Mybatis的默認實現類以下:

(3)整合ehcache的步驟

  • 一、引入ehcache的jar包
  • 二、在mapper映射文件中,配置cache標籤的type爲ehcache對cache接口的實現類類型
  • 三、加入ehcache的配置文件

第一步:引入ehcache的jar包,並添加至構建路徑


第二步:在mapper映射文件中,配置cache標籤的type爲ehcache對cache接口的實現類類型
    <!-- 開啓本mapper下的namespace的二級緩存,默認使用的是mybatis提供的PerpetualCache -->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache> 

第三步:添加ehcache的配置文件
在classpath下的config目錄下,添加ehcache.xml,內容以下:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

    <!-- 緩存數據要存放的磁盤地址 -->
    <diskStore path="e:\others\develop\ehcache" />
    <!-- diskStore:指定數據在磁盤中的存儲位置。
         defaultCache:當藉助CacheManager.add("demoCache")建立Cache時,EhCache便會採用<defalutCache/>指定的的管理策略 
            如下屬性是必須的:
                maxElementsInMemory - 在內存中緩存的element的最大數目
                maxElementsOnDisk - 在磁盤上緩存的element的最大數目,如果0表示無窮大
                eternal - 設定緩存的elements是否永遠不過時。若是爲true,則緩存的數據始終有效,若是爲false,那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷 
                overflowToDisk - 設定當內存緩存溢出的時候是否將過時的element緩存到磁盤上 如下屬性是可選的:
                timeToIdleSeconds - 當緩存在EhCache中的數據先後兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些數據便會刪除,默認值是0,也就是可閒置時間無窮大 
                timeToLiveSeconds - 緩存element的有效生命期,默認是0,也就是element存活時間無窮大 
                diskSpoolBufferSizeMB - 這個參數設置DiskStore(磁盤緩存)的緩存區大小,默認是30MB。每一個Cache都應該有本身的一個緩衝區
                diskPersistent - 在VM重啓的時候是否啓用磁盤保存EhCache中的數據,默認是false
                diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運行間隔,默認是120秒。每隔120s,相應的線程會進行一次EhCache中數據的清理工做
                memoryStoreEvictionPolicy - 當內存緩存達到最大,有新的element加入的時候, 移除緩存中element的策略。默認是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出) 
    -->

    <defaultCache maxElementsInMemory="1000"
        maxElementsOnDisk="10000000" 
        eternal="false" 
        overflowToDisk="false"
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120"
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="LRU">

    </defaultCache>
</ehcache>

第四步:測試ehcache的二級緩存,經過查看緩存命中率

3.3.九、應用場景

  • 使用場景:對於訪問響應速度要求高,可是實時性不高的查詢,能夠採用二級緩存技術
  • 注意:在使用二級緩存的時候,要設置一下刷新間隔(cache標籤中有一個flashInterval屬性)來定時刷新二級緩存,這個刷新間隔根據具體需求來設置,好比設置30分鐘、60分鐘等,單位爲毫秒。

3.3.十、侷限性

  • Mybatis二級緩存細粒度的數據級別的緩存實現很差。
  • 場景:對商品信息進行緩存,因爲商品信息查詢訪問量大,可是要求用戶每次查詢都是最新的商品信息,此時若是使用二級緩存,就沒法實現當一個商品發生變化只刷新該商品的緩存信息而不刷新其餘商品緩存信息,由於二級緩存是mapper級別的,當一個商品的信息發送更新,全部的商品信息緩存數據都會清空
  • 解決此類問題,須要在業務層根據須要對數據有針對性的緩存。
  • 好比:能夠對常常變化的數據操做單獨放到另外一個namespace的mapper中

四、mybatis與spring集成

4.一、集成思路

  • 須要spring來管理數據源信息。
  • 須要spring經過單例方式管理SqlSessionFactory。
  • 使用SqlSessionFactory建立SqlSession(spring和mybatis整合自動完成)。
  • 持久層的mapper都須要由spring進行管理,spring和mybatis整合生成mapper代理對象。
    • 即:由spring來管理原始dao的實現類或者mapper代理的代理類。

4.二、集成步驟

  • 一、jar包集成
  • 二、配置文件集成(數據源)
  • 三、SqlSessionFactory集成
  • 四、Mapper接口集成

4.三、開始集成

4.3.一、搭建工程結構

4.3.二、jar包集成

Jar包以下(一共28個):
  Mybatis3.2.7 的jar包(mybatis核心包、依賴包)


  Spring3.2.0 的jar包

  Spring與mybatis的集成包

  Junit包

  數據庫驅動包

  dbcp數據庫鏈接池包

4.3.三、配置文件集成

  在config下,建立mybatis目錄,而後建立SqlMapConfig.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>
    <!-- 自定義別名 -->
    <typeAliases>
        <!-- 單個定義別名 -->
        <!-- <typeAlias type="com.itheima.mybatis.po.User" alias="user"/> -->
        <!-- 批量定義別名(推薦) -->
        <!-- name:指定批量定義別名的類包,別名爲類名(首字母大小寫均可) -->
        <package name="com.itheima.ms.po"/>
    </typeAliases>

    <!-- 注意:與spring集成後,數據源和事務交給spring來管理 -->

    <!-- 加載mapper,即加載映射文件 -->
    <mappers>
        <!-- 使用相對於類路徑的資源,加載配置文件,後面講課用:爲了驗證原始dao的實現方式 -->
        <mapper resource="mybatis/sqlmap/User.xml"/>
        <!-- 推薦使用:批量加載mapper文件,須要mapper接口文件和mapper映射文件名稱相同且在同一個包下 -->
        <!-- 由spring來管理原始dao的實現類或者mapper代理的代理類,spring代理的時候,會自動將映射文件加載進去 -->
        <package name="com.itheima.ms.mapper"/> 
    </mappers>
</configuration>

  將db.properties和log4j.properties拷貝到config目錄下。

4.3.四、Spring對SqlSessionFactory進行管理配置

  在config下,建立spring目錄,而後建立applicationContext.xml,內容以下:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "
>

    <!-- 引用/加載java配置文件 -->
    <context:property-placeholder location="db.properties"/> <!-- java工程不須要指定classpath,web工程須要指定 classpath-->

    <!-- 配置數據源,使用dbcp數據庫鏈接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${db.driver}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
        <property name="maxActive" value="10"/>
        <property name="maxIdle" value="5"/>
    </bean>

    <!-- 配置spring對SqlSessionFactory進行管理 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 指定mybatis的全局配置文件的路徑 -->
        <property name="configLocation" value="mybatis/SqlMapConfig.xml"></property>
        <!-- SqlSessionFactory須要數據源信息,以前是寫在sqlmapconfig.xml,如今須要從新指定 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

4.四、Mybatis程序編寫

4.4.一、原始dao方式

(1)編寫dao接口

public interface UserDao {
    // 根據用戶ID來查詢用戶信息
    public User findUserById(int id);
}

(2)編寫dao實現類(繼承SqlSessionDaoSupport)
  經過this.getSqlSession()獲取sqlsession。

public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {

    @Override
    public User findUserById(int id) {
        return this.getSqlSession().selectOne("test.findUserById", id);
    }

}

(3)編寫Mapper映射文件
  在config/mybatis下建立sqlmap目錄,而後建立User.xml

<?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="test">
    <!-- 根據用戶ID,查詢用戶信息 -->
    <select id="findUserById" parameterType="int" resultType="user">
        SELECT * FROM USER WHERE id = #{id}
    </select>
</mapper>

(4)在spring配置文件中配置UserDao實現類
  在applicationContext.xml中配置UserDao的實現類

    <!-- 配置spring管理原始dao的實現 -->
    <bean id="userDao" class="com.itheima.ms.dao.UserDaoImpl">
        <!-- 依賴注入sqlSessionFactory,由於UserDao的實現類繼承 了SqlSessionDaoSupport-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean>

(5)編寫測試代碼

package com.itheima.ms.dao;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.itheima.ms.po.User;

public class UserDaoTest {

    // spring上下文
    private ApplicationContext ctx;

    @Before
    public void setUp() throws Exception {
        // 讀取spring的上下文,而後封裝到ctx
        ctx = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
    }

    @Test
    public void testFindUserById() {
        // 建立UserDao對象
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        // 調用UserDao對象的方法
        User user = userDao.findUserById(30);
        System.out.println(user);
    }

}

4.4.二、Mapper代理方式

(1)編寫mapper接口

public interface UserMapper {

    // 根據用戶ID來查詢用戶信息
    public User findUserById(int id);

}

(2)編寫mapper映射文件
  將映射文件放到UserMapper接口的同包下
  UserMapper.xml

<?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="com.itheima.ms.mapper.UserMapper">
    <!-- 根據用戶ID,查詢用戶信息 -->
    <select id="findUserById" parameterType="int" resultType="user">
        SELECT * FROM USER WHERE id = #{id}
    </select>
</mapper>

(3)配置mapper代理類
  在spring中定義bean,Mapper代理開發方式有兩種bean的定義方法,一種是MapperFactoryBean一種是MapperScannerConfigurer(推薦)

  • 經過MapperFactoryBean建立代理對象,即單個mapper代理類配置(瞭解)
    <!-- mapper代理開發方式之單個mapper配置 -->
    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <!-- 設置代理類的接口 -->
        <property name="mapperInterface" value="com.itheima.ms.mapper.UserMapper"></property>
        <!-- 依賴注入sqlSessionFactory,由於 MapperFactoryBean也繼承了SqlSessionDaoSupport-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean>
  • 存在問題:一個mapper定義一個bean,很麻煩。
  • 經過MapperScannerConfigurer批量掃描建立代理對象,即批量設置mapper代理類(掌握)
    <!-- mapper代理開發方式之批量mapper配置,默認bean的id爲類名首字母小寫 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 指定批量mapper配置的包名 -->
        <property name="basePackage" value="com.itheima.ms.mapper"></property>
        <!-- 當只有一個SqlSessionFactory時,默認是不須要配置SqlSessionFactory的 -->
        <!-- 當有多個SqlSessionFactory時,能夠指定使用的SqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

(4)編寫測試代碼

package com.itheima.ms.mapper;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.itheima.ms.po.User;

public class UserMapperTest {
    // spring上下文
    private ApplicationContext ctx;

    @Before
    public void setUp() throws Exception {
        // 讀取spring的上下文,而後封裝到ctx
        ctx = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
    }

    @Test
    public void testFindUserById() {
        // 建立UserMapper對象
        UserMapper userMapper = (UserMapper) ctx.getBean("userMapper");
        // 調用UserMapper對象的方法
        User user = userMapper.findUserById(30);
        System.out.println(user);
    }

}

五、Mybatis的逆向工程(會用就行)

5.一、什麼是逆向工程

  • 簡單點說,就是經過數據庫中的單表,自動生成java代碼。
  • Mybatis官方提供了逆向工程,能夠針對單表自動生成mybatis代碼(好比:mapper.java、mapper.xml、po類)
  • 企業開發中,逆向工程是個很經常使用的工具。

5.二、下載逆向工程

  • 下載連接:https://github.com/mybatis/generator/releases/tag/mybatis-generator-1.3.2

5.三、使用方法

  • 一、建立逆向工程(普通的java工程便可),導入jar包添加至構建路徑
  • 二、建立generator配置文件
  • 三、使用java類來執行逆向工程
  • 四、把生成的代碼拷貝到項目中
  • 五、在正式項目中使用逆向工程生成的代碼

5.3.一、建立逆向工程(普通的java工程便可),導入jar包添加至構建路徑

5.3.二、建立generator配置文件

  在config下,建立generatorConfig.xml配置文件:
  文件內容能夠從逆向工程的jar包中docs目錄下的index.html中找到相關代碼
  文件內容位置:mybatis-generator-core-1.3.2/docs/configreference/xmlconfig.html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="testTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自動生成的註釋 true:是,false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!--MySql數據庫鏈接的信息:驅動類、鏈接地址、用戶名、密碼 -->
        <jdbcConnection 
            driverClass="com.mysql.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/mybatis" 
            userId="root"
            password="root">

        </jdbcConnection>
        <!-- Oracle數據庫鏈接的信息:驅動類、鏈接地址、用戶名、密碼  -->
        <!-- 
        <jdbcConnection 
            driverClass="oracle.jdbc.OracleDriver" 
            connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg" 
            userId="yycg" 
            password="yycg"> 
        </jdbcConnection> 
        -->


        <!-- 默認 false時,把JDBC DECIMAL 和 NUMERIC 類型解析爲 Integer
             設置爲true時,把JDBC DECIMAL 和 NUMERIC 類型解析爲 java.math.BigDecimal 
        -->

        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- targetProject:生成PO類的位置 -->
        <javaModelGenerator targetPackage="com.itheima.ms.po" targetProject=".\src">
            <!-- enableSubPackages:是否讓schema做爲包的後綴 -->
            <property name="enableSubPackages" value="false" />
            <!-- 從數據庫返回的值被清理先後的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- targetProject:mapper映射文件生成的位置 -->
        <sqlMapGenerator targetPackage="com.itheima.ms.mapper" targetProject=".\src">
            <!-- enableSubPackages:是否讓schema做爲包的後綴 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>
        <!-- targetPackage:mapper接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.itheima.ms.mapper" targetProject=".\src">
            <!-- enableSubPackages:是否讓schema做爲包的後綴 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>

        <!-- 指定要生成mybatis代碼的數據庫表 -->
        <table tableName="items"></table>
        <table tableName="orders"></table>
        <table tableName="orderdetail"></table>
        <table tableName="user"></table>
    </context>
</generatorConfiguration>

5.3.三、使用Generator類來執行逆向工程

  文件內容位置:mybatis-generator-core-1.3.2/docs/running/runningWithJava.html
Generator.java

public class Generator {
    public static void main(String[] args) throws Exception {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("config/generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }
}

  小問題:經過逆向工程生成的代碼會有一個報錯,是由於缺乏mybatis的jar包致使的,雖然不影響咱們使用,可是爲了好看,咱們導入mybatis-3.2.7.jar,並添加至構建路徑,便可解決問題。

5.3.四、將逆向工程生成的代碼拷貝到指定項目中

  • 若是正式項目中已經有po類所在的包了,那麼就只須要拷貝po類到指定包下就能夠。
  • 若是正式項目中沒有po包,那麼就把逆向工程中整個po類的包拷貝過去。
  • Mapper.xml和mapper.java的拷貝與po類同樣。

5.3.五、使用逆向工程生成的代碼

  小需求:根據商品名稱查詢商品記錄

public class ItemsMapperTest {
    // spring上下文
    private ApplicationContext ctx;

    @Before
    public void setUp() throws Exception {
        // 讀取spring的上下文,而後封裝到ctx
        ctx = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
    }

    @Test
    public void testSelectByExample() {
        // 建立ItemsMapper對象
        ItemsMapper itemsMapper = (ItemsMapper) ctx.getBean("itemsMapper");

        ItemsExample example = new ItemsExample();
        // 使用ItemsExample對象建立離線查詢對象
        Criteria criteria = example.createCriteria();
        // 使用離線查詢對象Criteria來封裝查詢條件
        criteria.andNameLike("%包%"); // 由於模糊查詢的實現和等值查詢的實現式同樣的,用到的都是#{xxx},因此須要咱們手動添加%
        // 調用ItemsMapper對象的方法
        List<Items> list = itemsMapper.selectByExample(example);

        System.out.println(list);
    }

}

  SQL輸出結果,以下圖所示:

5.四、注意事項

  • Mapper.xml文件已經存在時,若是進行從新生成則mapper.xml文件時,內容不被覆蓋而是進行內容追加,結果致使mybatis解析失敗。
  • 解決方法:刪除原來已經生成的mapper.xml文件再進行生成。
  • Mybatis自動生成的po及mapper.java文件不是內容而是直接覆蓋沒有此問題。
相關文章
相關標籤/搜索