欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MyBatis動(dòng)態(tài)SQL與緩存原理深入分析

 更新時(shí)間:2023年02月20日 11:01:35   作者:綠仔牛奶_  
這篇文章主要介紹了MyBatis動(dòng)態(tài)SQL與緩存原理,Mybatis框架的動(dòng)態(tài)SQL技術(shù)是一種根據(jù)特定條件動(dòng)態(tài)拼裝SQL語句的功能,它存在的意義是為了解決拼接SQL語句字符串時(shí)的痛點(diǎn)問題

動(dòng)態(tài)SQL

為什么叫做動(dòng)態(tài)SQL:因?yàn)樵诔绦驁?zhí)行中,mybatis提供的sql可以根據(jù)用戶提供的字段數(shù)量、類型,合理的選擇對(duì)應(yīng)的執(zhí)行sql。正是這一動(dòng)態(tài)的選擇特性,極大的優(yōu)化了使用JDBC的代碼冗余。

根據(jù)不同條件生成不同的sql語句執(zhí)行

環(huán)境準(zhǔn)備

以博客表為例:

create table `blog`(
    `id` varchar(50) primary key comment '博客ID',
    `title` varchar(100) not null comment '博客標(biāo)題',
    `author` varchar(50) not null comment '博客作者',
    `create_time` datetime not null comment '創(chuàng)建時(shí)間',
    `view` int not null comment '瀏覽量'
);

Blog實(shí)體:

@Data
@AllArgsConstructor
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date creatTime;
    private int views;
}

IDutils,用于隨機(jī)生成的ID名稱

public static String getId(){
  return UUID.randomUUID().toString().replaceAll("-","");
}

IF語句

以上述搭建的環(huán)境為例,當(dāng)我們需要查詢博客時(shí),如果用戶指定了搜索搜索字段那就根據(jù)該字段查找,如果沒有指定那就查詢?nèi)?。如果用普通的sql語句實(shí)現(xiàn),需要我們?cè)贘ava程序中進(jìn)行判斷,但是MyBatis提供了動(dòng)態(tài)SQL,我們就可以利用內(nèi)置的IF標(biāo)簽來實(shí)現(xiàn):

BlogMapper.xml配置

<select id="getBlogListIF" parameterType="map" resultType="Blog">
  select * from blog where 1 = 1
  <if test="title != null">
    and title like "%"#{title}"%"
  </if>
  <if test="author != null">
    and author like "%"#{author}"%"
  </if>
</select>

測(cè)試:

@Test
public void testGetBlogList(){
  SqlSession sqlSession = MyBatisUtils.getSqlSession();
  BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
  HashMap<String, Object> map = new HashMap<String, Object>();
  map.put("title","Java");
  map.put("author",null);
  List<Blog> blogListIF = mapper.getBlogListIF(map);
  for (Blog blog : blogListIF) {
    System.out.println(blog);
  }
  sqlSession.commit();
  sqlSession.close();
}

此處采用模糊查詢,在xml中直接對(duì)title和author字段進(jìn)行判斷,如果非空則執(zhí)行拼接sql,反之查詢?nèi)?/p>

trim(where&Set) 

where

看下列代碼:

<select id="getBlogListIF" parameterType="map" resultType="Blog">
  select * from blog where 
  <if test="title != null">
    and title like "%"#{title}"%"
  </if>
  <if test="author != null">
    and author = #{author}
	</if>
</select>

此時(shí)當(dāng)上述兩個(gè)if滿足任一時(shí),sql拼接后變成:

select * from blog where and author = #{author}這是不符合sql語法規(guī)則的。對(duì)此MyBatis提供了where標(biāo)簽來處理這種情況。

<select id="getBlogListIF" parameterType="map" resultType="Blog">
  select * from blog
  <where>
  <if test="title != null">
    and title like "%"#{title}"%"
    </if>
      <if test="author != null">
        and author = #{author}
</if>
  </where>
  </select>

where元素只會(huì)在它的任一子元素返回內(nèi)容時(shí),才會(huì)在sql中插入where子句。如果返回的sql開頭為and 或on,where標(biāo)簽會(huì)自動(dòng)將其抹去

