【MyBatis 二级缓存】
概述:一级缓存作用域为同一个SqlSession对象,而二级缓存用来解决一级缓存不能夸会话共享,作用范围是namespace级,可以被多个SqlSession共享(只要是同一个接口方法的相同方法,都可同享)。
- MyBatis默认支持一级二级缓存。在没有任何配置情况下,默认开启一级缓存,MyBatis 的一级缓存是在会话(SqlSession)层面进行缓存的;
- mybatis的二级缓存默认也是开启的,但由于他的作用域是namespace,所以还需要在mapper.xml中开启才能生效;
- 缓存优先级:通过mybatis发起的查询,作用顺序为:二级缓存->一级缓存->数据库 ,其中一二级缓存不为空将直接返回结果,否则查询数据库;
- 缓存失效:当在一个缓存作用域中发生了update、insert、delete 操作后,将会触发缓存失效,下一次查询将命中数据库,从而保证不会查到脏数据;
- MyBatis一级缓存范围是SqlSession内部。二级缓存为namespace,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement,即进行如下配置
1、【缓存解析说明】
有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议实际开发应用中设定缓存级别为Statement(工程pom文件中)。如下:
解释:Executor为*接口,SimpleExecutor、BatchExecutor和ReuseExecutor是具体组件实现类,而CachingExecutor是具体的装饰器。具体组件实现类有一个父类BaseExecutor,而这个父类是一个模板模式的典型应用,一级缓存的操作都在这个类中实现,具体的操作数据库的功能子类实现。客户端通过工厂实现调用配置文件最后调用数据读取BaseExecutor中Query方法,首先缓存对象,缓存存在直接从缓存中获得,不存在查询数据后再写入缓存。
(图一):操作缓存业务图
(图二):类关系图
二级缓存开启步骤
A、工程配置文件中增加二级缓存配置信息
B、xxxMapper.xml文件中开启全局二级缓存支持,如果不在某一个Mapper.xml文件中开启cache就不存在二级缓存。
<!-- 声明此namespace开启二级缓存,MyBatis自带的二级缓存 --> <cache/>
【配置说明】:
A、配置后该namespace下所有 select 语句查询结果将会被缓存;
B、覆盖文件的所有 insert、update 和 delete 语句会刷新缓存;
C、缓存默认使用最近最少使用算法清除不需要缓存;
D、缓存默认不配置定时刷新;
E、缓存默认保存1024个引用;
【cache可以设置属性,举例说明】
<cache eviction="FIFO" flushInterval="60000" /* 每隔 60 秒刷新, (刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新 */ size="2048" /* 最多可以存储结果对象或列表的 2048 个引用, ,而且返回的对象被认为是只读因此对它们进行修改可能会在不同线程中的调用者产生冲突 */ readOnly="true"/> /* readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false */
[清除策略]:
LRU :最近最少使用:移除最长时间不被使用的对象;
FIFO :先进先出:按对象进入缓存的顺序来移除它们;
SOFT :软引用:基于垃圾回收器状态和软引用规则移除对象;
WEAK :弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象;
C、二级缓存必然会引起脏读,实时性较高的操作可单独关闭关闭二级缓存(某一查询禁用二级缓存)
<select id="queryPersonById" parameterType="int" resultType="com.cache.bean.Person" useCache="false"/>
D、清理二级缓存
二级缓存清理与一级方法相同,使用commit()(执行增、删、改时会执行commit操作,会清理掉缓存,设计这个机制原因是为了防止脏读。注意:二级缓存中commit不能是查询本身的commit); 在select标签中,增加属性flushCache=”true”
E、注意
1)、二级缓存是事务性的。当 SqlSession 完成commit时或是回滚,但未执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新(但不能是同一个SqlSession的查询commit);
2)、MyBatis在多表查询时,极大可能会出现脏数据,设计上存在缺陷;
3)、在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本。使用Redis、Memcached等分布
式缓存可能成本更低,安全性也更高(后续博文发布);
4)、触发二级缓存的时机为session.close时;
5)、XxxMapper.xml文件中的实体类需要做序列化操作,级联类及父类也需要序列化操作;
应用实例:
1、 实体类
public class Person implements Serializable { static final long serialVersionUID = 42L; /* 人员ID */ private int id; /* 人员名称 */ private String name; /* 人员年龄 */ private int age; /* 人员性别 */ private Boolean sex; public Person() { } public Person(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public Person(int id, String name, int age, Boolean sex) { this.id = id; this.name = name; this.age = age; this.sex = sex; } public Boolean getSex() { return sex; } public void setSex(Boolean sex) { this.sex = sex; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
2、 接口类
// 操作mybatis接口 public interface PersonMapper { Person queryPersonById(int id); }
3、 Mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace:该mapper.xml映射文件的唯一标识 --> <mapper namespace="com.cache.mapper.PersonMapper"> <!- 开启二级缓存--> <cache/> <select id="queryPersonById" parameterType="int" resultType="com.cache.bean.Person"> select id,name,age from t_person where id = #{id} </select> </mapper>
4、pom文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- 开启日志,并制定使用的具体日志,LOG4J 对应 log4j.properties的文件名 --> <!--<setting name="logImpl" value="LOG4J"/>--> <!-- 开启延迟加载 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 关闭立即加载 --> <setting name="aggressiveLazyLoading" value="false"/> <!-- 开启二级缓存,默认不开启 --> <setting name="cacheEnabled" value="true"/> <!--<setting name="localCacheScope" value="STATEMENT"/>--> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis01"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/cache/mapper/personMapper.xml"/> </mappers> </configuration>
5、 测试类
/* 二级缓存 */ @Test public void test001() throws Exception{ Reader reader = Resources.getResourceAsReader("mybatis-01.xml"); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession session01 = sessionFactory.openSession(); PersonMapper personMapper01 = session01.getMapper(PersonMapper.class); Person person0101 = personMapper01.queryPersonById(1001); System.out.println("Session01,第一次查询结果为:" + person0101); session01.close(); // 进行缓存操作 SqlSession session02 = sessionFactory.openSession(); PersonMapper personMapper02 = session02.getMapper(PersonMapper.class); Person person0201 = personMapper02.queryPersonById(1001); System.out.println("Session02,第一次查询结果为:" + person0201); Person person0202 = personMapper02.queryPersonById(1001); System.out.println("Session02,第二次查询结果为:" + person0202); Person person0203 = personMapper02.queryPersonById(1001); System.out.println("Session02,第三次查询结果为:" + person0203); session02.close(); // 进行缓存操作 }
6、测试结果
"C:\Program Files\Java\jdk1.8.0_25\bin\java" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\lib\idea_rt.jar=1045:C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\bin" -Dfile.encoding=UTF-8 -classpath "C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\lib\idea_rt.jar;C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\plugins\junit\lib\junit-rt.jar;C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\plugins\junit\lib\junit5-rt.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\platform\junit-platform-launcher\1.5.2\junit-platform-launcher-1.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\apiguardian\apiguardian-api\1.1.0\apiguardian-api-1.1.0.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\platform\junit-platform-engine\1.5.2\junit-platform-engine-1.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\opentest4j\opentest4j\1.2.0\opentest4j-1.2.0.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\platform\junit-platform-commons\1.5.2\junit-platform-commons-1.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\jupiter\junit-jupiter-engine\5.5.2\junit-jupiter-engine-5.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\jupiter\junit-jupiter-api\5.5.2\junit-jupiter-api-5.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\vintage\junit-vintage-engine\5.5.2\junit-vintage-engine-5.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\junit\junit\4.12\junit-4.12.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\rt.jar;D:\ideaworkspace\ProjectStudy\mybatis-cache-03\target\classes;D:\download\lib\mavenTollTransfer\mic-repository\org\hamcrest\hamcrest-core\2.1\hamcrest-core-2.1.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\hamcrest\hamcrest\2.1\hamcrest-2.1.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\mybatis\mybatis\3.4.5\mybatis-3.4.5.jar;D:\download\lib\mavenTollTransfer\mic-repository\mysql\mysql-connector-java\5.1.44\mysql-connector-java-5.1.44.jar;D:\download\lib\mavenTollTransfer\mic-repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\slf4j\slf4j-log4j12\1.7.12\slf4j-log4j12-1.7.12.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\download\lib\mavenTollTransfer\mic-repository\cglib\cglib\3.3.0\cglib-3.3.0.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\ow2\asm\asm\7.1\asm-7.1.jar;D:\download\lib\mavenTollTransfer\mic-repository\net\logstash\logback\logstash-logback-encoder\5.3\logstash-logback-encoder-5.3.jar;D:\download\lib\mavenTollTransfer\mic-repository\com\fasterxml\jackson\core\jackson-databind\2.10.3\jackson-databind-2.10.3.jar;D:\download\lib\mavenTollTransfer\mic-repository\com\fasterxml\jackson\core\jackson-annotations\2.10.3\jackson-annotations-2.10.3.jar;D:\download\lib\mavenTollTransfer\mic-repository\com\fasterxml\jackson\core\jackson-core\2.10.3\jackson-core-2.10.3.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit5 com.cache.test.test,test001 [lsjSso]2021-10-21 00:18:40,415-org.apache.ibatis.logging.LogFactory-0 [main]DEBUGorg.apache.ibatis.logging.LogFactory-Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. [lsjSso]2021-10-21 00:18:40,563-org.apache.ibatis.datasource.pooled.PooledDataSource-148 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-PooledDataSource forcefully closed/removed all connections. [lsjSso]2021-10-21 00:18:40,563-org.apache.ibatis.datasource.pooled.PooledDataSource-148 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-PooledDataSource forcefully closed/removed all connections. [lsjSso]2021-10-21 00:18:40,564-org.apache.ibatis.datasource.pooled.PooledDataSource-149 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-PooledDataSource forcefully closed/removed all connections. [lsjSso]2021-10-21 00:18:40,564-org.apache.ibatis.datasource.pooled.PooledDataSource-149 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-PooledDataSource forcefully closed/removed all connections. [lsjSso]2021-10-21 00:18:40,671-com.cache.mapper.PersonMapper-256 [main]DEBUGcom.cache.mapper.PersonMapper-Cache Hit Ratio [com.cache.mapper.PersonMapper]: 0.0 [lsjSso]2021-10-21 00:18:40,679-org.apache.ibatis.transaction.jdbc.JdbcTransaction-264 [main]DEBUGorg.apache.ibatis.transaction.jdbc.JdbcTransaction-Opening JDBC Connection [lsjSso]2021-10-21 00:18:40,938-org.apache.ibatis.datasource.pooled.PooledDataSource-523 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-Created connection 2009221452. [lsjSso]2021-10-21 00:18:40,938-org.apache.ibatis.transaction.jdbc.JdbcTransaction-523 [main]DEBUGorg.apache.ibatis.transaction.jdbc.JdbcTransaction-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77c2494c] [lsjSso]2021-10-21 00:18:40,942-com.cache.mapper.PersonMapper.queryPersonById-527 [main]DEBUGcom.cache.mapper.PersonMapper.queryPersonById-==> Preparing: select id,name,age from t_person where id = ? [lsjSso]2021-10-21 00:18:40,984-com.cache.mapper.PersonMapper.queryPersonById-569 [main]DEBUGcom.cache.mapper.PersonMapper.queryPersonById-==> Parameters: 1001(Integer) [lsjSso]2021-10-21 00:18:41,012-com.cache.mapper.PersonMapper.queryPersonById-597 [main]DEBUGcom.cache.mapper.PersonMapper.queryPersonById-<== Total: 1 Session01,第一次查询结果为:Person{id=1001, name='HuanCun', age=26} [lsjSso]2021-10-21 00:18:41,017-org.apache.ibatis.transaction.jdbc.JdbcTransaction-602 [main]DEBUGorg.apache.ibatis.transaction.jdbc.JdbcTransaction-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77c2494c] [lsjSso]2021-10-21 00:18:41,017-org.apache.ibatis.transaction.jdbc.JdbcTransaction-602 [main]DEBUGorg.apache.ibatis.transaction.jdbc.JdbcTransaction-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@77c2494c] [lsjSso]2021-10-21 00:18:41,017-org.apache.ibatis.datasource.pooled.PooledDataSource-602 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-Returned connection 2009221452 to pool. [lsjSso]2021-10-21 00:18:41,019-com.cache.mapper.PersonMapper-604 [main]DEBUGcom.cache.mapper.PersonMapper-Cache Hit Ratio [com.cache.mapper.PersonMapper]: 0.5 Session02,第一次查询结果为:Person{id=1001, name='HuanCun', age=26} [lsjSso]2021-10-21 00:18:41,019-com.cache.mapper.PersonMapper-604 [main]DEBUGcom.cache.mapper.PersonMapper-Cache Hit Ratio [com.cache.mapper.PersonMapper]: 0.6666666666666666 Session02,第二次查询结果为:Person{id=1001, name='HuanCun', age=26} [lsjSso]2021-10-21 00:18:41,020-com.cache.mapper.PersonMapper-605 [main]DEBUGcom.cache.mapper.PersonMapper-Cache Hit Ratio [com.cache.mapper.PersonMapper]: 0.75 Session02,第三次查询结果为:Person{id=1001, name='HuanCun', age=26} Process finished with exit code 0
结果分析说明: