MyBatis進階

来源:https://www.cnblogs.com/yangyuanhu/archive/2019/12/31/12127034.html
-Advertisement-
Play Games

Mapper代理 "上一節" 中直接利用session+id來執行sql的方式存在一些問題 session執行sql時都需要提供要執行sql的id,而這個id是字元串類型,意味著id是否正確在編譯期間是無法獲知的,必須等到運行時才能發現錯誤, sql需要的參數和返回值類都不明確,這也增加了出錯的概率 ...


Mapper代理

上一節中直接利用session+id來執行sql的方式存在一些問題

  • session執行sql時都需要提供要執行sql的id,而這個id是字元串類型,意味著id是否正確在編譯期間是無法獲知的,必須等到運行時才能發現錯誤,
  • sql需要的參數和返回值類都不明確,這也增加了出錯的概率

理想的狀況:像調用方法一樣調用sql,既避免了直接寫id的問題,也可以明確指定方法的參數類型和返回值類型

解決方案:

MyBatis通過動態代理來解決,簡單的說動態代理(動態生成),就是在運行過程中自動產生一個對象,用它來代理原本已經存在的對象的方法

MyBatis中本來由Executor(被代理對象)來完成sql的執行,現在由代理對象(自動生成)來代理Executor完成,代理對象會將我們的操作轉交給Executor

問題是:MyBatis怎麼知道代理對象是什麼樣的對象呢?,這就需要為MyBatis提供Mapper介面,這個介面就是對mapper.xml中的sql語句的聲明,與DAO層的介面一毛一樣

使用步驟

  1. 創建介面類

    package com.kkb.mapper;
    import com.kkb.pojo.Products;
    public interface ProductsMapper {
        //根據name查詢一個Products
        public Products selectProductByName(String name);
    }
  2. 提供響應的sql映射

    <mapper namespace="com.kkb.mapper.ProductsMapper">
        <select id="selectProductByName" parameterType="string" resultType="com.kkb.pojo.Products">
            select *from products where pname = #{name}
        </select>
    </mapper>
  3. 獲取代理對象 執行方法完成操作

    @Test
    public void proxyTest(){
        SqlSession session = factory.openSession();
        ProductsMapper mapper = session.getMapper(ProductsMapper.class);
        Products product = mapper.selectProductByName("泰國咖喱");
        System.out.println(product);
        session.close();
    }

註意事項:

  • 必須保證mapper.xml中的namespace與介面的全限定名稱一致
  • 方法的名稱必須與對應的sql statement的id一致
  • 方法的參數必須與對應的sql statement的parameterType一致
  • 方法的返回值必須與對應的sql statement的resultType一致

XML配置

MyBatis 的配置文件包含了會深深影響 MyBatis 行為的設置和屬性信息。 配置文檔的頂層結構如下:

  • configuration(配置)
    • properties(屬性)
    • settings(設置)
    • typeAliases(類型別名)
    • typeHandlers(類型處理器)
    • objectFactory(對象工廠)
    • plugins(插件)
    • environments(環境配置)
      • environment(環境變數)
        • transactionManager(事務管理器)
        • dataSource(數據源)
    • databaseIdProvider(資料庫廠商標識)
    • mappers(映射器)

註意配置文件各個節點個層次是固定,需按照上述的順序書寫否則報錯,

單獨使用MyBatis的場景是很少的,後續都會將其與Spring進行整合,並由Spring來對MyBatis進行配置,加粗的為需要重點關註,其餘的瞭解即可,若遇到特殊需求可查閱官方文檔

屬性(properties)

properties可從配置文件或是properties標簽中讀取需要的參數,使得配置文件各個部分更加獨立

內部properties標簽

image-20191229194337146

外部配置文件

jdbc.properties位於resource下

driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql:///mybatisDB?serverTimezone=Asia/Shanghai&characterEncoding=utf8
user = root
password = admin

引用方式

image-20191229194607724

當內部和外部屬性出現同名時,則優先使用外部的;

