原生Mybatis源碼簡(jiǎn)析(上)

1、概述

目前工作中,直接使用mybatis原生API開發(fā)的場(chǎng)景很少聂受,基本都是結(jié)合spring一起使用饥悴。但對(duì)于分析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();
    }
}

其中比較重要的類有SqlSessionFactoryBuilder瓣铣、SqlSessionFactorySqlSession贷揽、RoleMapper棠笑。下面先簡(jiǎn)要介紹下他們的使用范圍與作用。

  • SqlSessionFactoryBuilder
    SqlSessionFactoryBuilder是典型的建造者模式的應(yīng)用禽绪,一般他的生命周期就是存在于方法中腐晾,主要作用就是根據(jù)mybatis的全局配置文件(這里不對(duì)mybatis的配置文件做過多介紹,比較簡(jiǎn)單)構(gòu)建出SqlSessionFactory對(duì)象丐一,同時(shí)會(huì)建立好Configuration對(duì)象(該對(duì)象及其重要藻糖,貫穿整個(gè)mybatis的生命周期,后面會(huì)重點(diǎn)介紹)库车。
  • SqlSessionFactory
    SqlSessionFactory是典型的工廠模式巨柒,用于生成SqlSession對(duì)象,一般全局單例柠衍。SqlSessionFactory是個(gè)接口洋满,有兩個(gè)實(shí)現(xiàn)類DefaultSqlSessionFactorySqlSessionManager珍坊,系統(tǒng)默認(rèn)使用DefaultSqlSessionFactory牺勾。
  • SqlSession
    SqlSession可以簡(jiǎn)單理解為JDBC的connect對(duì)象泌辫,執(zhí)行SQL方法(具體其實(shí)是Executor對(duì)象執(zhí)行的sql方法纫普,具體后面會(huì)分析),生命周期也是方法級(jí)別悼嫉。SqlSession也是個(gè)接口履怯,有兩個(gè)實(shí)現(xiàn)類DefaultSqlSession回还、SqlSessionManager,系統(tǒng)默認(rèn)使用DefaultSqlSession叹洲。
  • RoleMapper
    RoleMapper接口是一整個(gè)Mapper接口的代表柠硕,它沒有實(shí)現(xiàn)類,作用是發(fā)送SQL(幫助SqlSession找到SQL)运提,生命周期與SqlSession一致蝗柔。

總結(jié):從上面簡(jiǎn)短的介紹就可以看出mybatis框架設(shè)計(jì)的巧妙闻葵,充分體現(xiàn)了依賴倒置原則(面向接口編程)。

2癣丧、從初始化開始說起

從上面概述的原生API的使用代碼片段可以看出笙隙,mybatis的初始化,是從構(gòu)建SqlSessionFactory對(duì)象開始的坎缭,那我們就進(jìn)入到SqlSessionFactoryBuilder內(nèi)部,看看SqlSessionFactory對(duì)象以及重要的Configuration對(duì)象是如何被構(gòu)建出來的签钩。

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文件構(gòu)建出Configuration對(duì)象掏呼,然后將該對(duì)象設(shè)置為DefaultSqlSessionFactory對(duì)象的屬性后返回。這里可以很明顯的看出铅檩,系統(tǒng)確實(shí)默認(rèn)是使用的DefaultSqlSessionFactory對(duì)象憎夷。這里先分析下Configuration對(duì)象是如何構(gòu)建的,然后再分析DefaultSqlSessionFactory對(duì)象的細(xì)節(jié)昧旨。

2.0 Configuration概述

