SpringBoot下mybatis-一级、二级缓存测试及总结
mybatis一级缓存和二级缓存相关源码

mybatis⼀级缓存和⼆级缓存相关源码⽬录mybatis简介MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings.MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results.MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records. debug版本:mybatis:3.4.5⾃我理解mybatis代理Dao类,当运⾏Dao中⽅法时,根据⽅法查找相应的sql,然后去执⾏sql,来提供返回值。
初始化⼀级缓存和⼆级缓存程序主函数因为mybatis采⽤的JDK反向代理,来代理Dao类。
如果你想看代理Dao类具体实现,可以⽤⼀下⽅式# idea打断点,查找运⾏java程序的pid# 管理员⾝份运⾏jdk中sa-jdi.jar 主函数sun.jvm.hotspot.HSDB,来attach 那个pid# 就可以查看Java程序运⾏到断点处的状态了,查找代理类# JDK代理⽅法,命名为 $Proxy + 数字,不好查找,可以在断点前,输出代理类的名字sudo java -classpath './jdk1.8.0_172/lib/sa-jdi.jar' sun.jvm.hotspot.HSDB代理Dao类,主要运⾏了MapperProxy.invoke⽅法Object.class.equals(method.getDeclaringClass()) ⽤来判断是否调⽤的是Object.class中⽅法,isDefaultMethod(method) ⽤来判断是否是Java8中Interface 中新特性default修饰的⽅法,MapperMethod.execute判断执⾏的是insert,update,delete,select,flush中的哪⼀个,insert,update,delete返回的都是影响的⾏数,select才需要特殊处理返回数据,⾄于flush是执⾏有@Flush注解的⽅法是才去执⾏。
缓存(一级缓存和二级缓存)

缓存(⼀级缓存和⼆级缓存)缓存可以将数据保存在内存中,是互联⽹系统常常⽤到的。
⽬前流⾏的缓存服务器有 MongoDB、Redis、Ehcache 等。
缓存是在计算机内存上保存的数据,读取时⽆需再从磁盘读⼊,因此具备快速读取和使⽤的特点。
和⼤多数持久化框架⼀样,MyBatis 提供了⼀级缓存和⼆级缓存的⽀持。
默认情况下,MyBatis 只开启⼀级缓存。
⼀级缓存⼀级缓存是基于 PerpetualCache(MyBatis⾃带)的 HashMap 本地缓存,作⽤范围为 session 域内。
当 session flush(刷新)或者close(关闭)之后,该 session 中所有的 cache(缓存)就会被清空。
在参数和 SQL 完全⼀样的情况下,我们使⽤同⼀个 SqlSession 对象调⽤同⼀个 mapper 的⽅法,往往只执⾏⼀次 SQL。
因为使⽤SqlSession 第⼀次查询后,MyBatis 会将其放在缓存中,再次查询时,如果没有刷新,并且缓存没有超时的情况下,SqlSession 会取出当前缓存的数据,⽽不会再次发送 SQL 到数据库。
由于 SqlSession 是相互隔离的,所以如果你使⽤不同的 SqlSession 对象,即使调⽤相同的 Mapper、参数和⽅法,MyBatis 还是会再次发送 SQL 到数据库执⾏,返回结果。
⽰例:WebsiteMapperpublic Website selectWebsiteById(int id);WebsiteMapper.xml<select id="selectWebsiteById"resultType="net.biancheng.po.Website">SELECT * FROM websiteWHERE id=#{id}</select>测试代码public class Test {public static Logger logger = Logger.getLogger(Test.class);public static void main(String[] args) throws IOException {InputStream config = Resources.getResourceAsStream("mybatis-config.xml"); // 根据配置⽂件构建SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);SqlSession ss = ssf.openSession();Website site = ss.selectOne("net.biancheng.mapper.WebsiteMapper.selectWebsiteById", 1);logger.debug("使⽤同⼀个sqlsession再执⾏⼀次");Website site2 = ss.selectOne("net.biancheng.mapper.WebsiteMapper.selectWebsiteById", 1);// 请注意,当我们使⽤⼆级缓存的时候,sqlSession调⽤了 commit⽅法后才会⽣效mit();logger.debug("现在创建⼀个新的SqlSeesion对象在执⾏⼀次");SqlSession ss2 = ssf.openSession();Website site3 = ss2.selectOne("net.biancheng.mapper.WebsiteMapper.selectWebsiteById", 1);// 请注意,当我们使⽤⼆级缓存的时候,sqlSession调⽤了 commit⽅法后才会⽣效mit();}}运⾏结果DEBUG [main] - ==> Preparing: SELECT * FROM website WHERE id=?DEBUG [main] - ==> Parameters: 1(Integer)DEBUG [main] - <== Total: 1DEBUG [main] - 使⽤同⼀个sqlsession再执⾏⼀次DEBUG [main] - 现在创建⼀个新的SqlSeesion对象在执⾏⼀次DEBUG [main] - ==> Preparing: SELECT * FROM website WHERE id=?DEBUG [main] - ==> Parameters: 1(Integer)DEBUG [main] - <== Total: 1从运⾏结果可以看出,第⼀个 SqlSession 实际只发⽣过⼀次查询,⽽第⼆次查询就从缓存中取出了,也就是 SqlSession 层⾯的⼀级缓存。
【Mybatis】Mybatis实战2(一对一、一对多、多对多的设计及实现,高级特性及二级缓存)

