MyBatis基础五(动态SQL,缓存)

发布于:2025-04-02 ⋅ 阅读:(13) ⋅ 点赞:(0)

什么是动态SQL?

动态SQL就是指根据不同的条件生成不同的SQL语句。

动态SQL元素和JSTL或基于类似xml的文本处理器相似。

搭建环境

创建表:

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'创建时间',
`view` int(30) not null comment'浏览量'
)engine=innodb default charset=utf8

创建一个基础工程:

1、导包

<dependencies>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
    </dependencies>

2、编写实体类

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    //属性名与字段名不一致,可在setting中配置驼峰命名映射
    private Date createTime;
    private int view;
}

由于字段与属性不同,在核心配置文件中采用驼峰命名映射:

3、编写实体类对应的接口和xml文件

编写自动生成id工具类:

//抑制警告
@SuppressWarnings("all")
public class IDutils {
    //生成随机id
    public static String getId(){
        return UUID.randomUUID().toString().replaceAll("-","");
    }
}

编写测试类:

@Test
    public void addBlogTest(){
        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.setView(6666);
        mapper.addBlog(blog);

        blog.setId(IDutils.getId());
        blog.setTitle("Web基础");
        mapper.addBlog(blog);

        blog.setId(IDutils.getId());
        blog.setTitle("SQL基础");
        mapper.addBlog(blog);

        blog.setId(IDutils.getId());
        blog.setTitle("Java基础");
        mapper.addBlog(blog);
        sqlSession.close();
    }

IF语句

编写接口:

编写接口对应的xml文件:

