回答
支持。延迟加载的意思是在使用的时候才去加载,这是一种性能优化策略。延迟加载针对有延迟设置的关联对象推迟查询,而主查询是直接执行SQL语句。
- 优点: 先从单表查询,需要时再从关联表查询,减少不必要的数据加载,提升数据库性能。
- 不足: 延迟加载会增加数据库访问次数以及增加事务管理的复杂性。
扩展
延迟加载配置
1、Mybatis 默认没有开启延迟加载,需要在全局配置文件 mybatis-config.xml 中进行设置,核心配置项:
- lazyLoadingEnabled:全局开关,启用或禁用延迟加载。
- aggressiveLazyLoading:侵入式延迟加载开关,false 表示按需加载。
<configuration>
<!-- 其他配置 -->
<settings>
<!-- 延迟加载开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入式延迟加载开关(false表示按需加载) -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
</configuration>
2、Mapper 中的关联查询collection
、association
标签上添加fetchType
属性。
- lazy:延迟加载
- eager:立即加载
备注:指定fetchType
属性后,会忽略全局配置参数的lazyLoadingEnabled
配置参数。
<resultMap id="BaseResultMap" type="com.damingge.entity.Employee">
<id column="id" property="id"/>
<result column="age" property="age"/>
<result column="name" property="name"/>
<result column="address" property="contactPhone"/>
<result column="department" property="department_id"/>
<association property="department" column="department_id" javaType="com.damingge.entity.Department"
select="com.damingge.dao.DepartmentMapper.findDepartmentById" fetchType="lazy">
</association>
</resultMap>
<select id="selectEmployeeById" resultMap="BaseResultMap">
select * from t_employee where id = #{id}
</select>
上述配置中,fetchType="lazy"
表示当访问Employee
对象的department
属性时才会执行DepartmentMapper.findDepartmentById
查询部门信息。
3、springboot 应用集成 mybatis-spring-boot-starter 后开启懒加载配置
### 开启懒加载
mybatis.configuration.lazy-loading-enabled=true
### false 为按需加载
mybatis.configuration.aggressive-lazy-loading=false
### 开启驼峰命名, 打印sql
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
实现原理
延迟加载(懒加载)通过创建目标对象的代理对象来实现。当访问代理对象的某个属性时,Mybatis 会触发一次数据库查询,将该属性的数据加载到内存中。通常利用 JDK 的动态代理或 CGLIB 代理。
- 创建代理对象:Mybatis 查询时会为需要延迟加载的属性创建代理对象。
- 触发延迟加载:当访问代理对象的延迟加载属性时,代理对象会触发实际的数据库查询,加载数据。
- 映射结果集:将查询结果映射到目标对象的属性。
核心类和接口
- ProxyFactory:代理工厂接口,用于创建代理对象。
public interface ProxyFactory {
default void setProperties(Properties properties) {
// NOP
}
// 创建代理对象
Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
}
- CglibProxyFactory:使用 CGLIB 代理创建代理对象实现延迟加载。
public class CglibProxyFactory implements ProxyFactory {
@Override
public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}
}
private static class EnhancedResultObjectProxyImpl implements MethodInterceptor {
public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
final Class<?> type = target.getClass();
EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
PropertyCopier.copyBeanProperties(type, target, enhanced);
return enhanced;
}
- ResultLoader:Mybatis 加载查询结果集。
public class ResultLoader {
protected final Configuration configuration;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final Object parameterObject;
protected final Class<?> targetType;
protected final ObjectFactory objectFactory;
protected final CacheKey cacheKey;
protected final BoundSql boundSql;
protected final ResultExtractor resultExtractor;
protected final long creatorThreadId;
protected boolean loaded;
protected Object resultObject;
public Object loadResult() throws SQLException {
// 查询结果集
List<Object> list = selectList();
// 结果集转换
resultObject = resultExtractor.extractObjectFromList(list, targetType);
return resultObject;
}
}
- ResultLoaderMap:管理和触发延迟加载操作。
public class ResultLoaderMap {
private final Map<String, LoadPair> loaderMap = new HashMap<>();
public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {
String upperFirst = getUppercaseFirstProperty(property);
if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {
throw new ExecutorException("Nested lazy loaded result property '" + property
+ "' for query id '" + resultLoader.mappedStatement.getId()
+ " already exists in the result map. The leftmost property of all lazy loaded properties must be unique within a result map.");
}
loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));
}
public final Map<String, LoadPair> getProperties() {
return new HashMap<>(this.loaderMap);
}
public Set<String> getPropertyNames() {
return loaderMap.keySet();
}
public int size() {
return loaderMap.size();
}
/**
* 检查指定属性是否包含在 loaderMap
*/
public boolean hasLoader(String property) {
return loaderMap.containsKey(property.toUpperCase(Locale.ENGLISH));
}
/**
* 根据指定属性执行延迟加载操作,将结果加载到目标对象中。
*/
public boolean load(String property) throws SQLException {
LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
if (pair != null) {
pair.load();
return true;
}
return false;
}
/**
* 移除指定属性的 LoadPair 实例。
*/
public void remove(String property) {
loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
}
public void loadAll() throws SQLException {
final Set<String> methodNameSet = loaderMap.keySet();
String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
for (String methodName : methodNames) {
load(methodName);
}
}
}
public static class LoadPair implements Serializable {
// 省略其他代码……
/**
* 延迟加载
*/
public void load() throws SQLException {
/* These field should not be null unless the loadpair was serialized.
* Yet in that case this method should not be called. */
if (this.metaResultObject == null) {
throw new IllegalArgumentException("metaResultObject is null");
}
if (this.resultLoader == null) {
throw new IllegalArgumentException("resultLoader is null");
}
this.load(null);
}
public void load(final Object userObject) throws SQLException {
if (this.metaResultObject == null || this.resultLoader == null) {
if (this.mappedParameter == null) {
throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
+ "required parameter of mapped statement ["
+ this.mappedStatement + "] is not serializable.");
}
// 获取配置
final Configuration config = this.getConfiguration();
// 得到映射
final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
if (ms == null) {
throw new ExecutorException("Cannot lazy load property [" + this.property
+ "] of deserialized object [" + userObject.getClass()
+ "] because configuration does not contain statement ["
+ this.mappedStatement + "]");
}
this.metaResultObject = config.newMetaObject(userObject);
this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
metaResultObject.getSetterType(this.property), null, null);
}
/* We are using a new executor because we may be (and likely are) on a new thread
* and executors aren't thread safe. (Is this sufficient?)
*
* A better approach would be making executors thread safe. */
if (this.serializationCheck == null) {
final ResultLoader old = this.resultLoader;
this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
}
// 加载结果
this.metaResultObject.setValue(property, this.resultLoader.loadResult());
}
}
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。