【Mybatis】Mybatis实战2(⼀对⼀、⼀对多、多对多的设计及实现,⾼级特性及⼆级缓存)6).多表查询-“⼀对多”(表设计、实体设计、DAO(mapper)设计)(1)关联关系操作(⼀对多)①表设计:以员⼯和部门表为例思想: 1个员⼯对应1个部门,1个部门对应多个员⼯添加数据原则:先添加没有外键的数据(部门信息),再添加存在外键的数据(员⼯信息)注意:将外键添加在n的⼀⽅部门表:create table t_dept(id varchar2(36) primary key,name varchar2(50));员⼯表:create table t_emp(id varchar2(36) primary key,name varchar2(50),age number(3),salary number(10,2),dept_id references t_dept(id));②实体设计a. 在实体中添加关系属性,来表⽰实体之间的关系(对应表数据的关系)b. 在N的⼀⽅添加1的⼀个关系属性。
c. 在1的⼀⽅添加N的⼀个List的关系属性DAO:(MyBatis如何查询两张表信息)需求1:查询员⼯信息(⼯号,名字,年龄,薪资,所属部门的编号和名称)根据员⼯⼯号?DAO接⼝⽅法:public Emp selectById(String id);Mapper⽂件:①SQL:select e.id,,e.age,e.salary,d.id, from t_emp e left join t_dept d on e.dept_id = d.id where e.id = '5';②参数③将查询结果映射成⼀个实体对象特点: 如果关系属性是”1” ,使⽤ <association></association>需求2:根据id查询部门信息,及其内部的所有员⼯信息?DAO接⼝⽅法:public Dept selectById(String id);Mapper⽂件中①SQL:select d.id,,e.id as eid, as ename,e.age as eage,e.salary as salary from t_dept d left join t_emp e on d.id = e.dept_idwhere d.id = ?;②参数绑定③结果映射:ReusultMap映射集合关系属性特点: 关系属性是”n”个的集合 ,使⽤ <collection></ collection >7).多表查询-“⼀对⼀”(表设计、实体设计、DAO(mapper)设计)关联关系操作(⼀对⼀)例如:需求: 学⽣电脑管理系统①库表设计表⽰1对1的关系a. 添加外键(那张表添加都可以)①从业务的⾓度分析,后添加的数据对应的表。
mybatis一级缓存原理