在介紹Configuration對(duì)象是如何構(gòu)建之前拾给,我們先簡(jiǎn)單介紹下Configuration對(duì)象的作用。簡(jiǎn)單理解兔沃,Configuration對(duì)象其實(shí)就是mybatis的XML配置文件對(duì)應(yīng)的Java類表示形式(類似的有tomact的XML配置文件和spring的XML配置文件蒋得,他們都是會(huì)解析成對(duì)應(yīng)的Java類)。在SqlSessionFactoryBuilder類內(nèi)部會(huì)解析XML文件乒疏,相應(yīng)的節(jié)點(diǎn)都會(huì)被解析成Configuration的相關(guān)屬性额衙。主要有如下的屬性解析過程:

  1. properties全局參數(shù)
  2. settings設(shè)置
  3. typeAliases別名
  4. typeHandler類型處理器
  5. ObjectFactory對(duì)象(很少使用)
  6. plugin插件
  7. environment環(huán)境(與spring結(jié)合后,由spring來管理環(huán)境的切換)
  8. DatabaseIdProvider數(shù)據(jù)庫(kù)標(biāo)識(shí)(很少使用)
  9. Mapper映射器

其中我們分析的重點(diǎn)是第9步怕吴,后面會(huì)詳細(xì)分析窍侧,前面的8步比較簡(jiǎn)單,在后面稍微提及转绷。

2.1 Configuration構(gòu)建過程

在上面的代碼中伟件,我們已經(jīng)看到,Configuration對(duì)象是通過XMLConfigBuilder對(duì)象構(gòu)建而成的议经,分析下該對(duì)象的初始化及其parse方法的具體實(shí)現(xiàn)斧账,即可明白Configuration對(duì)象的構(gòu)建過程。

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;
}

我們發(fā)現(xiàn)直接調(diào)用了new Configuration()方法煞肾,實(shí)例化了Configuration對(duì)象其骄,然后在parse方法中通過parseConfiguration方法解析"/configuration"節(jié)點(diǎn)來實(shí)現(xiàn)對(duì)Configuration對(duì)象的屬性填充。再看看該方法的實(shí)現(xiàn)

private void parseConfiguration(XNode root) {
    try {
        propertiesElement(root.evalNode("properties"));
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        loadCustomVfs(settings);
        loadCustomLogImpl(settings);
        // Configuration類中有typeAliasRegistry屬性扯旷,是對(duì)HashMap的簡(jiǎn)單封裝拯爽,里面存儲(chǔ)alias->Class的關(guān)聯(lián)關(guān)系
        typeAliasesElement(root.evalNode("typeAliases"));
        // Configuration類中有InterceptorChain屬性,是對(duì)ArrayList的簡(jiǎn)單封裝钧忽,里面存儲(chǔ)所有的插件實(shí)體對(duì)象毯炮,內(nèi)部有個(gè)pluginAll方法逼肯,通過層層包裝的方式來實(shí)現(xiàn)插件的調(diào)用
        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屬性,內(nèi)部有多個(gè)HashMap的屬性桃煎,里面存儲(chǔ)JDBC type到Java type的類型轉(zhuǎn)換規(guī)則
        typeHandlerElement(root.evalNode("typeHandlers"));
        // Configuration類中有mapperRegistry屬性篮幢,是對(duì)HashMap的簡(jiǎn)單封裝,里面存儲(chǔ)Mapper接口到`MapperProxyFactory`的映射關(guān)系
        // 解析的同時(shí)也會(huì)向mappedStatements屬性添加值为迈,該屬性也是對(duì)HashMap的簡(jiǎn)單封裝三椿,里面存儲(chǔ)namespace+id 與MappedStatement對(duì)象的映射關(guān)系,后者就是mapper.xml文件中的select葫辐、insert搜锰、update、delete等SQL語句的節(jié)點(diǎn)耿战。
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

代碼都做了相關(guān)注釋蛋叼,內(nèi)部實(shí)現(xiàn)也都不是很復(fù)雜(需要說明的是在settingsElement(settings);方法中,會(huì)根據(jù)配置的defaultExecutorType屬性設(shè)置Executor的類型剂陡,默認(rèn)是Simple狈涮,configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));),
其次一個(gè)較為復(fù)雜且最為重要的就是mapperElement(root.evalNode("mappers"));方法鸭栖,即是我們上面說的到第9步歌馍。我們看看他的實(shí)現(xiàn)。

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三個(gè)屬性不可同時(shí)存在
                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的方式來批量導(dǎo)入mapper的XML文件,所以我們重點(diǎn)分析他捏题,其他的導(dǎo)入方式在底層原理是類似的玻褪。重點(diǎn)是這段代碼configuration.addMappers(mapperPackage); ,它最終會(huì)調(diào)用到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接口與代理類工廠的映射關(guān)系带射,這是mapper接口能直接使用的關(guān)鍵
            // 這里mapper接口的代理類還沒實(shí)例化,只是綁定了工廠類循狰,代理類的實(shí)例化在sqlSession.getMapper()方法中實(shí)例化
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // 下面兩行代碼會(huì)具體實(shí)現(xiàn)上文提到了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接口中的注解信息窟社,這種方式對(duì)代碼的侵入性比較大,現(xiàn)階段很少使用绪钥,基本可以不用關(guān)注
        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();方法會(huì)調(diào)用到XMLMapperBuilder類的parse()方法灿里,

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

