MyBatis動(dòng)態(tài)SQL與緩存原理深入分析
動(dòng)態(tài)SQL
為什么叫做動(dòng)態(tài)SQL:因?yàn)樵诔绦驁?zhí)行中,mybatis提供的sql可以根據(jù)用戶提供的字段數(shù)量、類(lèi)型,合理的選擇對(duì)應(yīng)的執(zhí)行sql。正是這一動(dòng)態(tài)的選擇特性,極大的優(yōu)化了使用JDBC的代碼冗余。
根據(jù)不同條件生成不同的sql語(yǔ)句執(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語(yǔ)句
以上述搭建的環(huán)境為例,當(dāng)我們需要查詢博客時(shí),如果用戶指定了搜索搜索字段那就根據(jù)該字段查找,如果沒(méi)有指定那就查詢?nèi)?。如果用普通的sql語(yǔ)句實(shí)現(xiàn),需要我們?cè)贘ava程序中進(jìn)行判斷,但是MyBatis提供了動(dòng)態(tài)SQL,我們就可以利用內(nèi)置的IF標(biāo)簽來(lái)實(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語(yǔ)法規(guī)則的。對(duì)此MyBatis提供了where標(biāo)簽來(lái)處理這種情況。
<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開(kāi)頭為and 或on,where標(biāo)簽會(huì)自動(dòng)將其抹去
set
sql中更新語(yǔ)句update在mybatis常用set標(biāo)簽來(lái)判定都需要更新哪些字段,如果用戶設(shè)置了新的該字段屬性,則會(huì)在set檢測(cè)到,從而執(zhí)行更新語(yǔ)句
并且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)簽,類(lèi)似于Java中的switch語(yǔ)句。當(dāng)我們不想要執(zhí)行全部的sql,而只是選擇性的去執(zhí)行對(duì)應(yīng)的sql。
三者的關(guān)系類(lèi)似于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通過(guò)鍵“ids”獲取list,item為值,open為拼接sql的開(kāi)始,close為拼接sql的結(jié)束,separator表示分隔符
緩存
什么是緩存?
緩存是存在于內(nèi)存中的臨時(shí)數(shù)據(jù)
使用緩存可以減少和數(shù)據(jù)庫(kù)的交互次數(shù),提高數(shù)據(jù)庫(kù)性能和執(zhí)行效率
官網(wǎng)給出:
- 映射語(yǔ)句文件中的所有 select 語(yǔ)句的結(jié)果將會(huì)被緩存。
- 映射語(yǔ)句文件中的所有 insert、update 和 delete 語(yǔ)句會(huì)刷新緩存。
- 緩存會(huì)使用最近最少使用算法(LRU, Least Recently Used)算法來(lái)清除不需要的緩存。
- 緩存不會(huì)定時(shí)進(jìn)行刷新(也就是說(shuō),沒(méi)有刷新間隔)。
- 緩存會(huì)保存列表或?qū)ο螅o(wú)論查詢方法返回哪種)的 1024 個(gè)引用。
- 緩存會(huì)被視為讀/寫(xiě)緩存,這意味著獲取到的對(duì)象并不是共享的,可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。
一級(jí)緩存
也叫做本地緩存,對(duì)應(yīng)MyBatis中的sqlSession。
一級(jí)緩存是默認(rèn)開(kāi)啟的,作用域僅在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方法,如果沒(méi)有緩存機(jī)制那么最終應(yīng)該會(huì)執(zhí)行兩次查詢sql來(lái)返回?cái)?shù)據(jù),但是根據(jù)日志可以看到最終只執(zhí)行了一次sql。 這說(shuō)明,第一次查詢到的數(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í)行查詢語(yǔ)句
日志分析:
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í)行查詢語(yǔ)句 查詢剛剛修改過(guò)的數(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í),自然無(wú)法從緩存中直接拿到。
- 增刪改操作可能會(huì)改變?cè)瓟?shù)據(jù),所以一定會(huì)刷新緩存
- 手動(dòng)清理緩存:
sqlSession.clearCache();
- 創(chuàng)建不同的sqlSession對(duì)象查詢
二級(jí)緩存
一級(jí)緩存是默認(rèn)開(kāi)啟的,但是由于一級(jí)緩存作用域太低,所以誕生二級(jí)緩存
二級(jí)緩存就是全局緩存,它對(duì)應(yīng)于一個(gè)namespace命名空間級(jí)別。只要開(kāi)啟了二級(jí)緩存,在用一個(gè)Mapper下就始終有效
工作機(jī)制:
- 一個(gè)會(huì)話查詢一條數(shù)據(jù),查詢成功后該數(shù)據(jù)會(huì)存放在一級(jí)緩存中
- 如果當(dāng)前會(huì)話關(guān)閉了,則其對(duì)應(yīng)的一級(jí)緩存消失。
- 如果開(kāi)啟了二級(jí)緩存,那么一級(jí)緩存消失后,其中的數(shù)據(jù)就會(huì)被保存到二級(jí)緩存中
- 當(dāng)新開(kāi)的會(huì)話去查詢同一數(shù)據(jù)時(shí),就會(huì)從二級(jí)緩存中拿到
開(kāi)啟全局緩存
cacheEnabled 全局性地開(kāi)啟或關(guān)閉所有映射器配置文件中已配置的任何緩存。
有效值true | false 默認(rèn)值 true
但通常我們會(huì)在settings中顯式的開(kāi)啟
<setting name=" cacheEnabled " value="true"/>
然后需要在想要使用二級(jí)緩存的Mapper.xml文件中配置cache
<!-- 開(kāi)啟 使用默認(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í)緩存銷(xiāo)毀之后。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è)問(wèn)題:
上文提到了妖使用二級(jí)緩存則必須在對(duì)應(yīng)的Mapper.xml文件中配置cache標(biāo)簽。一種是隱式參數(shù)第一種,采用這種方式,就必須讓實(shí)體類(lèi)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ù)讀/寫(xiě)的規(guī)則。
但是通常無(wú)論是否采用自定義參數(shù),都會(huì)將實(shí)體類(lèi)實(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)文章
Spring Boot jpa Service層代碼實(shí)例
這篇文章主要介紹了Spring Boot jpa Service層代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10java語(yǔ)言實(shí)現(xiàn)權(quán)重隨機(jī)算法完整實(shí)例
這篇文章主要介紹了java語(yǔ)言實(shí)現(xiàn)權(quán)重隨機(jī)算法完整實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-11-11淺談String類(lèi)型等值比較引起的“==”、“equals()”和“hashCode”思考
這篇文章主要介紹了淺談String類(lèi)型等值比較引起的“==”、“equals()”和“hashCode”思考。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09Java求兩個(gè)正整數(shù)的最大公約數(shù)和最小公倍數(shù)
這篇文章主要介紹了輸入兩個(gè)正整數(shù)m和n,求其最大公約數(shù)和最小公倍數(shù),需要的朋友可以參考下2017-02-02Maven中pom.xml文件報(bào)錯(cuò)的原因解決
創(chuàng)建Maven項(xiàng)目的時(shí)候,如果你選擇的Packaging為war,那么就會(huì)報(bào)錯(cuò),本文主要介紹了Maven中pom.xml文件報(bào)錯(cuò)的原因解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07SpringSecurity的TokenStore四種實(shí)現(xiàn)方式小結(jié)
本文主要介紹了SpringSecurity的TokenStore四種實(shí)現(xiàn)方式小結(jié),分別是InMemoryTokenStore,JdbcTokenStore,JwkTokenStore,RedisTokenStore,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01