一、认识缓存
从计算机读取数据的运行原理来看,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, endTimeTRACE selectList - <== Row: 3, Executor8915, This is the most important goals in the world., 2017-12-31 16:00:00, 2099-12-30 15:59:59DEBUG selectList - <== Total: 1INFO 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, endTimeTRACE selectList - <== Row: 3, Executor8915, This is the most important goals in the world., 2017-12-31 16:00:00, 2099-12-30 15:59:59DEBUG selectList - <== Total: 1INFO 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, endTimeTRACE selectList - <== Row: 3, Executor8915, This is the most important goals in the world., 2017-12-31 16:00:00, 2099-12-30 15:59:59DEBUG selectList - <== Total: 1INFO 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>
复制代码
评论