Mybatis高级应用(下)

首先声明。该Mybatis系列的博客是我学习性博客,整个系列的博客是需要连贯起来看的。总结也是片段性总结的。如果觉得有些思维跳跃,或不连贯请参考代码。

本项目所有代码及文档都托管在 Github地址:https://github.com/JYG0723/mybatispractice

如果觉得不错的话,欢迎给个 star , 如果你想完善这个项目的话,你也可以 fork 后修改然后推送给我。


延迟加载

什么是延迟加载?

resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。

需求:
如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。

延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

整个延迟加载的思路:

  1. 执行上边mapper方法(findOrdersUserLazyLoading),内部去调用cn.zhisheng.mybatis.mapper.OrdersMapperCustom 中的 findOrdersUserLazyLoading 只查询 orders 信息(单表)。

  2. 在程序中去遍历上一步骤查询出的 List,当我们调用 Orders 中的 getUser 方法时,开始进行延迟加载。

  3. 延迟加载,去调用 UserMapper.xml 中 findUserbyId 这个方法获取用户信息。

思考:

不使用 mybatis 提供的 association 及 collection 中的延迟加载功能,如何实现延迟加载??

实现方法如下:
定义两个mapper方法:

  1. 查询订单列表
  2. 根据用户id查询用户信息

实现思路:
1. 先去查询第一个mapper方法,获取订单信息列表
2. 在程序中(service),按需去调用第二个mapper方法去查询用户信息。

总之:
使用延迟加载方法,先去查询简单的 sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。

一对多延迟加载

上面的那个案例是一对一延迟加载,那么如果我们想一对多进行延迟加载呢,其实也是很简单的。

一对多延迟加载的方法同一对一延迟加载,在collection标签中配置select内容。

延迟加载总结:
作用:
1. 当需要查询关联信息时再去数据库查询,默认不去关联查询,提高数据库性能。
只有使用resultMap支持延迟加载设置。

场合:
1. 当只有部分记录需要关联查询其它信息时,此时可按需延迟加载,需要关联查询时再向数据库发出sql,以提高数据库性能。
2. 当全部需要关联查询信息时,此时不用延迟加载,直接将关联查询信息全部返回即可,可使用resultType或resultMap完成映射。


查询缓存

什么是查询缓存?

mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。

mybaits提供一级缓存,和二级缓存。

  • 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
  • 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

alt text

  • 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。

    • 得到用户信息,将用户信息存储到一级缓存中。
  • 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

  • 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

一级缓存

Mybatis 默认支持一级缓存,不需要在配置文件中配置。

public void testCache1() throws Exception {
        sqlSession = sqlSessionFactory.openSession();
        //创建UserDao对象,mybatis自动生成代理对象
        UserDao userDao = sqlSession.getMapper(UserDao.class);

        //查询使用的是同一个session
        //第一次发起请求,查询Id 为1的用户信息
        User user1 = userDao.findUserById(1);
        System.out.println(user1);

        /*
            //如果sqlSession去执行commit操作(执行插入、更新、删除),
            // 清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
            //更新user1的信息,
            user1.setUsername("李飞");
            //user1.setSex("男");
            //user1.setAddress("北京");
            userDao.updateUser(user1);
            System.out.println("updateUser /================================/ had down");
            //提交事务,才会去清空缓存
            sqlSession.commit();
        */

        //第二次发起请求,查询Id 为1的用户信息
        //通过log结果可以看出两次查询之间sqlsession不含commit操作。第二次没有发出sql查询请求,
        User user2 = userDao.findUserById(1);
        System.out.println(user2);
    }

一级缓存应用:
正式开发,是将 mybatis 和 spring 进行整合开发,事务控制在 service 中。

一个 service 方法中包括很多 mapper 方法调用。

service{

     //开始执行时,开启事务,创建SqlSession对象

     //第一次调用mapper的方法findUserById(1)

     //第二次调用mapper的方法findUserById(1),从一级缓存中取数据

     //方法结束,sqlSession关闭
}

如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空

二级缓存

alt_text

首先开启mybatis的二级缓存。

  1. sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。

  2. 如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。

  3. sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

二级缓存与一级缓存区别:
+ 二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。

UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。

每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同(一个接口配置两个mapper),这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。

开启二级缓存:
mybaits的二级缓存是mapper范围级别,除了Configuration.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。

Configuration.xml:

<setting name="cacheEnabled" value="true"/><!--开启二级缓存-->

mapper.xml:

<cache/><!--开启该mapper下的二级缓存-->

调用 pojo 类实现序列化接口:
二级缓存需要查询结果映射的pojo对象实现Java.io.Serializable接口,来实现序列化和反序列化操作(因为二级缓存数据存储介质多种多样,在内存不一样),注意如果该需要缓存的类存在父类、或者属性成员 pojo都需要实现序列化接口。

public class Orders implements Serializable
public class User implements Serializable

useCache 配置

在 statement 中设置 useCache=false 可以禁用当前 select 语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。

<select id="findUserById" parameterType="int" resultType="user" useCache="false">

总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。

刷新缓存(清空缓存)

在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。

设置statement配置中的flushCache=”true” 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

如下:

<insert id="insetrUser" parameterType="cn.zhisheng.mybatis.po.User" 
flushCache="true">

一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。

Mybatis Cache参数

  • flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。相当于 cache 的全局刷新属性设置。到时间段清空

  • size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。

  • readOnly(只读)属性可以被设置为true或false(如果设置为false为可读写)。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此该属性默认是false。即缓存数据可以被修改

如下例子:

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

这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有, 默认的是 LRU:

  • eviction 缓存内容的回收策略

    • LRU – 最近最少使用的:移除最长时间不被使用的对象。

    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

    说意义不大是在于:
    a、面对一定规模的数据量,内置的cache方式就派不上用场了;
    b、对查询结果集做缓存并不是MyBatis框架擅长的,它专心做的应该是sql mapper。采用此框架的Application去构建缓存更合理,比如采用OSCache、Memcached啥的。

猜你喜欢

转载自blog.csdn.net/jyg0723/article/details/71079789