10、動(dòng)態(tài)SQL
10.1 什么是動(dòng)態(tài)SQL
動(dòng)態(tài)SQL指的是根據(jù)不同的查詢條件 , 生成不同的Sql語句.
官網(wǎng)描述:
MyBatis 的強(qiáng)大特性之一便是它的動(dòng)態(tài) SQL。如果你有使用 JDBC 或其它類似框架的經(jīng)驗(yàn),你就能體會(huì)到根據(jù)不同條件拼接 SQL 語句的痛苦。例如拼接時(shí)要確保不能忘記添加必要的空格,還要注意去掉列表最后一個(gè)列名的逗號(hào)。利用動(dòng)態(tài) SQL 這一特性可以徹底擺脫這種痛苦。
雖然在以前使用動(dòng)態(tài) SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射語句中的強(qiáng)大的動(dòng)態(tài) SQL 語言得以改進(jìn)這種情形。
動(dòng)態(tài) SQL 元素和 JSTL 或基于類似 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多元素需要花時(shí)間了解。MyBatis 3 大大精簡(jiǎn)了元素種類,現(xiàn)在只需學(xué)習(xí)原來一半的元素便可。MyBatis 采用功能強(qiáng)大的基于 OGNL 的表達(dá)式來淘汰其它大部分元素。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
之前寫的 SQL 語句都比較簡(jiǎn)單,如果有比較復(fù)雜的業(yè)務(wù),我們需要寫復(fù)雜的 SQL 語句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引號(hào),空格等缺失可能都會(huì)導(dǎo)致錯(cuò)誤。
那么怎么去解決這個(gè)問題呢?這就要使用 mybatis 動(dòng)態(tài)SQL,通過 if, choose, when, otherwise, trim, where, set, foreach等標(biāo)簽,可組合成非常靈活的SQL語句,從而在提高 SQL 語句的準(zhǔn)確性的同時(shí),也大大提高了開發(fā)人員的效率。
10.2 案例實(shí)現(xiàn)
搭建環(huán)境
新建一個(gè)數(shù)據(jù)庫表:blog
字段:id,title,author,create_time,views
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客標(biāo)題',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '創(chuàng)建時(shí)間',
`views` int(30) NOT NULL COMMENT '瀏覽量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1、創(chuàng)建Mybatis基礎(chǔ)工程
2、實(shí)體類編寫
import java.util.Date;
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
//set
//get
}
3、編寫Mapper接口及xml文件
public interface BlogMapper {
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-////DTD Mapper 3.0//EN"
"http:///dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chen.mapper.BlogMapper">
</mapper>
4、mybatis核心配置文件,下劃線駝峰自動(dòng)轉(zhuǎn)換
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注冊(cè)Mapper.xml-->
<mappers>
<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
5、插入初始數(shù)據(jù)
編寫接口
//新增一個(gè)博客
int addBlog(Blog blog);
sql配置文件
<insert id="addBlog" parameterType="blog">
insert into blog (id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
初始化博客方法
@Test
public void addInitBlog(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtil.genId());
blog.setTitle("Mybatis");
blog.setAuthor("黑馬程序員");
blog.setCreateTime(new Date());
blog.setViews(20);
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("Java");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("Spring");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("微服務(wù)");
mapper.addBlog(blog);
session.close();
}
初始化數(shù)據(jù)完畢
10.2.1 if 語句
需求:根據(jù)作者名字和博客名字來查詢博客!如果作者名字為空,那么只根據(jù)博客名字查詢,反之,則根據(jù)作者名來查詢
1、編寫接口類
//需求1
List<Blog> queryBlogIf(Map map);
2、編寫SQL語句
<!--需求1:
根據(jù)作者名字和博客名字來查詢博客!
如果作者名字為空,那么只根據(jù)博客名字查詢,反之,則根據(jù)作者名來查詢
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog where
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
3、測(cè)試
@Test
public void testQueryBlogIf(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","Mybatis");
map.put("author","黑馬程序員");
List<Blog> blogs = mapper.queryBlogIf(map);
System.out.println(blogs);
session.close();
}
這樣寫我們可以看到,如果 author 等于 null,那么查詢語句為 select * from user where title=#{title},但是如果title為空呢?那么查詢語句為 select * from user where and author=#{author},這是錯(cuò)誤的 SQL 語句,如何解決呢?請(qǐng)看下面的 where 語句!
10.2.2 Where
修改上面的SQL語句;
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
這個(gè)“where”標(biāo)簽會(huì)知道如果它包含的標(biāo)簽中有返回值的話,它就插入一個(gè)'where’。此外,如果標(biāo)簽返回的內(nèi)容是以AND 或OR 開頭的,則它會(huì)剔除掉。
10.2.3 Set
同理,上面的對(duì)于查詢 SQL 語句包含 where 關(guān)鍵字,如果在進(jìn)行更新操作的時(shí)候,含有 set 關(guān)鍵詞,我們?cè)趺刺幚砟兀?/p>
1、編寫接口方法
int updateBlog(Map map);
2、sql配置文件
<!--注意set是用的逗號(hào)隔開-->
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id};
</update>
3、測(cè)試
@Test
public void testUpdateBlog(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","動(dòng)態(tài)SQL");
map.put("author","秦疆");
map.put("id","9d6a763f5e1347cebda43e2a32687a77");
mapper.updateBlog(map);
session.close();
}
10.2.4 choose語句
有時(shí)候,我們不想用到所有的查詢條件,只想選擇其中的一個(gè),查詢條件有一個(gè)滿足即可,使用 choose 標(biāo)簽可以解決此類問題,類似于 Java 的 switch 語句
1、編寫接口方法
List<Blog> queryBlogChoose(Map map);
2、sql配置文件
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
3、測(cè)試類
@Test
public void testQueryBlogChoose(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title","Java如此簡(jiǎn)單");
map.put("author","狂神說");
map.put("views",9999);
List<Blog> blogs = mapper.queryBlogChoose(map);
System.out.println(blogs);
session.close();
}
10.2.5 SQL片段
有時(shí)候可能某個(gè) sql 語句我們用的特別多,為了增加代碼的重用性,簡(jiǎn)化代碼,我們需要將這些代碼抽取出來,然后使用時(shí)直接調(diào)用。
提取SQL片段:
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
引用SQL片段:
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
<include refid="if-title-author"></include>
<!-- 在這里還可以引用其他的 sql 片段 -->
</where>
</select>
注意:
①、最好基于 單表來定義 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where
10.2.6 Foreach
將數(shù)據(jù)庫中前三個(gè)數(shù)據(jù)的id修改為1,2,3;
需求:我們需要查詢 blog 表中 id 分別為1,2,3的博客信息
1、編寫接口
List<Blog> queryBlogForeach(Map map);
2、編寫SQL語句
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<!--
collection:指定輸入對(duì)象中的集合屬性
item:每次遍歷生成的對(duì)象
open:開始遍歷時(shí)的拼接字符串
close:結(jié)束時(shí)拼接的字符串
separator:遍歷對(duì)象之間需要拼接的字符串
select * from blog where 1=1 and (id=1 or id=2 or id=3)
-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
3、測(cè)試
@Test
public void testQueryBlogForeach(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
System.out.println(blogs);
session.close();
}
小結(jié):其實(shí)動(dòng)態(tài) sql 語句的編寫往往就是一個(gè)拼接的問題,為了保證拼接準(zhǔn)確,我們最好首先要寫原生的 sql 語句出來,然后在通過 mybatis 動(dòng)態(tài)sql 對(duì)照著改,防止出錯(cuò)。多在實(shí)踐中使用才是熟練掌握它的技巧。
11、緩存
11.1 簡(jiǎn)介
1、什么是緩存 [ Cache ]?
- 存在內(nèi)存中的臨時(shí)數(shù)據(jù)。
- 將用戶經(jīng)常查詢的數(shù)據(jù)放在緩存(內(nèi)存)中,用戶去查詢數(shù)據(jù)就不用從磁盤上(關(guān)系型數(shù)據(jù)庫數(shù)據(jù)文件)查詢,從緩存中查詢,從而提高查詢效率,解決了高并發(fā)系統(tǒng)的性能問題。
2、為什么使用緩存?
- 減少和數(shù)據(jù)庫的交互次數(shù),減少系統(tǒng)開銷,提高系統(tǒng)效率。
3、什么樣的數(shù)據(jù)能使用緩存?
- 經(jīng)常查詢并且不經(jīng)常改變的數(shù)據(jù)。
11.2 Mybatis緩存
11.3 一級(jí)緩存
一級(jí)緩存也叫本地緩存:
- 與數(shù)據(jù)庫同一次會(huì)話期間查詢到的數(shù)據(jù)會(huì)放在本地緩存中。
- 以后如果需要獲取相同的數(shù)據(jù),直接從緩存中拿,沒必須再去查詢數(shù)據(jù)庫;
11.4 一級(jí)緩存失效的四種情況
一級(jí)緩存是SqlSession級(jí)別的緩存,是一直開啟的,我們關(guān)閉不了它;
一級(jí)緩存失效情況:沒有使用到當(dāng)前的一級(jí)緩存,效果就是,還需要再向數(shù)據(jù)庫中發(fā)起一次查詢請(qǐng)求!
每個(gè)sqlSession中的緩存相互獨(dú)立
當(dāng)前緩存中,不存在這個(gè)數(shù)據(jù)
因?yàn)樵鰟h改操作可能會(huì)對(duì)當(dāng)前數(shù)據(jù)產(chǎn)生影響
11.5 二級(jí)緩存
11.6 使用步驟
1、開啟全局緩存 【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
2、去每個(gè)mapper.xml中配置使用二級(jí)緩存,這個(gè)配置非常簡(jiǎn)單;【xxxMapper.xml】
<cache/>
官方示例=====>查看官方文檔
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個(gè)更高級(jí)的配置創(chuàng)建了一個(gè) FIFO 緩存,每隔 60 秒刷新,最多可以存儲(chǔ)結(jié)果對(duì)象或列表的 512 個(gè)引用,而且返回的對(duì)象被認(rèn)為是只讀的,因此對(duì)它們進(jìn)行修改可能會(huì)在不同線程中的調(diào)用者產(chǎn)生沖突。
結(jié)論
- 只要開啟了二級(jí)緩存,我們?cè)谕粋€(gè)Mapper中的查詢,可以在二級(jí)緩存中拿到數(shù)據(jù)
- 查出的數(shù)據(jù)都會(huì)被默認(rèn)先放在一級(jí)緩存中
- 只有會(huì)話提交或者關(guān)閉以后,一級(jí)緩存中的數(shù)據(jù)才會(huì)轉(zhuǎn)到二級(jí)緩存中
|