测试:无参数

 @Test
    public void qureyBlog(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        List<Blog> blogs = mapper.queryBlogIF(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }

有参数Title:

choose(when,otherwise)

有时我们不想应用到所有的条件语句,只想要其中一项,choose能够满足我们的需求,它有点像Java中的switch语句。它能够按照提供了title就根据title查找,提供了author就按照author查找,若两者都没有提供,就返回所有符号条件的Blog

编写接口与xml文件:本节使用的where,在下节所描述与解释。

测试:

查询view=6666的blog信息

传入title=Java基础,view=666进行查询

传入author=‘张三’,title='Java基础',在xml中优先执行的是title=#{title}

trim(where,set)

where语句

select * from blog

where

and title like 'someTitle'

执行上述语句,如果仅仅第二个条件匹配,会执行失败。添加一个where标签,减少在if语句中出现where 1=1 的情况。

where元素只会在至少有一个子元素的条件返回SQL子句的情况下才去插入“where”子句,而且,若语句的开头为“and”或“or”,where元素也会将它们去除。rgwhere元素没有按正常套路出牌,我们可以通过自定义trim元素来定制where元素的功能。

改写if语句中的案例,除去where 1=1,加上where标签。

改写如下:

测试:

set更新语句

set元素会动态前置set关键字,同时也会删掉无关的逗号,因为用了条件语句之后就很可能会在生成的sql语句的后面留下这些逗号。利用set元素调整上述语句。

测试:

更新author

只修改title,让set实现自动去除逗号功能。

trim定制元素功能

<trim prefix="where" prefixOverrides="and|or">
    ...
</trim>

上述:利用trim自定义where元素的功能,prefixOverrides属性会忽略通过管道分割的文本序列。它的作用是移除指定在prefixOverrides属性中的内容,并且插入prefix属性中的指定内容。

<trim prefix="set" suffixOverrides=",">
    ...
</trim>

上述:此处添加了前缀值set,删除了后缀文本逗号。

SQL片段

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

抽取IF语句中使用的例子。

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

<sql id="select_queryBlogIF_if">
            <if test="title!=null">
                title=#{title}
            </if>
            <if test="author!=null">
                and author=#{author}
            </if>
    </sql>

2、在需要使用的地方使用include标签引用即可

<select id="queryBlogIF" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <where>
            <include refid="select_queryBlogIF_if"></include>
        </where>
    </select>

测试结果:

注意事项:

  • 最好基于单表来定义SQL片段。
  • 不要再片段中写入where标签。

foreach

foreach对一个集合进行遍历,通常在构建IN条件语句的时候。

编写接口:

编写接口的xml文件,以下拼接处的and可去掉

编写测试类:

该测试类中集合未传参数。得出结果:

参数参数1,2,3:

结果:

缓存

查询的时候,需要连接数据库,耗费资源。

一个查询的结果,给他暂存存在一个可以直接收取到的地方(内存)

我们再次查询相同的数据时候,直接走缓存,就不用走数据库了。

1、什么是缓存?

  • 存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

2、为什么使用缓存?

  • 减少和数据库的交互次数,减少系统开销,提高系统效率

3、什么样的数据能使用缓存?

  • 经常查询并且不经常改变的数据。(不经常查询但经常改变的数据不适合用缓存)

MyBatis缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。

  • 默认情况下,只有一级缓存开启,(SqlSession级别的缓存,也称为本地缓存
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  • 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

在MyBatis中存在一些策略,默认的清除策略是LRU

  • LRU:最近最少使用,移除最长时间不被使用的对象。
  • FIFO:先进先出,按对象进入缓存的顺序来移除它们。
  • SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象
  • WEAK:弱引用,更积极的基于垃圾收集器状态和弱引用规则移除对象。

一级缓存

一级缓存也叫本地缓存

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中拿,不用必须再去查询数据库

测试在一个Session中查询两次相同记录:

编写接口:

public interface UserMapper {
    //根据id查询用户
    User queryUsersByID(@Param("id") int id);
}

编写接口的xml文件:

 <select id="queryUsersByID" parameterType="_int" resultType="user">
        select * from user where id=#{id}
    </select>

编写测试:执行两次查询,而且第二次查询语句与第一次一致。

   @Test
    public void queryUsersByIDTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.queryUsersByID(1);
        System.out.println(user);
        System.out.println("===============");
        User user2 = mapper.queryUsersByID(1);
        System.out.println(user2);
        System.out.println(user==user2);
        sqlSession.close();
    }

注意:

  • 映射语句文件中的所有select语句的结果将会被缓存
  • 映射语句文件中的所有insert,update,delete语句会刷新缓存
  • 缓存会使用最近最少使用算法(LRU,Least Recently Used)算法来清除不需要的缓存
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)
  • 缓存会保存列表或对象(无法查询方法返回哪种)的1024个引用。
  • 缓存会被视为读、写缓存,这意味着获取到的对象并不是共享的,可以安全的被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

增加一个update修改语句,测试上述案例中的sql执行次数

编写接口:

编写接口的xml文件:

 <update id="updateUser" parameterType="user">
        update user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>

测试:

根据上述结果,增删改操作后,可能会改变原来的数据,所以必定会刷新缓存。

手动清理缓存:

sqlSession.clearCache();

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

二级缓存

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

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

工作机制:

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
  • 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中。
  • 新的会话查询信息,就可以从二级缓存中获取内容。
  • 不同的mapper查出的数据会放在自己对应的缓存(map)中。

开局全局缓存:在全部配置中setting中设置全局地开启配置文件中的所有映射器已经配置的任何缓存。cacheEnabled,默认值是true,但建议显式的写出。

要开启二级缓存,只需要在sql映射文件中添加一行

<cache/>

也可定制配置:

<cache eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="true"/>

创建一个FIFO缓存,每个60秒刷新,最多可以存储结果对象或列表的512个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

在未开启二级缓存时,创建两个sqlSession查询相同的内容,会执行两次sql。

开启二级缓存,二级缓存是事务性的,这意味着,当SqlSession完成并提交时,或是完成并回滚,但没有执行flushCache=true的insert/delete/update语句时,缓存会获得更新。

缓存原理

对该原理进行测试:


网站公告

今日签到

点亮在社区的每一天
去签到