美文网首页Mybatis随笔
Mybatis随笔(五) - 获取执行对象

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

作者: sunyelw | 来源:发表于2020-03-22 23:25 被阅读0次

    本文讲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<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
    
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
    
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
    

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

    Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)
    

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

    public class MapperProxy<T> implements InvocationHandler, Serializable {}
    

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

    1. 区别

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

    代理

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

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

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

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

    相关文章

      网友评论

        本文标题:Mybatis随笔(五) - 获取执行对象

        本文链接:https://www.haomeiwen.com/subject/kjrbyhtx.html