set

sql中更新語句update在mybatis常用set標(biāo)簽來判定都需要更新哪些字段,如果用戶設(shè)置了新的該字段屬性,則會(huì)在set檢測(cè)到,從而執(zhí)行更新語句

并且set子句會(huì)動(dòng)態(tài)的在行首添加上set關(guān)鍵字,包括刪除額外的逗號(hào)

<update id="updateBlogInfo" parameterType="map">
  update blog
  <set>
    <if test="title != null">
      title = #{title},
    </if>
    <if test="author != null">
      author = #{author},
    </if>
  </set>
  where id = #{id}
</update>

trim

trim包含四個(gè)屬性:

prefix前綴、prefixOverrides前綴覆蓋、suffix后綴、suffixOverrides后綴覆蓋

當(dāng)where和set不能得到預(yù)期的結(jié)果時(shí),可以使用trim進(jìn)行配置。也可以直接使用trim實(shí)現(xiàn)和where、set相同的效果:

<!-- trim實(shí)現(xiàn)set -->
<trim prefix="set" suffixOverrides=",">
  <if test="title != null">
    title = #{title},
  </if>
  <if test="author != null">
    author = #{author},
  </if>
</trim>
<!-- trim實(shí)現(xiàn)where -->
<trim prefix="where" prefixOverrides="and | or">
  <choose>
    <when test="title != null">
      title = #{title}
    </when>
    <when test="author != null">
      and author = #{author}
    </when>
    <otherwise>
      and view != 0
    </otherwise>
  </choose>
</trim>

choose&when&otherwise

choose標(biāo)簽,類似于Java中的switch語句。當(dāng)我們不想要執(zhí)行全部的sql,而只是選擇性的去執(zhí)行對(duì)應(yīng)的sql。

三者的關(guān)系類似于switch–>choose、case–>when、default–>otherwise

BlogMapper.xml編譯sql

<select id="queryBlogChoose" parameterType="map" resultType="blog">
  select * from blog
  <where>
    <choose>
      <!--title不為null執(zhí)行-->
      <when test="title != null">
        title = #{title}
      </when>
      <!--author不為null執(zhí)行-->
      <when test="author != null">
        and author = #{author}
      </when>
      <!--默認(rèn)執(zhí)行-->
      <otherwise>
        and view != 0
      </otherwise>
    </choose>
  </where>
</select>

sql片段

利用sql標(biāo)簽,抽離重復(fù)代碼。在需要使用的地方使用include標(biāo)簽直接引入即可

<!-- 抽離sql -->
<sql id="checkTitleAuthor">
  <if test="title != null">
    title = #{title},
  </if>
  <if test="author != null">
    author = #{author},
  </if>
</sql>
<select id="getBlogListIF" parameterType="map" resultType="Blog">
  select * from blog
  <where>
    <!-- 引入sql片段 -->
    <include refid="checkTitleAuthor"/>
  </where>
</select>

Foreach

利用Foreach可以在動(dòng)態(tài)sql中對(duì)集合進(jìn)行遍歷

BlogMapper.xml

<select id="getBlogForeach" parameterType="map" resultType="blog">
  select * from blog  
  <where>
    <foreach collection="ids" item="id" open="(" separator="or" close=")">
      id=#{id}
    </foreach>
  </where>
</select>

上述代碼,利用map集合存儲(chǔ)list集合交給foreach,此處collection通過鍵“ids”獲取list,item為值,open為拼接sql的開始,close為拼接sql的結(jié)束,separator表示分隔符

緩存

什么是緩存?

緩存是存在于內(nèi)存中的臨時(shí)數(shù)據(jù)

使用緩存可以減少和數(shù)據(jù)庫(kù)的交互次數(shù),提高數(shù)據(jù)庫(kù)性能和執(zhí)行效率

