目录
1、动态SQL
什么是动态SQL:动态SQL就是根据不同条件生成相应的SQL语句
元素种类:
-
if
-
choose(where,set)
-
trim(where,set)
-
foreach
1.1、搭建环境
-
创建一张blog博客表
CREATE TABLE `blogs` (
`blogid` char(32) NOT NULL COMMENT '博客id',
`title` varchar(100) DEFAULT NULL COMMENT '博客标题',
`author` varchar(50) DEFAULT NULL COMMENT '作者',
`type` varchar(50) DEFAULT NULL COMMENT '博客类型',
`publishDateTime` datetime DEFAULT NULL COMMENT '出版日期',
`content` varchar(300) DEFAULT NULL COMMENT '内容',
`isDisplay` char(1) DEFAULT NULL COMMENT '是否显示',
`readCount` int(11) DEFAULT NULL COMMENT '阅读量',
`image` varchar(100) DEFAULT NULL COMMENT '图片',
PRIMARY KEY (`blogid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
数据库里面自己随机插入数据就行,推荐使用com.github.javafaker.Faker这个去导数据,这里不做详细介绍。
-
创建Mybatis基础工程
-
导包(跟前面一样)
-
编写配置文件(db.properties和mybatis-config.xml配置文件基本不变)
注意:
db.properties中的数据库名要改成当前数据库名
-
-
mybatis-config.xml中要修改mapper映射器
-
-
编写实体类
-
@Data public class blog { private String id; private String title; private String auther; private String type; private Date publishDateTime; private String content; private String isDisplay; private int redCount; private String imge; }
-
编写工具类
//SqlSessionFactory --> SqlSession public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static{ try { //使用mybatis第一步:获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream; inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //有了sqlSessionFactory就可以获取sqlSession的实例 //sqlSession完全包含了面向数据库执行SQL命令所需的方法 public static SqlSession getsqlSession(){ return sqlSessionFactory.openSession(true); } }
-
编写实体类对应的Mapper接口和Mapper.xml文件
-
结构
-
1.2、if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如查询名为斯蒂芬且博客题目不为空的博客信息:
1.在BlogMapper接口中定义查询方法:
public interface BlogMapper {
//搜索斯蒂芬作者是否含有标题为123的博客
//对应的SQL语句:select * from blogs where author = '斯蒂芬' and title like '123';
public blog selectBlogif(String title);
}
2.在BlogMapper.xml中写对应的SQL语句以及if语句
<!--在这里添加了一个解决java实体类中的变量名称中数据库名不同的问题,前面有讲过
,记得在mybatis-config.xml中添加别名配置typeAliases
-->
<resultMap id="blogResultMap" type="blog">
<result property="id" column="blogid"/>
</resultMap>
<select id="selectBlog" resultMap="blogResultMap">
select * from blogs
where author = '斯蒂芬'
<if test="title != null">
and title like #{title}
</if>
</select>
3.在测试类Test中测试
@Test
public void selectBlog(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
mapper.selectBlogif("123");
sqlSession.close();
}
4.测试结果
1.title为空的情况
Opening JDBC Connection
Created connection 1313532469.
==> Preparing: select * from blogs where author = '斯蒂芬' and title like ?
==> Parameters: (String)
<== Total: 0
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4e4aea35]
Returned connection 1313532469 to pool.
2.title为123(该博客标题)的情况
Opening JDBC Connection
Created connection 1313532469.
==> Preparing: select * from blogs where author = '斯蒂芬' and title like ?
==> Parameters: 123(String)
<== Columns: blogid, title, author, type, publishDateTime, content, isDisplay, readCount, image
<== Row: e9118e01ed3d4fc49aa9cb448185df11, 123, 斯蒂芬, software, 2021-11-09 06:36:09.0, wrewqefasdfsafsadf, 1, 242, images/small-image.jpg
<== Total: 1
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4e4aea35]
Returned connection 1313532469 to pool.
这条if语句提供了可选的查找文本功能。如果不传入 “title”,那么所有author为“斯蒂芬”的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。
如果希望通过 “title” 和 “type” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。
接口定义方法:
//搜索斯蒂芬作者是否software类型且标题为123的博客
//对应的SQL语句:select * from blogs where author = '斯蒂芬' and type = 'software' and title like '123';
public List<blog> selectBlogif(Map map);//使用map参数方法写的
Mapper的SQL实现:
<select id="selectBlogif" parameterType="map" resultMap="blogResultMap">
select * from blogs
where author = '斯蒂芬'
<if test="type != null">
and type like #{type}
</if>
<if test="title != null">
and title like #{title}
</if>
</select>
测试:
@Test
public void selectBlogif(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("type","software");
map.put("title","123");
List<blog> blogs = mapper.selectBlogif(map);
System.out.println(blogs);
sqlSession.close();
}
结果:
Opening JDBC Connection
Created connection 849373393.
==> Preparing: select * from blogs where author = '斯蒂芬' and type like ? and title like ?
==> Parameters: software(String), 123(String)
<== Columns: blogid, title, author, type, publishDateTime, content, isDisplay, readCount, image
<== Row: e9118e01ed3d4fc49aa9cb448185df11, 123, 斯蒂芬, software, 2021-11-09 06:36:09.0, wrewqefasdfsafsadf, 1, 242, images/small-image.jpg
<== Total: 1
[blog(id=e9118e01ed3d4fc49aa9cb448185df11, title=123, author=斯蒂芬, type=software, publishDateTime=Tue Nov 09 06:36:09 CST 2021, content=wrewqefasdfsafsadf, isDisplay=1, redCount=0, imge=null)]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@32a068d1]
Returned connection 849373393 to pool.
1.3、trim、where、set
这次我们将 “author= ‘斯蒂芬’” 设置成动态条件,看看会发生什么。
<select id="selectBlogif" parameterType="map" resultMap="blogResultMap">
select * from blogs
where <!--在这个地方缺失了一个true,导致无法连接下一句语句-->
<if test="author != null">
and author = '斯蒂芬'
</if>
<if test="type != null">
and type like #{type}
</if>
<if test="title != null">
and title like #{title}
</if>
</select>
结果为:
Opening JDBC Connection
Created connection 849373393.
==> Preparing: select * from blogs where and type like ? and title like ?
==> Parameters: software(String), 123(String)
可以看到,SQL语句变成了select * from blogs where and type like ? and title like ?
正常的应该是select * from blogs where 1=1 and ... and ...,需要一个恒等式来连接SQL语句,但每次这样写有点麻烦,就用到了where元素
1.3.1、Where
可以将上面的SQL改正:
<select id="selectBlogif" parameterType="map" resultMap="blogResultMap">
select * from blogs
<where>
<if test="author != null">
and author = '斯蒂芬'
</if>
<if test="type != null">
and type like #{type}
</if>
<if test="title != null">
and title like #{title}
</if>
</where>
</select>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。如果传递的值为空,则where后面的SQL语句会自动删除。
1.3.2、Trim
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="where" prefixOverrides="and | or">
<if test="author != null">-->
and author = '斯蒂芬'
</if>
<if test="type != null">
and type like #{type}
</if>
<if test="title != null">
and title like #{title}
</if>
</trim>
上述例子会移除所有 prefixOverrides 属性中指定的内容(第一个and或者or),并且插入 prefix 属性中指定的内容。
1.3.3、Set
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的),一定要加逗号,不加要出错。
<update id="updateBlog" parameterType="map">
update blogs
<set>
<if test="title != null">title=#{title},</if>
<if test="author != null">author=#{author}</if>
</set>
where blogid=#{id}
</update>
结果:
Opening JDBC Connection
Created connection 1891546521.
==> Preparing: update blogs SET title=?, author=? where blogid=?
==> Parameters: 已修改这个标题(String), 被修改的作者(String), 0cfc526c437c4055b542b0e3e302d787(String)
<== Updates: 1
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@70beb599]
Returned connection 1891546521 to pool.
与 set 元素等价的自定义 trim 元素
<update id="updateBlog" parameterType="map">
update blogs
<trim prefix="set" prefixOverrides=",">
<if test="title != null">title=#{title},</if>
<if test="author != null">author=#{author}</if>
</trim>
where blogid=#{id}
</update>
1.4、choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回类型为 software 的 BLOG。
<select id="selectBlogChoose" resultMap="blogResultMap">
select * from blogs
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and type = #{type}
</otherwise>
</choose>
</where>
</select>
@Test
public void selectBlogChoose(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("author","斯蒂芬");
map.put("type","software");
//map.put("title","123");
List<blog> blogs = mapper.selectBlogChoose(map);
sqlSession.close();
}
结果
Opening JDBC Connection
Created connection 11902257.
==> Preparing: select * from blogs WHERE author = ?
==> Parameters: 斯蒂芬(String)
<== Columns: blogid, title, author, type, publishDateTime, content, isDisplay, readCount, image
<== Row: e9118e01ed3d4fc49aa9cb448185df11, 123, 斯蒂芬, software, 2021-11-09 06:36:09.0, wrewqefasdfsafsadf, 1, 242, images/small-image.jpg
<== Total: 1
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@b59d31]
Returned connection 11902257 to pool.
注意看,select * from blogs WHERE author= ? ,跟的是author语句,而不是第一个的title。如果前面两个都没有的话就会调用otherwise里的语句。
1.5、foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符。
//查询id 1到3的博客信息,记得修改博客id
public List<blog> selectBlogForeach(Map map);
<!--select * from blogs where id in ('1','2','3')-->
<select id="selectBlogForeach" parameterType="map" resultMap="blogResultMap">
select * from blogs
<where>
<foreach collection="ids" item="id" open="blogid in(" close=")" separator=",">
#{id}
</foreach>
</where>
</select>
Opening JDBC Connection
Created connection 673186785.
==> Preparing: select * from blogs WHERE blogid in( ? , ? , ? )
==> Parameters: 1(String), 2(String), 3(String)
<== Columns: blogid, title, author, type, publishDateTime, content, isDisplay, readCount, image
<== Row: 1, 已修改这个标题, 被修改的作者, 交朋友, 2019-06-04 06:44:57.0, Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s., 1, 356, images/small-image.jpg
<== Row: 2, Waiting for the Barbarians, Pearl E. Whites, 计算机, 2018-09-19 21:26:30.0, Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s., 1, 160, images/small-image.jpg
<== Row: 3, The Soldier's Art, Walter Melon, 学习, 2018-02-07 05:17:20.0, Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s., 1, 962, images/small-image.jpg
<== Total: 3
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@282003e1]
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
2、缓存(了解即可)
缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
什么是缓存(Cache)
-
存在内存中的历史数据
-
将用户经常查询的数据放在缓存中(内存),用户去查询数据就不再是从磁盘上查询而是从缓存中查询,提高了查询效率,解决高并发系统的性能问题
优点:减少数据库的交互次数,减少系统开销,提高系统效率
注意:建议经常查询且不常改变的数据可以使用缓存
2.1、Mybatis缓存
-
Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存,缓存可以极大地提升查询效率
-
Mybatis系统中默认定义了两级缓存:一级缓存,二级缓存
-
-默认情况下,只有一级缓存开启(SqlSession级别的缓存,也叫本地缓存)
-
二级缓存需要手动开启和配置,是基于namespace级别的缓存
-
为提高扩展性,Mybatis定义缓存接口Cache。可以通过实现Cache接口来自定义二级缓存
-
2.2、一级缓存
一级缓存也叫本地缓存,在sqlSession的创建到close这个间段。
-
与数据库同一次会话期间查询到的数据会放在本地缓存中
-
以后要获取相同的数据的话,可以直接在缓存中拿
步骤:
-
开启日志(前面讲过)
<!--日志-->
<settings>
<setting name="logImpl" value="Stdout_Logging"/>
</settings>
-
在一个session中查询两次信息
@Test
public void selectBlog(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
BlogMapper mapper1 = sqlSession.getMapper(BlogMapper.class);
blog blog1 = mapper1.selectBlog("123");
BlogMapper mapper2 = sqlSession.getMapper(BlogMapper.class);
blog blog2 = mapper2.selectBlog("123");
System.out.println("================");
System.out.println(blog1 == blog2);
sqlSession.close();
}
-
查看日志
Opening JDBC Connection
Created connection 297927961.
==> Preparing: select * from blogs where author = '斯蒂芬' and title like ?
==> Parameters: 123(String)
<== Columns: blogid, title, author, type, publishDateTime, content, isDisplay, readCount, image
<== Row: e9118e01ed3d4fc49aa9cb448185df11, 123, 斯蒂芬, software, 2021-11-09 06:36:09.0, wrewqefasdfsafsadf, 1, 242, images/small-image.jpg
<== Total: 1
================
true
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@11c20519]
可以看到值查询了一次,且返回值相等
缓存失效的情况:
-
查询不同的东西
-
增删改操作,因为会改变原来的数据库数据,所以缓存会刷新
-
查询不同的Mapper.xml
-
手动清除缓存:sqlSession.clearCache();
2.3、二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低,所以有了二级缓存
-
基于namepace级别的缓存,一个名称空间,对应一个二级缓存
-
工作机制
-
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
-
如果当前会话关闭,这个会话对应的以及缓存就会消失
-
新的会话查询信息,就可以从二级缓存中回去内容
-
不同的mapper查出的数据会放在自己对应的缓存(map)中
-