Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?

Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。当然了,不光是 Mybatis,几乎所有的包括 Hibernate,支持延迟加载的原理都是一样的。

题主你好,关于 MyBatis 创建代理对象用于延迟加载的实现,默认情况下并非使用 cglib 实现,而是使用 javassist,如果需要调整为 cglib 可以在配置中的 proxyFactory 进行设置,可以指定为 CGLIB 或 JAVASSIST,官网相关配置说明见 mybatis 配置

另外从源码中也可以验证官网的说明。源码中用于创建代理对象的代码如下:

public class DefaultResultSetHandler implements ResultSetHandler {
    /**
     * 创建返回结果
     *
     * @param rsw          结果集
     * @param resultMap    结果映射
     * @param lazyLoader   结果的属性加载器 Map
     * @param columnPrefix 列前缀
     * @return
     * @throws SQLException
     */
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        this.useConstructorMappings = false; // reset previous mapping result
        final List<Class<?>> constructorArgTypes = new ArrayList<>();
        final List<Object> constructorArgs = new ArrayList<>();
        Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
            for (ResultMapping propertyMapping : propertyMappings) {
                // issue gcode #109 && issue #149
                if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
                    // 创建懒加载的代理,用于懒加载属性
                    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
                    break;
                }
            }
        }
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
        return resultObject;
    }
}

这里的代理工厂 ProxyFactory 从配置 Configuration 中读取,Configuration 涉及 ProxyFactory 的代码如下。

public class Configuration {

    protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

    public ProxyFactory getProxyFactory() {
        return proxyFactory;
    }

    public void setProxyFactory(ProxyFactory proxyFactory) {
        if (proxyFactory == null) {
            proxyFactory = new JavassistProxyFactory();
        }
        this.proxyFactory = proxyFactory;
    }
}

从配置 Configuration 中可以看出,默认的 ProxyFactory 是 JavassistProxyFactory。

如果需要调整,对于纯 mybatis 项目,可以在 xml 配置文件中指定。

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties>
        <!--<property name="proxyFactory" value="JAVASSIST"/>-->
        <property name="proxyFactory" value="CGLIB"/>
    </properties>
</configuration>

mybatis 解析 proxyFactory 配置的代码如下。

public class XMLConfigBuilder extends BaseBuilder {

    private void settingsElement(Properties props) {
        ... 省略部分代码
        configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
        ... 省略部分代码
    }
}

对于技术的探索,最好还是持有怀疑态度,不要人云亦云。如果题主对自问自答这种形式感兴趣不妨写几篇博客。了解 mybatis 其他内容,还可以参阅我写的几篇关于 mybatis 的文章,专栏为 MyBatis 源码之旅