一级缓存
Mybatis支持缓存,默认情况下是开启的一级缓存,一级缓存是SqlSession级别,缓存的是SqlSession对象。在SQL语句、参数都相同的情况下,我们使用同一个SqlSession对象调用同一个Mapper方法的时候,只需要执行一次SQL,因为在第一次执行Mapper方法后,Mybatis会将其缓存起来,后面查询的时候如果参数,SQL语句相同就会直接取缓存的数据,而不会再去查询数据库,这样大大提高了查询效率。
这里借用一下别人的缓存示意图,清晰明了!
一级缓存的生命周期
- MyBatis一级缓存是基于SQLSession的,MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
- 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
- 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
- SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用
一级缓存触发条件
- 传入的statementId一样
- 查询时要求的结果集中的结果范围一样
- 这次查询所产生的最终要传递给JDBC java.sql.的SqlPreparedstatement语句字符串(boundSql.getSql() )一样
- 传递给java.sql.Statement要设置的参数值一样
一级缓存使用案例
首先,实体类(注意:需要可序列化,我这里实现了Serializable接口)
public class TtUser implements Serializable{
private Long id;
private String loginName;
private String password;
// 这里省略get、set方法
}
dao方法
public interface TtUserMapper {
TtUser selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(TtUser record);
}
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">
<mapper namespace="com.xiateng.dao.user.TtUserMapper">
<!--<cache eviction="LRU" flushInterval="10000" size="1024" />-->
<resultMap id="BaseResultMap" type="com.xiateng.entity.TtUser">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="login_name" jdbcType="VARCHAR" property="loginName" />
<result column="password" jdbcType="VARCHAR" property="password" />
</resultMap>
<sql id="Base_Column_List">
id, login_name, password
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_user
where id = #{id,jdbcType=BIGINT}
</select>
<update id="updateByPrimaryKeySelective" parameterType="com.xiateng.entity.TtUser">
update t_user
<set>
<if test="loginName != null">
login_name = #{loginName,jdbcType=VARCHAR},
</if>
<if test="password != null">
password = #{password,jdbcType=VARCHAR},
</if>
<if test="blong != null">
blong = #{blong,jdbcType=SMALLINT},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>
Controller调用
@RequestMapping(value = "/a")
@ResponseBody
public String a(){
// 开启一个SqlSession(会话)
SqlSession sqlSession = sqlSessionFactory.openSession();
TtUserMapper mapper = sqlSession.getMapper(TtUserMapper.class);
TtUser ttUser = mapper.selectByPrimaryKey(1L);
System.out.println("-----------------------------------------"+ttUser);
System.out.println("-----------------------------------------第二次执行");
TtUser ttUser2 = mapper.selectByPrimaryKey(1L);
System.out.println("-----------------------------------------"+ttUser2);
// 关闭会话
sqlSession.close();
return "成功";
}
控制台打印
我们会发现只打印了一次sql,说明第二次没有查询数据库
二级缓存
二级缓存是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出得结果的对象与
第一次存入的对象是不一样的。二级缓存是namespace级别的缓存,
看图
使用二级缓存需注意
- 映射语句文件中的所有select语句将会被缓存。
- 映射语句文件中的所有insert、update和delete语句会刷新缓存。
- 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
- 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
- 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。
Mybatis默认是不开启二级缓存的,要开启的话需要进行配置,并且返回的POJO必须是可序列化的(实体类实现Serializable)。下面我们举个例子:
实体跟方法还是上面的,只不过在mapper.xml里添加配置<cache/>配置如下
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
属性含义:
- eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。
- LRU,最近最少使用的,一处最长时间不用的对象
- FIFO,先进先出,按对象进入缓存的顺序来移除他们
- SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是LRU, 移除最长时间不用的对形象 flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当 SQL被执行的时候才会去刷新缓存。
- size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。 这里配置的是1024个对象
- readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有 办法修改缓存,他的默认值是false,不允许我们修改
接下来配置Mybatis开启二级缓存,因为我是SpringBoot项目,所以我是在application.yml文件中加入如下配置:
最后测试Controller代码
@RequestMapping(value = "/b")
@ResponseBody
public Map<String, Object> b(){
Map map = new HashMap();
TtUser ttUser = ttUserMapper.selectByPrimaryKey(1L);
map.put("ttUser",ttUser);
System.out.println("-----------------------------------------"+ttUser);
System.out.println("-----------------------------------------第二次执行");
TtUser ttUser2 = ttUserMapper.selectByPrimaryKey(1L);
map.put("ttUser2",ttUser2);
System.out.println("-----------------------------------------"+ttUser2);
System.out.println("-----------------------------------------第三次执行");
TtUser ttUser3 = ttUserMapper.selectByPrimaryKey(2L);
System.out.println("-----------------------------------------"+ttUser3);
map.put("ttUser3",ttUser3);
return map;
}
打印结果
从打印结果我们会发现,第一次查询打印了sql语句,它直接查询了数据库,第二次查询没打印sql语句,由于第二次参数,statementId,sql都一样,直接命中了二级缓存,没有查询数据库,而第三次参数不一样,打印了sql语句,直接查询了数据库
一级缓存和二级缓存区别
一级缓存:默认开启,基于SQLSession,在操作数据库时需要构造SQLSession对象,在对象中有一个HashMap用于存储缓存数据,不同的SQLSession直接是胡不影响的。在一个SQLSession中执行增删改并提交到数据库,一级缓存会清空,避免脏读。
当关闭一个sqlsession时,一级缓存也随之消失。
二级缓存:默认不开启,基于namespace,同一个namespace下的多个SQLSession会公用一个缓存,二级缓存同样是使用HashMap就行数据存储,对比一级缓存,二级缓存作用域更大,可跨越多个SQLSession。