MyBatis常見問題

来源:https://www.cnblogs.com/yaomagician/archive/2023/04/07/17297300.html
-Advertisement-
Play Games

Mybatis常見問題 1,大於號、小於號在sql語句中的轉換 使用 mybatis 時 sql 語句是寫在 xml 文件中,如果 sql 中有一些特殊的字元的話,比如< ,<=,>,>=等符號,會引起 xml 格式的錯誤,需要替換掉,或者不被轉義。 有兩種方法可以解決:轉義字元和標記 CDATA ...


Mybatis常見問題

1,大於號、小於號在sql語句中的轉換

使用 mybatis 時 sql 語句是寫在 xml 文件中,如果 sql 中有一些特殊的字元的話,比如< ,<=,>,>=等符號,會引起 xml 格式的錯誤,需要替換掉,或者不被轉義。 有兩種方法可以解決:轉義字元和標記 CDATA 塊。

方式一:轉義字元

<select id="searchByPrice" parameterType="Map" resultType="Product">
    <!-- 方式1、轉義字元 -->
    select * from Product where price &gt;= #{minPrice} and price &lt;= #{maxPrice}
 </select>

方式二:標記CDATA

<select id="searchByPrice" parameterType="Map" resultType="Product">
   <!-- 方式2、CDATA -->
  <![CDATA[select * from Product where price >= #{minPrice} and price <= #{maxPrice} ]]> </select>

轉義字元表

2.傳入參數時參數為0查詢條件失效

場景案例

場景是這樣的,需要做一個對賬單查詢,可以按金額範圍進行查詢,頁面參數寫完之後進行條件,輸入0測試了無數次均失效。

原因解析

當頁面參數為0,傳入到mybatis的xml中後,如果不是字元串,需指定數據類型,否則會被誤認為null

<if test="data.tatalAmount != null and data.totalAmount !='' ">
and total_Amount=#{data.totalAmount}
</if>

這種情況如果totalAmount為0時將被誤認為是null,裡面的條件不會被執行。

解決方案

1,添加0判斷

<if test="data.tatalAmount != null and data.totalAmount !='' or tatalAmount==0 ">
and total_Amount=#{data.totalAmount}
</if>

2,規定傳入參數的類型

<if test="data.tatalAmount != null and data.totalAmount !='' ">
and total_Amount=#{data.totalAmount,jdbc.Type=DECIMAL}
</if>

3,Mybatis中#{}和${}區別

#{}是預編譯處理,像傳進來的數據會加個" "(#將傳入的數據都當成一個字元串,會對自動傳入的數據加一個雙引號)

${}就是字元串拼接。直接替換掉占位符。$方式一般用於傳入資料庫對象,例如傳入表名。

使用${}的話會導致sql註入。什麼是sql註入呢?比如select * from user where id = #{value}

value本應該是一個數值。然後如果對方傳過來的是 001 and name = tom.這樣不就相當於多加了一條sql語句進去。

把SQL語句直接寫進來了。如果是攻擊性的語句呢?001;drop table user,直接把表給刪了

所以為了防止 SQL 註入,能用 #{} 的不要去用 ${}

 

如果非要用 ${} 的話,那要註意防止 SQL 註入問題,可以手動判定傳入的變數,進行過濾,一般 SQL 註入會輸入很長的一條 SQL 語句

4,Mybatis動態sql語句(OGNL語法)

1、if

解決當要查詢的多個條件有一個為空而導致的查詢結果為空的情況

<select id="select" resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="name!= null">
    AND name like #{title}
  </if>
</select>

2、where

像上面的那種情況,如果where後面沒有條件,然後需要直接寫if判斷(開頭如果是 and / or 的話,會去除掉)

<select id="select" resultType="Blog">
  SELECT * FROM BLOG
  <where>
      <if test="title != null">
        AND title like #{title}
      </if>
      <if test="name!= null">
        AND name like #{title}
      </if>
  <where>
</select>

3、choose(when、otherwise)

choose 相當於 java 裡面的 switch 語句。otherwise(其他情況)

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

4、trim

prefix:首碼prefixoverride:去掉第一個and或者是or

select * from test
<trim prefix="WHERE" prefixoverride="AND丨OR">
      <if test="a!=null and a!=' '">AND a=#{a}<if>
      <if test="b!=null and b!=' '">AND a=#{a}<if>
</trim>

 

5、set

set 元素主要是用在更新操作的時候,如果包含的語句是以逗號結束的話將會把該逗號忽略,如果set包含的內容為空的話則會出錯。

<update id="dynamicSetTest" parameterType="Blog">  
    update t_blog  
    <set>  
        <if test="title != null">  
            title = #{title},  
        </if>  
        <if test="content != null">  
            content = #{content},  
        </if>  
        <if test="owner != null">  
            owner = #{owner}  
        </if>  
    </set>  
    where id = #{id}  
</update> 

6、foreach

foreach主要用在構建in條件中

<select id="dynamicForeachTest" resultType="Blog">  
        select * from t_blog where id in  
        <foreach collection="list" index="index" item="item" open="(" separator="," close=")">  
            #{item}  
        </foreach>  
    </select>  

open separator close

相當於是in (?,?,?)

如果是個map怎麼辦

<select id="dynamicForeach3Test" resultType="Blog">  
        select * from t_blog where title like "%"#{title}"%" and id in  
        <foreach collection="ids" index="index" item="item" open="(" separator="," close=")">  
            #{item}  
        </foreach>  
    </select>  

collection對應map的鍵,像這樣

List<Integer> ids = new ArrayList<Integer>();  
        ids.add(1);  
        ids.add(2);  
        ids.add(3);  
        ids.add(6);  
        ids.add(7);  
        ids.add(9);  
        Map<String, Object> params = new HashMap<String, Object>();  
        params.put("ids", ids);  

5,Like模糊查詢

方式一

$ 這種方式,簡單,但是無法防止SQL註入,所以不推薦使用

LIKE '%${name}%'

方式二

LIKE "%"#{name}"%"

方式三:字元串拼接

AND name LIKE CONCAT(CONCAT('%',#{name},'%'))

方式四:bind標簽

 

<select id="searchStudents" resultType="com.example.entity.StudentEntity"
  parameterType="com.example.entity.StudentEntity">
  <bind name="pattern1" value="'%' + _parameter.name + '%'" />
  <bind name="pattern2" value="'%' + _parameter.address + '%'" />
  SELECT * FROM test_student
  <where>
   <if test="age != null and age != '' and compare != null and compare != ''">
    age
    ${compare}
    #{age}
   </if>
   <if test="name != null and name != ''">
    AND name LIKE #{pattern1}
   </if>
   <if test="address != null and address != ''">
    AND address LIKE #{pattern2}
   </if>
  </where>
  ORDER BY id
 </select>

6,傳遞多個參數

方法一:使用map介面傳遞參數

嚴格來說,map適用幾乎所有場景,但是我們用得不多。原因有兩個:首先,map是一個鍵值對應的集合,使用者要通過閱讀它的鍵,才能明瞭其作用;其次,使用map不能限定其傳遞的數據類型,因此業務性質不強,可讀性差,使用者要讀懂代碼才能知道需要傳遞什麼參數給它,所以不推薦用這種方式傳遞多個參數。

public List<Role> findRolesByMap(Map<String, Object> parameterMap);
<select id="findRolesByMap" parameterType="map" resultType="role">
    select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>

方法二:使用註解傳遞多個參數  

MyBatis為開發者提供了一個註解@Param(org.apache.ibatis.annotations.Param),可以通過它去定義映射器的參數名稱,使用它可以得到更好的可讀性  這個時候需要修改映射文件的代碼,此時並不需要給出parameterType屬性,讓MyBatis自動探索便可以了  使可讀性大大提高,使用者也方便了,但是這會帶來一個麻煩。如果SQL很複雜,擁有大於10個參數,那麼介面方法的參數個數就多了,使用起來就很不容易,不過不必擔心,MyBatis還提供傳遞Java Bean的形式。

public List<Role> findRolesByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
<select id="findRolesByAnnotation" resultType="role">
    select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>

方法三:通過Java Bean傳遞多個參數

public List<Role> findRolesByBean(RoleParams roleParam);
<select id="findRolesByBean" parameterType="com.xc.pojo.RoleParams" resultType="role">
    select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>

方法四:混合使用  

在某些情況下可能需要混合使用幾種方法來傳遞參數。舉個例子,查詢一個角色,可以通過角色名稱和備註進行查詢,與此同時還需要支持分頁

public List<Role> findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParam PageParam);
<select id="findByMix" resultType="role">
    select id, role_name as roleName, note from t_role
    where role_name like concat('%', #{params.roleName}, '%') and note like concat('%', #{params.note}, '%') 
     limit #{page.start}, #{page.limit}
</select>

 

總結:

描述了4種傳遞多個參數的方法,對各種方法加以點評和總結,以利於我們在實際操作中的應用。

  •使用 map 傳遞參數導致了業務可讀性的喪失,導致後續擴展和維護的困難,在實際的應用中要果斷廢棄這種方式。

  •使用 @Param 註解傳遞多個參數,受到參數個數(n)的影響。當 n≤5 時,這是最佳的傳參方式,它比用 Java Bean 更好,因為它更加直觀;當 n>5 時,多個參數將給調用帶來困難,此時不推薦使用它。

  •當參數個數多於5個時,建議使用 Java Bean 方式。

  •對於使用混合參數的,要明確參數的合理性。

7,MyBatis緩存機制

緩存機制減輕資料庫壓力,提高資料庫性能

mybatis的緩存分為兩級:一級緩存、二級緩存

一級緩存:

一級緩存為 SqlSession 緩存,緩存的數據只在 SqlSession 內有效。在操作資料庫的時候需要先創建 SqlSession 會話對象,在對象中有一個 HashMap 用於存儲緩存數據,此 HashMap 是當前會話對象私有的,別的 SqlSession 會話對象無法訪問。

具體流程:

第一次執行 select 完畢會將查到的數據寫入 SqlSession 內的 HashMap 中緩存起來

第二次執行 select 會從緩存中查數據,如果 select 同傳參數一樣,那麼就能從緩存中返回數據,不用去資料庫了,從而提高了效率

註意:

1、如果 SqlSession 執行了 DML 操作(insert、update、delete),並 commit 了,那麼 mybatis 就會清空當前 SqlSession 緩存中的所有緩存數據,這樣可以保證緩存中的存的數據永遠和資料庫中一致,避免出現差異

2、當一個 SqlSession 結束後那麼他裡面的一級緩存也就不存在了, mybatis 預設是開啟一級緩存,不需要配置

3、 mybatis 的緩存是基於 [namespace:sql語句:參數] 來進行緩存的,意思就是, SqlSession 的 HashMap 存儲緩存數據時,是使用 [namespace:sql:參數] 作為 key ,查詢返回的語句作為 value 保存的

二級緩存:

二級緩存是mapper 級別的緩存,也就是同一個 namespace 的 mapper.xml ,當多個 SqlSession 使用同一個 Mapper 操作資料庫的時候,得到的數據會緩存在同一個二級緩存區域

二級緩存預設是沒有開啟的。需要在 setting 全局參數中配置開啟二級緩存

開啟二級緩存步驟:

1、conf.xml 配置全局變數開啟二級緩存

<settings>
    <setting name="cacheEnabled" value="true"/>預設是false:關閉二級緩存
<settings>

2、在userMapper.xml中配置

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>當前mapper下所有語句開啟二級緩存

這裡配置了一個 FIFO緩存,並每隔60秒刷新,最大存儲512個對象,而返回的對象是只讀的,因此在不同線程中的調用者之間修改它們會導致衝突。可用的收回策略有,預設的是LRU

 

  1. LRU- 最近最少使用的;移除最長時間不被使用的對象。

  2. FIFO- 先進先出;按對象進入緩存的順序來移除它們。

  3. SOFT- 軟引用;移除基於垃圾回收器狀態和軟引用規則的對象

  4. WEAK- 弱引用;更積極地移除基幹垃圾收集器狀態和弱引用規則的對象

 

若想禁用當前select語句的二級緩存,添加 useCache="false"修改如下:

<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">

具體流程:

1.當一個sqlseesion執行了一次select 後,在關閉此session 的時候,會將查詢結果緩存到二級緩存

2.當另一個sqlsession執行select 時,首先會在他自己的一級緩存中找,如果沒找到,就回去二級緩存中找,找到了就返回,就不用去資料庫了,從而減少了資料庫壓力提高了性能

註意:

1、如果 SqlSession 執行了 DML 操作(insert、update、delete),並 commit 了,那麼 mybatis 就會清空當前mapper 緩存中的所有緩存數據,這樣可以保證緩存中的存的數據永遠和資料庫中一致,避免出現差異

2、mybatis 的一級緩存是基於[namespace:sql語句:參數]來進行緩存的,意思就是,SqlSessionHashMap 存儲緩存數據時,是使用 [namespace:sql:參數]作為 key ,查詢返回的語句作為 value 保存的。

是否應該使用二級緩存?

那麼究竟應該不應該使用二級緩存呢?先來看一下二級緩存的註意事項:

  1. 緩存是以namespace為單位的,不同namespace下的操作互不影響。

  2. insert,update,delete操作會清空所在namespace下的全部緩存。

  3. 通常使用MyBatis Generator生成的代碼中,都是各個表獨立的,每個表都有自己的namespace

  4. 多表操作一定不要使用二級緩存,因為多表操作進行更新操作,一定會產生臟數據。

如果你遵守二級緩存的註意事項,那麼你就可以使用二級緩存。

但是,如果不能使用多表操作,二級緩存不就可以用一級緩存來替換掉嗎?而且二級緩存是表級緩存,開銷大,沒有一級緩存直接使用 HashMap 來存儲的效率更高,所以二級緩存並不推薦使用

8,MyBatis時間timestamp做條件進行查詢

首先要將條件 轉換為 時間戳

long startTime = TimeUtil.parseTimestamp(start);
long endTime = TimeUtil.parseTimestamp(end);
 
/*對應工具類*/
public static long parseTimestamp(String datetime){
    try{
    SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date date = dateformat.parse(datetime);
    return date.getTime()/1000;         
    }catch(Exception e){
        e.printStackTrace();
    }
    return 0;
}

然後Mapper.xml中 使用BETWEEN and 和 to_timestamp

<if test="startDate !=null and startDate !='' and endDate !=null and endDate !=''">
            AND tdnm.create_time BETWEEN to_timestamp(#{startDate}) AND to_timestamp(#{endDate})
</if>

9,mybatis 是否支持延遲載入?延遲載入的原理是什麼?

1.MyBatis 支持延遲載入。

2.什麼是延遲載入:延遲載入,也稱為懶載入,是指在進行關聯查詢時,按照設置延遲規則推遲對關聯對象的select查詢。延遲載入可以有效的減少資料庫壓力。MyBatis的延遲載入只是對關聯對象的查詢有延遲設置,對於主載入對象都是直接執行查詢語句的。

3.MyBatis 對關聯對象的載入類型

(1)直接載入:執行完對主載入對象的select語句,馬上執行對關聯對象的select查詢。

(2)侵入式延遲:執行對主載入對象的查詢時,不會執行對關聯對象的查詢。但當要訪問主載入對象的詳情時,就會馬上執行關聯對象的select查詢。(將關聯對象的詳情作為主載入對象的詳情的一部分出現)

(3)深度延遲:執行對主載入對象的查詢時,不會執行對關聯對象的查詢。訪問主載入對象的詳情時也不會執行關聯對象的select查詢。只有當真正訪問關聯對象的詳情時,才會執行對關聯對象的select查詢。

4.延遲載入的原理:調用的時候觸發載入,而不是在初始化的時候就載入信息

例如:調用 a. getB(). getName(),這個時候發現 a. getB() 的值為 null,此時會單獨觸發事先保存好的關聯 B 對象的 SQL,先查詢出來 B,然後再調用 a. setB(b),而這時候再調用 a. getB(). getName() 就有值了


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

-Advertisement-
Play Games
更多相關文章
  • #1、方法 const formatStr = (str) => { const value = str.replace( /[`:_~!@#$%^&*() \+ =<>?"{}|, \/ ;' \\ [ \] ·~!@#¥%……&*()—— \+ ={}|《》?:“”【】、;‘’,。、-]/g, ...
  • MVVM模式(Model-View-ViewModel):它的目標是將用戶界面(UI)的邏輯與業務邏輯分離。該模式的核心思想是將UI分為視圖(View)和視圖模型(ViewModel),並通過數據綁定實現二者之間的通信。 在MVVM模式中,視圖(View)表示用戶界面的呈現部分,視圖模型(ViewM ...
  • 本博文記錄CSS中比較常用的背景屬性,包括背景顏色:background-color、背景圖片:background-image、背景平鋪:background-repeat、背景位置:background-position和背景附著:background-attachment。同時記錄了背景色半透... ...
  • 本文分享自天翼雲開發者社區@《基於SpringBoot實現單元測試的多種情境/方法(二)》, 作者:才開始學技術的小白 1 Mock基礎回顧 在上一篇分享中我們詳細介紹了簡單的、用mock來模擬介面測試環境的方法,具體的使用樣例我們再回顧一下: 1.首先是最簡單的不需要傳參的示例,需要註意的是,可能 ...
  • 簡介 外觀模式(Facade Pattern),也叫門面模式,是一種結構型設計模式。它向現有的系統添加一個高層介面,隱藏子系統的複雜性,這個介面使得子系統更加容易使用。 如果你需要一個指向複雜子系統的直接介面,且該介面的功能有限,則可以使用外觀模式。或者需要將子系統組織為多層結構,可以使用外觀。 作 ...
  • 一個非常簡單的小項目。 看到了楊旭大佬的教學視頻,自己跟著實現了一下,完善了一下游戲邏輯。 通過空格鍵進行控制。 游戲中可按 P 鍵 暫停/恢復 游戲 項目結構 · ├── Cargo.lock ├── Cargo.toml ├── src/ │ ├── main.rs │ ├──bird/ │ │ ...
  • 本文介紹了學習Spring源碼前需要掌握的核心知識點,包括IOC、AOP、Bean生命周期、初始化和Transaction事務。通過Hello World示例,講解瞭如何使用Spring,並指出了深入瞭解Spring內部機制的方向。 ...
  • 約束(Constraints) 上一章介紹了向模型中添加一些業務邏輯的能力。我們現在可以將按鈕鏈接到業務代碼,但如何防止用戶輸入錯誤的數據?例如,在我們的房地產模塊中,沒有什麼可以阻止用戶設置負預期價格。 odoo提供了兩種設置自動驗證恆定式的方法:Python約束 and SQL約束。 SQL 參 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...