別名(typeAliases)

typeAliases用於為Java的類型取別名,從而簡化mapper中類名的書寫

為某個類定義別名

image-20191229195926617

在mapper.xml中就可以直接使用別名 不區分大小寫

image-20191229195501526

當指定package時將批量為包下所有類指定別名為類名小寫

<typeAliases>
    <package name="com.kkb.pojo"/>
</typeAliases>

使用package批量設置時很容易出現別名衝突,這是就需要使用@Alias註解來為衝突的類單獨設置別名

@Alias("products1")
public class Products {
    private int pid;
    private String pname;
    private float price;
    private Date pdate;
    private String cid;
.....}

下麵列出MyBatis已存在的別名

_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
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

映射(Mappers)

在mapper.xml文件中定義sql語句後,就必須讓MyBatis知道,到哪裡去找這些定義好的sql,這就需要在配置文件中指出要載入的mapper的位置;MyBatis支持4種方式來載入mapper文件

 <mappers>
<!--        1.指定資源文件的相對目錄  相對於classpath
            maven項目會自動將java和resources都添加到classpath中
            所以相對與resources來指定路徑即可-->
        <mapper resource="mapper/ProductsMapper.xml"/>

<!--        2.指定文件的絕對路徑,MyBatis支持但是一般不用-->
        <mapper url="file:///Users/jerry/Downloads/MYB/src/main/resources/mapper/ProductsMapper.xml"/>

<!--        3.通過指定介面 讓MyBatis自動去查找對應的Mapper
            這種方式要求映射文件和介面處於同一目錄下,並且名稱相同
            要保證上述要求只需要在resources下創建於介面包名相同的目錄即可
            註意:運行前先clean,否則可能因為之前已經存在target而不會重新編譯,導致無法載入新建的mapper文件
            -->
        <mapper class="com.kkb.mapper.ProductsMapper"/>

<!--        4.指定包名稱,掃描包下所有介面和映射文件
            這種方式同樣要求映射文件和介面處於同一目錄下,並且名稱相同-->
        <package name="com.kkb.mapper"/>
    </mappers>

第3,4種方式映射文件目錄示例:

image-20191231104520663

動態SQL

動態SQL指的是SQL語句不是固定死的,可以根據某些條件而發生相應的變化,這樣的需求非常多見

例如:頁面提供的搜索功能,要根據用戶給出的一個或多個條件進行查詢

image-20191231120319174

在JDBC時代,我們通過Java代碼來判斷某個條件是否有效然後拼接SQL語句,這是非常繁瑣的,MyBatis的動態SQL很好的解決了這個問題,使判斷邏輯變得簡潔直觀;

在Mapper中通過標簽來完成動態SQL的生成

1. if

從頁面接受參數後我們會將參數打包為對象,然後將對象作為參數傳給MyBatis執行查詢操作,sql語句需要根據是否存在參數而動態的生成

mapper:

<!-- 根據姓名或cid進行搜索-->
<select id="searchProducts" parameterType="products" resultType="products">
    select *from products
            where 1=1
        <if test="pname != null">
            and pname like '%${pname}%'
        </if>
        <if test="cid != null">
            and cid = #{cid}
        </if>
</select>

測試:

@Test
public void searchTest(){
    SqlSession session = factory.openSession();
    ProductsMapper mapper = session.getMapper(ProductsMapper.class);
    //查詢條件對象
    Products condition = new Products();
    condition.setName("新疆");
    condition.setCid("s001");
    //執行查詢
    List<Products> product = mapper.searchProducts(condition);
    System.out.println(product);
    session.close();
}

where1=1用於保持sql的正確性,當不存在條件時,則查詢全部

2.where

where的作用就是用於取出上面的where 1=1,因為這會讓人看起來產生疑惑,其作用是將內部語句中的第一個and去除

<select id="searchProducts" parameterType="products" resultType="products">
    select *from products
    <where>
        <if test="pname != null">
            and pname like '%${pname}%'
        </if>
        <if test="cid != null">
            and cid = #{cid}
        </if>
    </where>
