zl程序教程

您现在的位置是:首页 >  后端

当前栏目

不能吧,你不能这样,都最后了-------sql和缓存(“最易懂得MyBatis学习”)

mybatisSQL缓存学习 不能 最后 这样 易懂
2023-09-11 14:20:20 时间

🏇 哇 咔 咔 : \textcolor{blue}{哇咔咔:} 古 娜 拉 黑 暗 之 神 , 请 赐 予 我 力 量 , 让 我 起 个 床 \textcolor{green}{古娜拉黑暗之神,请赐予我力量,让我起个床} 😈
💥gitee中MyBatis学习源码💥所用到的代码都可以在这里找到🐳

十二、动态SQL

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片段

有时候我们会将一些功能的部分抽取出来,方便复用

  1. 使用SQL标签抽取公共的部分。

在这里插入图片描述

  1. 在需要使用的地方使用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>
    
  2. 注意事项:

    • 最好基于单表来定义SQL片段
    • 不要存在where标签

8. 总结

所谓的动态SQL,本质还是SQL语句,只是我们可以在sql层面,去执行一个逻辑代码。

if,where,set,choose,when

动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就ok了

建议:

  • 先在Mysql中写出完整的SQL,再对应的去修改成为我们的动态SQL。实现通用。

十三、缓存

1. 简介

查询-->需要连接数据库,耗资源
	一次查询的结果,给他暂存一个可以直接取到的地方-->内存:缓存
我们再次查询相同数据的时候,直接走缓存,就不用走数据库了 
  1. 什么是缓存[Cache]?
    • 存在内存中的临时数据
    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
  2. 为什么要使用缓存?
    • 减少和数据库的交互次数,减少系统开销,提高系统效率
  3. 什么样的数据能使用缓存?
    • 经常查询并且不经常改变的数据

2. Mybatis缓存

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

3. 一级缓存

  • 一级缓存也叫本地缓存:SqlSession
    • 与数据库同一次会话期间查询到得数据会放在本地缓存中
    • 以后如果需要获取相同得数据,直接从换从中拿,没必要再去查询数据库

3.1 测试步骤:

  1. 开启日志

    在核心配置文件中

    <settings>
        <!--标准的日志工厂实现-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
  2. 测试再一个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. 查看日志输出

    在这里插入图片描述

3.2 缓存失效得情况

  • 查询不同得东西

  • 增删改操作可能会改变原来得数据,所以必定会刷新缓存

    • 比如说在查两个相同的用户,中间插一个修改用户的操作
  • 查询不同Mapper.xml

  • 手动清理缓存

    • sqlSession.clearCache();//手动清理缓存
      

3.3 小结

一级缓存默认是开启的,只在一次sqlSession中有效,也就是拿到连接到关闭连接这个区间

一级缓存就相当于一个map

4. 二级缓存

4.1 简介

4.2 开启步骤

  1. 开启全局缓存

    虽然默认就是true但是还是需要显示的开启一下,增强可读性

    在这里插入图片描述

    在核心配置文件中

    <settings>
        <!--显示的开启全局缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 要启用二级缓存需要在sql映射文件中添加一行代码

    <!--在当前Mapper.xml中开启二级缓存-->
    <cache/>
    

    也可以自定义一些参数

    <!--官网上是这个。加上会有一些设定,比如先进先出,60秒,512个引用,只读为true-->
    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    

    还可以在你想要的地方关闭缓存,只需要在sql语句标签下添加下面的代码

    在这里插入图片描述

4.3 测试

  1. 如果没有加二级缓存,查询两个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();
    }
    

    在这里插入图片描述

  2. 如果开启了二级缓存

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

    在这里插入图片描述

  3. 问题:

    我们需要将实体类序列化,否则就会报错

    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 小结

  1. 只要开启了二级缓存,在同一个Mapper下就有效
  2. 所有的数据都会先放在一级缓存中
  3. 只有当会话提交,或者关闭的时候,才会提交到二级缓存中
  4. 实体类要序列化

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>

测试

在这里插入图片描述