mybatis缓存(上)---一级缓存
- 什么是一级缓存
在日常开发过程中,经常会有相同的sql执行多次查询的情况,mybatis提供了一级缓存来优化这些查询,避免多次请求数据库。
一级缓存在mybatis中默认是开启的并且是session级别,它的作用域为一次sqlSession会话。
- 什么是二级缓存
相对于一级缓存,二级缓存的作用域更广泛,它不止局限于一个sqlSession,可以在多个sqlSession之间共享,事实上,它的作用域是namespace。
mybatis的二级缓存默认也是开启的,但由于它的作用域是namespace,所以还需要在mapper.xml中开启才能生效
- 缓存的优先级
通过mybatis发起的查询,作用顺序为:二级缓存->一级缓存->数据库 ,其中任何一个环节查到不为空的数据,都将直接返回结果
- 缓存失效
当在一个缓存作用域中发生了update、insert、delete 动作后,将会触发缓存失效,下一次查询将命中数据库,从而保证不会查到脏数据。相当于缓存只是针对select有效。
下面通过一些示例来分别说明。(本文采用的是springboot+mybatis的方式进行测试的)
准备条件:
1 create table book( 2 id int auto_increment comment '书ID', 3 name varchar(50) comment '书名', 4 primary key(id)); 5 6 7 insert into book(name) values('三国演义'); 8 insert into book(name) values('红楼梦'); 9 insert into book(name) values('水浒传');
默认引擎是InnoDB.
一. 一级缓存
1.实体类Book
1 package com.example.demo.dao; 2 3 public class Book { 4 private int id; 5 private String name; 6 7 public int getId() { 8 return id; 9 } 10 11 public void setId(int id) { 12 this.id = id; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 }
2.Controller: BookController
1 package com.example.demo.controller; 2 3 import com.example.demo.dao.Book; 4 import com.example.demo.service.BookService; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 7 import org.springframework.web.bind.annotation.GetMapping; 8 import org.springframework.web.bind.annotation.RequestMapping; 9 import org.springframework.web.bind.annotation.RestController; 10 11 @RestController 12 @EnableAutoConfiguration 13 @RequestMapping("/book") 14 public class BookController { 15 @Autowired 16 BookService bookService; 17 18 @GetMapping("/selectBookById") 19 Book selectBookById(){ 20 return bookService.selectBookById(); 21 } 22 }
3.Service:BookService/Impl
1 package com.example.demo.service; 2 3 import com.example.demo.dao.Book; 4 5 6 public interface BookService { 7 Book selectBookById(); 8 }
1 package com.example.demo.service.impl; 2 3 import com.example.demo.dao.Book; 4 import com.example.demo.dao.mapper.BookMapper; 5 import com.example.demo.service.BookService; 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.stereotype.Service; 8 9 10 @Service 11 public class BookServiceImpl implements BookService { 12 13 @Autowired 14 BookMapper bookMapper; 15 @Override 16 17 public Book selectBookById() { 18 int id=1; 19 Book book1=bookMapper.selectBookById(id); 20 Book book2=bookMapper.selectBookById(id); 21 System.out.println("***********start***********"); 22 System.out.println(book1==book2); 23 System.out.println("***********end***********"); 24 return bookMapper.selectBookById(id); 25 } 26 }
4.Mapper接口:BookMapper
1 package com.example.demo.dao.mapper; 2 3 import com.example.demo.dao.Book; 4 import org.apache.ibatis.annotations.Mapper; 5 6 @Mapper 7 public interface BookMapper { 8 Book selectBookById(int id); 9 }
5.mapper.xml文件:BookMapper.xml
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > 3 <mapper namespace="com.example.demo.dao.mapper.BookMapper"> 4 5 <select id="selectBookById" resultType="com.example.demo.dao.Book"> 6 select id,name from book where id=#{id} 7 </select> 8 9 </mapper>
6.application.yml配置为:
1 server: 2 port: 8081 3 spring: 4 #���ݿ��������� 5 datasource: 6 driver-class-name: com.mysql.cj.jdbc.Driver 7 url: jdbc:mysql://localhost:3306/mytest?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC 8 username: root 9 password: root 10 11 #mybatis��������� 12 mybatis: 13 #mapper�����ļ� 14 mapper-locations: classpath:mapper/*.xml 15 #type-aliases-package: com.example.demo.dao 16 #�����շ����� 17 configuration: 18 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 19 20 type-handlers-package: com.example.demo.dao.mapper.MyEnumHandler
浏览器中输入:http://localhost:8081/book/selectBookById
查看控制台:
从控制台可以看见创建了3次SqlSession会话,一级缓存失效。并且得到的book1和book2两个对象不等。实际上,默认是开启的一级缓存,只是我们没有添加事务管理,从而拿到了我们以为错误的结果。
修改Service如下:
@Transactional public Book selectBookById() { int id=1; Book book1=bookMapper.selectBookById(id); Book book2=bookMapper.selectBookById(id); System.out.println("***********start***********"); System.out.println(book1==book2); System.out.println("***********end***********"); return bookMapper.selectBookById(id); }
仅仅是添加了@Transactional注解:
此时控制台输出为:
可以看到只创建了一次SqlSession会话,book2的值对应第一个Fetched SqlSession,方法的返回对应第二个Fetched SqlSession. 说明确实是从缓存拿到的数据。
note:增删改无论加不加这个注解,都不会存在缓存现象。它们的每一次submit就认为会关闭当前SqlSession。一级缓存是和SqlSession是绑定的,只存在于SqlSession生命周期中。增删改操作都会清空一级缓存。
note:添加@Transaction注解的原因: 由于使用了数据库连接池,默认每次查询完之后自动commit,这就导致两次查询使用的不是同一个sqlSessioin,根据一级缓存的原理,它将永远不会生效。
当我们开启了事务,两次查询都在同一个sqlSession中,从而让第二次查询命中了一级缓存。
note: mybatis默认的session级别一级缓存,由于springboot中默认使用了hikariCP,所以基本没用,需要开启事务才有用。但一级缓存作用域仅限同一sqlSession内,无法感知到其他sqlSession的增删改,所以极易产生脏数据