在該方法里,我們重點(diǎn)關(guān)注parsePendingStatements()程腹,在其內(nèi)部會(huì)調(diào)用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);
}

這個(gè)方法就是重頭戲了匣吊,解析mapper的XML文件,將XML中的select、delete色鸳、update社痛、insert等節(jié)點(diǎn)解析成MappedStatement對(duì)象,并緩存到Configure類中命雀。其中不同的節(jié)點(diǎn)會(huì)匹配不同的statementType蒜哀,SQL語句會(huì)封裝到SqlSource對(duì)象中,默認(rèn)是DynamicSqlSource實(shí)現(xiàn)類吏砂。
至此整個(gè)configuration對(duì)象的初始化過程中的重要階段就介紹完畢了撵儿。configuration對(duì)象在整個(gè)mybatis運(yùn)行過程中的每個(gè)階段都有它的影子,這是一個(gè)非常重的對(duì)象狐血。對(duì)其中的一些比較重要的屬性的理解淀歇,直接關(guān)系到對(duì)整個(gè)mybatis的運(yùn)行機(jī)制的理解。所以這里再簡(jiǎn)單總結(jié)下configuration對(duì)象中比較重要的一些屬性氛雪。

// 記錄Mapper接口與代理工廠類的綁定關(guān)系
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 責(zé)任鏈模式,記錄所有的插件
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 記錄所有的類型轉(zhuǎn)換器
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 記錄所有的類型簡(jiǎn)稱
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
// 記錄所有的namespace+id與MappedStatement的綁定關(guān)系耸成,其中MappedStatement內(nèi)部關(guān)聯(lián)具體的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簡(jiǎn)介

在之前的分析中报亩,我們已經(jīng)知道,SqlSessionFactoryBuilder對(duì)象井氢,默認(rèn)是實(shí)例化了DefaultSqlSessionFactory對(duì)象弦追,我們先看下該對(duì)象的初始化過程

private final Configuration configuration;

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

很簡(jiǎn)單,DefaultSqlSessionFactory對(duì)象內(nèi)部持有Configuration對(duì)象的引用花竞,初始化過程就是完成該屬性的復(fù)制劲件。下面再分析下其獲取SqlSession對(duì)象的過程

@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默認(rèn)確實(shí)是使用的DefaultSqlSession實(shí)現(xiàn)類约急。其中ExecutorType在上面已經(jīng)介紹過零远,默認(rèn)使用的Simple。SqlSession對(duì)象持有兩個(gè)比較重要的對(duì)象的引用厌蔽,ConfigurationExecutor牵辣,Configuration我們?cè)谇懊嬉呀?jīng)介紹過了,Executor我們?cè)诤竺嬖僭敿?xì)介紹奴饮。
SqlSession就簡(jiǎn)單介紹到這里纬向,具體的使用原理,在下面介紹Mapper接口的時(shí)候再詳細(xì)說明戴卜。

3逾条、Mapper接口的調(diào)用過程

