Mybatis学习笔记3

动态SQL

介绍

什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句

之前写的 SQL 语句都比较简单,如果有比较复杂的业务,需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。

那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

动态 SQL 是 MyBatis 的强大特性之一,与 JSTL 或基于类 XML 语言的文本处理器类似。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

搭建环境

1.创建数据库表blog

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.创建mybatis基础工程
在这里插入图片描述

实际项目中一般需要保持ID的唯一性,固可以编写一个IDutils,来随机生成ID

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

3.编写实体类

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;  // 属性名与字段名不一致
    private int views;
}

4.编写Mapper接口及xml文件

public interface BlogMapper {
}
<?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.NATE.dao.BlogMapper">

</mapper>

5.mybatis核心配置文件,解决属性名和字段名不一致问题——下划线驼峰自动转换,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn

<settings>
    <!-- 是否开启驼峰命名自动映射 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

6.插入数据

  • 在接口里编写方法
 public interface BlogMapper {
    // 插入数据
    int addBlog(Blog blog);
}
  • 在对应xml文件进行配置
 <insert id="addBlog" parameterType="Blog">
    insert into blog (id, title, author, create_time, views)
    values (#{id}, #{title}, #{author}, #{createTime}, #{views})
</insert>
  • 在测试类中插入数据
 @Test
public void addInitBlog(){
    SqlSession sqlSession = MybatisUtils.getSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

    Blog blog = new Blog();
    blog.setId(IDutils.getId());
    blog.setTitle("凡人终有一死");
    blog.setAuthor("达摩流浪者");
    blog.setCreateTime(new Date());
    blog.setViews(10);
    mapper.addBlog(blog);

    blog.setId(IDutils.getId());
    blog.setTitle("go for it");
    mapper.addBlog(blog);

    sqlSession.close();
}

If标签

测试案例:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询

1.编写接口类

   // 查询博客
    List<Blog> queryBlogIF(Map map);

2.编写SQL语句

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog where 1=1
    <!-- 若传入title,则拼接sql -->
    <if test="title != null">
        and title = #{title}
    </if>
    <!-- 若传入author,则拼接sql -->
    <if test="author != null">
        and author = #{author}
    </if>
</select>

3.测试

@Test
public void queryBlogIF(){
    SqlSession sqlSession = MybatisUtils.getSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    map.put("title","凡人终有一死");
    map.put("author","达摩流浪者");
    List<Blog> blogs = mapper.queryBlogIF(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

Where标签

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

测试案例:修改优化上述案例

修改SQL语句:

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog
    <!-- 若传入title,则拼接sql -->
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

Choose标签

有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句

1.编写接口类

List<Blog> queryBlogChoose(Map map);

2.编写SQL语句

  <select id="queryBlogChoose" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <choose>
                <when test="tittle != null">
                    title = #{title}
                </when>
                <when test="author != null">
                    and author = #{author}
                </when>
                <otherwise>
                    and views = #{views}
                </otherwise>
            </choose>
        </where>
    </select>

3.测试

@Test
public void queryBlogChoose(){
    SqlSession sqlSession = MybatisUtils.getSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    //map.put("title","凡人终有一死");
    // map.put("author","达摩流浪者");
    map.put("views",10);
    List<Blog> blogs = mapper.queryBlogChoose(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

Set标签

用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

1.编写接口方法

//更新博客
int updateBlog(Map map);

2.编写SQL语句

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

3.测试

@Test
public void updateBlog(){
    SqlSession sqlSession = MybatisUtils.getSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    map.put("title","凡人终有一死1");
    map.put("author","达摩流浪者1");
    map.put("id","97213138eda644af9056fff279ac86ee");
   mapper.updateBlog(map);
    sqlSession.close();
}

SQL片段

有时候可能某个 sql 语句用的特别多,为了增加代码的重用性,简化代码,可以将这些代码抽取出来,然后使用时直接调用。

1.提取SQL片段

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

2.引用SQL片段

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

注意点:

  • 最好基于单表来定义 sql 片段,提高片段的可重用性
  • 在 sql 片段中不要包括 where

Forech标签

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符。

测试案例:
将数据库中前三个数据的id修改为1,2,3;
需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

1.编写接口方法

//查询第1,2,3号记录的博客
List<Blog> queryBlogForeach(Map map);

2.编写SQL语句

  <!--
         select * from blog where 1=1 and (id=1 or id=2 or id=3)
    -->
    <select id="queryBlogForeach" parameterType="Map" resultType="blog">
        select * from blog
        <where>
            <foreach collection="ids" item="id" open="and (" close=")" separator="or">
                id = #{id}
            </foreach>       
        </where>
    </select>

3.测试

@Test
public void queryBlogForeach(){
    SqlSession sqlSession = MybatisUtils.getSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    ArrayList<Integer> ids = new ArrayList<Integer>();
    ids.add(1);
    ids.add(2);

    map.put("ids",ids);
    List<Blog> blogs = mapper.queryBlogForeach(map);

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

小结

其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错

缓存

简介

1.什么是缓存 [ Cache ]?

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

2.为什么使用缓存?

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

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

  • 经常查询并且不经常改变的数据【可以使用使用缓存】

Mybatis缓存

  • MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。
  • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
  • 默认情况下,只有一级缓存开启。(SqlSession【接口】级别的缓存,也称为本地缓存)
  • 二级缓存需要手动开启和配置,是基于namespace级别的缓存。
  • 为了提高扩展性,MyBatis定义了缓存接口Cache。可以通过实现Cache接口来自定义二级缓存

一级缓存

一级缓存也叫本地缓存:SqlSession

  • 与数据库同一次会话期间查询到数据会放在本地缓存中
  • 之后若需要获得相同的数据,直接从缓存中拿,不会再连接查询数据库

测试步骤:
1.开启日志

<!-- 标准的日志工厂实现 -->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

2.编写接口方法

<select id="queryUsersById"  resultType="user">
    select * from user where id = #{id}
</select>

3.测试

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

@Test
    public void queryUsersById(){
        SqlSession sqlSession = MybatisUtils.getSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.queryUsersById(1);
        System.out.println(user);
        System.out.println("=====================");
        User user1 = mapper.queryUsersById(1);
        System.out.println(user1);
        System.out.println(user==user1);
        sqlSession.close();
    }

4.结果分析
在这里插入图片描述

一级缓存失效的情况

1.sqlSession不同

@Test
public void queryUsersById(){
   SqlSession session = MybatisUtils.getSession();
   SqlSession session2 = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper2.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
   session2.close();
}

2.增删改操作,可能会改变原来的数据,所以一点会刷新缓存

在两次查询中加入一条与查询语句无关的更新语句

@Test
public void queryUsersById(){
    SqlSession sqlSession = MybatisUtils.getSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUsersById(1);
    System.out.println(user);
    mapper.updateUser(new User(2,"wjz","321132")); //更新id=2的用户数据
    System.out.println("=====================");
    User user1 = mapper.queryUsersById(1);
    System.out.println(user1);
    System.out.println(user==user1);
    sqlSession.close();
}

3.sqlSession相同,查询条件不同

@Test
public void queryUsersById(){
    SqlSession sqlSession = MybatisUtils.getSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUsersById(1);
    System.out.println(user);
    System.out.println("=====================");
    User user1 = mapper.queryUsersById(2);
    System.out.println(user1);
    System.out.println(user==user1);
    sqlSession.close();
}

4.sqlSession相同,手动清除一级缓存

@Test
public void queryUsersById(){
    SqlSession sqlSession = MybatisUtils.getSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUsersById(1);
    System.out.println(user);
    session.clearCache();//手动清除缓存
    System.out.println("=====================");
    User user1 = mapper.queryUsersById(2);
    System.out.println(user1);
    System.out.println(user==user1);
    sqlSession.close();
}

小结:一级缓存是默认开启的,只在一次SqlSession中有效,也就是拿到连接到关闭数据库连接。一级缓存相当于一个Map

二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
  • 工作机制:
    1.一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    2.如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是:会话关闭后,一级缓存中的数据被保存到二级缓存中
    3.新的会话查询信息,就可以从二级缓存中获取内容
    4.不同的mapper查出的数据会放在自己对应的缓存(map)中

使用步骤:
1.在mybatis-config.xml中开启全局配置

<settings>
    <!-- 显示的开启全局缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

2.在mapper.xml中配置二级缓存

<!-- 在当前Mapper.xml使用二级缓存 -->
<cache/>

也可以自定义参数

<!-- 这里配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突-->
      <cache 
            eviction="FIFO"
            flushInterval="60000"
            size="512"
            readOnly="true"/>
            

3.测试

@Test
public void queryUsersById(){
    //创建两个Session
    SqlSession sqlSession = MybatisUtils.getSession();
    SqlSession sqlSession2 = MybatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUsersById(2);
    System.out.println(user);
    sqlSession.close();

    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    User user2 = mapper2.queryUsersById(2);
    System.out.println(user2);
    
    System.out.println(user==user2);

    
    sqlSession2.close();
}

4.结果分析
在这里插入图片描述

遇见的问题:
在这里插入图片描述
原因:对应实体类没有序列化
解决方法:需要将该实体类序列化

public class User implements Serializable {
    private int id;
    private String name;
    private String pwd;
}

总结:

  • 只要开启了二级缓存,在同一个Mapper中的查询,就能在二级缓存中拿到数据
  • 查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值