SSM之MyBatis 04 —— 动态SQL、缓存Cache

系列文章

SSM之MyBatis 01 —— 第一个MyBatis程序、增删改查(模糊查询)

SSM之MyBatis 02 —— 配置文件说明、日志工厂、分页(Limit和RowBounds)

SSM之MyBatis 03 —— 使用注解开发、Lombok、多对一&一对多处理

SSM之MyBatis 04 —— 动态SQL、缓存Cache



十二、动态SQL

动态SQL就是可以根据不同条件生成不同的SQL语句,在之前的smbms项目中,我们都是利用StringBuffer进行SQL语句的拼接,而MyBatis动态SQL可以避免这种麻烦,使用起来和JSTL类似。

所谓的动态SQL,其本质还是SQL语句,只是我们可以在SQL层面去执行逻辑代码(例如if)

1、搭建环境

SQL语句

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

1、创建基础工程,编写工具类

package com.zcy.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtils {
    
    
    private static SqlSessionFactory sqlSessionFactory = null;
    static{
    
    
        String resource = "mybatis-config.xml";
        try {
    
    
            InputStream is = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
    public static SqlSession getSqlSession(){
    
    
        return sqlSessionFactory.openSession(true);
    }
}

IdUtils.java 通过UUID获得随机且唯一的id(实际项目都不会是从1一直加,而是随机ID号)

package com.zcy.utils;

import org.junit.Test;

import java.util.UUID;

public class IdUtils {
    
    
    public static String getId(){
    
    
        return UUID.randomUUID().toString().replace("-", "");
    }
    @Test
    public void test(){
    
    
        System.out.println(getId());
    }
}

2、编写配置文件

<?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">
        <property name="name" value="root"/>
        <property name="pwd" value="123456"/>
    </properties>

    <!-- 配置日志    -->
    <settings>
        <!-- 使用日志,需要导包log4j        -->
        <setting name="logImpl" value="LOG4J"/>
        <!-- 开启驼峰命名自动映射:例如 实体类中属性名 userName <-> 数据库字段 user_name -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!-- 别名  -->
    <typeAliases>
        <typeAlias type="com.zcy.pojo.Blog" alias="Blog"/>
    </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="${name}"/>
                <property name="password" value="${pwd}"/>
            </dataSource>
        </environment>
    </environments>
	<!-- 注册mapper -->
    <mappers>
        <mapper class="com.zcy.dao.BlogMapper"/>
    </mappers>
</configuration>

3、编写实体类

package com.zcy.pojo;

import lombok.Data;

import java.util.Date;

@Data
public class Blog {
    
    
    private String id;
    private String title;
    private String author;
    //和数据库字段不一致,create_Time。在核心配置文件中开启驼峰命名自动映射
    private Date createTime;
    private int views;
}

4、编写实体类对应的Mapper接口和Mapper.xml

package com.zcy.dao;

import com.zcy.pojo.Blog;

public interface BlogMapper {
    
    

    //插入数据
    public int addBlog(Blog blog);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcy.dao.BlogMapper">

    <!-- 插入数据 -->
    <insert id="addBlog" parameterType="Blog">
        insert into blog (id, title, author, create_time, views)
        values (#{id},#{title},#{author},#{createTime},#{views})
    </insert>

</mapper>

5、测试

package com.zcy.dao;

import com.zcy.pojo.Blog;
import com.zcy.utils.IdUtils;
import com.zcy.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.Date;

public class BlogTest {
    
    
    @Test
    public void addBlog(){
    
    
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

        String id = IdUtils.getId();
        Blog blog = new Blog(id, "Java如此简单", "小白",new Date(), 100);
        blogMapper.addBlog(blog);

        id = IdUtils.getId();
        blog = new Blog(id, "MyBatis如此简单", "小红",new Date(), 300);
        blogMapper.addBlog(blog);

        id = IdUtils.getId();
        blog = new Blog(id, "Spring如此简单", "小白",new Date(), 500);
        blogMapper.addBlog(blog);

        id = IdUtils.getId();
        blog = new Blog(id, "微服务如此简单", "小白",new Date(), 250);
        blogMapper.addBlog(blog);

        sqlSession.close();
    }
}

2、IF

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分,比如当我们想查询某个记录时,要求id为xxx,并且姓名为xxx,或者只输入id或者只输入姓名,这种多场景查询。

BlogMapper.java

public interface BlogMapper {
    
    

    //插入数据
    public int addBlog(Blog blog);

    //查询数据,多个参数用Map(这里没用泛型也可以)
    public List<Blog> queryBlogIF(Map map);
}

where 标签等同于where,并且假如我们只满足第二个if,它会自动帮我们去掉and或or。

if标签,test是必填参数,当test里的条件满足时,就追加if标签里的SQL。

BlogMapper.xml

<!-- 查询数据 -->
<!-- map是Map的别名 -->
<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>
        <if test="views != null">
            and views >= #{views}
        </if>
    </where>
</select>

<!-- 
	事实上我们当然也可以写成 
select * from blog where 1=1
<if test="title != null">
	and title = #{title}
</if>
	但1=1这种方式不正规
-->

BlogTest.java

@Test
public void selectBlog(){
    
    
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

    //通过Map添加多个参数,然后select里的if会进行判断并追加SQL
    HashMap map = new HashMap();
    //map.put("title","Java如此简单");
    map.put("author", "小白");
    map.put("views", 300);
    List<Blog> blogs = blogMapper.queryBlogIF(map);

    for (Blog blog : blogs)
        System.out.println(blog);
    sqlSession.close();
}

结果:可以发现,author前面没有and,因为where标签自动帮我们去掉了

在这里插入图片描述
 

3、choose(when, otherwise)

choose相当于Java的switch,它只会选择一个执行,而前面的if是满足条件都会执行。而when相当于switch的case,otherwise相当于default。

BlogMapper.java

//查询数据 choose(when,otherwise)
public List<Blog> queryBlogChoose(Map map);

BlogMapper.xml

<!-- 查询数据 -->
<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>views = #{views}</otherwise>
        </choose>
    </where>
</select>

BlogTest.java

@Test
public void selectBlog(){
    
    
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    map.put("author", "小白");
    map.put("views", 300);
    List<Blog> blogs = blogMapper.queryBlogChoose(map);

    for (Blog blog : blogs)
        System.out.println(blog);
    sqlSession.close();
}

结果:可以发现,测试中写了author和views,但结果中的SQL只有author,没有追加views

在这里插入图片描述
 

4、trim(where、set)

where标签就如同前面if里的使用,它替代SQL中的where,同时会帮我们去掉多余的and、or。

set则是用在update中,会帮我们去掉多余的逗号。

BlogMapper.java

//更新
public int updateBlog(Map map);

BlogMapper.xml

<!-- 更新数据 -->
<update id="updateBlog" parameterType="map">
    update blog
    <set>
        <!-- 这里要有逗号,不然满足两个if时,SQL语法会有错 -->
        <if test="title != null">title = #{title},</if>
        <if test="author != null">author = #{author}</if>
    </set>
    where id = #{id}
</update>

BlogTest.java

@Test
public void updateBlog(){
    
    
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    map.put("author", "小新");
    map.put("id","2b8a9e125ae9475b864c2032746caa97");
    blogMapper.updateBlog(map);

    sqlSession.close();
}

结果:可以发现auther后面没有逗号,因为set自动帮我们去掉了

在这里插入图片描述

trim

trim相当于可以自定义where和set这种功能的标签,但一般用where和set就够用了。

<!-- 等价于where -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>
<!-- 等价于set -->
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

5、SQL片段

可以将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>
        <if test="views != null">
            and views >= #{views}
        </if>
    </where>
</select>

//抽取SQL后的样子
<!-- id作为标识,include标签引用 -->
<sql id="if-title-author-views">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
    <if test="views != null">
        and views >= #{views}
    </if>
</sql>

<select id="queryBlogIF" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <include refid="if-title-author-views"/>
    </where>
</select>

**注意事项:**sql片段里最好不要存在where标签,因为它会优化sql,比较复杂,SQL片段里最好放简单的,比如if判断。
 

6、Foreach

考虑下面的SQL

    select * from blog where 1=1 and (id=2 or id=3)

这种SQL我们就需要用到foreach,因为当我们用Map作为参数传进来的时候,它只有一个键值对能叫做id,而上面的SQL中需要判断两个id。

BlogMapper.java

//查询数据 foreach
public List<Blog> queryBlogForeach(Map map);

BlogMapper.xml

<!-- foreach 相当于便利传递进来的集合,对集合每个值进行操作
	下面就等价于select * from blog where (id=2 or id=3)
	open:表示开头
	separator:表示分隔符
	close:表示结尾
	collection:表示传递进来的集合,item:集合中的每个元素
-->
<select id="queryBlogForeach" parameterType="map" resultType="Blog">
    select * from blog
    <!-- where标签自动去掉and -->
    <where>
        <foreach item="id" collection="ids" open="and (" separator="or" close=")">
            id = #{id}
        </foreach>
    </where>
</select>

BlogTest.java

@Test
public void queryBlogForeach(){
    
    
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

    ArrayList<String> arrayList = new ArrayList<String>();
    arrayList.add("2f0a4ca699e24dfea15bd90807874749");
    arrayList.add("2b8a9e125ae9475b864c2032746caa97");

    HashMap map = new HashMap();
    map.put("ids",arrayList);
    List<Blog> blogs = blogMapper.queryBlogForeach(map);
    for (Blog blog : blogs)
        System.out.println(blog);
    sqlSession.close();
}

建议:先在MySQL写好SQL语句,再将其修改为对应的动态SQL

十三、缓存

13.1、简介

  1. 什么是缓存[Cache]?
    • 存在内存中的临时数据
    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,而是从缓存中查询,从而提高查询效率,解决高并发系统的性能问题。
  2. 为什么使用缓存?
    • 减少和数据库的交互次数,减少系统开销,提高系统效率
  3. 什么样的数据能使用缓存?
    • 经常查询且不会经常改变的数据
       

13.2、MyBatis缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存,只在SqlSession开启到它关闭期间有效)
    • 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
    • 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口自定义二级缓存。
       

13.3、一级缓存

一级缓存作用不太大,它底层就是一个Map集合。

  • 一级缓存也叫本地缓存:SqlSession连接时自动开启
    • 有效期从SqlSession打开到关闭,数据存在本地
    • 后面如果需要获取相同的数据,直接会从缓存中获取,不再查询数据库
  • 一级缓存失效:
    • 查询不同的东西
    • 增删改操作,会改变原来数据,所以必定刷新缓存
    • 查询不同的mapper.xml
    • 手动清理

测试代码

@Test
public void testCache(){
    
    
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    System.out.println("--------------------------");
    User user1 = userMapper.getUserById(1);
    User user2 = userMapper.getUserById(1);
    System.out.println(user1==user2);
    System.out.println("--------------------------");
    User user3 = userMapper.getUserById(2);
    userMapper.updateUser(new User(3, "123", "1234567"));
    //只要增删改都会刷新一级缓存
    User user4 = userMapper.getUserById(2);
    //手动刷新缓存 sqlSession.clearCache();
    System.out.println(user3==user4);
    System.out.println("--------------------------");

    sqlSession.close();
}

结果:可以发现第一次和第二次查询得到的User是同一个对象,而第三次和第二次中间进行了更新操作,刷新了缓存,所以第三次和第四次的对象不同。

在这里插入图片描述
 

13.4、二级缓存

  • 二级缓存也叫全局缓存,一级缓存的作用域太低,所以诞生了二级缓存。

  • 基于namespace级别的缓存,一个命名空间对应一个二级缓存

  • 工作机制:

    • 一个会话查询一条数据,这个数据会被放在当前会话的一级缓存中
    • 如果当前会话关闭,这个会话对应的一级缓存就没了。开启二级缓存后,会话关闭了,一级缓存的数据会被放在二级缓存中。
    • 新会话会到二级缓存中获取数据。
    • 不同mapper查出的数据会放在自己对应的缓存(map)中。

    步骤:

    1. 核心配置文件中开启全局缓存

      <settings>
          <!-- 开启全局缓存 -->
          <setting name="cacheEnabled" value="true"/>
      </settings>
      
    2. 在mapper.xml中使用二级缓存

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.zcy.dao.UserMapper">
          <!-- 这样就算使用了,但也可以配置参数 -->
          <!--  <cache/> -->
          <!-- 配置参数 eviction:缓存清除的策略包括 LRU-最近最少使用、FIFO先进先出、SOFT、WEAK
          flushInterval:刷新时间60秒,最多存512个对象,返回的对象是只读  -->
          <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
      
          <select id="getUserById" resultType="user">
              select * from user where id = #{id}
          </select>
      
          <update id="updateUser" parameterType="user">
              update user set name=#{name}, password=#{password} where id=#{id}
          </update>
      </mapper>
      
    3. 测试

      @Test
      public void testCache(){
              
              
          SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
          UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
          User user1 = userMapper1.getUserById(1);
          sqlSession1.close();
      
          SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
          UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
          User user2 = userMapper2.getUserById(1);
          sqlSession2.close();
      
          System.out.println(user1==user2);
      }
      

      结果:
      在这里插入图片描述

小结:

  • 二级缓存只在同一个mapper下有效
  • 所有数据都会先放在一级缓存,SqlSession关闭后才会转存到二级缓存
  • 实体类尽量实现序列化,实现Serializable接口
     

13.5、缓存原理

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_39763246/article/details/113988999