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);
}
结果
流程图
核心思路:先完成一条不重复的链子后将他们头尾连接