</select>

3. foreach

當一個條件中中需要需要多個參數時則需要將多個參數拼接到一起,例如: in, not in

mapper:

<select id="searchProducts" parameterType="products" resultType="products">
    select *from products
    <where>
        <if test="pname != null">
            and pname like '%${pname}%'
        </if>
        <if test="cid != null">
            and cid = #{cid}
        </if>
        <if test="ids != null">
            <foreach collection="ids" open="and pid in (" close=")" separator="," item="id" index="i">
                #{id}
            </foreach>
        </if>
    </where>
</select>
<!--
<if test="ids != null"> 這裡不僅判斷屬性是否為空還判斷集合中是否有元素
foreache 標簽屬性說明:
    強調:動態sql本質就是在拼接字元串,帶著自己拼接sql的思路來編寫動態sql會更好理解
  collection    要遍歷的集合
  open              拼接的首碼
  close             拼接的尾碼
  separator     拼接元素之間的分隔符
  item              遍歷得到的臨時變數名
  index             當前元素的索引(不常用)
-->

測試代碼:

@Test
public void searchTest(){
    SqlSession session = factory.openSession();
    ProductsMapper mapper = session.getMapper(ProductsMapper.class);
    //查詢條件對象
    Products condition = new Products();
    int[] ids = new int[]{1,2,3,4,5,};
    condition.setIds(ids);
    //執行查詢
    List<Products> product = mapper.searchProducts(condition);
    System.out.println(product);
    session.close();
}

註意需要為POJO(Products)對象增加ids屬性

4. set

set標簽用於更新語句,當同事要更新多個欄位時,我們需要留意當前是否是最後一個set,避免在後面出現,符號,使用set標簽後可自動去除最後的逗號

mapper:

<update id="updateProductTest" parameterType="products">
    update products
    <set>
        <if test="pname != null and pname != ''">
            pname = #{pname},
        </if>
        <if test="price != null and price > 0">
            price = #{price},
        </if>
        <if test="pdate != null">
            pdate = #{pdate},
        </if>
        <if test="cid != null and cid != ''">
            cid = #{cid},
        </if>
    </set>
    where pid = #{pid}
</update>

測試代碼:

@Test
public void updateTest2(){
    SqlSession session = factory.openSession();
    ProductsMapper mapper = session.getMapper(ProductsMapper.class);
    //獲取已有對象
    Products product = mapper.selectProductById(7);
    product.setPname("雲南小土豆");
    product.setPrice(10.5f);
    //執行更新
    mapper.updateProductTest(product);
    System.out.println(product);
    session.commit();
    session.close();
}

5. sql與include

Sql中可將重覆的sql提取出來,使用時用include引用即可,最終達到sql重用的目的。 例如可以吧sql語句中的欄位列表提取出來作為通用的sql片段。然後在sql語句中使用 include 節點引用這個sql片段。

<!--提取片段-->
<sql id="fields">pid,pname,price,cid</sql>

<select id="includeTest" resultType="products">
    select
    <include refid="fields"/> <!-- 引用片段-->
    from products
</select>

高級映射

在一些情況下資料庫的記錄和POJO對象無法直接映射,包括兩種情形:

  • 資料庫欄位與POJO欄位名稱不同(可以避免);
  • 關聯查詢時,需要將關聯表的數據映射為另一個類型的POJO(一對一),或List中(一對多);

在MyBatis中通過resultMap來完成自定義映射

1.自定義欄位與屬性映射

先將Products表中的欄位更名為p_xxx,如下所示:

image-20191231144158478

mapper:

<!--自定義映射關係 id:該映射關係的標識    type:映射到的POJO類型此處為別名-->
<resultMap id="product_resultMap" type="products">
    <!--主鍵-->
    <id column="p_id" property="pid"/>
    <!--其他欄位-->
    <result column="p_name" property="pname"/>
    <result column="p_price" property="price"/>
    <result column="p_date" property="pdate"/>
    <result column="p_cid" property="cid"/>
