mybatis05

缓存机制

引入缓存的目的在于减少数据库的访问次数,提高操作执行效率
Mybatis框架内置了一级缓存机制和二级缓存机制

  • 一级缓存机制又称为会话Session缓存,默认是开启的且无法关闭。除非配置范围变小或者关闭SqlSession
  • 二级缓存机制又成为namespace缓存,作用于某个namespace,可以理解为是接口
  • 二级缓存的应用优先级高于一级缓存,也就是说框架将优先从二级缓存中查找数据,如果命中将返回,如果未命中,则从一级缓存中查找数据,如果命中将返回,如果仍未命中,将执行真正的查询

一级缓存

一级缓存是基于PerpetualCache的HashMap本地缓存,其存储作用域为SqlSession,当 Session flush
或 close 之后,该 Session 中的所有 Cache 就将清空。
一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有
一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法
访问。
一级缓存是基于SqlSession的缓存,一级缓存的内容不能跨SqlSession。由mybatis自动维护。不同的
sqlsession之间的缓存区域是互相不影响的。

具体编程

//两次查询使用的是同一个SqlSession,因为ThreadLocal<SqlSession>
@Test
public void test1(){
UserMapper um=MyBatisSessionFactory.getMapper(UserMapper.class);
User user1 = um.selectByPrimaryKey(1L);
System.out.println(user1);
User user2=um.selectByPrimaryKey(1l);
System.out.println(user1==user2);
}

控制台上输出执行的SQL语句

具体的查询只执行了一次,所以第二次根据id查询值bean,并没有查询数据,因为是一级缓存
> Preparing: select id, username, password, birth, sex, salary from
t_users where id = ?
> Parameters: 1(Long)
<
Columns: id, username, password, birth, sex, salary
<
Row: 1, yanjun, 123456, 1989-02-03, 1, 12345.00
<== Total: 1
User(id=1, username=yanjun, password=123456, birth=Fri Feb 03 00:00:00 CST
1989, sex=true, salary=12345.0)
true // 因为user1==user2,所以两次查询实际上是使用同一个user对象

具体流程:

  • 第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来
    • User user1 = um.selectByPrimaryKey(1L);
  • 第二次执行select会从缓存中查数据,如果select相同且传参数一样,那么就能从缓存中返回数据,不用去访问数据库了,从而提高了效率
    编程调用
@Test
public void test1(){
UserMapper um=MyBatisSessionFactory.getMapper(UserMapper.class);
User user1 = um.selectByPrimaryKey(1L);
System.out.println(user1);
MyBatisSessionFactory.closeSession(); //关闭了user1对应的sqlSession
um=MyBatisSessionFactory.getMapper(UserMapper.class);//获取一个新的
SqlSession
User user2=um.selectByPrimaryKey(1l);
System.out.println(user2);
System.out.println(user1==user2);
}

执行的SQL语句

> Preparing: select id, username, password, birth, sex, salary from
t_users where id = ?
> Parameters: 1(Long)
<
Columns: id, username, password, birth, sex, salary
<
Row: 1, yanjun, 123456, 1989-02-03, 1, 12345.00
<== Total: 1
User(id=1, username=yanjun, password=123456, birth=Fri Feb 03 00:00:00 CST
1989, sex=true, salary=12345.0)
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@e15b7e8]
Returned connection 236304360 to pool.
Opening JDBC Connection
Checked out connection 236304360 from pool.
Setting autocommit to false on JDBC Connection
[com.mysql.cj.jdbc.ConnectionImpl@e15b7e8]
> Preparing: select id, username, password, birth, sex, salary from
t_users where id = ? 两次查询操作
> Parameters: 1(Long)
<
Columns: id, username, password, birth, sex, salary
<
Row: 1, yanjun, 123456, 1989-02-03, 1, 12345.00
<== Total: 1
User(id=1, username=yanjun, password=123456, birth=Fri Feb 03 00:00:00 CST
1989, sex=true, salary=12345.0)
false

注意事项:

  • mybatis默认是开启一级缓存,不需要配置
  • 如果SqlSession执行了DML操作insert、update、delete并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,这样可以保证缓存中存的数据永远和数据库中一致,避免出现脏读
UserMapper um=MyBatisSessionFactory.getMapper(UserMapper.class);
User user1 = um.selectByPrimaryKey(1L);
System.out.println(user1);
MyBatisSessionFactory.commitTransaction(); //提交事务,则自动刷新缓存
um=MyBatisSessionFactory.getMapper(UserMapper.class);
User user2=um.selectByPrimaryKey(1l);
System.out.println(user2);
System.out.println(user1==user2);

