MyBatis 是一个流行的持久层框架,它提供了一个灵活且高效的方式来访问关系型数据库。其中一个重要的特性是缓存机制,它可以帮助提升数据库查询的性能。
MyBatis 的缓存分为一级缓存和二级缓存两种:
- 一级缓存:默认情况下,MyBatis 开启了一级缓存。一级缓存是指在同一个 SqlSession 中,执行相同的 SQL 语句,返回的结果会被缓存起来,下次再执行相同的 SQL 语句时,直接从缓存中获取结果,而不需要再去数据库查询。一级缓存的范围是 SqlSession 级别的,也就是在同一个 SqlSession 中,多次执行相同的 SQL 语句只会查询数据库一次。
- 二级缓存:二级缓存是指在不同的 SqlSession 之间共享缓存。通过配置开启二级缓存后,当一个 SqlSession 执行查询时,查询的结果会被保存到共享的缓存中,其他的 SqlSession 在执行相同的查询时,可以直接从缓存中获取结果,而不需要再去查询数据库。二级缓存的范围是 Mapper 级别的,默认情况下是关闭的,需要手动进行配置开启。
需要注意的是,一级缓存是默认开启的,并且无法关闭,而二级缓存是需要手动进行配置开启的。
使用 MyBatis 缓存机制需要注意以下几点:
- 对于频繁更新和变动的数据,不适合使用缓存。
- 对于数据的一致性要求比较高的场景,不适合使用缓存。
- 如果配置了二级缓存,需要确保缓存的数据不会影响到其他业务模块的数据。
- 在使用缓存时,需要注意缓存的命中率和缓存的过期策略,避免缓存过期导致查询性能下降。
目录
一、描述一下MyBatis的一级缓存是什么?
二、MyBatis的一级缓存如何工作?
三、MyBatis的一级缓存是怎样管理的?
四、在MyBatis中如何清除一级缓存?
五、何时使用一级缓存是不合适的?
六、MyBatis 一级缓存可能带来的问题是什么?
七、如何配置MyBatis以禁用一级缓存?
八、如何监控MyBatis的一级缓存?
九、在分布式系统中,一级缓存会造成哪些影响?
十、MyBatis一级缓存的范围是如何确定的?
十一、对于MyBatis来说,有哪些操作会触发一级缓存的失效?
十二、使用MyBatis一级缓存需要注意哪些最佳实践?
十三、MyBatis在缓存查询结果时用的是哪些关键信息?
十四、MyBatis的懒加载是什么,它与一级缓存有什么关联?
十五、MyBatis 一级缓存和二级缓存的区别是什么?
十六、什么是MyBatis的二级缓存?
十七、如何启用MyBatis的二级缓存?
十八、MyBatis二级缓存可以整合哪些第三方缓存框架?
十九、MyBatis的二级缓存在什么时候会清空?
二十、使用MyBatis二级缓存有哪些注意事项或最佳实践?
二十一、二级缓存的读写策略有哪些?
二十二、如果开启了二级缓存,MyBatis是如何确定缓存的键值的?
二十三、在嵌套查询中,MyBatis的二级缓存如何工作?
二十四、MyBatis二级缓存中的数据如何保证同步/一致性?
二十五、MyBatis的缓存穿透是什么意思,如何防止?
二十六、如果应用重启,MyBatis二级缓存中的数据会怎样?
二十七、如何动态地清除MyBatis的二级缓存?
二十八、MyBatis二级缓存是否支持事务?
二十九、MyBatis二级缓存数据如何保证一致性?
三十、如何描述MyBatis的二级缓存,并说明它与其他缓存(如数据库缓存、应用缓存等)的区别?
答:在MyBatis中,一级缓存是指SqlSession级别的缓存。当执行查询操作时,查询结果会存储在SqlSession的一级缓存中。后续如果在相同的SqlSession中执行相同的查询操作,则会直接从缓存中获取数据,而不是访问数据库。这个缓存只在SqlSession内部有效,也就是说,当SqlSession被关闭或者提交(commit)时,缓存将被清空。
答:一级缓存的工作机制是:在第一次查询数据时,会从数据库中检索结果并将其存储在一级缓存(即当前的SqlSession中)。之后,如果有相同的查询请求发起,并且这个查询请求在同一个SqlSession的生命周期内,MyBatis会检测到这个请求的结果已经被缓存,于是直接从缓存中获取数据,而不是再执行数据库查询。
答:MyBatis的一级缓存默认是开启的,并不需要专门的配置。它是通过SqlSession来维护的,每个SqlSession都有自己的一级缓存。一级缓存的生命周期与SqlSession的生命周期相同,当SqlSession关闭或提交时,它的一级缓存也会被清空。
答:MyBatis中的一级缓存可以通过以下几种方式清除:
SqlSession.clearCache()
:这个方法可以显式地清除当前SqlSession的一级缓存。SqlSession.commit()
:执行提交(commit)操作时,会清除SqlSession的一级缓存。SqlSession.rollback()
:执行回滚(rollback)操作时也会清除一级缓存。- 除上述方法外,如果在一个SqlSession中执行了任何INSERT、UPDATE 或 DELETE 操作,则一级缓存也会被自动清空。
答:一级缓存适用于SqlSession生命周期内多次执行相同查询的情况,但在一些情况下使用一级缓存可能会导致问题:
- 如果在一个SqlSession的生命周期内,数据在数据库中被其他SqlSession或者应用修改了,那么这个SqlSession的一级缓存中的数据可能不再是最新的,导致数据的一致性问题。
- 在处理不需要重复执行相同查询或数据库操作有较高更改频率的操作时,一级缓存的作用可能并不显著,甚至可能会因为频繁的清除缓存而降低性能。
答:尽管一级缓存有助于减少数据库访问,提高应用性能,但是它也可能导致一些问题,尤其是数据一致性问题。如果在缓存生效期间,同一个数据在数据库中被其他程序修改,那么缓存中的数据将会变得陈旧,但是该SqlSession仍然会返回旧数据,导致数据不一致。此外,过度依赖一级缓存可能会导致开发者对事务管理不够重视,从而产生一些难以发现的错误。
答:一级缓存是MyBatis内置的,无法禁用,但你可以通过每次查询后调用
SqlSession.clearCache()
方法来清空缓存,避免缓存数据。另一种较为极端的方法是每次查询后关闭SqlSession,并且每次需要查询时重新打开一个新的SqlSession,这样可以确保每次查询都是直接对数据库进行而不使用缓存。不过,这样做事务管理可能会变得复杂并且减少了MyBatis缓存机制带来的性能优势。
答:一级缓存是由SqlSession来维护的,因此你可以通过拦截器(Interceptor)或日志记录器来监控SqlSession的活动。自定义一个MyBatis拦截器,可以捕捉查询、更新、提交、关闭等事件,通过这些拦截逻辑可以监控到一级缓存的命中与否,以及何时进行了缓存清除。
答:在分布式系统中,由于数据库会被多个不同的服务器访问,一级缓存(SqlSession级别的缓存)可能会导致数据不一致的问题。因为每个应用服务器的SqlSession都可能拥有自己的一级缓存,所以当一个服务器更新了数据库的数据而其他服务器的SqlSession还保持着旧数据的缓存时,就会导致数据在不同服务器之间的不一致。在分布式系统中,更推荐使用分布式缓存解决方案,如Redis,来替代或者辅助一级缓存和二级缓存,以保证数据的一致性。
答:MyBatis一级缓存的范围是基于SqlSession的。当你打开一个SqlSession,执行查询,那么查询结果会被缓存在这个SqlSession中。后续的同一个查询如果在这个SqlSession中执行,就会直接从缓存中获取结果,直到这个SqlSession被关闭或者调用了清除缓存的操作。不同的SqlSession之间的一级缓存是彼此独立的,它们不会共享数据。
答:一级缓存会在以下几种情况下失效:
- 当执行了SqlSession的清缓存操作
clearCache()
时。- 当执行了提交(
commit()
)或回滚(rollback()
)操作时。- 当在SqlSession中执行了更新操作,如insert、update或delete,这些操作可能会修改数据库中的数据,因此会清除缓存以保证数据的一致性。
- 在默认的情况下,只要对数据库进行了CUD(创建、更新、删除)操作,一级缓存就会失效。
答: 当使用MyBatis的一级缓存时,以下是一些推荐的最佳实践:
- 只在事务内使用一级缓存。这能确保在同一事务内部数据的一致性。
- 在需要保证数据实时更新的业务场景下,慎用或规避使用一级缓存。或者合理地使用
clearCache()
来手动清除缓存。- 监控SqlSession的生命周期,正确关闭SqlSession以释放资源。
- 使用短时间内执行多次相同查询的业务场景,以利用一级缓存减少数据库访问。
- 如果使用了多个数据库实例,比如主从数据库,需要注意主数据库数据变更后如何同步更新缓存在从数据库SqlSession中的数据。
答:MyBatis在缓存查询结果时,使用到的关键信息包括:
- 语句的ID:对应XML映射文件中的mapper statement id,或是Mapper接口中的方法名称。
- 查询中使用的参数:SQL语句执行时,传递的参数对象。
- 分页信息:如果查询进行了分页,分页的参数也会作为缓存的一部分。
- SQL语句本身:实际执行的SQL命令文本。
- MyBatis的配置设置:可能会影响缓存行为的各种配置设置。
答:MyBatis的懒加载指的是它可以配置在需要的时候才加载关联的其他数据。例如,对于一对多或多对多关系,你可以选择在访问关联对象时才进行查询,而不是在加载原始对象时就加载所有的关联数据。懒加载可以加快最初查询的速度,减少立即不必要的数据库访问。懒加载与一级缓存相关,因为通过懒加载获取的关联数据也会被一级缓存(如果数据尚未变更),这样当你再次访问这些关联数据时,它们可以从缓存中提取而不必重新查询数据库。
在准备面试MyBatis相关内容时,请确保理解缓存机制的细节,并能够根据具体情况分析可能出现的问题及解决策略。
答:在MyBatis中,一级缓存和二级缓存用于减少数据库的查询次数,提高应用的查询效率。但它们在作用域、生命周期、配置方式等方面存在如下区别:
一级缓存(Local/Session Cache)
- 作用域:一级缓存的范围限定于同一个SqlSession内。如果多次执行相同的SQL查询,则只有第一次会实际执行并访问数据库,之后的查询会直接使用缓存中的结果,直到SqlSession关闭或清除。
- 生命周期:与SqlSession绑定,SqlSession结束或调用
clearCache()
方法时,一级缓存中的数据会被清空。- 配置:默认开启,无需任何特殊配置。
- 共享性:不可跨SqlSession共享数据。
二级缓存(Global Cache)
- 作用域:二级缓存是应用范围的缓存,它可以跨越多个SqlSession,甚至可以在多个用户间共享。
- 生命周期:一般绑定到SqlSessionFactory的生命周期。当SqlSessionFactory关闭时,二级缓存也随之销毁。除此之外,它也可以通过配置来设置缓存的刷新策略,例如定时刷新或某些事件触发时刷新。
- 配置:需要在MyBatis配置文件中明确地开启,并且通常要结合使用第三方缓存提供者(比如Ehcache或Redis)。
- 共享性:可跨Session共享,在不同的会话、不同的事务中均可访问到相同的缓存数据。
需要注意的是,虽然这两种缓存机制能够提升应用性能,但也可能引入数据不一致性的问题。在实际应用中需要谨慎使用,特别是在有高并发和数据频繁变动的场景下。对于二级缓存,需合理配置缓存失效时间、考虑缓存数据的时效性,还可能需要整合第三方缓存解决方案来实现更高级的缓存需求。
重点强调
- 事务影响
- 一级缓存会受到当前事务影响,如果在同一个SqlSession中执行了任何写操作(插入、更新、删除),默认情况下,MyBatis会清空一级缓存,以确保后续查询能够得到最新的数据,反映最近的更改。
- 二级缓存更加复杂,因为它跨越了多个SqlSession。如果多个会话共享相同的数据,则数据的写操作可能导致其他会话中的数据陈旧。MyBatis提供了某些机制来尝试维护二级缓存的更新,如基于时间戳的缓存刷新策略,乃至与外部缓存机制(如Ehcache集成)的 integration,对缓存数据进行更复杂的管理。
- 缓存配置和自定义
- MyBatis的二级缓存可以配置不同的属性,如最大元素数量、生存时间、空闲时间等,以控制缓存的行为。
- 高级配置 include using custom cache implementations. MyBatis提供了一种可以创建自定义缓存的方法,或者使用已存在的第三方缓存实现作为二级缓存。
- 正确性与性能
- 使用缓存必须平衡数据的正确性和性能。缓存可以显著提高性能,但如果不正确地管理,可能导致用户获得旧数据。面试时,可以强调在设计系统时应当注意的缓存策略,以及缓存可能带来的一些风险(如缓存击穿、缓存穿透和缓存雪崩)。
- 例如,对于缓存失效,应当考虑使用"写穿"(write-through)策略或者"写后"(write-behind)策略来保持数据库和缓存之间的同步。
- 与其他技术的集成
- 在一些高级场景下,可能需要结合使用数据库的其他功能,如MySQL的查询缓存,或者其他持久层框架和数据库中间件提供的缓存功能,例如Hibernate的二级缓存,或者分布式缓存系统(Redis、Memcached)等。
MyBatis 是一个流行的 Java 持久层框架,它提供 SQL 映射和对象关系映射。MyBatis 支持两种缓存机制:一级缓存和二级缓存。一级缓存是会话级别的,二级缓存是全局的或者命名空间级别的。以下是一些跟 MyBatis 二级缓存相关的面试题:
答:MyBatis的二级缓存是指整个应用级别的缓存。它是跨多个 SqlSession 的,存储在全局,可以跨越多个数据库操作会话,只要会话被提交或关闭,它的结果就可以被其他会话重用。它使用一个全局的配置来存储缓存数据。
答:要启用MyBatis的二级缓存,你需要做如下配置:
- 在mybatis的配置文件中,设置全局配置属性
cacheEnabled
为true
,这是开启二级缓存的前提。- 对于需要使用二级缓存的mapper映射文件,你需要在映射文件中添加
标签。
- 你还需要在你的POJO(实体类)上实现序列化接口(
java.io.Serializable
),因为二级缓存会将数据序列化后存储。
MyBatis可以整合多种第三方缓存框架,如Ehcache、Redis、Memcached等。
答:MyBatis的二级缓存会发生清空的情况包括以下几个:
- 当会话被关闭时,该会话的一级缓存会被清空,但不一定会影响到二级缓存;
- 当会话执行了commit操作并提交了对数据库的更改,依赖于这些更改的二级缓存会被清空;
- 清空或调用
evict
方法时,指定的或所有的缓存条目被移除。
答:使用MyBatis二级缓存时,需要注意的事项包括:
- 仅对读多写少,且不会被频繁修改的数据进行缓存;
- 如果数据是很敏感的,不适合长时间缓存,应当避免使用二级缓存;
- 保持缓存的数据的时效性,适时地更新或清空缓存;
- 考虑使用合适的第三方缓存提供更好的缓存策略和更大的缓存空间;
- 对于复杂关联查询结果,要谨慎缓存,以避免数据一致性问题。
答:二级缓存的读写策略通常是由所集成的第三方缓存框架提供,不同的缓存框架如Ehcache、Redis等会有不同的策略可供选择。一些常见的缓存策略包括:
- FIFO(First In First Out):先进先出策略。
- LRU(Least Recently Used):最近最少使用算法,优先移除最长时间未被访问的对象。
- LFU(Least Frequently Used):最少使用算法,根据对象被访问的频率来移除对象。
- TTl(Time To Live):生存时间,对象存活的时间。
- TTI(Time To Idle):空闲时间,对象在最后一次访问之后能够存活的时间。
答:MyBatis 二级缓存的键值是由一个复合键生成的,这个复合键包含了映射语句的id(例如 namespace + “.” + statementId),传递给映射语句的参数(通过
hashCode
和equals
方法判断参数是否相同),以及分页信息(比如RowBounds中的offset和limit)等。所有这些信息共同决定了缓存中的一个唯一的数据项。
答:在嵌套查询中,只要涉及到的映射语句开启了二级缓存,MyBatis会首先尝试从二级缓存中获取数据。如果二级缓存中没有对应数据,MyBatis就会执行数据库查询并将结果缓存起来。在执行嵌套查询时,MyBatis会分别对每个查询进行缓存,每个查询的结果都可以被缓存,并可以被未来的查询重用。这种方式可以有效减少数据库的访问次数,提高性能。
答:如果应用在集群环境下,多个应用节点都使用同一个数据库,并且使用了二级缓存,会存在数据一致性问题。为了解决这个问题,可以选用支持集群环境的缓存方案,比如集成分布式缓存Redis作为二级缓存。
对于非分布式缓存方案,比如Ehcache,数据一致性策略通常取决于以下几点:
- 只缓存共享读,但不缓存/即时失效特定更新导致的变化较大的数据;
- 使用缓存框架提供的分布式缓存同步工具(如果有的话);
- 在更新数据时显式地更新或者清理相关缓存。
这样可以确保各个应用节点看到的数据是一致的。遵循这些策略,可以在一定程度上保证二级缓存数据的同步和一致性。不过,二级缓存与高并发、高更新频率的数据一致性问题依然是一个复杂的挑战,需谨慎使用。
答:缓存穿透是指查询一个数据库中不存在的数据,由于缓存中也不会有这个数据,导致每次请求都会打到数据库上,增加数据库的压力。如果这种查询变得频繁,将严重影响系统的性能。
防止缓存穿透的策略包括:
- 空对象缓存:当查找的数据在数据库中不存在时,可以将一个特殊的空对象或标记存入缓存,这样再次访问这个数据时,可以直接从缓存中获取到这个空标记。
- 布隆过滤器:使用布隆过滤器,在请求数据库查询前,先检查数据是否存在于一个大概率正确的数据集合中,如果不在这个集合中,则直接返回数据不存在的结果,不执行数据库查询。
答:默认情况下,MyBatis二级缓存是存储在内存中的,所以如果应用重启,缓存中存储的数据将会丢失。为了解决这个问题,可以将MyBatis的二级缓存配置成使用第三方集中式缓存如Redis等,因为这些建立在独立进程或持久化存储基础上的缓存系统,可以在应用重启后继续保留数据。
答:可以通过 MyBatis API 动态地清除二级缓存。例如,通过使用 sqlSession 的 clearCache() 方法可以清除当前 sqlSession 范围内的一级缓存,而二级缓存的清除通常需要通过映射器定义的 cache-ref 区域来处理。需要注意的是,清除操作需要谨慎进行,防止在不适当的时间清除缓存导致性能问题。
答:MyBatis二级缓存自身并不处理事务。事务管理依赖于数据库访问框架或服务容器(如 Spring)来提供。当发生事务回滚时,MyBatis并不会从二级缓存中清除此前的查询结果。因此,应当确保在使用二级缓存时,缓存的使用方式与事务管理策略兼容,防止在事务回滚后仍然使用了错误的缓存数据。
答:保证MyBatis二级缓存数据一致性的核心是理解和恰当地配置以下各种机制,确保在数据更新时缓存能得到正确的更新或失效,以及在需要时提供合适的同步或刷新策略。在实际应用中,这需要对业务场景和数据变更的频率有一个综合考量。
在MyBatis中,二级缓存的数据一致性主要依赖于以下几种机制来保证:
- 事务和缓存同步:MyBatis的二级缓存与数据库事务紧密集成。在事务提交或回滚时,MyBatis会清空二级缓存中与该事务相关的缓存,以确保缓存数据与数据库保持一致
- 缓存作用域限制:MyBatis的二级缓存是基于同一个Mapper namespace来工作的。这意味着,只有在同一个namespace下的Mapper才会从二级缓存中获取数据,这里的作用域限制有助于降低数据一致性出问题的可能性。
- 缓存的自动刷新:配置二级缓存时,开发者可以设置特定的属性,如
flushInterval
(刷新间隔),这可以指定一个固定的时间间隔来刷新缓存。- 依赖于第三方缓存提供者:MyBatis允许使用第三方缓存提供者(如Ehcache、Redis等)作为二级缓存。这些缓存提供者一般具有更强大的一致性控制机制,可用于维护缓存数据的一致性。
- 配置依赖的数据项:在Mapper的映射文件中可以通过
标签来引用其他namespace的二级缓存配置,这样可以确保当被引用的缓存发生变化时,引用它的缓存也会相应地更新。
- 手动编程控制:在某些情况下,开发者可能需要手动控制缓存的清除。可以通过调用
SqlSession.clearCache()
或在Mapper的映射语句中设置flushCache="true"
以手动清空缓存。- 监听数据库变化:虽然这不是MyBatis自带的,但可以通过集成其他工具来实现,比如Oracle的数据库触发器结合中间件来通知应用程序更新缓存。
答:
MyBatis的二级缓存:
- MyBatis的二级缓存是一个跨SqlSession的缓存,当数据在一个Session中被加载,它就可以被其他的SqlSession重用。这意味着二级缓存可以在同一个Mapper namespace级别上跨多个SqlSession和事务共享数据。
- 二级缓存可以减少数据库的查询次数并提高了应用性能,尤其是对于不经常变化的数据。
- 二级缓存可以使用第三方缓存实现,如Ehcache、Redis等,并且可以自定义缓存策略和属性,例如缓存的大小、数据存活时间、清理策略等。
- MyBatis的二级缓存默认是不启用的,开发者需要在mybatis-config.xml中配置来启用它,并且要在Mapper文件中添加相应的配置。
与数据库缓存的区别:
- 数据库缓存通常是由数据库提供的内建功能,例如MySQL的查询缓存,主要用于缓存执行相同SQL语句的结果集,工作在数据库层面。
- 数据库缓存对应用程序是透明的,开发者可能不需要在应用层面进行特别的配置或管理,但也因此更少控制权。
- MyBatis的二级缓存工作在应用程序层面,可以细致地由应用程序控制和配置,比如可以更精细地控制何时使用缓存、何时更新缓存,以及如何维护缓存数据的一致性。
与应用缓存的区别:
- 应用缓存指的是应用程序中的缓存机制,它可以是自定义的缓存逻辑,也可以是集成第三方缓存库(例如Spring Cache、Guava Cache等)实现。
- 应用缓存可以存在于应用程序的任何层次,如Service层、DAO层等,由应用决定其的粒度和作用域。
- 相比之下,MyBatis的二级缓存是专门为Mapper层的数据交互提供的缓存机制,是更集成于MyBatis框架的一部分,专门用于ORM层面的缓存优化。