1. 一级缓存
查询缓存的作用 :主要是为了提高查询访问速度。将用户对用一数据的重复查询过程简化,不再每次均从数据库查询获取结果数据,从而提高访问速度。 MyBatis的查询缓存机制 ,根据缓冲区的作用域与生命周期,可划分为两种:一级缓存与二级缓存; MyBatis查询缓存的作用域 是根据映射文件mapper的namespace划分的,相同namespace的mapper查询数据存放在同一个缓存区域。不同namespace下的数据互不干扰。无论是一级缓存还是二级缓存,都是按照namespace进行分别存放的; 但一、二级缓存的不同之处在于:SqlSession 一旦关闭,则SqlSession中的数据将不存在,即一级缓存就不覆存在。而二级缓存的生命周期会与整个应用同步,与SqlSesson是否关闭无关。换句话说,一级缓存是在同一线程(同一SqlSesson)间共享数据,而二级缓存是在不用线程(不同的SqlSesson)间共享数据——生命周期 。
证明一级缓存是存在的
@Test
public void testSelectStudentByName ( ) {
Student student = dao. selectStudentById ( 2 ) ;
System. out. println ( student) ;
Student student2 = dao. selectStudentById ( 2 ) ;
System. out. println ( student2) ;
}
[ DEBUG] = = > Preparing: select tid id, tname name, tage age, score from student where tid= ?
[ DEBUG] = = > Parameters: 2 ( Integer )
[ TRACE] <= = Columns : id, name, age, score
[ TRACE] <= = Row : 2 , 王五, 23 , 93.5
[ DEBUG] <= = Total: 1
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
MyBatis一级缓存是基于org.apache.ibatis.cache.impl.PerpetualCache 类的HashMap本地缓存,其作用域是SqlSession。在同一个SqlSession中两次执行相同的sql查询语句,第一次执行完毕后,会将查询结果写入到缓存中,第二次会从缓存中直接获取数据,而不再到数据库中进行查询,从而提高查询效率。 当一个SqlSession结束后,该SqlSession中的一级缓存也就不存在了。myBatis默认一级缓存是开启状态,且不能关闭。
证明从一级缓存中读取数据的依据
@Test
public void testSelectStudentByName2 ( ) {
Student student = dao. selectStudentById ( 2 ) ;
System. out. println ( student) ;
Student student2 = dao. selectStudentById2 ( 2 ) ;
System. out. println ( student2) ;
}
[ DEBUG] = = > Preparing: select tid id, tname name, tage age, score from student where tid= ?
[ DEBUG] = = > Parameters: 2 ( Integer )
[ TRACE] <= = Columns : id, name, age, score
[ TRACE] <= = Row : 2 , 王五, 23 , 93.5
[ DEBUG] <= = Total: 1
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
[ DEBUG] = = > Preparing: select tid id, tname name, tage age, score from student where tid= ?
[ DEBUG] = = > Parameters: 2 ( Integer )
[ TRACE] <= = Columns : id, name, age, score
[ TRACE] <= = Row : 2 , 王五, 23 , 93.5
[ DEBUG] <= = Total: 1
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
缓存的底层实现是一个Map,Map的value是查询结果 Map的key,即查询依据,使用的ORM框架不同,查询依据是不同的。 一级缓存中读取数据的依据 :
MyBatis的查询依据是:Hash值 + Sql的id + SQL语句 Hibernate的查询依据是:查询结果对象的id
增删改操作对一级缓存的影响
@Test
public void testSelectInsertStudentByName ( ) {
Student student = dao. selectStudentById ( 2 ) ;
System. out. println ( student) ;
dao. insertStudent ( new Student ( "赵六" , 26 , 96.5 ) ) ;
Student student2 = dao. selectStudentById ( 2 ) ;
System. out. println ( student2) ;
}
[ DEBUG] = = > Preparing: select id, name, age, score from student where id= ?
[ DEBUG] = = > Parameters: 2 ( Integer )
[ TRACE] <= = Columns : id, name, age, score
[ TRACE] <= = Row : 2 , 王五, 23 , 93.5
[ DEBUG] <= = Total: 1
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
[ DEBUG] = = > Preparing: insert into student( name, age, score) values ( ?, ?, ?)
[ DEBUG] = = > Parameters: 赵六( String) , 26 ( Integer ) , 96.5 ( Double )
[ DEBUG] <= = Updates: 1
[ DEBUG] = = > Preparing: select id, name, age, score from student where id= ?
[ DEBUG] = = > Parameters: 2 ( Integer )
[ TRACE] <= = Columns : id, name, age, score
[ TRACE] <= = Row : 2 , 王五, 23 , 93.5
[ DEBUG] <= = Total: 1
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
2. 二级缓存
由于MyBatis从缓存中读取数据的依据与SQL的id相关,而非查询出的对象。所以,使用二级缓存的目的,不是在多个查询间共享查询结果 (所有查询中只要查询结果中存在该对象的,就直接从缓存中读取,这是对查询结果的共享,但MyBatis不是),而是为了防止同一查询 (相同的Sql id 、相同的sql语句 )的反复执行 。 myBatis内置的二级缓存为 org.apache.ibatis.cache.impl.PerpetualCache .
二级缓存的开启
< cache/ >
< insert id= "insertStudent" >
insert into student( name, age, score) values (
< / insert >
< select id= "selectStudentById" resultType= "Student" >
select id, name, age, score from student where id=
< / select >
< select id= "selectStudentById2" resultType= "Student" >
select id, name, age, score from student where id=
< / select >
第二步:实例化
如果不实例化,程序会报java.io.NotSerializableException 异常
public class Student implements Serializable {
@Test
public void testSelectStudentByName ( ) {
sqlSession = MyBatisUtils. getSqlSession ( ) ;
dao = sqlSession. getMapper ( IStudentDao. class ) ;
Student student = dao. selectStudentById ( 2 ) ;
System. out. println ( student) ;
sqlSession. close ( ) ;
sqlSession = MyBatisUtils. getSqlSession ( ) ;
dao = sqlSession. getMapper ( IStudentDao. class ) ;
Student student2 = dao. selectStudentById ( 2 ) ;
System. out. println ( student2) ;
}
[ DEBUG] Cache Hit Ratio [ com. huahua. dao. IStudentDao] : 0.0
[ DEBUG] = = > Preparing: select id, name, age, score from student where id= ?
[ DEBUG] = = > Parameters: 2 ( Integer )
[ TRACE] <= = Columns : id, name, age, score
[ TRACE] <= = Row : 2 , 王五, 23 , 93.5
[ DEBUG] <= = Total: 1
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
[ DEBUG] Cache Hit Ratio [ com. huahua. dao. IStudentDao] : 0.5
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
其中,Cache Hit Ratio :表示缓存命中率。第一次缓存命中率为0,这是因为程序第一次调用selectStudentById方法时,先从缓存空间中找结果,第一次因为缓存空间为空,所有没找到,一共找了1次结果为0;第二次缓存命中率为0.5,这是因为程序第二次调用selectStudentById方法时,先从缓存空间中找到了第一次调用方法时的缓存,找到了,此时,一共找了两次,找到了一次,所以结果为0.5;以此类推。 就执行了一次select语句 ,证明二级缓存存在! 开启内置的二级缓存步骤:
1) 对实体进行序列化 2)在映射文件中添加cache 标签
增删改对二级缓存的影响
@Test
public void testSelectInsertStudentByName ( ) {
sqlSession = MyBatisUtils. getSqlSession ( ) ;
dao = sqlSession. getMapper ( IStudentDao. class ) ;
Student student = dao. selectStudentById ( 2 ) ;
System. out. println ( student) ;
sqlSession. close ( ) ;
sqlSession = MyBatisUtils. getSqlSession ( ) ;
dao = sqlSession. getMapper ( IStudentDao. class ) ;
dao. insertStudent ( new Student ( "" , 0 , 0 ) ) ;
Student student2 = dao. selectStudentById ( 2 ) ;
System. out. println ( student2) ;
}
[ DEBUG] Cache Hit Ratio [ com. huahua. dao. IStudentDao] : 0.0
[ DEBUG] = = > Preparing: select id, name, age, score from student where id= ?
[ DEBUG] = = > Parameters: 2 ( Integer )
[ TRACE] <= = Columns : id, name, age, score
[ TRACE] <= = Row : 2 , 王五, 23 , 93.5
[ DEBUG] <= = Total: 1
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
[ DEBUG] = = > Preparing: insert into student( name, age, score) values ( ?, ?, ?)
[ DEBUG] = = > Parameters: ( String) , 0 ( Integer ) , 0.0 ( Double )
[ DEBUG] <= = Updates: 1
[ DEBUG] Cache Hit Ratio [ com. huahua. dao. IStudentDao] : 0.5
[ DEBUG] = = > Preparing: select id, name, age, score from student where id= ?
[ DEBUG] = = > Parameters: 2 ( Integer )
[ TRACE] <= = Columns : id, name, age, score
[ TRACE] <= = Row : 2 , 王五, 23 , 93.5
[ DEBUG] <= = Total: 1
Student [ id= 2 , name= 王五, age= 23 , score= 93.5 ]
说明:
1) 增删改同样也会清空二级缓存; 2) 对于二级缓存的清空,实质上是对所查找的key对应的value置为null,而并非将<key, value>键值对,即Entry对象删除; 3) 从DB中进行select查询的条件是:
① 缓存中根本就不存在这个key所对应的Entry对象——根本就不存在这个key; ② 缓存中存在该key所对应的Entry对象,但其value为null。
二级缓存的配置
为cache标签添加一些相关属性设置,可以对二级缓存的运行性能进行控制。当然,若不指定设置,则均保持默认值。
< cache eviction= "FIFO" flushInterval= "10800000"
readOnly= "true" size= "512" / >
eviction:逐出策略。当二级缓存中的对象达到最大值时,就需要通过逐出策略将缓存中的对象移出缓存。默认为LRU。常用的策略有:
FIFO:First In First Out,先进先出 LRU:Least Recently Used,未被使用时间最长的 flushInterval:刷新缓存的时间间隔,单位毫秒。这里的刷新缓存即清空缓存。一般不指定,即当执行增删改时刷新缓存。 readOnly:设置缓存中数据是否只读。只读的缓存会给所有调用者返回缓存对象的相同实例,因此,这些对象不能修改,这提供了很重要的性能优势。但读写的缓存会返回缓存对象的拷贝。这会慢一些,但是安全,因此默认是false。 size:二级缓存中可以存放的最多对象个数。默认为1024个。
二级缓存的关闭
二级缓存默认开启状态。若要将其关闭,则需要进行相关设置。根据关闭的范围大小,可以分为全局关闭于局部关闭。
全局关闭
所谓全局关闭是指,整个应用的二级缓存全部关闭,所有查询均不使用二级缓存。全局开关设置在主配置文件的全局配置settings中,该属性为cacheEnabled,设置为false,则关闭;设置为true,则开启,默认值为true。即二级缓存默认是开启的。
<settings>
<!-- 关闭(全局)二级缓存 -->
<setting name="cacheEnabled" value="false"/>
</settings>
<select id="selectStudentById" useCache="false" resultType="Student">
select id,name,age,score from student where id=#{xxx}
</select>
<select id="selectStudentById2" resultType="Student">
select id,name,age,score from student where id=#{xxx}
</select>
ehcache 二级缓存
使用ehecache 二级缓存,实体类无需实现序列化接口。 下载ehcache jar包 -
< cache type = "org.mybatis.caches.ehcache.EncacheCache" / >
第三步:需要ehcache的配置文件——ehcache-failsafe.xml(其在ehcache-core-2.6.8 jar包中),但不能直接用,需要改名,比如改成:ehcache.xml。