</resultMap>
<!--引用映射關係-->
<select id="selectProductsCustomMapping" resultMap="product_resultMap">
    select *from products
</select>

2. 關聯查詢

2.1 關聯關係

兩個表之間記錄的對應關係,分為一對一和一對多,而多堆多則是三張表之間的關係,若掌握了兩張表之間的一對多關係的處理,則多堆多也就不是問題了,因為本質上多對多就是兩個一對多組成的

案例表:

image-20191231152140019

這兩張表之間存在一對一和一對多

站在user表角度來看 一個用戶可能對應多個訂單即一對一

站在order表角度來看 一個訂單隻能對應一個用戶即一對多

POJO類:

Order.java

import java.util.Date;
public class Order {
    private int id,user_id;
    private String number;
    private Date createtime;
    private String note;
    private User user;
      //get/set....
}

User.java

import java.util.Date;
import java.util.List;
public class User {
    private int id;
    private String name;
    private Date birthday;
    private String sex,address;
    private List<Order> orders;
    public User() {
    }
  //get/set....
}

2.2 一對一映射

需求:根據訂單編號查詢訂單信息以及用戶名稱和地址

sql語句為:

select  o.*,u.username,u.address 
from orders o  join kuser u 
on o.user_id = u.id 
where o.id = 8;
#要查詢的欄位根據需求來定,這裡只需要查詢用戶的姓名和地址

OrdersMapper.xml:

<!--自定義映射-->
<resultMap id="order_resultMap" type="com.kkb.pojo.Order">
<!--orders表映射-->
    <id column="id" property="id"/>
    <result column="number" property="number"/>
    <result column="createtime" property="createtime"/>
    <result column="note" property="note"/>
    <!--關聯的user表映射-->
    <association property="user" javaType="user">
        <id column="user_id" property="id"/>
        <result column="username" property="name"/>
        <result column="address" property="address"/>
    </association>
</resultMap>

<select id="selectOrderByID" parameterType="int" resultMap="order_resultMap">
    select
        o.*,u.username,u.address
    from
        orders o  join kuser u
    on
        o.user_id = u.id
    where
        o.id = #{oid};
</select>

測試代碼:

@Test
public void test(){
    SqlSession session = factory.openSession();
    OrdersMapper mapper = session.getMapper(OrdersMapper.class);//記得提供映射介面
    Order order = mapper.selectOrderByID(8);
    System.out.println(order);
    session.close();
}

補充:當連接查詢出現重覆欄位時如:兩個表都有id欄位,MyBatis簡單的取第一個匹配的欄位值,很多時候這是不正確的,我們可以給這個重覆的欄位取個別名來避免,像這樣:

<select id="selectOrderByID" parameterType="int" resultMap="order_resultMap">
    select
        u.*,o.*,o.id oid
    from
        orders o  join kuser u
    on
        o.user_id = u.id
    where
        o.id = #{oid};
</select>

理所當然的 在resultMap中則使用oid來進行映射

<id column="oid" property="id"/>

2.3 一對多映射

需求:根據用戶編號查詢用戶信息以及用戶所有訂單信息

sql語句為:

 select u.*,o.*,o.id oid
 from kuser u left join orders o
 on o.user_id = u.id
 where u.id = 1;

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.kkb.mapper.UserMapper">

    <!--自定義映射-->
    <resultMap id="user_resultMap" type="user" autoMapping="true">
        <result column="username" property="name"/>
        <collection property="orders"  ofType="order" autoMapping="true">
            <result column="oid" property="id"/>
        </collection>
    </resultMap>

    <select id="selectUserByID" parameterType="int" resultMap="user_resultMap">
        select
            u.*,o.*,o.id oid
        from
            kuser u left join orders o
        on
            o.user_id = u.id
        where
            u.id = #{uid}
        </select>
</mapper>