官網(wǎng)給出:

  • 映射語句文件中的所有 select 語句的結(jié)果將會(huì)被緩存。
  • 映射語句文件中的所有 insert、update 和 delete 語句會(huì)刷新緩存。
  • 緩存會(huì)使用最近最少使用算法(LRU, Least Recently Used)算法來清除不需要的緩存。
  • 緩存不會(huì)定時(shí)進(jìn)行刷新(也就是說,沒有刷新間隔)。
  • 緩存會(huì)保存列表或?qū)ο螅o論查詢方法返回哪種)的 1024 個(gè)引用。
  • 緩存會(huì)被視為讀/寫緩存,這意味著獲取到的對(duì)象并不是共享的,可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。

一級(jí)緩存

也叫做本地緩存,對(duì)應(yīng)MyBatis中的sqlSession。

一級(jí)緩存是默認(rèn)開啟的,作用域僅在sqlSession中有效

緩存示例

用戶user表id查詢兩次相同數(shù)據(jù)示例:

@Select("select * from user where id = #{id}")
Users getUserById(@Param("id") int id);
// 測(cè)試
public void testGetUsersList() {
  SqlSession sqlSession = MyBatisUtils.getSqlSession();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  Users user1 = mapper.getUserById(2);
  System.out.println(user1);
  Users user2 = mapper.getUserById(2);
  System.out.println(user2);
  System.out.println(user1==user2);
  sqlSession.close();
}

打印效果分析:

上述程序分別調(diào)用兩次getUserById方法,如果沒有緩存機(jī)制那么最終應(yīng)該會(huì)執(zhí)行兩次查詢sql來返回?cái)?shù)據(jù),但是根據(jù)日志可以看到最終只執(zhí)行了一次sql。 這說明,第一次查詢到的數(shù)據(jù)就已經(jīng)存放在了緩存當(dāng)中,而第二次執(zhí)行查詢時(shí)將會(huì)直接從緩存中獲取,不再進(jìn)入sql層面查詢。

看下面的示例:

<update id="updateUserInfo" parameterType="map" >
  update user
  <set>
    <if test="name != null">
      name = #{name},
    </if>
    <if test="pwd != null">
      pwd = #{pwd},
    </if>
  </set>
  where id = #{id}
</update>
@Test
public void testGetUsersList() {
  SqlSession sqlSession = MyBatisUtils.getSqlSession();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  // 第一次查詢id=2數(shù)據(jù)
  Users user1 = mapper.getUserById(2);
  System.out.println(user1);
  System.out.println("-----------------------------------------------");
  // 修改id=2數(shù)據(jù)
  HashMap<String, Object> map = new HashMap<String, Object>();
  map.put("name","馮七七");
  map.put("id",2);
  int i = mapper.updateUserInfo(map);
  sqlSession.commit();
  // 第二次查詢id=2數(shù)據(jù)
  Users user2 = mapper.getUserById(2);
  System.out.println(user2);
  System.out.println(user1==user2);
  sqlSession.close();
}

首先第一次查詢數(shù)據(jù),查詢完之后調(diào)用修改方法將name修改為“ 馮七七 ” 然后再次執(zhí)行查詢語句

日志分析:

Created connection 594427726.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@236e3f4e]
-- 第一次執(zhí)行查詢sql  數(shù)據(jù)保存在sqlSession中
==>  Preparing: select * from user where id = ?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, 馮子, 234
<==      Total: 1
Users(id=2, name=馮子, pwd=234)
-----------------------------------------------
-- 修改剛剛查詢的數(shù)據(jù)
==>  Preparing: update user SET name = ? where id = ?
==> Parameters: 馮七七(String), 2(Integer)
<==    Updates: 1
Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@236e3f4e]
-- 再次執(zhí)行查詢語句  查詢剛剛修改過的數(shù)據(jù)
==>  Preparing: select * from user where id = ?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, 馮七七, 234
<==      Total: 1
Users(id=2, name=馮七七, pwd=234)
false-- 數(shù)據(jù)發(fā)生改變

得出結(jié)論,數(shù)據(jù)在執(zhí)行select之后會(huì)將查詢到的數(shù)據(jù)保存在緩存中,以便下次直接使用

對(duì)于增刪改則會(huì)在完成之后刷新緩存,刷新之后如果需要獲取數(shù)據(jù)智能再次查詢數(shù)據(jù)庫(kù)