我們知道在Java中,接口方法是沒法調(diào)用的投剥,在Mybatis中卻很奇怪的可以調(diào)用师脂,且能正常運(yùn)行。這其中是什么原理呢?
這其實(shí)就是代理在其中作祟了危彩。我們從概述介紹的代碼片段中的RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);代碼行開始攒磨,介紹Mapper接口的調(diào)用過程。我們知道汤徽,Mybatis默認(rèn)是使用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方法完疫,該類中有個(gè)knownMappers屬性泰鸡,在上面介紹初始化過程中已經(jīng)介紹過,里面存儲(chǔ)著type接口與MapperProxyFactory類的映射關(guān)系壳鹤。最終我們得知盛龄,getMapper方法返回了type的JDK動(dòng)態(tài)代理對(duì)象。其中MapperProxy類實(shí)現(xiàn)了InvocationHandler接口芳誓,我們?cè)诤竺嬲{(diào)用接口方法時(shí)余舶,其實(shí)最終都會(huì)調(diào)用該類的invoke方法(其實(shí)就是最簡(jiǎn)單的JDK動(dòng)態(tài)代理技術(shù),這里就不詳細(xì)介紹了)锹淌。我們就看下該方法的具體實(shí)現(xiàn)吧

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // Object類中的方法匿值,比如wait、notify等不做代理赂摆,直接調(diào)用
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) { // JDK8中接口有默認(rèn)方法實(shí)現(xiàn)挟憔,也不做代理,直接調(diào)用
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 對(duì)method和MapperMethod關(guān)聯(lián)值做了緩存
    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對(duì)象有兩個(gè)內(nèi)部類绊谭,這里得介紹下:

  • SqlCommand 根據(jù)傳入的configurationmapperInterface汪拥、method可以獲取該method對(duì)象具體需要執(zhí)行的sql語句龙誊。這里就用到了上面初始化過程中介紹的configuration類的一個(gè)重要屬性mappedStatements(內(nèi)部保存了namespace+id-->MappedStatement的映射關(guān)系)
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 根據(jù)傳入的configurationmapperInterface喷楣、method可以獲取該方法的返回值類型趟大,參數(shù)解析器等信息,這里就不詳細(xì)分析铣焊。
    如此我們知道逊朽,Mapper接口的方法調(diào)用,最終會(huì)執(zhí)行MapperMethod對(duì)象的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;
}

方法有點(diǎn)長(zhǎng)叽讳,我們這里就以select類型作為分析對(duì)象追他。首頁根據(jù)方法的返回值類型決定具體的實(shí)現(xiàn)方式,其中標(biāo)注的1和2分別表示返回?cái)?shù)組和返回普通對(duì)象(這里不是很嚴(yán)謹(jǐn))岛蚤,這兩個(gè)分支是調(diào)用頻率最高的邑狸,我們這里以1位代表,分析下執(zhí)行的過程涤妒。其中executeForMany方法最終會(huì)調(diào)用到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對(duì)象包含有具體需要執(zhí)行的SQL語句,executor對(duì)象是在DefaultSqlSessionFactory調(diào)用openSession方法時(shí)構(gòu)建成功的她紫,默認(rèn)是SimpleExecutor對(duì)象(目前大部分的項(xiàng)目不會(huì)開啟Mybatis的二級(jí)緩存硅堆,否則這里默認(rèn)會(huì)是CachingExecutor對(duì)象,其實(shí)就是對(duì)SimpleExecutor對(duì)象的裝飾)贿讹,并注入到DefaultSqlSession對(duì)象的屬性中渐逃。(這里的executor對(duì)象已經(jīng)是被插件代理過后的對(duì)象了)。

4民褂、總結(jié)

