MyBatis循环依赖问题是怎么解决的?
问题分析
A依赖B B又依赖A所构成的一种循环,也可以称为循环依赖,试想下这个场景在MyBatis中会怎样?如果不管的话那就是无限制的去数据库查询了。
demo
<resultMap id="authorMap" type="org.apache.ibatis.demo.Author"> <result column="id" property="id"/> <result column="name" property="name"/> <collection property="book" column="book_id" select="selectBookByid" fetchType="eager"/> </resultMap> <resultMap id="bookMap" type="org.apache.ibatis.demo.Book"> <result column="id" property="id"></result> <result column="name" property="name"></result> <collection property="author" column="author_id" select="selectAuthorByid" fetchType="eager"></collection> </resultMap> <select id="selectAuthorByid" resultMap="authorMap"> select * from author where id = #{id} </select> <select id="selectBookByid" resultMap="bookMap"> select * from book where id = #{id} </select>
结果可以看到每个SQL只查询一次,看到这个结果应该就可以猜到用什么方式解决了吧?没错用的就是缓存预先占位处理,如果重复查询就不去数据库查询了,而是直接返回缓存的引用!!!
MyBatis采用空占位符的方式
//从数据库查 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 缓存中放入占位符 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } // 查询完加入缓存 localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
如果是嵌套查询中间有一段逻辑处理,可以理解为映射到需要嵌套的属性时,创建Executor.query方法重新调用一遍查询嵌套的属性
//加载结果 public Object loadResult() throws SQLException { //1.selectList List<Object> list = selectList(); //2.ResultExtractor.extractObjectFromList resultObject = resultExtractor.extractObjectFromList(list, targetType); return resultObject; } private <E> List<E> selectList() throws SQLException { Executor localExecutor = executor; // 如果executor已经被关闭了,则创建一个新的 if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) { localExecutor = newExecutor(); } try { // 又调回Executor.query去了,相当于B的查询 return localExecutor.<E> query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql); } finally { if (localExecutor != executor) { localExecutor.close(false); } } }
如果之前的查询有被缓存过那么会走executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType)这个逻辑
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final String nestedQueryId = propertyMapping.getNestedQueryId(); final String property = propertyMapping.getProperty(); final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType(); final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix); Object value = NO_VALUE; if (nestedQueryParameterObject != null) { final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql); final Class<?> targetType = propertyMapping.getJavaType(); if (executor.isCached(nestedQuery, key)) { // 判断是否有占位符,如果进入延迟加载逻辑 executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType); } else { final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); if (propertyMapping.isLazy()) { // 懒加载 lazyLoader.addLoader(property, metaResultObject, resultLoader); } else { // 立即加载 value = resultLoader.loadResult(); } } } return value; }
如果重复查询进入延迟加载逻辑,可以看到延迟加载之后没有再递归调用query方法了,说明已经到头了,之后一路返回。
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) { if (closed) { throw new ExecutorException("Executor was closed."); } DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType); // 如果存入不是占位符,而是具体的值就立刻加载 if (deferredLoad.canLoad()) { deferredLoad.load(); } else { // 队列加入,整体查询完在加载(延迟加载)这里new一个新的,直接拿deferredLoad不行吗?? deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType)); } }
查询返回完以后,将队列的元素通过反射映射嵌套查询的字段就可以了
//加载 public void load() { List<Object> list = (List<Object>) localCache.getObject(key); // 取出缓存的值 Object value = resultExtractor.extractObjectFromList(list, targetType); resultObject.setValue(property, value); }
结果
流程图
核心思路:先完成一条不重复的链子后将他们头尾连接
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报