一、认识缓存
从计算机读取数据的运行原理来看,CPU 从内存中读取资源,而硬盘又不断将数据写进内存中。然后,CPU 将处理好的数据写进内存,再由内存将数据写到硬盘上。CPU 的运转速度大于内存的速度,而内存的运转速度又大于硬盘速度。极容易出现,硬盘往内存读取数据的消耗,大于 CPU 对于内存操作的消耗。那么,当需要执行的进程足够多时,很容易造成系统性能下降,会出现 CPU 假死的状态,也就是 CPU 一直处于等待状态。这是计算机硬件性能差别造成的。
为了解决以上问题,尤其在系统进行大数据量查询的时候,减少硬盘的读取次数,提高性能,产生了缓存的机制。
图 缓存机制
那么,当出现了缓存机制,就需要考虑以下问题:
什么数据该放置于缓存中;
数据应该在什么时候清空;
数据是否应该只保存在一个缓存里。
MyBatis 框架中缓存分为了两种:一级缓存(单个用户,SqlSession)、二级缓存(所有用户,SqlSessionFactory)。
因使用了缓存机制,故会出现缓存与数据库数据不一致的情况,也即失去了自动同步数据功能;
对于缓存的使用,需要注意内存的大小。缓存“过多”,也会造成系统性能下降;
MyBatis 缓存机制,类似数据库缓存,只有提交了 commit 了,才是真正清空缓存。
1.1、一级缓存
当单个用户 SqlSession 存在时,对应的一级缓存就存在,开发者无法对其进行销毁处理。
1.1.1、同一个 SqlSession 查询相同条件下的记录
@Test
public void test() {
Goal goal = new Goal();
goal.setName("8");
SqlSession sessionA = MyBatisSqlSessionFactory.getSqlSession();
List<Goal> goalListA = sessionA.selectList("org.fuys.owndb.vo.mapping.Goal.selectList" ,goal);
logger.info(goalListA.toString());
logger.info("**********************************************");
List<Goal> goalListB = sessionA.selectList("org.fuys.owndb.vo.mapping.Goal.selectList" ,goal);
logger.info(goalListB.toString());
}
复制代码
程序结果:
DEBUG selectList - ==> Preparing: SELECT goalId,name,description,start_time AS "startTime",end_time AS "endTime" FROM goal WHERE name LIKE concat(concat('%',?),'%')
DEBUG selectList - ==> Parameters: 8(String)
TRACE selectList - <== Columns: goalId, name, description, startTime, endTime
TRACE selectList - <== Row: 3, Executor8915, This is the most important goals in the world., 2017-12-31 16:00:00, 2099-12-30 15:59:59
DEBUG selectList - <== Total: 1
INFO TestMyBatisCache - [Goal [goalId=3, name=Executor8915, description=This is the most important goals in the world., startTime=Mon Jan 01 00:00:00 CST 2018, endTime=Wed Dec 30 23:59:59 CST 2099]]
INFO TestMyBatisCache - **********************************************
INFO TestMyBatisCache - [Goal [goalId=3, name=Executor8915, description=This is the most important goals in the world., startTime=Mon Jan 01 00:00:00 CST 2018, endTime=Wed Dec 30 23:59:59 CST 2099]]
复制代码
当 SqlSession 进行提交的情况时,缓存会自动进行清空。
@Test
public void test() {
Goal goal = new Goal();
goal.setName("8");
SqlSession sessionA = MyBatisSqlSessionFactory.getSqlSession();
List<Goal> goalListA = sessionA.selectList("org.fuys.owndb.vo.mapping.Goal.selectList" ,goal);
logger.info(goalListA.toString());
sessionA.commit();
logger.info("**********************************************");
List<Goal> goalListB = sessionA.selectList("org.fuys.owndb.vo.mapping.Goal.selectList" ,goal);
logger.info(goalListB.toString());
}
复制代码
程序执行结果:
DEBUG selectList - ==> Preparing: SELECT goalId,name,description,start_time AS "startTime",end_time AS "endTime" FROM goal WHERE name LIKE concat(concat('%',?),'%')
DEBUG selectList - ==> Parameters: 8(String)
TRACE selectList - <== Columns: goalId, name, description, startTime, endTime
TRACE selectList - <== Row: 3, Executor8915, This is the most important goals in the world., 2017-12-31 16:00:00, 2099-12-30 15:59:59
DEBUG selectList - <== Total: 1
INFO TestMyBatisCache - [Goal [goalId=3, name=Executor8915, description=This is the most important goals in the world., startTime=Mon Jan 01 00:00:00 CST 2018, endTime=Wed Dec 30 23:59:59 CST 2099]]
INFO TestMyBatisCache - **********************************************
DEBUG selectList - ==> Preparing: SELECT goalId,name,description,start_time AS "startTime",end_time AS "endTime" FROM goal WHERE name LIKE concat(concat('%',?),'%')
DEBUG selectList - ==> Parameters: 8(String)
TRACE selectList - <== Columns: goalId, name, description, startTime, endTime
TRACE selectList - <== Row: 3, Executor8915, This is the most important goals in the world., 2017-12-31 16:00:00, 2099-12-30 15:59:59
DEBUG selectList - <== Total: 1
INFO TestMyBatisCache - [Goal [goalId=3, name=Executor8915, description=This is the most important goals in the world., startTime=Mon Jan 01 00:00:00 CST 2018, endTime=Wed Dec 30 23:59:59 CST 2099]]
复制代码
当需要进行人工清空时,则可以调用 SqlSession 接口的 clearCache()方法。
@Test
public void test() {
Goal goal = new Goal();
goal.setName("8");
SqlSession sessionA = MyBatisSqlSessionFactory.getSqlSession();
List<Goal> goalListA = sessionA.selectList("org.fuys.owndb.vo.mapping.Goal.selectList" ,goal);
logger.info(goalListA.toString());
sessionA.clearCache();
logger.info("**********************************************");
List<Goal> goalListB = sessionA.selectList("org.fuys.owndb.vo.mapping.Goal.selectList" ,goal);
logger.info(goalListB.toString());
}
复制代码
问题:
当不同的 SqlSession 访问同一个数据,情况如何?
1.2、二级缓存
当不同的 SqlSession 访问同一个数据,默认情况下,是没有缓存的。要想使用二级缓存,则需要进行如下配置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
复制代码
<cache/>
或者
<cache eviction="FIFO" flushInterval="10000" readOnly="true" size="1024"/>
复制代码
具体解释如下:
eviction="FIFO":缓存操作使用的算法。LRU(默认):最近最少使用算法,将最近不使用的对象进行清空;FIFO:先进先出算法,默认清除最早缓存的数据对象;SOFT:软引用算法,当内存不足时,执行 GC 立刻清除缓存;WEAK:弱引用算法,执行 GC,缓存对象立即清除。
flushInterval="10000":缓存的刷新时间,单位为毫秒。
readOnly="true":缓存做制度配置,建议不要做读写配置。
size="1024":占用内存大小,默认 1024K。
需要注意以下几点:
1、第一个 SqlSession 必须关闭之后,才能将数据写到二级缓存中,就像第一个会话必须提交,否则数据一直存在一级缓存中;
2、VO 类必须实现 java.io.Serializable 接口;
3、因配置的二级缓存,都是配置在公共的文件里,因此,如果具体查询需求不需要二级缓存,可以进行缓存的取消标记。
<select id="selectCount" parameterType="java.util.Map" resultType="int" useCache="false">
SELECT
count(goalId)
FROM goal
WHERE ${column} like #{keyword}
</select>
复制代码
二级缓存的 Java 代码实现:
@Test
public void testSecondCache() throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("column", "name");
map.put("keyword", "7");
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sessionA = factory.openSession();
int countA = sessionA.selectOne("org.fuys.owndb.vo.mapping.Goal.selectCount" ,map);
sessionA.close();
logger.info("Count = " + countA);
logger.info("**********************************************");
SqlSession sessionB = factory.openSession();
int countB = sessionB.selectOne("org.fuys.owndb.vo.mapping.Goal.selectCount" ,map);
logger.info("Count = " + countB);
sessionB.close();
}
复制代码
问题:
1、缓存与动态 SQL 是否相互影响?
2、为何查询所有数据不适用缓存?
二、动态 SQL
相对于固定格式的 SQL 语句,MyBatis 提供了灵活多变的动态 SQL,可以实现 SQL 语句的变化。
2.1、if 语句
<select id="selectList" parameterType="Goal" resultType="Goal">
SELECT goalId,name,description,start_time AS "startTime",end_time AS
"endTime"
FROM goal
<where>
<if test="name!=null">
and name LIKE concat(concat('%',#{name}),'%')
</if>
<if test="description!=null">
and description concat(concat('%',#{description}),'%')
</if>
</where>
</select>
复制代码
2.2、choose 语句
<select id="selectList" parameterType="User" resultType="User">
SELECT uid,name,sex
FROM user
<where>
<choose>
<when test="uid != null">
and uid = #{uid}
</when>
<when test="name != null">
and name = #{name}
</when>
<otherwise>
and 1=1
</otherwise>
</choose>
</where>
</select>
复制代码
2.3、set 语句
<update id="update" parameterType="User">
UPDATE user
<set>
<trim suffixOverrides=",">
<if test="name !=null">
name = #{name},
</if>
<if test="sex !=null">
sex = #{sex},
</if>
</trim>
</set>
<where>
<choose>
<when test="uid !=null">
and uid = #{uid}
</when>
<otherwise>
and sex is null
</otherwise>
</choose>
</where>
</update>
复制代码
2.4、foreach 语句
<select id="selectByUidArray" parameterType="Integer" resultType="User">
SELECT uid,name,sex
FROM user
<where>
uid in
<foreach collection="array" open="(" close=")" separator="," item="uid">
#{uid}
</foreach>
</where>
</select>
复制代码
评论