Mybatis随笔(五) - 获取执行对象

本文讲SQL执行过程中的第一步 获取执行对象


先上一张总流程图


  1. SqlSessionFactory构造的SqlSession对象都是 DefaultSqlSession 类型的
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

2.从 DefaultSqlSession 开始获取对象

AccountMapper accountMapper = SqlSession.getMapper(AccountMapper.class);

跟着一步一步往下走,发现是从 MapperRegistry 的 knowMappers 中获取的 MapperProxyFactory 对象,不管这个工厂类,先问一句什么时候加载进去的,对 knowMappers 使用进行溯源,发现只有一处调用了 put 方法


addMapper

而其调用方法 addMapper 只有两处,其对外的调用都是 Configuration.addMapper,沿着这条线往上追溯,发现都是在解析的时候调用的

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    Class mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

四种方式都会尝试加载一番
其中配置package-name和mapper-class的都是直接 调用configuration.addMapper
而另外两种配置 xml 方式的则需要一个条件才会加载

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class boundType = null;
        try {
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
        }
        if (boundType != null) {
            if (!configuration.hasMapper(boundType)) {
                // Spring may not know the real resource name so we set a flag
                // to prevent loading again this resource from the mapper interface
                // look at MapperAnnotationBuilder#loadXmlResource
                configuration.addLoadedResource("namespace:" + namespace);
                configuration.addMapper(boundType);
            }
        }
    }
}
  • 需要配置文件的 namespace 属性可以当做类路径加载成功,然后才会将这个接口加入 MapperRegistry 的 knowMappers 中。

总之,这个属性就是在解析配置文件构造 Configuration 时加载进去的。

  1. 构造 MapperProxyFactory
private final Map methodCache = new ConcurrentHashMap<>();

public MapperProxyFactory(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

在这个 MapperProxyFactory 中我们发现了一个很眼熟的东西

Proxy.newProxyInstance(ClassLoader, Class[], InvocationHandler)

这不是一个典型的JDK动态代理吗? 那么 MapperProxy 显然也实现了 InvocationHandler 接口

public class MapperProxy implements InvocationHandler, Serializable {}

所以 MapperProxy 是 Mybatis 通过JDK动态代理生成的一个代理对象,用于SQL的执行。

  1. 区别

上一篇我们讲过了代理模式,再来回顾一下这张图

代理

我们发现 MapperProxy 的代理模式跟上面有一个很大的区别

请问你见过 Mybatis 接口类的 *Mapper.java 的实现类吗?
是根本就不存在这种实现类。

所以 MapperProxy 重写的 invoke 方法就需要多做点事了。

这也告诉我们,JDK动态代理模式虽然一定要接口但不一定要有实现类。

你可能感兴趣的:(Mybatis随笔(五) - 获取执行对象)