autoMapping="true" 將自動映射欄位名與屬性名能對應的欄位,我們只需要添加對應不上的即可

測試代碼:

@Test
public void test2(){
    SqlSession session = factory.openSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectUserByID(1);
    System.out.println(user);
    session.close();
}

註意:無論如何在collection標簽中必須至少存在一個手動映射欄位否則,將不會合併重覆的主記錄(user) 按照官方的說法,建議手動映射id欄位,可提高整體性能:去看看

另外collection標簽中 ofType用於指定元素的類型 javaType指定容器類型

2.4嵌套映射,select

當關聯查詢非常複雜時,可以用嵌套的select,其原理是在映射複雜數據時執行另一個select來完成

<resultMap id="order_resultMap2" type="Order" autoMapping="true">
    <id column="id" property="id"/>
  <!-- 指定嵌套查詢 column是傳給內層查詢的參數 -->
    <association property="user" column="user_id" select="selectUserByUid" javaType="user"/>
</resultMap>

<!-- 外層查詢-->
<select id="selectOrderByID2" parameterType="int" resultMap="order_resultMap2">
    select
        *
    from
        orders
    where
        id = #{id}
</select>

<!-- 嵌套查詢-->
<select id="selectUserByUid" parameterType="int" resultType="user">
    select *from kuser where id = #{id}
</select>

這種方式同樣適用於一對多的關聯關係

<resultMap id="blogResult" type="Blog">
  <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.泛型類 普通的類 這樣的代碼是完全可以執行了,那為什麼還需要泛型類? 1.安全性 上面的代碼編譯是完全可以通過的,但是執行的時候就會出現ClassCastException異常 2.可讀性好,省去了反覆的強制類型轉換。 對於泛型類,java編譯器會將泛型代碼轉換成普通的非泛型代碼, 所以對於虛擬 ...
  • 一、Spring Data JPA 1、簡介 (1)官網地址: https://spring.io/projects/spring-data-jpa參考文檔: https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/ht ...
  • 本篇文章主要介紹PHP+swoole實現聊天群發功能,感興趣的朋友參考下,希望對大家有所幫助。 php代碼: $serv = new swoole_websocket_server("127.0.0.1",3999); //服務的基本設置 $serv->set(array( 'worker_num' ...
  • 這篇文章主要介紹了關於PHP實現微信網頁登陸授權開發,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下 更多PHP相關知識請關註我的專欄PHP​zhuanlan.zhihu.com 微信開放平臺和公眾平臺的區別 1.公眾平臺面向的時普通的用戶,比如自媒體和媒體,企業官方微信公眾賬號運營人 ...
  • 程式入口 SpringApplication.run(BeautyApplication.class, args); 執行此方法來載入整個SpringBoot的環境。 1. 從哪兒開始? SpringApplication.java /** * Run the Spring application, ...
  • 責編 | 劉靜 天氣降溫,感情卻升溫了? 上午剛到公司,就收到小Q發來的靈魂拷問: ​ ​ “Q仔!要不然下午請個假!我帶你去精神科看看!?”我實在忍不了,脫口而出。 話音未落,前排的運營小花回頭看向小Q,莞爾一笑,百媚橫生。 ​ 這個悶騷小伙子什麼時候勾搭上運營一枝花了?我正要追問,小Q看穿了我的 ...
  • Talk is cheap, show me the code! 以上這段mybatis的入門案例代碼,相信每一個瞭解mybatis的朋友都能看得懂,知碼醬同學今天也細細品了品! 1. 項目的路徑問題 : 在實際的項目中,並不推薦眾所周知的相對路徑和絕對路徑。 相對路徑: web應用是需要部署到服務 ...
  • 一、生成表格1.創建模型類(在 models.py文件中創建一個person類並且繼承models.Models類) 2.生成表格(在項目目錄下)(1)生成遷移文件:在pycharm下方的命令行Terminal中寫入python manage.py makemigrations,回車鍵後顯示遷移文件 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...