七、Mybatis 动态SQL和缓存
12、动态SQL
动态SQL就是根据不同的条件生成不同的SQL
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
环境搭建
新建一个项目然后准备一张新表Blog,并插入数据。
CREATE TABLE blog(
`id` VARCHAR(40) 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
pojo/Blog.java
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
dao/BlogMapper.java
public interface BlogMapper {
//插入数据
int addBlog(Blog blog);
}
dao/BlogMapper.xml
<insert id="addBlog" parameterType="blog">
insert into blog(id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views})
</insert>
在test下编写测试方法插入数据:
@Test
public void testAddBlog() {
Blog blog = new Blog();
blog.setId(IDUtils.getId());
blog.setTitle("Mybatis学习-03");
blog.setAuthor("小张");
blog.setCreateTime(new Date());
blog.setViews(9999);
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
int i = mapper.addBlog(blog);
System.out.println(i);
sqlSession.commit();
sqlSession.close();
}
Tip: 在mybatis-config.xml中设置
可以自动映射数据库字段create_time到createTime(从下划线转换到驼峰)
IF
BlogMapper.xml
<select id="queryBlogByIF" resultType="blog" parameterType="map">
select * from blog
<where>
<if test="title != null">
title like concat('%',#{title},'%')
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
MyTest.java
@Test
public void testQueryBlogByIF() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("author","小芳");
map.put("title","学习");
List<Blog> blogs = mapper.queryBlogByIF(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
上面的例子使用了IF
关键字,如果map中包含title就在sql语句中添加title的查询条件,如果map中包含author,则在sql中添加author的查询条件。
choose(when、otherwise)
<select id="queryBlogByChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="title != null">
title like concat('%',#{title},'%')
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views > #{views}
</otherwise>
</choose>
</where>
</select>
这里使用的choose标签与Java中的switch/case语句的效果是类似的,如果when中的条件成立则执行,不会在查看后面的when标签;如果所有的when标签都不满足,则执行otherwise中的语句。
CHOOSE
和IF
的区别就是,if的查询条件是叠加的,而choose的查询条件是多个当中取一个。
trim(where、set)
在数据库更新语句中,需要用到set关键字来更新sql的值。
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
author = #{author}
</if>
<if test="views != null">
views = #{views}
</if>
</set>
where id = #{id}
</update>
SQL片段
SQL片段指的是,将一段xml配置抽取出来作为模版,方便对这段配置进行复用。我们将上面choose查询的配置抽取出来,简单的展示下:
<select id="queryBlogByChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<include refid="chooseSql">
</include>
</where>
</select>
<sql id="chooseSql">
<choose>
<when test="title != null">
title like concat('%',#{title},'%')
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views > #{views}
</otherwise>
</choose>
</sql>
将要抽取的片段放在sql标签中,在需要使用的地方使用include引用。
Foreach
Foreach是mybatis中的一个强大的用法,它可以从一个可迭代的对象中取出元素,按照设定的规则拼装进sql语句中。比较常见的用法是in
的查询场景:
select * from blog where id in (1,2,3)
<select id="queryBlogByForeach" parameterType="map" resultType="blog">
select * from blog where author in
<foreach collection="authors" item="author" open="(" separator="," close=")">
#{author}
</foreach>
</select>
这里可以看出来,我们设置了如下规则:
- 从authors中遍历取出元素,元素命名为author
- 每个元素以逗号分隔
- 所有的元素用括号包裹
MyTest.java
@Test
public void testQueryBlogByForeach() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<>();
ArrayList<String> list = new ArrayList<>();
list.add("小芳");
list.add("小张");
map.put("authors", list);
List<Blog> blogs = mapper.queryBlogByForeach(map);
for (Blog blog : blogs) {
System.out.print(blog);
}
sqlSession.close();
}
我们在测试方法中,传入authors列表,即可查询出对应作者的博客信息。
面试高频
- MySQL引擎
- InnoDB底层原理
- 索引&索引优化
13、缓存
什么是缓存?
一般我们在系统中使用缓存技术是为了提升数据查询的效率。当我们从数据库中查询到一批数据后将其放入到缓存中(简单理解就是一块内存区域),下次再查询相同数据的时候就直接从缓存中获取数据就行了。这样少了一步和数据库的交互,可以提升查询的效率。
13.1、Mybatis缓存
在Mybatis中提供了一级缓存和二级缓存供我们使用。一级缓存是默认开启的,它针对的范围是sqlSession,二级缓存则需要我们手动开启。
13.2、一级缓存
Mybatis的一级缓存是sqlSession级别的,即在一个sqlSession创建出来到关闭结束这段期间。
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.getUserById(1);
System.out.println(user1);
System.out.println("===================");
User user2 = mapper.getUserById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession.close();
}
在上面的测试方法中,我们在一个sqlSession期间查询了两次id为1的用户信息,我们来看下查询的日志:
可以从日志中看到,查询的语句只执行了一次,第二次查询是直接获取到的结果,这就是从缓存中直接取出的;并且可以看到user1==user2返回的是true,说名两次查询的对象是同一个。
但是,一级缓存的作用域只在sqlSession中生效,如果是两个sqlSession查询了同样的语句,是不会共用缓存的。
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = mapper.getUserById(1);
System.out.println(user1);
System.out.println("===================");
User user2 = mapper2.getUserById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession.close();
sqlSession2.close();
}
我们分别创建了两个sqlSession,分别去查询id=1的用户信息,我们看结果:
13.3、二级缓存
由于一级缓存的作用范围很小,可使用的场景很少;Mybatis提供了二级缓存的使用。
什么是二级缓存?
在一级缓存要被清理掉的时候,如果开启了二级缓存服务,则会将一级缓存中的信息存放到二级缓存中。二级缓存的作用域是namespace级别的,即一个Mapper.xml配置文件中的范围。
开启二级缓存
mybatis-config.xml
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--显式开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
在需要开启二级缓存的Mapper.xml中增加cache标签
UserMapper.xml
<cache/>
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
上面是默认的配置,下面是使用自定义的配置:eviction是缓存的策略FIFO是先进先出策略,flushInterval刷新时间等等......
还有一点非常重要,要使用二级缓存,需要我们的实体类支持序列化
pojo/User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private String password;
}
使用二级缓存
这样我们的二级缓存就可以使用了,我们还运行上面的2个sqlSession查询同样数据的测试。需要注意的是,只有在sqlSession关闭后才会将信息放入二级缓存,我们需要调整下顺序:
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = mapper.getUserById(1);
System.out.println(user1);
sqlSession.close();
System.out.println("===================");
User user2 = mapper2.getUserById(1);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1==user2);
}
可以看到日志中只查询了一次,二级缓存确实生效了。
需要注意的是,如果进行了更新操作(包含insert update delete)二级缓存会自动清空,因为查询的结果可能会有变化。例如在上面的例子中,增加一个更新id=2的用户密码的操作,那么在更新前后查询id=1的用户信息,仍会查询2次。
小结
- 只要开启了二级缓存,在同一个Mapper下都生效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交或关闭的时候,才会提交到二级缓存中
13.4、Ehcache
略