mybatis一级缓存原理MyBatis一级缓存原理解析一级缓存介绍•一级缓存是MyBatis默认开启的缓存机制,通过在内存中缓存查询结果,提高查询性能。
•一级缓存在同一个Session的范围内生效,不同Session之间的缓存不共享。
缓存的工作原理1.首先,当执行一个查询语句时,MyBatis会检查是否开启了一级缓存。
2.如果开启了一级缓存,MyBatis会先从缓存中查找是否存在相同的查询语句及其参数的缓存结果。
3.如果缓存中存在,则直接返回缓存结果,不再访问数据库。
4.如果缓存中不存在,则执行查询操作,将查询结果缓存起来。
5.缓存结果是一个对象,包含查询的结果集及其所对应的映射关系。
缓存的生命周期•缓存存在于SqlSession中,当SqlSession关闭时,缓存中的数据也被清空。
•当执行了插入、更新或删除操作时,会清空对应的缓存,保证缓存的数据与数据库数据一致。
缓存的失效机制•MyBatis通过以下方式实现缓存的失效:–当执行了插入、更新或删除操作时,对应的缓存会被清空。
–当手动调用clearCache()方法时,会清空全部缓存。
–当开启了flushCache属性时,在执行查询操作前,会先清空全部缓存。
缓存的注意事项•一级缓存只在同一Session中生效,不同Session之间的缓存不共享。
•更新操作会清空对应的缓存,所以在更新之后的查询中,会重新执行查询操作。
•如果需要在同一Session中多次查询相同的数据,可以考虑使用缓存提高性能。
•当查询的数据频繁变化时,缓存可能会导致数据不一致的问题,需要慎重使用。
结语通过本文的介绍,我们对MyBatis一级缓存的原理有了更深入的了解。
一级缓存在同一个Session中起到了提高查询性能的作用,但也需要注意缓存的生命周期与失效机制,以及潜在的数据一致性问题。
一级缓存的使用场景•查询频率高:如果多次查询的数据在同一个Session中不会发生变化,那么将查询结果缓存起来可以避免多次访问数据库,提升性能。
mybatis中的一二级缓存的实现原理

mybatis中的一二级缓存的实现原理Mybatis是一个优秀的ORM(ObjectRelationalMapping)框架,它提供了一种简单的方式来访问数据库。
在Mybatis中,缓存是一个非常重要的概念,它可以提高应用程序的性能。
Mybatis中的缓存分为一级缓存和二级缓存,下面将详细介绍它们的实现原理。
一级缓存Mybatis中的一级缓存是指在同一个SqlSession中执行相同的SQL语句时,返回的结果会被缓存起来,下一次执行相同的SQL语句时,可以直接从缓存中获取结果,而不需要再次查询数据库。
一级缓存的实现是基于内存的,缓存的生命周期与SqlSession的生命周期一致。
一级缓存是Mybatis默认开启的,可以通过SqlSession的clearCache()方法来清空一级缓存。
二级缓存Mybatis中的二级缓存是指在同一个应用程序中多个SqlSession之间共享缓存数据,它的作用是提高应用程序的性能。
二级缓存的实现是基于缓存机制的,缓存的生命周期与应用程序的生命周期一致。
二级缓存是通过使用单独的缓存空间来实现的,可以使用第三方缓存框架如Ehcache、Redis等来实现。
Mybatis中的二级缓存的使用需要注意以下几点:1. Mapper文件中需要配置开启二级缓存<cacheeviction='LRU'flushInterval='100000'size='1024'readOnly='true'/>2. 对于需要缓存的对象,需要实现Serializable接口3. 在不同的SqlSession中使用同一个Mapper,才能共享缓存数据4. 对于更新、插入、删除操作,会清空相关的缓存总结:Mybatis中的缓存是提高应用程序性能的重要手段,一级缓存和二级缓存的实现原理不同,使用时需要注意它们的区别和限制条件。
Mybatis一级缓存与二级缓存的实现

Mybatis⼀级缓存与⼆级缓存的实现mybatis缓存mybatis作为⼀个流⾏的持久化⼯具,缓存必然是缺少不了的组件。
通过这篇⽂章,就让我们来了解⼀下mybatis的缓存。
mybatis缓存类型说起mybatis的缓存,了解过的同学都知道,mybatis中可以有两种缓存类型:第⼀种,我们通常称为以及缓存,或者sqlSession级别的缓存,这种缓存是mybatis⾃带的,如果mapper中的配置都是默认的话,那么⼀级缓存也是默认开启的。
第⼆种,就是⾮sqlSession级别的缓存了,我们通常称为⼆级缓存,mybatis中的⼆级缓存需要实现Cache接⼝,并且配置在mapper中,要先开启的话,需要⼀些配置,下⾯我们会详细说到。
⼀级缓存作为mybatis⾃带的缓存,我们通过代码来分析⼀下其原理。
⾸先,我们来看下⼀级缓存的效果。
测试代码:@Testpublic void test_Cache() throws Exception {InputStream input = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(input);SqlSession sqlSession = factory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);System.out.println("the first query : ");mapper.queryAllUsers();System.out.println("====================================");System.out.println("the second query : ");mapper.queryAllUsers();mit();}mapper配置如下,我们采⽤默认配置:<select id="queryAllUsers" resultType="User">select * from hwc_users</select>运⾏结果如下:Created connection 1191654595.Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@470734c3]==> Preparing: select * from hwc_users==> Parameters:<== Columns: id, name, age, email<== Row: 1, 胡⽂超, 35, huwenchao@<== Row: 2, 胡⽂超, 35, huwenchao@<== Row: 3, 胡⽂超, 35, huwenchao@<== Row: 4, 胡⽂超, 35, huwenchao@<== Row: 5, 胡⽂超, 35, huwenchao@<== Row: 6, 胡⽂超, 35, huwenchao@<== Row: 7, 胡⽂超, 35, huwenchao@<== Row: 8, 胡⽂超, 35, huwenchao@<== Row: 9, 胡⽂超, 35, huwenchao@<== Total: 9====================================the second query :Cache Hit Ratio [erMapper]: 0.0Process finished with exit code 0从上述结果可以看到,第⼆次查询并没有从数据库获取,并且没有从⼆级缓存中获取,由此可见,默认配置情况下,同⼀个sqlSession中会默认使⽤mybatis的⼀级缓存。
mybatis的缓存机制及用例介绍

