美文网首页
原生Mybatis源码简析(上)

原生Mybatis源码简析(上)

作者: Hogantry | 来源:发表于2019-03-24 21:04 被阅读0次

    1、概述

    目前工作中,直接使用mybatis原生API开发的场景很少,基本都是结合spring一起使用。但对于分析mybatis的源码来说,使用API的方式能更容易的理清思路。先介绍下原生API的使用方式。

    public static void main(String[] args) {
        String resource = "configuration.xml";
        Reader reader;
        try {
            reader = Resources.getResourceAsReader(resource);
            SqlSessionFactory sqlSessionFactory = new  SqlSessionFactoryBuilder().build(reader);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            try {
                RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
                Role role = roleMapper.getRole(1);
                System.out.println(role.getLfPartyId() + "," + role.getPartyName());
            } finally {
                session.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    其中比较重要的类有SqlSessionFactoryBuilderSqlSessionFactorySqlSessionRoleMapper。下面先简要介绍下他们的使用范围与作用。

    • SqlSessionFactoryBuilder
      SqlSessionFactoryBuilder是典型的建造者模式的应用,一般他的生命周期就是存在于方法中,主要作用就是根据mybatis的全局配置文件(这里不对mybatis的配置文件做过多介绍,比较简单)构建出SqlSessionFactory对象,同时会建立好Configuration对象(该对象及其重要,贯穿整个mybatis的生命周期,后面会重点介绍)。
    • SqlSessionFactory
      SqlSessionFactory是典型的工厂模式,用于生成SqlSession对象,一般全局单例。SqlSessionFactory是个接口,有两个实现类DefaultSqlSessionFactorySqlSessionManager,系统默认使用DefaultSqlSessionFactory
    • SqlSession
      SqlSession可以简单理解为JDBC的connect对象,执行SQL方法(具体其实是Executor对象执行的sql方法,具体后面会分析),生命周期也是方法级别。SqlSession也是个接口,有两个实现类DefaultSqlSessionSqlSessionManager,系统默认使用DefaultSqlSession
    • RoleMapper
      RoleMapper接口是一整个Mapper接口的代表,它没有实现类,作用是发送SQL(帮助SqlSession找到SQL),生命周期与SqlSession一致。

    总结:从上面简短的介绍就可以看出mybatis框架设计的巧妙,充分体现了依赖倒置原则(面向接口编程)。

    2、从初始化开始说起

    从上面概述的原生API的使用代码片段可以看出,mybatis的初始化,是从构建SqlSessionFactory对象开始的,那我们就进入到SqlSessionFactoryBuilder内部,看看SqlSessionFactory对象以及重要的Configuration对象是如何被构建出来的。

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            return build(parser.parse());
        } catch (Exception e) {
            ....
        }
    }
    
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
    

    首先解析XML文件构建出Configuration对象,然后将该对象设置为DefaultSqlSessionFactory对象的属性后返回。这里可以很明显的看出,系统确实默认是使用的DefaultSqlSessionFactory对象。这里先分析下Configuration对象是如何构建的,然后再分析DefaultSqlSessionFactory对象的细节。

    2.0 Configuration概述

    在介绍Configuration对象是如何构建之前,我们先简单介绍下Configuration对象的作用。简单理解,Configuration对象其实就是mybatis的XML配置文件对应的Java类表示形式(类似的有tomact的XML配置文件和spring的XML配置文件,他们都是会解析成对应的Java类)。在SqlSessionFactoryBuilder类内部会解析XML文件,相应的节点都会被解析成Configuration的相关属性。主要有如下的属性解析过程:

    1. properties全局参数
    2. settings设置
    3. typeAliases别名
    4. typeHandler类型处理器
    5. ObjectFactory对象(很少使用)
    6. plugin插件
    7. environment环境(与spring结合后,由spring来管理环境的切换)
    8. DatabaseIdProvider数据库标识(很少使用)
    9. Mapper映射器

    其中我们分析的重点是第9步,后面会详细分析,前面的8步比较简单,在后面稍微提及。

    2.1 Configuration构建过程

    在上面的代码中,我们已经看到,Configuration对象是通过XMLConfigBuilder对象构建而成的,分析下该对象的初始化及其parse方法的具体实现,即可明白Configuration对象的构建过程。

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
    
    public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
    

    我们发现直接调用了new Configuration()方法,实例化了Configuration对象,然后在parse方法中通过parseConfiguration方法解析"/configuration"节点来实现对Configuration对象的属性填充。再看看该方法的实现

    private void parseConfiguration(XNode root) {
        try {
            propertiesElement(root.evalNode("properties"));
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            // Configuration类中有typeAliasRegistry属性,是对HashMap的简单封装,里面存储alias->Class的关联关系
            typeAliasesElement(root.evalNode("typeAliases"));
            // Configuration类中有InterceptorChain属性,是对ArrayList的简单封装,里面存储所有的插件实体对象,内部有个pluginAll方法,通过层层包装的方式来实现插件的调用
            pluginElement(root.evalNode("plugins"));
            objectFactoryElement(root.evalNode("objectFactory"));
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            environmentsElement(root.evalNode("environments"));
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // Configuration类中有typeHandlerRegistry属性,内部有多个HashMap的属性,里面存储JDBC type到Java type的类型转换规则
            typeHandlerElement(root.evalNode("typeHandlers"));
            // Configuration类中有mapperRegistry属性,是对HashMap的简单封装,里面存储Mapper接口到`MapperProxyFactory`的映射关系
            // 解析的同时也会向mappedStatements属性添加值,该属性也是对HashMap的简单封装,里面存储namespace+id 与MappedStatement对象的映射关系,后者就是mapper.xml文件中的select、insert、update、delete等SQL语句的节点。
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
    

    代码都做了相关注释,内部实现也都不是很复杂(需要说明的是在settingsElement(settings);方法中,会根据配置的defaultExecutorType属性设置Executor的类型,默认是Simple,configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));),
    其次一个较为复杂且最为重要的就是mapperElement(root.evalNode("mappers"));方法,即是我们上面说的到第9步。我们看看他的实现。

    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 {
                    // resource、url、class三个属性不可同时存在
                    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的方式来批量导入mapper的XML文件,所以我们重点分析他,其他的导入方式在底层原理是类似的。重点是这段代码configuration.addMappers(mapperPackage); ,它最终会调用到MapperRegistry类的addMapper方法中。

    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            // knownMappers.containsKey判断是否存在
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // 绑定mapper接口与代理类工厂的映射关系,这是mapper接口能直接使用的关键
                // 这里mapper接口的代理类还没实例化,只是绑定了工厂类,代理类的实例化在sqlSession.getMapper()方法中实例化
                knownMappers.put(type, new MapperProxyFactory<>(type));
                // 下面两行代码会具体实现上文提到了mappedStatements属性填充
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                  knownMappers.remove(type);
                }
            }
        }
    }
    

    再看MapperAnnotationBuilder类的parse方法

    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            // 解析mapper的XML文件 
            loadXmlResource();
            // 下面是解析mapper接口中的注解信息,这种方式对代码的侵入性比较大,现阶段很少使用,基本可以不用关注
            configuration.addLoadedResource(resource);
            assistant.setCurrentNamespace(type.getName());
            parseCache();
            parseCacheRef();
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                try {
                    if (!method.isBridge()) {
                        parseStatement(method);
                    }
                } catch (IncompleteElementException e) {
                    configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }
        parsePendingMethods();
    }
    

    loadXmlResource();方法会调用到XMLMapperBuilder类的parse()方法,

    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
            configurationElement(parser.evalNode("/mapper"));
            configuration.addLoadedResource(resource);
            bindMapperForNamespace();
        }
        // 解析resultMap
        parsePendingResultMaps();
        // 解析cache
        parsePendingCacheRefs();
        // 解析SQL语句
        parsePendingStatements();
    }
    

    在该方法里,我们重点关注parsePendingStatements(),在其内部会调用XMLStatementBuilder类的parseStatementNode()方法

    public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
        }
    
        Integer fetchSize = context.getIntAttribute("fetchSize");
        Integer timeout = context.getIntAttribute("timeout");
        String parameterMap = context.getStringAttribute("parameterMap");
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);
        String resultMap = context.getStringAttribute("resultMap");
        String resultType = context.getStringAttribute("resultType");
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);
    
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultSetType = context.getStringAttribute("resultSetType");
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        // Include Fragments before parsing
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // Parse selectKey after includes and remove them.
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        String resultSets = context.getStringAttribute("resultSets");
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        KeyGenerator keyGenerator;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        if (configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
    
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
    

    这个方法就是重头戏了,解析mapper的XML文件,将XML中的select、delete、update、insert等节点解析成MappedStatement对象,并缓存到Configure类中。其中不同的节点会匹配不同的statementType,SQL语句会封装到SqlSource对象中,默认是DynamicSqlSource实现类。
    至此整个configuration对象的初始化过程中的重要阶段就介绍完毕了。configuration对象在整个mybatis运行过程中的每个阶段都有它的影子,这是一个非常重的对象。对其中的一些比较重要的属性的理解,直接关系到对整个mybatis的运行机制的理解。所以这里再简单总结下configuration对象中比较重要的一些属性。

    // 记录Mapper接口与代理工厂类的绑定关系
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    // 责任链模式,记录所有的插件
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    // 记录所有的类型转换器
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    // 记录所有的类型简称
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    // 记录所有的namespace+id与MappedStatement的绑定关系,其中MappedStatement内部关联具体的SQL语句
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
          .conflictMessageProducer((savedValue, targetValue) ->
              ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
    

    2.2 SqlSessionFactory简介

    在之前的分析中,我们已经知道,SqlSessionFactoryBuilder对象,默认是实例化了DefaultSqlSessionFactory对象,我们先看下该对象的初始化过程

    private final Configuration configuration;
    
    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    

    很简单,DefaultSqlSessionFactory对象内部持有Configuration对象的引用,初始化过程就是完成该属性的复制。下面再分析下其获取SqlSession对象的过程

    @Override
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
    
    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();
        }
    }
    

    这里我们可以看到,mybatis默认确实是使用的DefaultSqlSession实现类。其中ExecutorType在上面已经介绍过,默认使用的Simple。SqlSession对象持有两个比较重要的对象的引用,ConfigurationExecutorConfiguration我们在前面已经介绍过了,Executor我们在后面再详细介绍。
    SqlSession就简单介绍到这里,具体的使用原理,在下面介绍Mapper接口的时候再详细说明。

    3、Mapper接口的调用过程

    我们知道在Java中,接口方法是没法调用的,在Mybatis中却很奇怪的可以调用,且能正常运行。这其中是什么原理呢?
    这其实就是代理在其中作祟了。我们从概述介绍的代码片段中的RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);代码行开始,介绍Mapper接口的调用过程。我们知道,Mybatis默认是使用DefaultSqlSession,所以我们就从该类的getMapper方法入手。

    // DefaultSqlSession.class
    public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
    }
    
    // Configuration.class
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }
    
    // MapperRegistry.class
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
    

    如上代码可以看出,DefaultSqlSession的getMapper方法,通过层层委托,最终到了MapperRegistry类的getMapper方法,该类中有个knownMappers属性,在上面介绍初始化过程中已经介绍过,里面存储着type接口与MapperProxyFactory类的映射关系。最终我们得知,getMapper方法返回了type的JDK动态代理对象。其中MapperProxy类实现了InvocationHandler接口,我们在后面调用接口方法时,其实最终都会调用该类的invoke方法(其实就是最简单的JDK动态代理技术,这里就不详细介绍了)。我们就看下该方法的具体实现吧

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // Object类中的方法,比如wait、notify等不做代理,直接调用
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) { // JDK8中接口有默认方法实现,也不做代理,直接调用
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        // 对method和MapperMethod关联值做了缓存
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
    private MapperMethod cachedMapperMethod(Method method) {
        return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
    }
    

    其中,MapperMethod对象有两个内部类,这里得介绍下:

    • SqlCommand 根据传入的configurationmapperInterfacemethod可以获取该method对象具体需要执行的sql语句。这里就用到了上面初始化过程中介绍的configuration类的一个重要属性mappedStatements(内部保存了namespace+id-->MappedStatement的映射关系)
    public static class SqlCommand {
    
        private final String name;
        private final SqlCommandType type;
    
        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
          final String methodName = method.getName();
          final Class<?> declaringClass = method.getDeclaringClass();
          MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
              configuration);
          if (ms == null) {
            if(method.getAnnotation(Flush.class) != null){
              name = null;
              type = SqlCommandType.FLUSH;
            } else {
              throw new BindingException("Invalid bound statement (not found): "
                  + mapperInterface.getName() + "." + methodName);
            }
          } else {
            name = ms.getId();
            type = ms.getSqlCommandType();
            if (type == SqlCommandType.UNKNOWN) {
              throw new BindingException("Unknown execution method for: " + name);
            }
          }
        }
    
        public String getName() {
          return name;
        }
    
        public SqlCommandType getType() {
          return type;
        }
    
        private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
            Class<?> declaringClass, Configuration configuration) {
          String statementId = mapperInterface.getName() + "." + methodName;
          if (configuration.hasStatement(statementId)) {
            return configuration.getMappedStatement(statementId);
          } else if (mapperInterface.equals(declaringClass)) {
            return null;
          }
          for (Class<?> superInterface : mapperInterface.getInterfaces()) {
            if (declaringClass.isAssignableFrom(superInterface)) {
              MappedStatement ms = resolveMappedStatement(superInterface, methodName,
                  declaringClass, configuration);
              if (ms != null) {
                return ms;
              }
            }
          }
          return null;
        }
      }
    
    • MethodSignature 根据传入的configurationmapperInterfacemethod可以获取该方法的返回值类型,参数解析器等信息,这里就不详细分析。
      如此我们知道,Mapper接口的方法调用,最终会执行MapperMethod对象的execute方法中。
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    // 1
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    // 2
                    Object param = method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional() &&
                        (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
             ...
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }
    

    方法有点长,我们这里就以select类型作为分析对象。首页根据方法的返回值类型决定具体的实现方式,其中标注的1和2分别表示返回数组和返回普通对象(这里不是很严谨),这两个分支是调用频率最高的,我们这里以1位代表,分析下执行的过程。其中executeForMany方法最终会调用到DefaultSqlSession类的selectList方法中,如下:

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    

    MappedStatement对象包含有具体需要执行的SQL语句,executor对象是在DefaultSqlSessionFactory调用openSession方法时构建成功的,默认是SimpleExecutor对象(目前大部分的项目不会开启Mybatis的二级缓存,否则这里默认会是CachingExecutor对象,其实就是对SimpleExecutor对象的装饰),并注入到DefaultSqlSession对象的属性中。(这里的executor对象已经是被插件代理过后的对象了)。

    4、总结

    对于初始化的过程,就不做总结了。比较复杂的过程就是configuration对象的构建比较复杂,因为这个对象太重了,但是在使用的过程中,我们基本是感知不到这个对象的存在的。这也是写一个框架并能让大多数人能接受这个框架的需要注意的点,将复杂留给自己,将简单给与用户。
    这里主要总结下Mapper接口具体是如何运行并能执行SQL拿到数据的过程。

    1. 调用SqlSession的getMapper方法
    2. 委托给MapperRegistry对象的getMapper方法,通过type拿到MapperProxyFactory对象,然后该对象会返回一个MapperProxy对象,作为Mapper接口的代理类。
    3. 调用Mapper接口的方法,会知道到MapperProxy对象的invoke方法中,该方法会委托给MapperMethod对象的execute方法
    4. MapperMethod对象的execute方法会根据MapperMethod对象的command属性的type值决定决定SQL的执行类型。这里已select为例,分析executeForMany方法。
    5. executeForMany方法会再委托给SqlSession的selectList方法,且传入command属性的name值即Mapper接口名+方法名。
    6. SqlSession的selectList方法会委托给executor类的query方法去执行具体的SQL

    SQL的具体执行又涉及到ExecutorStatementHandlerResultSetHandlerParameterHandler四个重要的类,下次与mybatis的插件机制一起分析。

    相关文章

      网友评论

          本文标题:原生Mybatis源码简析(上)

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