写点什么

MyBatis 之缓存机制和动态 SQL

作者:andy
  • 2022-10-27
    北京
  • 本文字数:4344 字

    阅读完需:约 14 分钟

一、认识缓存


从计算机读取数据的运行原理来看,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 访问同一个数据,默认情况下,是没有缓存的。要想使用二级缓存,则需要进行如下配置:

  • 修改核心配置文件(mybatis-config.xml)


<settings>		<setting name="cacheEnabled" value="true"/>	</settings>
复制代码


  • 在需要使用二级缓存的 mapper 映射文件添加配置,表示允许使用二级缓存。


<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>
复制代码


用户头像

andy

关注

还未添加个人签名 2019-11-21 加入

还未添加个人简介

评论

发布
暂无评论
MyBatis之缓存机制和动态SQL_andy_InfoQ写作社区