MyBatis 学习(三)
一、缓存简介
1、例:查询时需要连接访问数据库,这时非常消耗资源!【性能】
- 解决:将一次查询的结果,给它暂存到一个可以直接取到的地方---》内存:缓存(我们再次查询相同数据的时候,直接走缓存,就不用了访问数据库)
2、什么是缓存【cache】
- 存在内存中的临时数据
- 经用户查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决高并发系统的性能问题。
3、为什么要使用缓存
- 性能——将相应数据存储起来以避免数据的重复创建、处理和传输,可有效提高性能。比如将不改变的数据缓存起来
- 稳定性——同一个应用中,对同一数据、逻辑功能和用户界面的多次请求时经常发生的。
- 当用户基数很大时,如果每次请求都进行处理,消耗的资源是很大的浪费,也同时造成系统的不稳定。
- 例如,web应用中,对一些静态页面的呈现内容进行缓存能有效的节省资源,提高稳定性。而缓存数据也能降低对数据库的访问次数,降低数据库的负担和提高数据库的服务能力;
- 可用性——有时,提供数据信息的服务可能会意外停止,如果使用了缓存技术,可以在一定时间内仍正常提供对最终用户的支持,提高了系统的可用性。
3、什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据啥东西
二、MyBatista 缓存
-
MyBatis包含非常强大的查询缓存特性,它可以非常方便指定和配置缓存。缓存可以极大的提升查询效率。
-
MyBatis系统中默认定义两级缓存:一级缓存和二级缓存。
-
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
-
二级缓存需要手动开启和配置,他是基于那么,那么,namespace级别的缓存。
-
为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来定义二级缓存
-
-
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
三、一级缓存
-
一级缓存(本地缓存)
- 与数据同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没要必要再去查询数据库。
-
测试:
1、在配置文件开启日志输出(前提各种配置和MyBatis工具类已经写好==Mybatis的环境已经搭建)
2、编写通过id查询用户信息的Mapper.xml以及测试类(确保可以运行)
3、在测试类中测试通过相同的id同样查找用户信息。
- 两次相同的查找都在一个SQLSession中执行第二次的查询是访问的是以及缓存(本地缓存)所以没有直接执行sql(访问数据库)
- 即映射语句文件中的所有 select 语句的结果将会被缓存。
4、测试:映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。(测试:在两次查询中加入更新语句,看是否会刷新缓存)
-
缓存失效的情况:
-
查询不同的东西
-
增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
-
查询不同的Mapper.xml
-
手动清理缓存!
//手动刷新缓存 sqlSession.clearCache();
-
-
小结:
- 一级缓存默认是开启的,只在一次Sqlsession中有效,也就是拿到连接到关闭连接这个区间段之间。
四、二级缓存
二级缓存也称为全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的话,会话关闭了,一级缓存中的数据就会保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的Mapper查出的数据会放在自己对应的缓存(map)中;
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
-
<cache/>
-
缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。(默认的清除策略是 LRU。)FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。
-
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
-
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
-
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
-
注:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
-
应用:
-
1、首先在配置文件中开启(settings)全局缓存(这样二级缓存才能有效)
-
设置名 描述 有效值 默认值 cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true <!--开启全局缓存--> <setting name="cacheEnabled" value="true"/>
-
2、在要使用二级缓存的Mapper中开启
<!--开启二级缓存--> <cache/> <select id="userById" resultType="user" useCache="true"> select * from mybatis.user where id = #{id} </select>
也可以自定义写自定义参数
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
-
3、测试:
@Test public void userByIdCache(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); SqlSession sqlSession1 = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); System.out.println("==========查询============="); User user = mapper.userById(1); System.out.println(user); sqlSession.close(); System.out.println("==========查询============="); User user1 = mapper1.userById(1); System.out.println(user1); sqlSession1.close(); }
-
4、报错处理:
-
org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: com.study.pojo.User
在开启二级缓存时候没有设置缓存机制时就需要对实体类进行序列化
-
-
小结:
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有的数据都会先放在一级缓存中;
- 只有当会话提交,或者关闭的时候,才会提交到二级缓存中(转存)!
五、缓存原理
-
@Test public void userByIdCache(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); SqlSession sqlSession1 = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); System.out.println("==========一级缓存============="); User user = mapper.userById(1); System.out.println(user); sqlSession.close(); System.out.println("==========一级缓存失效============="); System.out.println("==========二级缓存生效============="); User user1 = mapper1.userById(1); System.out.println(user1); System.out.println("==========二级缓存中没有一级缓存也没直接去数据库============="); User user2 = mapper1.userById(2); System.out.println(user2); System.out.println("==========直接在二级缓存中找============="); User user3 = mapper1.userById(2); System.out.println(user3); sqlSession1.close(); }
PooledDataSource forcefully closed/removed all connections. ==========一级缓存============= Cache Hit Ratio [com.study.dao.UserMapper]: 0.0 Opening JDBC Connection Created connection 2070529722. ==> Preparing: select * from mybatis.user where id = ? ==> Parameters: 1(Integer) <== Columns: id, username, password <== Row: 1, yyb, 123467 <== Total: 1 User(id=1, username=yyb, password=123467) Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7b69c6ba] Returned connection 2070529722 to pool. ==========一级缓存失效============= ==========二级缓存生效============= Cache Hit Ratio [com.study.dao.UserMapper]: 0.5 User(id=1, username=yyb, password=123467) ==========二级缓存中没有一级缓存也没直接去数据库============= Cache Hit Ratio [com.study.dao.UserMapper]: 0.3333333333333333 Opening JDBC Connection Checked out connection 2070529722 from pool. ==> Preparing: select * from mybatis.user where id = ? ==> Parameters: 2(Integer) <== Columns: id, username, password <== Row: 2, 李白, 1234343 <== Total: 1 User(id=2, username=李白, password=1234343) ==========直接在二级缓存中找============= Cache Hit Ratio [com.study.dao.UserMapper]: 0.25 User(id=2, username=李白, password=1234343) Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7b69c6ba] Returned connection 2070529722 to pool.