MyBatis高阶

MyBatis高阶

MyBatis高阶

1.上节回顾

  1. 使用MyBatis的基本步骤
  2. mapper中的标签

2.常见面试题

  1. resultMap和resultType的区别?
  2. 如何使用mybatis返回自动生成的主键值?
  3. MyBatis动态SQL执行原理
  4. 如何实现1对N,N对1的查询

3.本节重点

  1. 延迟加载
  2. MyBatis一级缓存
  3. MyBatis二级缓存

4.内容详解

4.1延迟加载

​ 字面理解是对信息的推迟加载(先加载主信息,需要关联信息时再去按需加载关联信息),在MyBatis中通常会进行多表联合查询,但有时不会立即用到所有表的信息,若想等到需要时再取出的这种 按需查询 机制,就可以使用延迟加载来实现

		<setting name="lazyLoadingEnabled" value="true"/>

延迟加载提高系统查询效率

   <select id="selectBlog"  resultMap="blogCommentMap">
        <include refid="sqlQuery"/>
        from blog b where blog_id =#{id}
    </select>

<resultMap id="blogCommentMap" type="blog">
        <id property="blogId" column="blog_id" />
        <result property="blogName" column="blog_name"/>
        <result property="blogContent" column="blog_content"/>
        <!--  当使用多个结果集时,该属性指定结果集中用于与 foreignColumn 匹配的列(多个列名以逗号隔开),
      以识别关系中的父类型与子类型。-->
        <collection property="commentList"  ofType="comment" column="blog_id" select="commentSelect"/>
    </resultMap>

    <select id="commentSelect" resultType="comment">
        select * from comment where comm_blog_id=#{blogId}
    </select>

4.2 动态SQL

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

  1. if

    动态 SQL 通常要做的事情是根据条件包含 where 子句的一部分。

    <select id="queryBlogWithName" parameterType="blog" resultType="blog">
            select * from blog where id=#{id}
            <if test="name !=null">
                and name =#{name}
            </if>
            <if test="content !=null">
                and content =#{content}
            </if>
        </select>
    
  2. choose, when, otherwise

    有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

    <choose>   
        <when test="name !=null">        
            and name like #{name}    </when>
        <when test="name !=null and content !=null ">        
            and name like #{name} and content like #{content}    </when>
        <otherwise> and name like '%name1%'    </otherwise>
    </choose>
    
  3. trim, where, set

    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG
      WHERE
      <if test="state != null">
        state = #{state}
      </if>
      <if test="title != null">
        AND title like #{title}
      </if>
      <if test="author != null and author.name != null">
        AND author_name like #{author.name}
      </if>
    </select>	
    

    如果这些条件没有一个能匹配上会发生什么?最终这条 SQL 会变成这样:

    SELECT * FROM BLOG
    WHERE
    

    如何解决上述问题:

    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG
      <where>
        <if test="state != null">
             state = #{state}
        </if>
        <if test="title != null">
            AND title like #{title}
        </if>
        <if test="author != null and author.name != null">
            AND author_name like #{author.name}
        </if>
      </where>
    </select>
    

    where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。

    ​ 如果 where 元素没有按正常套路出牌,我们可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

    		select * from blog
            <trim prefix="WHERE" prefixOverrides="AND">
                <if test="blogId != null">
                 AND blog_id=#{blogId}
                </if>
            </trim>
    
  4. foreach

    • 执行查询时循环遍历

    动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:

      <select id="selectBlogDynaSql"  resultType="blog">
            select * from blog where blog_id in
            <foreach item="item" index="index" collection="list"
                     open="(" separator="," close=")">
                #{item}
            </foreach>
        </select>
    

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

    • 执行插入时循环遍历

    执行insert语句,插入多条数据

    <insert id="addBlogList" >
            insert into blog(name,content) values
            <foreach item="item" index="index" collection="list" separator=",">
                (#{item.name},#{item.content})
            </foreach>
    </insert>
    

    注意 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象传递给 foreach 作为集合参数。当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

动态SQL原理:其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。

补充点:OGNL(Object-Graph Navigation Language的简称),对象图导航语言,它是一门表达式语言,除了用来设置和获取Java对象的属性之外,另外提供诸如集合的投影和过滤以及lambda表达式等

4.3 MyBatis一级缓存

比较SQL语句执行了几次

​ MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。一级缓存只相对于同一个sqlSession而言,所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用同一个Mapper 的方法,往往只执行一次SQL,因为第一次查询之后,MyBatis会把结果放入缓存中,以后的查询如果没有刷新缓存并且缓存韦超时的情况下,都不会再次查询数据库

blog1和blog2的内存地址空间是一样的,而且SQL只执行了一次,因此blog2是从一级缓存中取得

 			SqlSession session = sqlSessionFactory.openSession();
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            Blog blog1 = mapper.selectBlogOnly(2);
            Blog blog2 = mapper.selectBlogOnly(2);

应用场景:正式开发中,是将mybatis和spring进行整合开发,事务控制在service中。一个service方法中包括很多mapper方法调用

映射语句文件中的所有 insert、update 和 delete 语句会刷新一级缓存。

一级缓存只在当前会话中有效

4.4 MyBatis二级缓存

二级缓存存在于Mapper实例中,当多个SqlSession类的实例对象加载相同的Mapper文件,并执行其中的SQL配置时,他们共享一个Mapper缓存。与一级缓存相比,二级缓存范围更大。一个Mapper有一个自己的二级缓存区域(按照namespace划分)两个Mapper的namespace如果相同,那么两个Mapper执行的Sql查询会缓存到同一个二级缓存中。

要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

全局配置文件中添加:

<setting name="cacheEnabled" value="true"/>

Java代码:

try(SqlSession sqlSession = sqlSessionFactory.openSession()){
            BlogMapperResultMap mapper = sqlSession.getMapper(BlogMapperResultMap.class);
            Blog blog1 = mapper.queryBlogById(1);
        }
        try(SqlSession sqlSession = sqlSessionFactory.openSession()){
            BlogMapperResultMap mapper = sqlSession.getMapper(BlogMapperResultMap.class);
            Blog blog3 = mapper.queryBlogById(1);
        }

特别注意:

​ 这里要讲解一下二级缓存的缓存什么时候存入了:只有当当前的session.close()时,该session的数据才会存入二级缓存.在同一session下时,肯定没有执行.close()关闭session,自然也就没有存入二级缓存.第二次执行却没有重新发送sql语句,是因为第二次调用的是一次缓存中的数据.

比较的是SQL语句执行了几次而不是对象是否相等,因为一级缓存存入到二级缓存时,内存地址空间发生了改变

cache元素的属性:

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

5.总结

查询顺序:二级缓存-->一级缓存-->数据库

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://championmi.cn/archives/mybatis高阶

Buy me a cup of coffee ☕.