緩存失效場(chǎng)景

  • 查詢不同數(shù)據(jù)時(shí),自然無法從緩存中直接拿到。
  • 增刪改操作可能會(huì)改變?cè)瓟?shù)據(jù),所以一定會(huì)刷新緩存
  • 手動(dòng)清理緩存:sqlSession.clearCache();
  • 創(chuàng)建不同的sqlSession對(duì)象查詢

二級(jí)緩存

一級(jí)緩存是默認(rèn)開啟的,但是由于一級(jí)緩存作用域太低,所以誕生二級(jí)緩存

二級(jí)緩存就是全局緩存,它對(duì)應(yīng)于一個(gè)namespace命名空間級(jí)別。只要開啟了二級(jí)緩存,在用一個(gè)Mapper下就始終有效

工作機(jī)制:

  • 一個(gè)會(huì)話查詢一條數(shù)據(jù),查詢成功后該數(shù)據(jù)會(huì)存放在一級(jí)緩存中
  • 如果當(dāng)前會(huì)話關(guān)閉了,則其對(duì)應(yīng)的一級(jí)緩存消失。
  • 如果開啟了二級(jí)緩存,那么一級(jí)緩存消失后,其中的數(shù)據(jù)就會(huì)被保存到二級(jí)緩存中
  • 當(dāng)新開的會(huì)話去查詢同一數(shù)據(jù)時(shí),就會(huì)從二級(jí)緩存中拿到

開啟全局緩存

cacheEnabled 全局性地開啟或關(guān)閉所有映射器配置文件中已配置的任何緩存。

有效值true | false 默認(rèn)值 true

但通常我們會(huì)在settings中顯式的開啟

<setting name=" cacheEnabled " value="true"/>

然后需要在想要使用二級(jí)緩存的Mapper.xml文件中配置cache

<!-- 開啟 使用默認(rèn)參數(shù) -->
<cache/>
<!-- 自定義參數(shù) -->
<cache
       eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

上述eviction是指驅(qū)逐策略,F(xiàn)IFO先進(jìn)先出,按照對(duì)象進(jìn)入緩存的順序移出

flushInterval為刷新緩存的間隔時(shí)間,size為最大緩存容量,readOnly是否設(shè)置為只讀

二級(jí)緩存示例

要注意的一點(diǎn)是,只有在一級(jí)緩存銷毀之后。sqlSession才會(huì)將它緩存的東西交給二級(jí)緩存

// 測(cè)試二級(jí)緩存
@Test
public void testGetUserById(){
  // 創(chuàng)建兩個(gè)sqlSession會(huì)話
  SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
  SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
  // selSession1查詢一次
  UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
  Users user = mapper1.getUserById(2);
  System.out.println(user);
  sqlSession1.close();// 關(guān)閉sqlSession1
  System.out.println("---------------------------------------------");
  // selSession2查詢與sqlSession相同的數(shù)據(jù)
  UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
  Users user1 = mapper2.getUserById(2);
  System.out.println(user1);
  System.out.println(user==user1);
  sqlSession2.close();
}

打印日志如下:

Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@55536d9e]
-- sqlSession1執(zhí)行sql查詢數(shù)據(jù)
==>  Preparing: select * from user where id = ?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, 馮七七, 234
<==      Total: 1
Users(id=2, name=馮七七, pwd=234)
-- 回收sqlSession1到鏈接池
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@55536d9e]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@55536d9e]
Returned connection 1431530910 to pool.
---------------------------------------------
Cache Hit Ratio [com.yuqu.dao.UserMapper]: 0.5
-- sqlSession查詢數(shù)據(jù)結(jié)果
Users(id=2, name=馮七七, pwd=234)
true

很明顯,sqlSession1查詢到的數(shù)據(jù)首先保存在了自己的緩存中,也就是一級(jí)緩存。那么關(guān)閉sqlSession1之后,數(shù)據(jù)被交給到二級(jí)緩存。此時(shí)sqlSession再次查詢相同數(shù)據(jù),則會(huì)直接在二級(jí)緩存中拿到

一個(gè)問題:

上文提到了妖使用二級(jí)緩存則必須在對(duì)應(yīng)的Mapper.xml文件中配置cache標(biāo)簽。一種是隱式參數(shù)第一種,采用這種方式,就必須讓實(shí)體類pojo實(shí)現(xiàn)serializable接口,否則會(huì)報(bào)出異常

java.io.NotSerializableException: com.yuqu.pojo.Users

如果采用自定義參數(shù)形式,就不需要實(shí)現(xiàn)Serializable接口。因?yàn)閏ache中有一個(gè)參數(shù)為eviction驅(qū)逐策略直接就規(guī)定了緩存中的數(shù)據(jù)讀/寫的規(guī)則。

但是通常無論是否采用自定義參數(shù),都會(huì)將實(shí)體類實(shí)現(xiàn)序列化接口

到此這篇關(guān)于MyBatis動(dòng)態(tài)SQL與緩存原理深入分析的文章就介紹到這了,更多相關(guān)MyBatis動(dòng)態(tài)SQL與緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java I/O技術(shù)之文件操作詳解

    Java I/O技術(shù)之文件操作詳解

    這篇文章主要介紹了Java I/O技術(shù)之文件操作詳解,需要的朋友可以參考下
    2014-07-07
  • Spring Boot jpa Service層代碼實(shí)例

    Spring Boot jpa Service層代碼實(shí)例

    這篇文章主要介紹了Spring Boot jpa Service層代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • Java Executor 框架的實(shí)例詳解

    Java Executor 框架的實(shí)例詳解

    這篇文章主要介紹了Java Executor 框架的實(shí)例詳解的相關(guān)資料,這里提供實(shí)例來幫助大家學(xué)習(xí)理解這部分內(nèi)容,需要的朋友可以參考下
    2017-09-09
  • java語言實(shí)現(xiàn)權(quán)重隨機(jī)算法完整實(shí)例

    java語言實(shí)現(xiàn)權(quán)重隨機(jī)算法完整實(shí)例

    這篇文章主要介紹了java語言實(shí)現(xiàn)權(quán)重隨機(jī)算法完整實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-11-11
  • 淺談String類型等值比較引起的“==”、“equals()”和“hashCode”思考

    淺談String類型等值比較引起的“==”、“equals()”和“hashCode”思考

    這篇文章主要介紹了淺談String類型等值比較引起的“==”、“equals()”和“hashCode”思考。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java結(jié)束線程的三種方法及該如何選擇

    Java結(jié)束線程的三種方法及該如何選擇

    這篇文章主要介紹了Java結(jié)束線程的三種方法及該如何選擇,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下
    2021-03-03
  • 了解JAVA Future類

    了解JAVA Future類

    Future是并發(fā)編程中的一種設(shè)計(jì)模式,F(xiàn)uture它代表一個(gè)異步計(jì)算的結(jié)果,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面小編和大家來一起學(xué)習(xí)一下吧
    2019-06-06
  • Java求兩個(gè)正整數(shù)的最大公約數(shù)和最小公倍數(shù)

    Java求兩個(gè)正整數(shù)的最大公約數(shù)和最小公倍數(shù)

    這篇文章主要介紹了輸入兩個(gè)正整數(shù)m和n,求其最大公約數(shù)和最小公倍數(shù),需要的朋友可以參考下
    2017-02-02
  • Maven中pom.xml文件報(bào)錯(cuò)的原因解決

    Maven中pom.xml文件報(bào)錯(cuò)的原因解決

    創(chuàng)建Maven項(xiàng)目的時(shí)候,如果你選擇的Packaging為war,那么就會(huì)報(bào)錯(cuò),本文主要介紹了Maven中pom.xml文件報(bào)錯(cuò)的原因解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • SpringSecurity的TokenStore四種實(shí)現(xiàn)方式小結(jié)

    SpringSecurity的TokenStore四種實(shí)現(xiàn)方式小結(jié)

    本文主要介紹了SpringSecurity的TokenStore四種實(shí)現(xiàn)方式小結(jié),分別是InMemoryTokenStore,JdbcTokenStore,JwkTokenStore,RedisTokenStore,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01

最新評(píng)論