對(duì)于初始化的過程茄菊,就不做總結(jié)了。比較復(fù)雜的過程就是configuration對(duì)象的構(gòu)建比較復(fù)雜赊堪,因?yàn)檫@個(gè)對(duì)象太重了面殖,但是在使用的過程中,我們基本是感知不到這個(gè)對(duì)象的存在的雹食。這也是寫一個(gè)框架并能讓大多數(shù)人能接受這個(gè)框架的需要注意的點(diǎn)畜普,將復(fù)雜留給自己期丰,將簡(jiǎn)單給與用戶群叶。
這里主要總結(jié)下Mapper接口具體是如何運(yùn)行并能執(zhí)行SQL拿到數(shù)據(jù)的過程。

  1. 調(diào)用SqlSession的getMapper方法
  2. 委托給MapperRegistry對(duì)象的getMapper方法钝荡,通過type拿到MapperProxyFactory對(duì)象街立,然后該對(duì)象會(huì)返回一個(gè)MapperProxy對(duì)象,作為Mapper接口的代理類埠通。
  3. 調(diào)用Mapper接口的方法赎离,會(huì)知道到MapperProxy對(duì)象的invoke方法中,該方法會(huì)委托給MapperMethod對(duì)象的execute方法
  4. MapperMethod對(duì)象的execute方法會(huì)根據(jù)MapperMethod對(duì)象的command屬性的type值決定決定SQL的執(zhí)行類型端辱。這里已select為例梁剔,分析executeForMany方法。
  5. executeForMany方法會(huì)再委托給SqlSession的selectList方法舞蔽,且傳入command屬性的name值即Mapper接口名+方法名荣病。
  6. SqlSession的selectList方法會(huì)委托給executor類的query方法去執(zhí)行具體的SQL

SQL的具體執(zhí)行又涉及到ExecutorStatementHandler渗柿、ResultSetHandler个盆、ParameterHandler四個(gè)重要的類,下次與mybatis的插件機(jī)制一起分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颊亮,一起剝皮案震驚了整個(gè)濱河市柴梆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌终惑,老刑警劉巖绍在,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異狠鸳,居然都是意外死亡揣苏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門件舵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卸察,“玉大人,你說我怎么就攤上這事铅祸】又剩” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵临梗,是天一觀的道長(zhǎng)涡扼。 經(jīng)常有香客問我,道長(zhǎng)盟庞,這世上最難降的妖魔是什么吃沪? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮什猖,結(jié)果婚禮上票彪,老公的妹妹穿的比我還像新娘。我一直安慰自己不狮,他們只是感情好降铸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著摇零,像睡著了一般推掸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驻仅,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天谅畅,我揣著相機(jī)與錄音,去河邊找鬼噪服。 笑死毡泻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芯咧。 我是一名探鬼主播牙捉,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼竹揍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了邪铲?” 一聲冷哼從身側(cè)響起芬位,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎带到,沒想到半個(gè)月后昧碉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揽惹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年被饿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搪搏。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狭握,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疯溺,到底是詐尸還是另有隱情论颅,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布囱嫩,位于F島的核電站恃疯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏墨闲。R本人自食惡果不足惜今妄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸳碧。 院中可真熱鬧盾鳞,春花似錦、人聲如沸杆兵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琐脏。三九已至,卻和暖如春缸兔,著一層夾襖步出監(jiān)牢的瞬間日裙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工惰蜜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昂拂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓抛猖,卻偏偏與公主長(zhǎng)得像格侯,于是被迫代替她去往敵國(guó)和親鼻听。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 我的以前是個(gè)男人联四,緊抓著棍子撑碴,象征著力量的棍子,試圖控制別人朝墩,掌控一切醉拓,用強(qiáng)大力量武裝自己,保護(hù)自己收苏,其實(shí)什么都控...
    兮兮AX閱讀 84評(píng)論 0 1
  • 1亿卤、移植wvdial ,移植pppd /usr/sbin/pppd 2鹿霸、wvdial【1.6.1版本】 排吴,pppd...
    咖喱雞蛋閱讀 1,342評(píng)論 0 0
  • 文/穩(wěn)心山人圖/穩(wěn)心山人 臨近教師節(jié),高中班群里懦鼠,博士在讀的前數(shù)學(xué)科代表傍念,放出了不少珍貴的“一手史料”,有智力大賽...
    穩(wěn)心山人閱讀 263評(píng)論 2 3
  • 第一次聽說滾包貨葛闷,網(wǎng)上搜了下憋槐,真可怕。衣服也不能只圖好看淑趾。 外貿(mào)入港時(shí)被水壓機(jī)壓縮成的1立方米大小的貨包阳仔,每包有2...
    桑杰卓瑪閱讀 1,177評(píng)論 0 0