mybatis的缓存机制及⽤例介绍在实际的项⽬开发中,通常对数据库的查询性能要求很⾼,⽽mybatis提供了查询缓存来缓存数据,从⽽达到提⾼查询性能的要求。
mybatis的查询缓存分为⼀级缓存和⼆级缓存,⼀级缓存是SqlSession级别的缓存,⼆级缓存时mapper级别的缓存,⼆级缓存是多个SqlSession共享的。
mybatis通过缓存机制减轻数据压⼒,提⾼数据库性能。
⼀级缓存:mybatis的⼀级缓存是SQLSession级别的缓存,在操作数据库时需要构造SqlSession对象,在对象中有⼀个HashMap⽤于存储缓存数据,不同的SqlSession之间缓存数据区域(HashMap)是互相不影响的。
⼀级缓存的作⽤域是SqlSession范围的,当在同⼀个SqlSession中执⾏两次相同的sql语句时,第⼀次执⾏完毕会将数据库中查询的数据写到缓存(内存)中,第⼆次查询时会从缓存中获取数据,不再去底层进⾏数据库查询,从⽽提⾼了查询效率。
需要注意的是:如果SqlSession执⾏了DML操作(insert、update、delete),并执⾏commit()操作,mybatis则会清空SqlSession中的⼀级缓存,这样做的⽬的是为了保证缓存数据中存储的是最新的信息,避免出现脏读现象。
当⼀个SqlSession结束后该SqlSession中的⼀级缓存也就不存在了,Mybatis默认开启⼀级缓存,不需要进⾏任何配置。
注意:Mybatis的缓存机制是基于id进⾏缓存,也就是说Mybatis在使⽤HashMap缓存数据时,是使⽤对象的id作为key,⽽对象作为value保存例⼦说明:⼯程架构图:pom.xml<project xmlns="/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance" xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion><groupId>com.gm.test</groupId><artifactId>MybatisCacheTest</artifactId><version>0.0.1-SNAPSHOT</version><packaging>war</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!-- spring版本号 --><spring.version>4.3.10.RELEASE</spring.version><!-- mybatis版本号 --><mybatis.version>3.2.6</mybatis.version><!-- log4j⽇志⽂件管理包版本 --><slf4j.version>1.7.7</slf4j.version><log4j.version>1.2.17</log4j.version></properties><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><!-- 表⽰开发的时候引⼊,发布的时候不会加载此包 --><scope>test</scope></dependency><!-- spring核⼼包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-oxm</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version></dependency><!-- aop --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.4</version></dependency><!-- mybatis核⼼包 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>${mybatis.version}</version></dependency><!-- mybatis/spring包 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.2.2</version></dependency><!-- 导⼊java ee jar 包 --><dependency><groupId>javax</groupId><artifactId>javaee-api</artifactId><version>7.0</version></dependency><!-- 导⼊Mysql数据库链接jar包 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.30</version></dependency><!-- 导⼊dbcp的jar包,⽤来在applicationContext.xml中配置数据库 --> <dependency><groupId>commons-dbcp</groupId><artifactId>commons-dbcp</artifactId><version>1.2.2</version></dependency><!-- JSTL标签类 --><dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><!-- ⽇志⽂件管理包 --><!-- log start --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version></dependency><!-- 格式化对象,⽅便输出⽇志 --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.1.41</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>${slf4j.version}</version></dependency><!-- log end --></dependencies><build><plugins><plugin><artifactId>maven-war-plugin</artifactId><configuration><version>3.1</version></configuration></plugin></plugins></build></project>sql语句:CREATE TABLE employee(id INT(11) PRIMARY KEY AUTO_INCREMENT,loginname VARCHAR(18),PASSWORD VARCHAR(18),NAME VARCHAR(18) DEFAULT NULL,sex CHAR(2) DEFAULT NULL,age INT(11) DEFAULT NULL,phone VARCHAR(21),sal DOUBLE,state VARCHAR(18));INSERT INTO employee (loginname,PASSWORD,NAME,sex,age,phone,sal,state) VALUES('jack','123456','杰克','男',26,'12345678936',9800,'ACTIVE'); INSERT INTO employee (loginname,PASSWORD,NAME,sex,age,phone,sal,state) VALUES('rose','123456','露丝','⼥',21,'78965412395',6800,'ACTIVE'); INSERT INTO employee (loginname,PASSWORD,NAME,sex,age,phone,sal,state) VALUES('tom','123456','汤姆','男',25,'139********',8800,'ACTIVE'); INSERT INTO employee (loginname,PASSWORD,NAME,sex,age,phone,sal,state) VALUES('alice','123456','爱丽丝','⼥',20,'74185296375',5800,'ACTIVE'); log4j.properties:log4j.rootLogger=ERROR, stdout.gm.mapper.EmployeeMapper=DEBUGlog4j.appender.console=org.apache.log4j.ConsoleAppenderyout=org.apache.log4j.PatternLayoutyout.ConversionPattern=[%-12d{HH\:mm\:ss.SS}] [%p] %l %m%nlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderyout=org.apache.log4j.PatternLayoutyout.ConversionPattern=%d %p [%c] - %m%nmybatis-config.xml:<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-////DTD Config 3.0//EN" "/dtd/mybatis-3-config.dtd"><configuration><!-- 全局参数 --><settings><!-- 设置但JDBC类型为空时,某些驱动程序要指定值,default:OTHER,插⼊空值时不需要指定类型 --><setting name="jdbcTypeForNull" value="NULL" /><!-- 要使延迟加载⽣效必须配置下⾯两个属性 --><setting name="lazyLoadingEnabled" value="true" /><setting name="aggressiveLazyLoading" value="false" /><setting name="logImpl" value="LOG4J" /></settings><!-- <plugins> <plugin interceptor="com.manager.util.MybatisInterceptor"></plugin></plugins> --><environments default="mysql"><environment id="mysql"><!-- 指定事务管理类型,type="JDBC"指直接简单使⽤了JDBC的提交和回滚设置 --><transactionManager type="JDBC" /><!-- dataSource指数据源配置,POOLED是JDBC连接对象的数据源连接池的实现 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis" /><property name="username" value="root" /><property name="password" value="1qaz@wsx" /></dataSource></environment></environments><mappers><mapper resource="com/gm/mapper/EmployeeMapper.xml" /></mappers></configuration>Employee.java:package com.gm.domain;import java.io.Serializable;public class Employee implements Serializable {/****/private static final long serialVersionUID = 1L;private Integer id;private String loginname;private String password;private String name;private String sex;private String age;private String phone;private Double sal;private String state;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getLoginname() {return loginname;}public void setLoginname(String loginname) {this.loginname = loginname;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getName() {return name;}public void setName(String name) { = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public Double getSal() {return sal;}public void setSal(Double sal) {this.sal = sal;}public String getState() {return state;}public void setState(String state) {this.state = state;}}EmployeeMapper.xml:<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.gm.mapper.EmployeeMapper"><select id="selectEmployeeById" parameterType="int"resultType="com.gm.domain.Employee">SELECT * FROM employee WHERE id = #{id}</select><!-- 查询所有Employee --><select id="selectAllEmployee" parameterType="int"resultType="com.gm.domain.Employee">SELECT * FROM employee</select><!-- 根据id删除Employee --><delete id="deleteEmployeeById" parameterType="int">DELETE FROMemployee WHERE id = #{id}</delete></mapper>EmployeeMapper.java:package com.gm.mapper;import java.util.List;import com.gm.domain.Employee;public interface EmployeeMapper {// 根据id查询EmployeeEmployee selectEmployeeById(Integer id);// 查询所有EmployeeList<Employee> selectAllEmployee();// 根据id删除Employeevoid deleteEmployeeById(Integer id);}⼯⼚⼯具类:MySqlSessionFactory.java:package com.gm.factory;import java.io.IOException;import java.io.InputStream;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;public class MySqlSessionFactory {private static SqlSessionFactory sqlSessionFactory = null;static {try {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);} catch (IOException e) {e.printStackTrace();}}// 获取SqlSession对象的静态⽅法public static SqlSession getSqlSession() {return sqlSessionFactory.openSession();}// 获取SqlSessionFactory的静态⽅法public static SqlSessionFactory getSessionFactory() {return sqlSessionFactory;}}执⾏⽅法类⼀:OneLevelCacheTest.javapackage com.gm.test;import org.apache.ibatis.session.SqlSession;import com.gm.domain.Employee;import com.gm.factory.MySqlSessionFactory;import com.gm.mapper.EmployeeMapper;public class OneLevelCacheTest {public void testCache1() {// 使⽤⼯⼚类获得SqlSession对象SqlSession sqlSession = MySqlSessionFactory.getSqlSession();// 获得EmployeeMapping对象EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);Employee employee1 = employeeMapper.selectEmployeeById(1);System.out.println(employee1);// 再次查询id为1的Employee对象,因为是同⼀个SqlSession,所以会从之前的⼀级缓存中查找数据Employee employee2 = employeeMapper.selectEmployeeById(1);System.out.println(employee2);sqlSession.close();}public static void main(String[] args) {OneLevelCacheTest t = new OneLevelCacheTest();t.testCache1();}public void testCache2() {// 使⽤⼯⼚类获得SqlSession对象SqlSession sqlSession = MySqlSessionFactory.getSqlSession();// 获得EmployeeMapping对象EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);Employee employee1 = employeeMapper.selectEmployeeById(1);System.out.println(employee1);// 执⾏delete操作employeeMapper.deleteEmployeeById(4);// commit提交mit();// 再次查询id为1的Employee对象,因为DML操作会清空sqlSession缓存,所以会再次执⾏select语句Employee employee2 = employeeMapper.selectEmployeeById(1);System.out.println(employee2);sqlSession.close();}public void testCache3() {// 使⽤⼯⼚类获得SqlSession对象SqlSession sqlSession = MySqlSessionFactory.getSqlSession();// 获得EmployeeMapping对象EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);Employee employee1 = employeeMapper.selectEmployeeById(1);System.out.println(employee1);// 关闭⼀级缓存sqlSession.close();// 再次访问,需要再次获取⼀级缓存,然后才能查找数据,否则会抛出异常sqlSession = MySqlSessionFactory.getSqlSession();// 再次获得EmployeeMapper对象employeeMapper = sqlSession.getMapper(EmployeeMapper.class);Employee employee2 = employeeMapper.selectEmployeeById(1);System.out.println(employee2);sqlSession.close();}public void testCache4() {// 使⽤⼯⼚类获得SqlSession对象SqlSession sqlSession = MySqlSessionFactory.getSqlSession();// 获得EmployeeMapping对象EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);Employee employee1 = employeeMapper.selectEmployeeById(1);System.out.println(employee1);// 关闭⼀级缓存sqlSession.close();// 再次访问,需要再次获取⼀级缓存,然后才能查找数据,否则会抛出异常sqlSession = MySqlSessionFactory.getSqlSession();// 再次获得EmployeeMapper对象employeeMapper = sqlSession.getMapper(EmployeeMapper.class);// 再次查询id为1的Employee对象,因为DML操作会清空sqlSession缓存,所以会再次执⾏select语句Employee employee2 = employeeMapper.selectEmployeeById(1);System.out.println(employee2);sqlSession.close();}}运⾏之后结果:2018-08-22 09:35:47,858 DEBUG [com.gm.mapper.EmployeeMapper.selectEmployeeById] - ==> Preparing: SELECT * FROM employee WHERE id = ? 2018-08-22 09:35:47,886 DEBUG [com.gm.mapper.EmployeeMapper.selectEmployeeById] - ==> Parameters: 1(Integer)2018-08-22 09:35:47,899 DEBUG [com.gm.mapper.EmployeeMapper.selectEmployeeById] - <== Total: 1com.gm.domain.Employee@56235b8ecom.gm.domain.Employee@56235b8e通过观察结果可以看出,在第⼀次查询id为1的Employee对象时执⾏了⼀条select语句,但是第⼆次获取id为1的Employee对象时并没有执⾏select语句,因为此时⼀级缓存也就是SqlSession缓存中已经缓存了id为1的Employee对象,Mybatis直接从缓存中将对象取出来,并没有再次去查询数据库,所以第⼆次也就没有执⾏select语句执⾏⽅法类⼆:public static void main(String[] args) {OneLevelCacheTest t = new OneLevelCacheTest();t.testCache2();}运⾏之后的结果:2018-08-22 09:41:17,024 DEBUG [com.gm.mapper.EmployeeMapper.selectEmployeeById] - ==> Preparing: SELECT * FROM employee WHERE id = ?2018-08-22 09:41:17,045 DEBUG [com.gm.mapper.EmployeeMapper.selectEmployeeById] - ==> Parameters: 1(Integer)2018-08-22 09:41:17,058 DEBUG [com.gm.mapper.EmployeeMapper.selectEmployeeById] - <== Total: 1com.gm.domain.Employee@56235b8e2018-08-22 09:41:17,058 DEBUG [com.gm.mapper.EmployeeMapper.deleteEmployeeById] - ==> Preparing: DELETE FROM employee WHERE id = ?2018-08-22 09:41:17,059 DEBUG [com.gm.mapper.EmployeeMapper.deleteEmployeeById] - ==> Parameters: 4(Integer)2018-08-22 09:41:17,079 DEBUG [com.gm.mapper.EmployeeMapper.deleteEmployeeById] - <== Updates: 12018-08-22 09:41:17,125 DEBUG [com.gm.mapper.EmployeeMapper.selectEmployeeById] - ==> Preparing: SELECT * FROM employee WHERE id = ?2018-08-22 09:41:17,125 DEBUG [com.gm.mapper.EmployeeMapper.selectEmployeeById] - ==> Parameters: 1(Integer)2018-08-22 09:41:17,128 DEBUG [com.gm.mapper.EmployeeMapper.selectEmployeeById] - <== Total: 1com.gm.domain.Employee@370736d9结果分析:在第⼀次查询id为1的employee对象时执⾏了⼀条select语句,接下来执⾏了⼀个delete并commit操作,Mybatis为了保证缓存中存储的是最新消息,会清空SqlSession缓存。
mybatis原理-mybatis的一级缓存详解和注意事项_文章_DevStore

