MyBatis 的缓存机制是其性能优化的关键模块之一,通过减少对数据库的直接访问来大幅提升查询效率。在深入二级缓存之前,理解整个缓存体系的基础(一级缓存)以及支撑它的核心源码至关重要。🔍 缓存体系概览MyBatis 提供两级缓存,均基于Cache接口实现。一级缓存 (Local Cache):默认开启且无法关闭,作用域为SqlSession级别。其本质是一个HashMap,执行相同的查询时,会先从该SqlSession的本地缓存中查找。二级缓存 (Global Cache):作用域为Mapper的namespace级别,可跨SqlSession共享,需要手动配置开启。🏗️ 核心源码剖析:Cache 接口与装饰器模式org.apache.ibatis.cache.Cache是所有缓存实现的核心接口,定义了几个基本方法:String getId():获取缓存对象的唯一标识符。void putObject(Object key, Object value):将数据放入缓存。Object getObject(Object key):从缓存中获取数据。Object removeObject(Object key):从缓存中移除数据。void clear():清空整个缓存。MyBatis 的缓存模块大量运用了装饰器模式,通过层层包装为基础缓存PerpetualCache增加多样化的功能。PerpetualCache(核心组件):最基础的缓存实现,内部直接使用HashMap存储键值对,是缓存模块的"核心"。LruCache(装饰器):实现最近最少使用 (LRU)策略的缓存。它内部维护一个LinkedHashMap,并覆写removeEldestEntry方法,当缓存大小超过设定值 (setSize) 时,自动移除链表头部的元素(即最久未使用的条目)。SynchronizedCache(装饰器):一个线程安全的缓存装饰器,其所有方法都使用synchronized关键字修饰,确保多线程环境下的数据一致性。BlockingCache(装饰器):提供阻塞功能的缓存,用于防止缓存击穿。当一个线程在查询某个 Key 时,如果缓存未命中,该线程会获得这个 Key 的锁,并去数据库加载数据,其他线程必须等待,从而避免大量并发请求同时落到数据库。此外,还有FifoCache(先进先出策略)和SoftCache/WeakCache(基于引用类型)等多种装饰器,可以根据需求灵活组合。🔬 一级缓存源码分析 (以 SqlSession 为基础)一级缓存的实现逻辑主要集中在org.apache.ibatis.executor.BaseExecutor类中。缓存存储:BaseExecutor维护了两个PerpetualCache实例:localCache:用于缓存查询结果。localOutputParameterCache:用于缓存存储过程的输出参数。缓存键: 一级缓存使用CacheKey对象作为键,以确保查询的精确匹配。CacheKey的构建涉及多个要素:MappedStatement 的 ID(namespace + id)。查询结果的偏移量(RowBounds.offset)和限制条数(RowBounds.limit)。具体的 SQL 语句(BoundSql.getSql())。SQL 语句中所有参数的实际值。环境 ID(configuration.getEnvironment().getId())。执行流程 (BaseExecutor.query):// 代码逻辑简化自 org.apache.ibatis.executor.BaseExecutorpublicEListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException{// 1. 根据查询条件生成CacheKeyCacheKeykey=createCacheKey(ms,parameter,rowBounds,boundSql);// 2. 检查是否配置了在查询前清空一级缓存 (flushCacheRequested)if(ms.isFlushCacheRequired()){clearLocalCache();// 清空localCache}ListElist=null;try{// 3. 尝试从一级缓存(localCache)中获取结果list=queryFromDatabase(ms,parameter,rowBounds,resultHandler,key,boundSql);}catch(SQLExceptione){throwe;}// ... 其他处理returnlist;}privateEListEqueryFromDatabase(...){ListElist=(ListE)localCache.getObject(key);if(list!=null){returnlist;// 缓存命中,直接返回}// 缓存未命中,执行数据库查询list=doQuery(ms,parameter,rowBounds,resultHandler,boundSql