一、 一级缓存
MyBatis的一级缓存级别
MyBatis的一级缓存是SqlSession级别的缓存。如果同一个SqlSession对象多次执行完全相同的SQL语句时,在第一次执行完成后,MyBatis会将查询结果写入到一级缓存中,此后,如果程序没有执行插入、更新、删除操作,当第二次执行相同的查询语句时,MyBatis会直接读取一级缓存中的数据,而不用再去数据库查询,从而提高了数据库的查询效率。
举例说明MyBatis的一级缓存级别
例如,存在数据表tb_book,从表中多次查询id为1的图书信息,当程序第一次查询id为1的图书信息时,程序会将查询结果写入MyBatis一级缓存,当程序第二次查询id为1的图书信息时,MyBatis直接从一级缓存中读取,不再访问数据库进行查询。当程序对数据库执行了插入、更新、删除操作,MyBatis会清空一级缓存中的内容以防止程序误读。
案例的查询过程
通过一个案例来对MyBatis一级缓存的应用进行详细讲解,该案例要求根据图书id查询图书信息。案例具体步骤如下。
1、在mybatis数据库中创建名为tb_book的数据表,同时预先插入几条测试数据。
USE mybatis;
# 创建一个名称为tb_book的表,并插入数据,这里只展示一条
CREATE TABLE tb_book(
id INT PRIMARY KEY AUTO_INCREMENT,
bookName VARCHAR(255),
price double,
author VARCHAR(40) );
INSERT INTO tb_book(bookName,price,author) VALUES('Java基础入门',45.0,' 传智播客高教产品研发部');
INSERT INTO tb_book(bookName,price,author) VALUES('Java基础案例教程',48.0,' 程序员');
INSERT INTO tb_book(bookName,price,author) VALUES('JavaWeb程序设计任务教程',50.0,' 程序员');
- 创建持久化类Book,在Book类中定义图书id、图书名称、图书价格、图书作者属性,以及属性对应的getter/setter方法。
import java.io.Serializable; public class Book implements Serializable { private Integer id; //主键 private String bookName; //图书名称 private double price; //价格 private String author; //作者 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @Override public String toString() { return "Book{" + "id=" + id + ", bookName='" + bookName + ", price=" + price + ", author='" + author + '}'; } }
- 创建图书映射文件BookMapper.xml,并在该文件中编写根据图书id查询图书信息的SQL语句。
<mapper namespace="com.mac.mapper.BookMapper">
<!-- 根据id查询图书信息 -->
<select id="findBookById" parameterType="Integer"
resultType="com.mac.pojo.Book">
SELECT * from tb_book where id=#{id} </select>
<!-- 根据id更新图书信息 -->
<update id="updateBook"
parameterType="com.mac.pojo.Book">
update tb_book set bookName=#{bookName},price=#{price}
where id=#{id} </update>
</mapper>
- 在核心配置文件mybatis-config.xml中的<mappers>标签下,引入BookMapper.xml映射文件。
<mapper resource="com/mac/mapper/BookMapper.xml" />
- 由于需要通过log4j日志组件查看一级缓存的工作状态,因此需要在pom.xml中引入log4j的相关依赖。
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
6、创建log4j.properties文件,用于配置MyBatis和控制台的。
#全局日志配置
log4j.rootLogger=DEBUG, Console
#控制台输出配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
#日志输出级别
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
7、在测试类MyBatisTest中,编写测试方法findBookByIdTest1()。
public void findBookByIdTest1() {
// 1.通过工具类生成SqlSession对象
SqlSession session1 = MyBatisUtils.getSession();
// 2.使用session1查询id为1的图书的信息
Book book1 = session.selectOne("com.mac.mapper."
+ "BookMapper.findBookById", 1);
System.out.println(book1.toString()); // 3.输出查询结果信息
// 再次使用session1查询id为1的图书的信息,同2、3步
// 4.关闭SqlSession
session1.close();
}
MyBatis如何防止程序误读
当程序对数据库执行了插入、更新、删除操作后,MyBatis会清空一级缓存中的内容,以防止程序误读。MyBatis一级缓存被清空之后,再次使用SQL查询语句访问数据库时,MyBatis会重新访问数据库。例如上面的例子,首先查询id为1的图书信息,然后使用更新语句对数据库中的图书信息进行更改,更改之后,再次对id为1的图书信息进行查询时,MyBatis依然会从数据库中查询。
二、二级缓存
使用二级缓存的好处
由4.5.1节的内容可知,相同的Mapper类,相同的SQL语句,如果SqlSession不同,则两个SqlSession查询数据库时,会查询数据库两次,这样也会降低数据库的查询效率。为了解决这个问题,就需要用到MyBatis的二级缓存。MyBatis的二级缓存是Mapper级别的缓存,与一级缓存相比,二级缓存的范围更大,多个SqlSession可以共用二级缓存,并且二级缓存可以自定义缓存资源。
MyBatis二级缓存的执行过程
在MyBatis中,一个Mapper.xml文件通常称为一个Mapper,MyBatis以namespace区分Mapper,如果多个SqlSession对象使用同一个Mapper的相同查询语句去操作数据库,在第一个SqlSession对象执行完后,MyBatis会将查询结果写入二级缓存,此后,如果程序没有执行插入、更新、删除操作,当第二个SqlSession对象执行相同的查询语句时,MyBatis会直接读取二级缓存中的数据。
MyBatis二级缓存的执行过程图解
二级缓存与一级缓存的不同点
与MyBatis的一级缓存不同的是,MyBatis的二级缓存需要手动开启,开启二级缓存通常要完成以下两个步骤。
1、开启二级缓存的全局配置
与使用二级缓存前,需要在MyBatis的核心配置mybatis-config.xml文件中通过<settings>元素开启二级缓存的全局配置。
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
2、开启当前Mapper的namespace下的二级缓存
开启当前Mapper的namespace下的二级缓存,可以通过MyBatis映射文件中的<cache>元素来完成。
<!-- 开启当前Mapper的namespace下的二级缓存-->
<cache>
</cache>
默认状态的二级缓存可实现的功能
(1)映射文件中所有select语句将会被缓存。
(2)映射文件中的所有insert、update和delete语句都会刷新缓存。
(3)缓存会使用LRU算法回收。
(4)没有刷新间隔,缓存不会以任何时间顺序来刷新。
(5)缓存会存储列表集合或对象的1024个引用。
(6)缓存是可读/可写的缓存,这意味着对象检索不是共享的,缓存可以安全的被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
<cache>元素的属性 如果需要调整级缓存的特性,可通过<cache>元素的属性来实现。
属性 | 说明 |
flushInterval | 刷新间隔。该属性可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况下是不设置值。 |
size | 引用数目。该属性可以被设置为任意正整数,默认值为1024。 |
readOnly | 只读。该属性可以被设置为true或者false。当缓存设置为只读时,缓存对象不能被修改,但此时缓存性能较高。当缓存设置为可读写时,性能较低,但安全性高。 |
eviction | 回收策略。该属性有4个可选值。 |
eviction4个可选值:
LRU:最近最少使用的策略。移除最长时间不被使用的对象。
FIFO:先进先出策略。按对象进入缓存的顺序来移除它们。
SOFT:软引用策略。移除基于垃圾回收器状态和软引用规则的对象。
WEAK:弱引用策略。更积极地移除基于垃圾收集器状态和弱引用规则的对象.
接下来通过一个案例演示MyBatis二级缓存的应用,该案例仍旧根据id查询图书信息,案例具体步骤如下。
1、修改映射文件BookMapper.xml,在映射文件的<mapper>元素下追加编写<cache>元素开启当前Mapper的namespace的二级缓存。
<!— 开启当前BookMapper的namespace下的二级缓存-->
<cache>
</cache>
2、在测试类MyBatisTest中,编写测试方法findBookByIdTest1()。
public void findBookByIdTest3() {
// 1.通过工具类生成两个SqlSession对象,这里只展示了一个
SqlSession session1 = MyBatisUtils.getSession();
// 2.使用session1查询id为1的图书的信息
Book book1 = session1.selectOne("com.mac.mapper."
+ "BookMapper.findBookById", 1);
System.out.println(book1.toString()); // 3.输出查询结果信息
// 4.关闭SqlSession1
session1.close();
}
3、执行MyBatisTest测试类的findBookByIdTest3()方法,控制台会输出结果。
对MyBatis二级缓存的应用案例的运行结果分析
控制台输出了执行SQL语句的日志信息以及查询结果。通过分析SQL语句日志信息可以发现,当第一个SqlSession对象session1执行查询时,Cache Hit Ratio(缓存命中率)为0,程序发送了SQL语句;当第二个SqlSession对象session2执行相同的查询时,Cache Hit Ratio为0.5,程序没有发出SQL语句,这就说明,程序直接从二级缓存中获取了数据。
多个SqlSession在同一个Mapper中执行
在实际开发中,经常会遇到多个SqlSession在同一个Mapper中执行操作,例如,SqlSession1执行查询操作,SqlSession2执行插入、更新、删除操作,SqlSession3又执行和SqlSession1相同的查询操作。当SqlSession1执行查询操作时,程序会将查询结果写入MyBatis二级缓存,当SqlSession2对数据库执行了插入、更新、删除操作后,MyBatis会清空二级缓存中的内容,以防止程序误读。当SqlSession3执行和SqlSession1相同的查询操作时,MyBatis会重新访问数据库。
多学一招:Cache Hit Ratio(缓存命中率)
终端用户访问缓存时,如果在缓存中查找到了要被访问的数据,就叫做命中。如果缓存中没有查找到要被访问的数据,就是没有命中。当多次执行查询操作时,缓存命中次数与总的查询次数(缓存命中次数+缓存没有命中次数)的比,就叫作缓存命中率,即缓存命中率=缓存命中次数/总的查询次数。当MyBatis开启二级缓存后,第一次查询数据时,由于数据还没有进入缓存,所以需要在数据库中查询而不是在缓存中查询,此时,缓存命中率为0。第一次查询过后,MyBatis会将查询到的数据写入缓存中,当第二次再查询相同的数据时,MyBatis会直接从缓存中获取这条数据,缓存将命中,此时的缓存命中率为0.5(1/2)。当第三次查询相同的数据,则缓存命中率为0.66666(2/3),以此类推。