七、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中的语句。

CHOOSEIF的区别就是,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>

这里可以看出来,我们设置了如下规则:

  1. 从authors中遍历取出元素,元素命名为author
  2. 每个元素以逗号分隔
  3. 所有的元素用括号包裹

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的用户信息,我们来看下查询的日志:

七、Mybatis 动态SQL和缓存

可以从日志中看到,查询的语句只执行了一次,第二次查询是直接获取到的结果,这就是从缓存中直接取出的;并且可以看到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的用户信息,我们看结果:

七、Mybatis 动态SQL和缓存

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);
}

七、Mybatis 动态SQL和缓存

可以看到日志中只查询了一次,二级缓存确实生效了。

需要注意的是,如果进行了更新操作(包含insert update delete)二级缓存会自动清空,因为查询的结果可能会有变化。例如在上面的例子中,增加一个更新id=2的用户密码的操作,那么在更新前后查询id=1的用户信息,仍会查询2次。

小结

  • 只要开启了二级缓存,在同一个Mapper下都生效
  • 所有的数据都会先放在一级缓存中
  • 只有当会话提交或关闭的时候,才会提交到二级缓存中

13.4、Ehcache

七、Mybatis 动态SQL和缓存

上一篇:使用Pandas,Numpy解析的MNIST数据


下一篇:2021.9.12模拟赛