概念这里不赘述了,上节都已介绍过了。
前提准备:
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('水浒传');
二. 二级缓存
二级缓存的作用域更广泛,它不止局限于一个sqlSession,可以在多个sqlSession之间共享,事实上,它的作用域是namespace。
mybatis的二级缓存默认也是开启的,但由于他的作用域是namespace,所以还需要在mapper.xml中开启才能生效。
缓存的执行原理和上一节提到的一级缓存是差不多的,二级缓存与一级缓存区别在于二级缓存的范围更大,多个sqlSession可以共享一个mapper中的二级缓存区域。mybatis是如何区分不同mapper的二级缓存区域呢?它是按照不同mapper有不同的namespace来区分的,也就是说,如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。
1. 开启二级缓存
1)因为springboot+mybatis这种集成方式是默认开启了二级缓存,如果非要显示配置,可以在appliacation.yml中添加如下黄色部分:
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
cache-enabled: true
2)配置mapper.xml
直接在<mapper>标签中添加<cache/>标签即可。就可以开启二级缓存了。
note:除了在cache标签中配置这些属性,还可以通过注解的形式是在Mapper接口中配置这些属性,这里就不介绍了。
cache 标签有多个属性:
eviction
: 缓存回收策略,有这几种回收策略- LRU - 最近最少回收,移除最长时间不被使用的对象
- FIFO - 先进先出,按照缓存进入的顺序来移除它们
- SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
默认是 LRU 最近最少回收策略
flushinterval
缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值readOnly
: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改size
: 缓存存放多少个元素type
: 指定自定义缓存的全类名(实现Cache 接口即可)blocking
: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存
2. 实现序列化接口
mybatis的二级缓存是属于序列化,序列化的意思就是从内存中的数据传到硬盘中,这个过程就是序列化;也就是说,mybatis的二级缓存,实际上就是将数据放进了硬盘文件中去了。
因此Book这个实体类需要实现序列化Serializable。
1 package com.example.demo.dao; 2 3 import java.io.Serializable; 4 5 public class Book implements Serializable { 6 private int id; 7 private String name; 8 9 public int getId() { 10 return id; 11 } 12 13 public void setId(int id) { 14 this.id = id; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 public void setName(String name) { 22 this.name = name; 23 } 24 25 public String toString(){ 26 return this.getId()+":"+this.getName(); 27 } 28 }
note:序列化的是book这个类,如果碰到它还有父类、级联属性,它们是不会被序列化的,我们需要让这些类也实现序列化。
3. 缓存时机
前面的准备工作都做好了:缓存开启、序列化已经做了。但是实际效果发现缓存并没有起效,而是关闭SqlSession才生效。这实际上就是mybatis的一个缓存实现机制。原理如下:
比如,我现在去查询ID为1的这个书名,获取该书名之后我们需要经过序列化然后存到硬盘上,因为mybatis的二级缓存实际上就是将数据保存到硬盘上的某个文件中。每来一个新的数据,比如三国演义存进来了,水浒传也需要存,如果是存储到硬盘上,那么就会用到IO技术。可是IO也是比较费性能,所以这个机制就是当你关闭sqlSesion的时候,我们把这些三国演义等书名数据一块保存到硬盘上,而不是来一个保存一个,这样IO承受不了,所以就存在关闭SqlSession缓存才生效的机制啦。
4. 案例
(1)controller
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 }
(2)service
package com.example.demo.service.impl; import com.example.demo.dao.Book; import com.example.demo.dao.mapper.BookMapper; import com.example.demo.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class BookServiceImpl implements BookService { @Autowired BookMapper bookMapper; @Override public Book selectBookById() { int id=1; Book book1=bookMapper.selectBookById(id);
System.out.println(book1); Book book2=bookMapper.selectBookById(id); System.out.println(book2); return book2; } }
(3)mapper
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 10 int updateNameById(int id,String name); 11 }
(4)mapper.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 <cache></cache> 6 <select id="selectBookById" resultType="com.example.demo.dao.Book"> 7 select id,name from book where id=#{id} 8 </select> 9 10 <update id="updateNameById"> 11 update book set name=#{name} where id=#{id} 12 </update> 13 14 </mapper>
在浏览器中输入:http://localhost:8081/book/selectBookById 后,控制台将会输出如下:
说明:第一条红线代表缓存命中率为0,因为一开始并没有开始缓存,拿不到。
第二条红线代表查了一次数据库,且放入二级缓存中。
第三条红线代表查了二级缓存,因为加上第一次命中概率为0的那次,总共有两次查询缓存动作,但是只有第二次是命中缓存的,所以本次命中概率为0.5。
(5)增删改操作会清空缓存
修改Service实现,增加一个更新操作:
public Book selectBookById() { int id=1; Book book1=bookMapper.selectBookById(id); System.out.println(book1); Book book2=bookMapper.selectBookById(id); System.out.println(book2); String name="三国演义3"; bookMapper.updateNameById(id,name); Book book3=bookMapper.selectBookById(id); System.out.println(book3); Book book4=bookMapper.selectBookById(id); System.out.println(book4); return book2; }
控制台为:
Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4005023d] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.BookMapper]: 0.0 2020-04-06 16:24:51.528 INFO 13608 --- [nio-8081-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2020-04-06 16:24:51.733 INFO 13608 --- [nio-8081-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. JDBC Connection [HikariProxyConnection@787513428 wrapping com.mysql.cj.jdbc.ConnectionImpl@1c908c18] will not be managed by Spring ==> Preparing: select id,name from book where id=? ==> Parameters: 1(Integer) <== Columns: id, name <== Row: 1, 三国演义2 <== Total: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4005023d] 1:三国演义2 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6178d2d1] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.BookMapper]: 0.5 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6178d2d1] 1:三国演义2 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75a05846] was not registered for synchronization because synchronization is not active JDBC Connection [HikariProxyConnection@1391063673 wrapping com.mysql.cj.jdbc.ConnectionImpl@1c908c18] will not be managed by Spring ==> Preparing: update book set name=? where id=? ==> Parameters: 三国演义3(String), 1(Integer) <== Updates: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75a05846] Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f2aa7cb] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.BookMapper]: 0.3333333333333333 JDBC Connection [HikariProxyConnection@2025827328 wrapping com.mysql.cj.jdbc.ConnectionImpl@1c908c18] will not be managed by Spring ==> Preparing: select id,name from book where id=? ==> Parameters: 1(Integer) <== Columns: id, name <== Row: 1, 三国演义3 <== Total: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f2aa7cb] 1:三国演义3 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2f985f79] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.BookMapper]: 0.5 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2f985f79] 1:三国演义3
说明:上面共有7个标红的地方。从上到下的解释依次如下:
- 第一条红色代表缓存还没开始,因此命中率为0;
- 第二条红色代表开始写入二级缓存了,因为它往数据库查询结束,会往硬盘传值。
- 第三条红色代表命中二级缓存,因为是第二次进行缓存命中,共2次,命中了1次,所以概率为0.5。
- 第四条红色代表一条SQL语句,进行数据的更新,更新操作会清除缓存。
- 第五条红色可以看到缓存命中率为0.333,因为更新操作清除了缓存,但是本次去查缓存并不知道,这是第3次查询缓存了,但是只有第2次命中,所以概率为0.33。
- 第六条红色因为第五条红色没有找到缓存,被update操作清空,所以它就像数据库发起了select查询,查询结束后将查询结果写入到硬盘,作为二次缓存数据。
- 第七条因为有最新的缓存数据了,可以进行缓存查询,且命中了。因为总共进行了4次缓存查询,只有第二次,第四次命中,因此本次概率为0.5.
(6)禁用二级缓存
<select id="findOrderListResultMap" resultMap="bookMap" useCache="false"> 可以采用useCache来决定select语句是否禁用二级缓存
(7)禁用增删改的清空缓存
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。 设置statement配置中的flushCache=”true” 属性,默认情况下为true,即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
<insert id="insertUser" parameterType="Book" flushCache="true">
flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可。
5. 集成别的缓存机制
Mybatis默认提供的缓存实现是基于Map实现的内存缓存,已经可以满足基本的应用。但是当需要缓存大量的数据时,不能仅仅通过提高内存来使用Mybatis的二级缓存,还可以采用集成一些别的缓存技术。比如继承EhCache缓存、Redis缓存。
三. 二级缓存的适用场景
- 以查询为主的应用中,只有尽可能少的增删改操作。
- 很少存在关联表操作,以单表操作为主。
- 没脏数据的情况下。
note:在任何情况下,都可以考虑在业务层使用可控制的缓存来代替二级缓存。
四.二级缓存出现脏数据时的处理办法
1. 二级缓存脏数据出现原因
MyBatis 的二级缓存是和命名空间绑定的,所以通常情况下每一个 Mapper 映射文件都拥有自己的二级缓存,不同 Mapper 的二级缓存互不影响。因为多表联合查询非常常见,由于关系型数据库的设计, 使得很多时候需要关联多个表才能获得想要的数据。在关联多表查询时肯定会将该查询放到某个命名空间下的映射文件中,这样一个多表的查询就会缓存在该命名空间的二级缓存中。涉及这些表的增、删、改操作通常不在一个映射文件中,它们的命名空间不同, 因此当有数据变化时,多表查询的缓存未必会被清空,这种情况下就会产生脏数据。
2. 二级缓存产生脏数据案例
先准备如下数据表,我们查询bookId为3的书籍信息。不同的书的销售渠道不一样。
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('水浒传'); 10 11 select *from book; 12 13 14 15 create table salechannel( 16 id int auto_increment, 17 name varchar(50), 18 primary key(id)); 19 20 insert into salechannel(name) values('京东'); 21 insert into salechannel(name) values('京东'); 22 insert into salechannel(name) values('淘宝'); 23 24 select *from salechannel; 25 26 27 create table book_sale( 28 book_id int, 29 sale_id int); 30 31 insert into book_sale values(1,1); 32 insert into book_sale values(1,2); 33 insert into book_sale values(3,3);
ID为3的书籍它的名字是水浒传,它的销售渠道为淘宝。现在在中途将它改成当当。查看二级缓存有没有产生脏数据:
(1)实体类
1 package com.example.demo.dao; 2 3 import java.io.Serializable; 4 5 public class Book implements Serializable { 6 private int bookId; 7 private String bookName; 8 private SaleChannel saleChannel; 9 10 11 public int getBookId() { 12 return bookId; 13 } 14 15 public void setBookId(int bookId) { 16 this.bookId = bookId; 17 } 18 19 public String getBookName() { 20 return bookName; 21 } 22 23 public void setBookName(String bookName) { 24 this.bookName = bookName; 25 } 26 27 public SaleChannel getSaleChannel() { 28 return saleChannel; 29 } 30 31 public void setSaleChannel(SaleChannel saleChannel) { 32 this.saleChannel = saleChannel; 33 } 34 35 public String toString(){ 36 return "bookId:"+this.getBookId()+",bookName:"+this.getBookName()+",saleChannel的销售渠道为:"+this.getSaleChannel().getSaleName(); 37 } 38 }
1 package com.example.demo.dao; 2 3 import java.io.Serializable; 4 5 public class SaleChannel implements Serializable { 6 private int saleId; 7 private String saleName; 8 9 10 public int getSaleId() { 11 return saleId; 12 } 13 14 public void setSaleId(int saleId) { 15 this.saleId = saleId; 16 } 17 18 public String getSaleName() { 19 return saleName; 20 } 21 22 public void setSaleName(String saleName) { 23 this.saleName = saleName; 24 } 25 }
(2)controller
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
1 package com.example.demo.service.impl; 2 3 import com.example.demo.dao.Book; 4 import com.example.demo.dao.mapper.AuthorMapper; 5 import com.example.demo.dao.mapper.BookMapper; 6 import com.example.demo.dao.mapper.SaleChannelMapper; 7 import com.example.demo.service.BookService; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.stereotype.Service; 10 11 @Service 12 public class BookServiceImpl implements BookService { 13 14 @Autowired 15 BookMapper bookMapper; 16 @Autowired 17 SaleChannelMapper saleChannelMapper; 18 @Override 19 20 public Book selectBookById() { 21 int id=3; 22 Book book1=bookMapper.selectBookById(id); 23 System.out.println(book1); 24 Book book2=bookMapper.selectBookById(id); 25 System.out.println(book2); 26 String name="当当";//原本的销售渠道是淘宝,现在更新为当当 27 saleChannelMapper.updateChannelById(id,name); 28 Book book3=bookMapper.selectBookById(id); 29 System.out.println(book3); 30 Book book4=bookMapper.selectBookById(id); 31 System.out.println(book4); 32 return book2; 33 } 34 35 }
(4)mapper接口
1 package com.example.demo.dao.mapper; 2 3 import org.apache.ibatis.annotations.Mapper; 4 5 @Mapper 6 public interface SaleChannelMapper { 7 int updateChannelById(int id,String name); 8 }
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
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 <cache></cache> 6 <select id="selectBookById" resultType="com.example.demo.dao.Book"> 7 select 8 b.id bookId, 9 b.name bookName, 10 sale.id "saleChannel.saleId", 11 sale.name "saleChannel.saleName" 12 from book b 13 inner join book_sale bs on b.id=bs.book_id 14 inner join salechannel sale on bs.sale_id=sale.id 15 where b.id=#{id} 16 17 </select> 18 19 20 </mapper>
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.SaleChannelMapper"> 4 5 <cache></cache> 6 7 8 <update id="updateChannelById"> 9 update salechannel set name=#{name} where id=#{id} 10 </update> 11 12 </mapper>
浏览器中输入:http://localhost:8081/book/selectBookById 后,控制台输出如下:
Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c7335f8] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.BookMapper]: 0.0 2020-04-06 18:48:00.849 INFO 9652 --- [nio-8081-exec-3] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2020-04-06 18:48:00.931 INFO 9652 --- [nio-8081-exec-3] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. JDBC Connection [HikariProxyConnection@694526659 wrapping com.mysql.cj.jdbc.ConnectionImpl@a315a04] will not be managed by Spring ==> Preparing: select b.id bookId, b.name bookName, sale.id "saleChannel.saleId", sale.name "saleChannel.saleName" from book b inner join book_sale bs on b.id=bs.book_id inner join salechannel sale on bs.sale_id=sale.id where b.id=? ==> Parameters: 3(Integer) <== Columns: bookId, bookName, saleChannel.saleId, saleChannel.saleName <== Row: 3, 水浒传, 3, 淘宝 <== Total: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c7335f8] bookId:3,bookName:水浒传,saleChannel的销售渠道为:淘宝 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4c883888] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.BookMapper]: 0.5 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4c883888] bookId:3,bookName:水浒传,saleChannel的销售渠道为:淘宝 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5a227dd8] was not registered for synchronization because synchronization is not active JDBC Connection [HikariProxyConnection@1152655662 wrapping com.mysql.cj.jdbc.ConnectionImpl@a315a04] will not be managed by Spring ==> Preparing: update salechannel set name=? where id=? ==> Parameters: 当当(String), 3(Integer) <== Updates: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5a227dd8] Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@349526fa] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.BookMapper]: 0.6666666666666666 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@349526fa] bookId:3,bookName:水浒传,saleChannel的销售渠道为:淘宝 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@79a8f236] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.BookMapper]: 0.75 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@79a8f236] bookId:3,bookName:水浒传,saleChannel的销售渠道为:淘宝
说明:查看上面结果,发现更新前和更新后查询的结果一样。说明缓存联表查询的时候出现了脏数据。
3. 祛除脏数据
该如何避免脏数据的出现呢?这时就需要用到参照缓存了。 当某几个表可以作为一个业务整体时,通常是让几个会关联的 ER 表同时使用同一个二级缓存,这样就能解决脏数据问题。
在上面那个例子中,将BookMapper.xml中的缓存配置改成参照缓存:
<mapper namespace="com.example.demo.dao.mapper.BookMapper"> <cache-ref namespace="com.example.demo.dao.mapper.SaleChannelMapper"></cache-ref> <select id="selectBookById" resultType="com.example.demo.dao.Book"> select b.id bookId, b.name bookName, sale.id "saleChannel.saleId", sale.name "saleChannel.saleName" from book b inner join book_sale bs on b.id=bs.book_id inner join salechannel sale on bs.sale_id=sale.id where b.id=#{id} </select>
再次运行:
Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@42514c0e] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.SaleChannelMapper]: 0.0 2020-04-06 18:51:31.685 INFO 14292 --- [nio-8081-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2020-04-06 18:51:31.768 INFO 14292 --- [nio-8081-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. JDBC Connection [HikariProxyConnection@1658628143 wrapping com.mysql.cj.jdbc.ConnectionImpl@b85bd7d] will not be managed by Spring ==> Preparing: select b.id bookId, b.name bookName, sale.id "saleChannel.saleId", sale.name "saleChannel.saleName" from book b inner join book_sale bs on b.id=bs.book_id inner join salechannel sale on bs.sale_id=sale.id where b.id=? ==> Parameters: 3(Integer) <== Columns: bookId, bookName, saleChannel.saleId, saleChannel.saleName <== Row: 3, 水浒传, 3, 淘宝 <== Total: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@42514c0e] bookId:3,bookName:水浒传,saleChannel的销售渠道为:淘宝 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1a584f9a] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.SaleChannelMapper]: 0.5 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1a584f9a] bookId:3,bookName:水浒传,saleChannel的销售渠道为:淘宝 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3bd2cdea] was not registered for synchronization because synchronization is not active JDBC Connection [HikariProxyConnection@1663984021 wrapping com.mysql.cj.jdbc.ConnectionImpl@b85bd7d] will not be managed by Spring ==> Preparing: update salechannel set name=? where id=? ==> Parameters: 当当(String), 3(Integer) <== Updates: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3bd2cdea] Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@534d3e7c] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.SaleChannelMapper]: 0.3333333333333333 JDBC Connection [HikariProxyConnection@2128417890 wrapping com.mysql.cj.jdbc.ConnectionImpl@b85bd7d] will not be managed by Spring ==> Preparing: select b.id bookId, b.name bookName, sale.id "saleChannel.saleId", sale.name "saleChannel.saleName" from book b inner join book_sale bs on b.id=bs.book_id inner join salechannel sale on bs.sale_id=sale.id where b.id=? ==> Parameters: 3(Integer) <== Columns: bookId, bookName, saleChannel.saleId, saleChannel.saleName <== Row: 3, 水浒传, 3, 当当 <== Total: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@534d3e7c] bookId:3,bookName:水浒传,saleChannel的销售渠道为:当当 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c85a76e] was not registered for synchronization because synchronization is not active Cache Hit Ratio [com.example.demo.dao.mapper.SaleChannelMapper]: 0.5 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c85a76e] bookId:3,bookName:水浒传,saleChannel的销售渠道为:当当
说明:可以发现更新后,查询的数据变了,由淘宝变成当当啦。这个脏数据解决啦。