会出现两次查询操作
> Preparing: select id, username, password, birth, sex, salary from t_users where id
= ?
> Parameters: 1(Long)
<
Columns: id, username, password, birth, sex, salary
<
Row: 1, yanjun, 123456, 1989-02-03, 1, 12345.00
<== Total: 1
User(id=1, username=yanjun, password=123456, birth=Fri Feb 03 00:00:00 CST 1989,
sex=true, salary=12345.0)
> Preparing: select id, username, password, birth, sex, salary from t_users where id
= ?
> Parameters: 1(Long)
<
Columns: id, username, password, birth, sex, salary
<
Row: 1, yanjun, 123456, 1989-02-03, 1, 12345.00
<== Total: 1
User(id=1, username=yanjun, password=123456, birth=Fri Feb 03 00:00:00 CST 1989,
sex=true, salary=12345.0)
false

  • 一个SqlSession结束后那么它里面的一级缓存也就不存在了。
  • mybatis的缓存是基于【namespace:sql语句:参数】来进行缓存的。意思就是SqlSession的
    HashMap存储缓存数据时,是使用【namespace:sql:参数】作为key,查询返回的语句作为value保存的。

一级缓存失效

使用全局配置 则可以关闭session级别本地缓
存。利用本地缓存机制Local Cache可以防止循环引用和加速重复的嵌套查询。默认值为SESSION,会缓存一个会话中执行的所有查询。 若设置值为STATEMENT,本地缓存将仅用于执行语句,对相同
SqlSession的不同查询将不会进行缓存

@Test
public void test1(){
UserMapper um=MyBatisSessionFactory.getMapper(UserMapper.class);
User user1 = um.selectByPrimaryKey(1L);
System.out.println(user1);
User user2=um.selectByPrimaryKey(1l);
System.out.println(user2);
System.out.println(user1==user2);
}

全局配置mybatis-config.xml

<settings>
<setting name="logImpl" value="stdout_logging"/>
<!-- 将缓存从会话级调整称为语句级,配置的有效范围是当前应用 -->
<setting name="localCacheScope" value="STATEMENT"/>
</settings>

运行结果
> Preparing: select id, username, password, birth, sex, salary from t_users where id = ?
> Parameters: 1(Long)
<
Columns: id, username, password, birth, sex, salary
<
Row: 1, yanjun, 123456, 1989-02-03, 1, 12345.00
<== Total: 1
User(id=1, username=yanjun, password=123456, birth=Fri Feb 03 00:00:00 CST 1989,
sex=true, salary=12345.0)
> Preparing: select id, username, password, birth, sex, salary from t_users where id = ?
> Parameters: 1(Long)
<
Columns: id, username, password, birth, sex, salary
<
Row: 1, yanjun, 123456, 1989-02-03, 1, 12345.00
<== Total: 1
User(id=1, username=yanjun, password=123456, birth=Fri Feb 03 00:00:00 CST 1989,
sex=true, salary=12345.0)
false

sqlSession.clearCache() 清理缓存

@Test
public void test1(){
UserMapper um=MyBatisSessionFactory.getMapper(UserMapper.class);
User user1 = um.selectByPrimaryKey(1L);
System.out.println(user1);
MyBatisSessionFactory.getSession().clearCache();
User user2=um.selectByPrimaryKey(1l);
System.out.println(user2);
System.out.println(user1==user2);
}

insert、update和delete语句会刷新缓存,比如查询1号用户,然后更新2号用户,这时候再查询1号用
户,则会重新查询查询不同的Mapper.xm会刷新缓存。一级缓存默认开启,只在一次sqlsession中有
效,也就是拿到连接的一个过程中有效一级缓存就是一个Map

@Test
public void test1(){
UserMapper um=MyBatisSessionFactory.getMapper(UserMapper.class);
User user1 = um.selectByPrimaryKey(1L);
System.out.println(user1);
User user=new User();
user.setUsername("张三丰");
user.setPassword("6789012");
user.setSex(false);
um.insertSelective(user);//增删改会导致自动刷新缓存,则前面缓存的数据失效
User user2=um.selectByPrimaryKey(1l); //会有新的查询语句
System.out.println(user2);
System.out.println(user1==user2);
}

扩展

UserMapper um=MyBatisSessionFactory.getMapper(UserMapper.class);
User user1 = um.selectByPrimaryKey(1L);
user1.setUsername("wangwu");
System.out.println(user1);
User user2=um.selectByPrimaryKey(1l); //这里读取到的数据实际上是脏数据
System.out.println(user2);
System.out.println(user1==user2);

MyBatis中内置了一个强大的事务性查询缓存机制,可以方便地进行配置和定制,默认情况下只是启用
了本地的会话缓存,仅仅只是针对一个会话中的数据进行缓存。二级缓存是用来解决一级缓存不能跨会
话共享的问题的,范围是namespace级别的,可以被多个SqlSession共享,只要是同一个接口里的相同
方法都可以共享,生命周期和应用同步。

二级缓存

二级缓存与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper或者Namespace,并且可自定义存储源,如Ehcache。作用域为 namespance 是指对
该 namespance 对应的配置文件中所有的 select 操作结果都缓存,这样不同线程之间就可以共用二级
缓存。

具体应用步骤

二级缓存默认是没有开启的。需要在setting全局参数中配置开启二级缓存

<settings>
<setting name="cacheEnabled" value="true"/> <!--默认是false:关闭二级缓
存-->
<settings>

如果只是简单使用,则在需要使用二级缓存的映射元文件中添加<cache/>

<mapper namespace="com.yan.dao.UserMapper">
<cache/>
... ...

