🏇 哇 咔 咔 : \textcolor{blue}{哇咔咔:} 哇咔咔: 古 娜 拉 黑 暗 之 神 , 请 赐 予 我 力 量 , 让 我 起 个 床 \textcolor{green}{古娜拉黑暗之神,请赐予我力量,让我起个床} 古娜拉黑暗之神,请赐予我力量,让我起个床😈
💥gitee中MyBatis学习源码💥所用到的代码都可以在这里找到🐳
十二、动态SQL
什么是动态SQL:动态SQL就是根据不同的条件生成不同的SQL语句
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。
1. 搭建环境
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8
2. 创建一个基础工程
2.1导包
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
2.2 编写配置文件
mybaits-config.xml和db.properties
2.3 编写实体类
import lombok.Data;
import java.util.Date;
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;//属性名和字段名不一致
private int views;
}
2.4 编写实体类对应的Mapper接口和Mapper.xml文件
BlogMapper和BlogMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<settings>
<!--标准的日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--可以给实体类起别名-->
<typeAliases>
<package name="com.hxl.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<!--事务管理-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.hxl.dao.BlogMapper"/>
</mappers>
</configuration>
2.5 生成UUID的工具类
import java.util.UUID;
@SuppressWarnings("all")//抑制警告
public class IDutils {
public static String getId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
2.6 测试加入数据
public interface BlogMapper {
//插入数据
int addBlog(Blog blog);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--核心配置文件-->
<mapper namespace="com.hxl.dao.BlogMapper">
<insert id="addBlog" parameterType="blog">
insert into blog (id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
</mapper>
import com.hxl.dao.BlogMapper;
import com.hxl.pojo.Blog;
import com.hxl.utils.IDutils;
import com.hxl.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
public class MyTest {
@Test
public void addInitBlog(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDutils.getId());
blog.setTitle("Mybatis");
blog.setAuthor("王木木");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("Java");
mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("Spring");
mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("微服务");
mapper.addBlog(blog);
sqlSession.close();
}
}
-
出现了问题
他说是无效绑定。
这里需要绑定,但是我是绑定了的,所以又进行了下面的操作
-
在pom文件下加入下面的话既可以解决,加入后记得刷新maven
-
<build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build>
-
-
我觉得出现上述的问题是因为我的BlogMapper.xml是放在src/main/java/com/hxl/dao下的。而不是像上面的那个放在resources下的。所以出现了那个问题。
3. if
List<Blog> queryBlogIF(Map map);
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
@Test
public void queryBlogIF(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title","Java");
List<Blog> blogs = mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
4. trim(where,set)
4.1 where
<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>
-
where元素只会在至少有一个子元素的条件返回SQL子句的情况下采取插入“WHERE”自子句,而且,若语句的开头为“AND“或”OR“,where元素也会将它们去除。
- 我们很容易看到,之前提到的if是用了一个where 1=1.其实这个是不合适的。但是现在加了个标签,它可以自动识别是不是第一个where,然后在判断是否加上and。
- where的存在会判断and是不是需要,第二个前面及以后要加上and。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
4.2 set
- set元素会动态前置SET关键字,同时也会删掉无关的逗号,因为用了条件语句之后很可能就会在生成的SQL语句的后面留下这些逗号
<!--基本类型不需要resultType-->
<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>
@Test
public void updateBlog(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("id","ac040b29e3b047a09a4fe78797193597");
map.put("author","王木木");
map.put("title","Java1");
mapper.updateBlog(map);
sqlSession.close();
}
来看看与 set 元素等价的自定义 trim 元素吧:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
5. choose(when,otherwise)
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
这个choose有点像switch,case。他只能运行一个。比如说前面的都不符合,那他必须有一个views要符合,否则会报错。前面的按照顺序有一个符合了,那他就跳出去了。
6. foreach
select * from user where 1=1 and
<foreach item="id" collection="ids"
open="(" separator="or" close=")">
</foreach>
(id=1 or id=2 or id=3)
使用
//查询第1-2-3号记录的博客
List<Blog> queryBlogForeach(Map map);
<!--传递一个万能的map,这个map可以存在一个集合-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach item="id" collection="ids" open="and (" separator="or" close=")">
id = #{id}
</foreach>
</where>
</select>
@Test
public void queryBlogForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
//这里因为是uuid是String,如果是1,2这种就用Integer
ArrayList<String> ids = new ArrayList<String>();
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
我们还可以进行下面的测试
@Test
public void queryBlogForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList<String> ids = new ArrayList<String>();
ids.add("ac040b29e3b047a09a4fe78797193597");
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
@Test
public void queryBlogForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList<String> ids = new ArrayList<String>();
ids.add("ac040b29e3b047a09a4fe78797193597");
ids.add("a46a7874d69f4d69b9572816d8f30ab7");
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
7. SQL片段
有时候我们会将一些功能的部分抽取出来,方便复用
- 使用SQL标签抽取公共的部分。
-
在需要使用的地方使用include标签引用即可
我们可以加一个sql标签,id可以随意取,然后下面来一个include refid是引用id,和上面的id是一个。这样就可以实现复用。
<sql id="if-title-author"> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql> <select id="queryBlogIF" parameterType="map" resultType="blog"> select * from blog <where> <include refid="if-title-author"/> </where> </select>
-
注意事项:
- 最好基于单表来定义SQL片段
- 不要存在where标签
8. 总结
所谓的动态SQL,本质还是SQL语句,只是我们可以在sql层面,去执行一个逻辑代码。
if,where,set,choose,when
动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就ok了
建议:
- 先在Mysql中写出完整的SQL,再对应的去修改成为我们的动态SQL。实现通用。
十三、缓存
1. 简介
查询-->需要连接数据库,耗资源
一次查询的结果,给他暂存一个可以直接取到的地方-->内存:缓存
我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
- 什么是缓存[Cache]?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
- 为什么要使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率
- 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据
2. Mybatis缓存
- Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率。
- Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(Sqlsession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,是基于namespace级别的缓存
- 为了提高扩展性,mybatis定义了缓存接口,我们可以通过实现Cache接口来自定义二级缓存。
3. 一级缓存
- 一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到得数据会放在本地缓存中
- 以后如果需要获取相同得数据,直接从换从中拿,没必要再去查询数据库
3.1 测试步骤:
-
开启日志
在核心配置文件中
<settings> <!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
-
测试再一个Session中查询两次相同得记录
@Test public void queryUserByIdTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); System.out.println("========"); User user1 = mapper.queryUserById(1); System.out.println(user1); sqlSession.close(); }
-
查看日志输出
3.2 缓存失效得情况
-
查询不同得东西
-
增删改操作可能会改变原来得数据,所以必定会刷新缓存
- 比如说在查两个相同的用户,中间插一个修改用户的操作
-
查询不同Mapper.xml
-
手动清理缓存
-
sqlSession.clearCache();//手动清理缓存
-
3.3 小结
一级缓存默认是开启的,只在一次sqlSession中有效,也就是拿到连接到关闭连接这个区间
一级缓存就相当于一个map
4. 二级缓存
4.1 简介
4.2 开启步骤
-
开启全局缓存
虽然默认就是true但是还是需要显示的开启一下,增强可读性
在核心配置文件中
<settings> <!--显示的开启全局缓存--> <setting name="cacheEnabled" value="true"/> </settings>
-
要启用二级缓存需要在sql映射文件中添加一行代码
<!--在当前Mapper.xml中开启二级缓存--> <cache/>
也可以自定义一些参数
<!--官网上是这个。加上会有一些设定,比如先进先出,60秒,512个引用,只读为true--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
还可以在你想要的地方关闭缓存,只需要在sql语句标签下添加下面的代码
4.3 测试
-
如果没有加二级缓存,查询两个sqlSession是会进入两次数据库的
@Test public void queryUserByIdTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); SqlSession sqlSession2 = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); User user1 = mapper2.queryUserById(1); System.out.println(user1); sqlSession.close(); sqlSession2.close(); }
-
如果开启了二级缓存
@Test public void queryUserByIdTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); SqlSession sqlSession2 = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); sqlSession.close(); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User user1 = mapper2.queryUserById(1); System.out.println(user1); System.out.println(user == user1); sqlSession2.close(); }
-
问题:
我们需要将实体类序列化,否则就会报错
Caused by:java.io.NotSerializableException:com.hxl.pojo.User
我们需要在实体类后加上implements Serializable
@Data public class User implements Serializable { private int id; private String name; private String pwd; }
4.4 小结
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交,或者关闭的时候,才会提交到二级缓存中
- 实体类要序列化
5. 缓存原理
6. 自定义缓存-Ehcache
Ehcache是一种广泛使用的开源Java分布式缓存,主要面向通用缓存
要在程序中使用Ehcache,先要导包
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
在resources下建立一个ehcache.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
-->
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
在需要的Mapper.xml下
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
测试