前言
MyBatis是一个简单、小巧功能非常强大的ORM开源框架,它的强大在缓存机制上展示的淋淋尽致。MyBatis提 供了一级缓存、二级缓存 这两个缓存机制,能够很好地处理和维护缓存,提高系统的性能。本文详细介绍 MyBatis的一级缓存,深入源码,解析MyBatis一级缓存实现原理,并且针对一级缓存的特点提出了在实际使用过 程中应该注意的事项。
由于Session级别的一级缓存实际上就是使用 怎样实现的呢?
PerpetualCache维护的,那么PerpetualCache是
PerpetualCache实现原理其实很简单,其内部就是通过一个简单的HashMap<k,v> 来实现的, 没有其他的任何限制。如下是PerpetualCache的实现代码:
rowBounds.offset和rowBounds.limit来过滤查询出来的结果集,这种分页功能是基 于查询结果的再过滤,而不是进行数据库的物理分页; 由于
MyBatis底层还是依赖于JDBC实现的,那么,对于两次完全一模一样的查询, MyBatis要保证对于底层JDBC而言,也是完全一致的查询才行。而对于JDBC而言,两
8. import org.apache.ibatis.cache.CacheException; 9. 10. /** 11. * 使用简单的HashMap来维护缓存 12. * @author Clinton Begin 13. */ 14. public class PerpetualCache implements Cache { 15. 16. private String id; 17. 18. private Map<Object, Object> cache = new HashMap<Object, 19. 20. public PerpetualCache(String id) { 21. this.id = id; 22. } 23. 24. public String getId() { 25. return id; 26. } 27. 28. public int getSize() { 29. return cache.size(); 30. } 31. 32. public void putObject(Object key, Object value) { 33. cache.put(key, value); 34. } 35. 36. public Object getObject(Object key) { 37. return cache.get(key); 38. } 39. 40. public Object removeObject(Object key) { 41. return cache.remove(key); 42. } 43. 44. public void clear() { 45. cache.clear(); 46.
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一、默认开启一级缓存。
一级缓存是 SqlSession 级别的。
具体什么意思测试一下。
一次事务中,同一语句调用两次,代码:
VarDateEntity varDateEntity = dateMapper.getVarDate();
System.out.println("var1 var:"+varDateEntity.getVarTime());
System.out.println("var1 not:"+varDateEntity.getNotTime());
System.out.println("var1 null:"+varDateEntity.getNullTime());
varDateEntity = dateMapper.getVarDate();
System.out.println("var2 var:"+varDateEntity.getVarTime());
System.out.println("var2 not:"+varDateEntity.getNotTime());
System.out.println("var2 null:"+varDateEntity.getNullTime());
Postman 中执行两次:
测试结果:
结论1:
同一事务中的两次查询,只查询了一次。
但是两次事务中,每次均进行了一次查询;
一级缓存的 scope 默认值session;
设置为:statment 。
重启 springboot 应用再次测试:
结论2:
设置mybatis.configuration.local-cache-scope=statement后,即使 xmxxxxl 语句中配置useCache="true",一级缓存均失效。
总结:
•Mybatis一级缓存的生命周期和SqlSession一致。
•Mybatis的缓存是一个粗粒度的缓存,没有更新缓存和缓存过期的概念,同时只是使用了默认的hashmap,也没有做容量上的限定。
•Mybatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,有操作数据库写的话,会引起脏数据,建议是把一级缓存的默认级别设定为
Statement,即不使用一级缓存。
二、开启二级缓存,需要进行设置。
#一级缓存状态:关闭 #
mybatis.configuration.local-cache-scope=statement
#二级缓存状态:开启 #
mybatis.configuration.cache-enabled=true
mapper定义中添加标签<cache />或<cache-ref />。
<cache />
cache 用来声明使用二级缓存,可以自定义属性:
•type: cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
•eviction: 定义回收的策略,常见的有FIFO,默认LRU
•flushInterval: 配置一定时间自动刷新缓存,单位是毫秒
•size: 最多缓存对象的个数
•readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
•blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
cache-ref代表引用别的命名空间配置,使用同一个 Cache。
<cache-ref namespace="com.xxx.table_name"/>
测试<cache />:重启应用报错:
Caused by: java.io.NotSerializableException: com.sitech.acctmgr.pub.entity.VarDateEntity
没有序列化?将VarDateEntity序列化。
public class VarDateEntity implements Serializable {
重启应用,Postman第一次SqlSession 事务调用中,两次相同的查询:
两次均调用 sql查询了数据库。
原因:同一个 SqlSession 中,在执行完毕后进行提交commit()才会进入缓存。
Postman第二次事务执行,调用相同的 sql:
使用了缓存。
命中率:Cache Hit Ratio : 0.3333333333333333,为何是0.3,因为总共查询了3次,本次使用了缓存。
再次查询,命中率就会变为0.5:
总结:
二级缓存不同于一级缓存,需要使接受数据的 POJO类序列化;
同一事务中的查询信息,在事务提交之后,才会把查询数据提交缓存。
返回的缓存数据是实际缓存实例,不是拷贝,所以存在被修改的情况!!!
频繁更新的操作不建议使用。
手动修改数据库表中的查询数据会出现脏读!!!
二级缓存对细粒度的数据级别的缓存实现不好,不能够单独刷新某条数据!!!方案?EhcacheCache。
以上是在 SpringBoot 中,对Mybatis的缓存的查询类测试。
关于更新类的测试结果总结:
update操作是否会刷新该namespace下的二级缓存;
Mybatis的二级缓存不适应用于映射文件中存在多表查询的情况。
如何解决?使用cache-ref 引入其他关联表所载 namespace。
单应用方案:
二级缓存?业务第一次的时候做的缓存。
单独做缓存?应用启动的时候静态加载配置。
业务无感知。
分布式方案:
分布式缓存redis?
使用内存数据库?
mybatis源码分析可以查看下附帖子,在很多搜索结果里面,是比较全面的。