编程调用

@Test
public void test1(){
UserMapper um=MyBatisSessionFactory.getMapper(UserMapper.class);
User user1 = um.selectByPrimaryKey(1L);
System.out.println(user1);
MyBatisSessionFactory.closeSession();//关闭了session,所以一级缓存失效
UserMapper um2=MyBatisSessionFactory.getMapper(UserMapper.class);
User user2=um2.selectByPrimaryKey(1l); //会有新的查询语句
System.out.println(user2);
System.out.println(user1==user2);
}

控制台输出

Cache Hit Ratio [com.yan.dao.UserMapper]: 0.0 额外的日志信息,标识命中率为0%,没有
命中
执行数据库查询
> Preparing: select id, username, password, birth, sex, salary from
t_users where id = ?
> Parameters: 1(Long)
<
Columns: id, username, password, birth, sex, salary
<
Row: 1, yanjun, 123456, 1989-02-03, 1, 12345.00
<== Total: 1
User(id=1, username=yanjun, password=123456, birth=Fri Feb 03 00:00:00 CST
1989, sex=true, salary=12345.0)
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2ea41516] 关闭数据库
连接
Cache Hit Ratio [com.yan.dao.UserMapper]: 0.5 二次查询,首先访问二级缓存,数据直接
命中,则命中率为50%。执行2次查询,只有一次访问数据库,因为二级缓存生效。
User(id=1, username=yanjun, password=123456, birth=Fri Feb 03 00:00:00 CST
1989, sex=true, salary=12345.0)
false 不是同一个对象

默认二级缓存配置

  • 映射文件中的所有select语句结果都会被缓存,所有的insert、update、delete语句都会刷新缓存
  • 使用LRU算法清除不需要缓存的对象,不会定时进行刷新
  • 缓存会保存列表或者对象的1024个引用
  • 缓存采用读写缓存,获取的对象并不是共享的,可以安全地被调用者修改,而不干扰其它调用者或者线程的潜在修改
    允许在mapper.xml中针对二级缓存进行配置:配置了一个LRU缓存,并每隔60秒刷新,最大存储512个
    对象,而却返回的对象是只读的。
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

eviction:代表的是缓存回收策略,目前MyBatis提供以下策略:

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

默认采用的是LRU,移除最长时间不用的对形象
flushInterval刷新间隔时间,单位为毫秒,这里配置的是60000秒刷新,如果不配置它,那么当SQL被执
行的时候才会去刷新缓存。

size引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存
溢出。

readOnly是否为只读缓存,意味着缓存数据只能读取而不能修改,这样设置的好处是可以快速读取缓
存,缺点是没有办法修改缓存,他的默认值是false,不允许修改。

二级缓存可以设置返回的缓存对象策略:
8 当 readOnly="true"时,表示二级缓存返回给所有调用者同一个缓存对象实例,调用者可以update 获取的缓存实例,但是这样可能会造成其他调用者出现数据不一致的情况,因为所有调用者调用的是同一个实例

  • 当 readOnly=“false”,默认值,返回给调用者的是二级缓存总缓存对象的拷贝,即不同调用者获取的是缓存对象不同的实例,这样调用者对各自的缓存对象的修改不会影响到其他的调用者,即是安全的。

二级缓存是mapper级别的缓存,也就是同一个namespace中的mappe.xml。当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域。

<mapper namespace="com.yan.dao.RoleMapper">
<cache-ref namespace="com.yan.dao.UserMapper"/>

二级缓存是基于映射文件的缓存namespace,缓存范围比一级缓存更大,不同的SqlSession可以访问二
级缓存的内容。哪些数据放入二级缓存需要自己指定。

对于访问多的查询请求并且用户对查询结果实时性要求不高的情况下,可采用mybatis二级缓存,
降低数据库访问量,提高访问速度。

若想禁用当前select语句的二级缓存,添加useCache=“false”(默认情况下为true)就可以了:

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

在实际开发中,针对每次查询都需要最新数据的sql,要设置为useCache=“false” ,禁用二级缓存

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

具体流程:

  • 当一个sqlsession执行了一次select后,关闭此session的时候,会将查询结果缓存到二级缓存
  • 当另一个sqlsession执行select时,首先会在二级缓存中找,如果没找到,就回去一级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能

注意事项:

  • 如果SqlSession执行了DML操作并commit了,那么mybatis就会清空当前mapper缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
  • mybatis的缓存是基于【namespace:sql语句:参数】来进行缓存的,意思就是SqlSession的HashMap存储缓存数据时,是使用【namespace:sql:参数】作为key,查询返回的语句作为value保存的。

二级缓存的局限性:
mybatis二级缓存对细粒度级别的数据的缓存实现不好,比如:对商品信息进行缓存,由于商品信息查
询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实
现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息。因为mybaits的二级缓存区域
以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。

解决此类问题需要在业务层根据需求对数据有针对性缓存。

总结

由于缓存的层面越高则性能越好,所以在实际开发应用中一般不依赖于MyBatis的二级缓存,而是在业务层或者在表现层上添加Redis缓存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值