IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 较真儿学源码系列-MyBatis核心流程源码分析 -> 正文阅读

[Java知识库]较真儿学源码系列-MyBatis核心流程源码分析

? ? ? ? Mybatis版本:3.5.10-SNAPSHOT。

????????MyBatis-Spring版本:2.1.0-SNAPSHOT。


1 简介

????????MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置,并将原始类型、接口和Java POJO映射到数据库的记录中。

1.1 MyBatis与Hibernate? ? ? ??

????????Hibernate几乎不需要写SQL,甚至不用懂SQL的语法。只需要定义好POJO到数据库表的映射关系,即可通过Hibernate提供的方法来完成持久层操作;而MyBatis是需要自己写SQL的,工作量稍大,但是可以对SQL进行调优,更灵活。通过Mybatis的代码生成器,可以自动帮我们生成Java实体类、mapper接口和XML文件,一定程度上可以简化开发。

????????在国内的环境中,MyBatis相比于Hibernate更流行些。而在国外,Hibernate用得多一些。在我看来,两者都有各自的适用场景。因为Hibernate我用得不多(只在当年毕设时有用过),所以就不妄加评论了。

1.2 MyBatis与MyBatis-Plus

????????MyBatis-Plus是在MyBatis的基础上进行二次开发的框架,除了保留MyBatis的原有功能之外,还提供了类似Hibernate那样直接通过Java代码即可操作数据库的能力。但是MyBatis-Plus使用的是自己定义的语法来操作数据库,所以需要增加学习成本。它更适合那些规模小、SQL简单(最好是单表的增删改查)、需要快速进行上线的项目使用(以下一段内容讲述了我对于MyBatis-Plus这个框架的一些看法。如果不感兴趣,请直接跳到第2小节进行查看)

? ? ? ? 我在之前的公司中有用过MyBatis-Plus,但从我个人的角度而言,我并不是特别能看好MyBatis-Plus能达到MyBatis的地位(正如其官网所言:愿景是成为MyBatis最好的搭档),甚至说能取代MyBatis(取名为“Plus”的野心),它只会满足一部分人的需求。MyBatis-Plus留给我的印象是:如果是复杂的查询语句的话,它的语法将会写得很复杂,不利于排查问题和调优。所以这也就限制住了MyBatis-Plus只适合那些简单业务的场景,而MyBatis的代码生成器又从一定程度上蚕食了这个场景下的市场。

????????也许有人会说了,MyBatis-Plus和MyBatis是可以兼容的,简单的业务场景可以用MyBatis-Plus,复杂的业务场景还是用MyBatis。这里我想说的是:别看官网说的只做增强不做改变,在你没看过MyBatis-Plus的源码之前,你真的放心敢去用吗?是真的不做改变还是只是表面使用上的不做改变?这些都需要查看源码后才能得知,增加了使用上的成本(如果你不在乎框架成不成熟,出不出错,只要用得爽就行,那当我没说)。还有一点,如果哪天MyBatis的源码发生了大的架构上的变化,MyBatis-Plus何时才能进行跟进?像这样依托于其他框架之上发展出来的二开框架,注定会受到原有框架的限制。

? ? ? ? 在这里我不想过多地去评价MyBatis-Plus的语法封装,也许它现在并不完美,但是毕竟使用量还是很多的,所以要相信迭代的力量,这些都可以进行完善。但我在这里想要说的是:我不认同MyBatis-Plus这个框架存在的意义,确切的说是依托于MyBatis从而存在的意义。

? ? ? ? 现在国内的开发环境不知道从什么时候开始传出一种风气,不管开发出什么框架,都要往大而全的方向写,或者说是自己搞个生态,重复造轮子(可能是国内竞争压力大,导致越来越卷)。而MyBatis那种大道至简的源码设计风格,是非常能切中我的点的。它其实只干了一件事,就是专注于Java接口与SQL之间的映射,其他的功能也都是为了这个核心功能去服务的。所以MyBatis非常适合那些想学习源码的初学者,作为他们的第一个进行学习的源码框架。正如MyBatis源码中的pom文件中的描述所言:简单是MyBatis最大的优势。

<description>
    The MyBatis SQL mapper framework makes it easier to use a relational database with object-oriented
    applications. MyBatis couples objects with stored procedures or SQL statements using a XML descriptor or
    annotations. Simplicity is the biggest advantage of the MyBatis data mapper over object relational mapping
    tools.
</description>

????????在了解了MyBatis的设计理念之后再来看MyBatis-Plus,是否感觉MyBatis-Plus的设计有点儿违背初心、背道而驰了呢(也别老怪别人拿你和MyBatis比较,谁叫你取了这样一个名字呢)?对于源码层面上来说,改写或者增强了MyBatis原有的一些功能,在我看来并不是在做简化。MyBatis-Plus所谓的“简化”只是针对于使用者的角度来说的,而且这种简化也是有条件限制的(只适合简单业务)。同时MyBatis-Plus又内置了许多其他的功能,像自动分页、乐观锁、逻辑删除等等。看得出来,MyBatis-Plus的作者是想要去做一个大而全的框架的(这点本身没毛病)。诚然我知道MyBatis也有自定义插件的功能,但是如果将这些功能内置,并写进官网首页中作为框架亮点的话,就完全是另一种导向了。所以我认为MyBatis-Plus的设计理念和MyBatis的完全不同,尤其是在看过MyBatis的源码之后,更能体会到这一点。这个时候再叫“MyBatis-Plus”就有点不合适了(如果“Plus”代表的是更加臃肿,而不是更强,那我倒是能理解了)。

????????想用MyBatis的人都是为了能自己写SQL、自己掌握主动权的;不想用MyBatis的、图省心的人可以去用Spring Data JPA或者其他,两者之间都有着很清晰的用户群区分。而MyBatis-Plus相当于架在两者中间,明明是个全自动ORM框架(或者说是全/半自动?手自一体?),却是在半自动ORM框架上改写出来的,给人感觉不伦不类的。在我看来,如果MyBatis-Plus能不依托于MyBatis而独立开发并发展,它的前景应该会更好一些(但是这样也就蹭不到MyBatis的热度了,所以说成也MyBatis,败也MyBatis-_-)。

????????扯得有点远了,下面开始MyBatis源码的学习。


2 配置

? ? ? ? 以下的代码是以XML的方式为例来进行配置的,首先是构建SqlSessionFactory:

String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
//将配置文件中的配置内容加载进Configuration对象中
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

????????下面就来分析一下它的build方法的实现:

/**
 * SqlSessionFactoryBuilder:
 */
public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
}

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        //解析XML
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        //走到这里,已经将配置文件中的内容解析成了Configuration对象
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            reader.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

/**
 * XMLConfigBuilder:
 * 第11行代码处:
 */
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    //使用DOM和SAX的方式来构建XPathParser对象,将XML文件的内容进行读取并放进Configuration中
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //Configuration的构造器中会默认注册一些别名映射,例如JDBC、POOLED等等,这样就可以直接使用别名
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

/**
 * 第13行代码处:
 */
public Configuration parse() {
    //已经解析过了就抛出异常
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    //设置解析标志位
    parsed = true;
    //解析mybatis-config.xml的<configuration>节点下的内容
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

/**
 * 第56行代码处:
 */
private void parseConfiguration(XNode root) {
    try {
        // issue #117 read properties first
        //解析properties节点内容并设置进Configuration对象中
        propertiesElement(root.evalNode("properties"));
        //解析settings节点
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        //加载VFS虚拟文件系统
        loadCustomVfs(settings);
        //指定日志实现
        loadCustomLogImpl(settings);
        /*
        解析typeAliases别名节点
        TypeAliasRegistry是Configuration对象中的属性,其构造器中会默认注册一些基本类型及对象类型的别名映射,这样我们就可以直接使用这些基本类型的别名了
         */
        typeAliasesElement(root.evalNode("typeAliases"));
        /*
        添加插件
        插件最终会存入到Configuration的InterceptorChain中,通过责任链的方式调用
         */
        pluginElement(root.evalNode("plugins"));
        //加载对象工厂,用于反射实例化对象(不常用)
        objectFactoryElement(root.evalNode("objectFactory"));
        //加载对象包装工厂(不常用)
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        //加载反射工厂,用于属性和getter/setter获取(不常用)
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        //设置一些settings和默认值到Configuration对象中
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        //设置环境
        environmentsElement(root.evalNode("environments"));
        //设置数据库厂商id
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        /*
        解析typeHandler类型处理器(javaType和jdbcType之间的转换)
        跟解析别名一样,TypeHandlerRegistry也会加载一些默认的转换,也可以自定义
         */
        typeHandlerElement(root.evalNode("typeHandlers"));
        //解析mapper接口
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

/**
 * 第94行代码处:
 */
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
          environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                //获取transactionManager
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                //获取dataSource
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);
                //设置进Configuration对象中的Environment中
                configuration.setEnvironment(environmentBuilder.build());
                break;
            }
        }
    }
}

/**
 * 第103行代码处:
 */
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        //获取mappers节点下的内容
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                /*
                <1>通过package批量的方式
                <package name="com.demo.mapper"/>
                 */
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                /*
                <2>通过resource、classpath下读取的方式
                <mapper resource="org/apache/ibatis/demo/xml/UserMapper.xml"/>
                 */
                String resource = child.getStringAttribute("resource");
                /*
                <3>通过url、本地或网络资源的方式
                <mapper url="D:/mapper/UserMapper.xml"/>
                 */
                String url = child.getStringAttribute("url");
                /*
                <4>通过class、mapper接口的方式
                <mapper class="com.demo.mapper.UserMapper"/>
                 */
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    //resource解析方式
                    ErrorContext.instance().resource(resource);
                    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                        //真正解析mapper的地方
                        mapperParser.parse();
                    }
                } else if (resource == null && url != null && mapperClass == null) {
                    //url解析方式
                    ErrorContext.instance().resource(url);
                    try (InputStream inputStream = Resources.getUrlAsStream(url)) {
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                        //真正解析mapper的地方
                        mapperParser.parse();
                    }
                } else if (resource == null && url == null && mapperClass != null) {
                    //class解析方式
                    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.");
                }
            }
        }
    }
}

/**
 * XMLMapperBuilder:
 * 第170行和第178行代码处:
 */
public void parse() {
    //如果没有解析过的话
    if (!configuration.isResourceLoaded(resource)) {
        //解析mapper
        configurationElement(parser.evalNode("/mapper"));
        //已加载的内容放进Configuration的loadedResources中
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

/**
 * 第200行代码处:
 */
private void configurationElement(XNode context) {
    try {
        //解析namespace属性
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        //解析缓存引用节点
        cacheRefElement(context.evalNode("cache-ref"));
        //解析缓存节点(二级缓存)
        cacheElement(context.evalNode("cache"));
        //解析parameterMap节点(不推荐使用)
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        //解析resultMap节点
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        //解析sql节点
        sqlElement(context.evalNodes("/mapper/sql"));
        //解析增删改查节点
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}

/**
 * XMLMapperBuilder:
 * 第233行代码处:
 */
private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}

/**
 * 第245行和第247代码处:
 */
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

/**
 * XMLStatementBuilder:
 * 第257行代码处:
 */
public void parseStatementNode() {
    //拿到增删改查节点的id属性
    String id = context.getStringAttribute("id");
    //拿到增删改查节点的数据库厂商id
    String databaseId = context.getStringAttribute("databaseId");

    //如果和当前数据源的数据库厂商id不匹配,则直接返回
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }

    //获取节点名称(select|insert|update|delete)
    String nodeName = context.getNode().getNodeName();
    //通过节点名称获取相应的枚举
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    //判断是否是查询语句
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //获取flushCache属性(为空的话查sql不会刷新缓存,增删改sql会)
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //获取useCache属性(为空的话查sql会使用到缓存,增删改sql不会)
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    //是否需要分组
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    //解析include片段
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    //解析parameterType
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    String lang = context.getStringAttribute("lang");
    //获取自定义SQL脚本语言驱动
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    //解析selectKey节点(自增id)
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    //设置自增列
    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;
    }

    /*
    通过XMLLanguageDriver来解析SQL脚本,具体分为:
    DynamicSqlSource:需要通过参数才能确定的SQL语句;
    RawSqlSource:不需要通过参数就能确定的SQL语句。
     */
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    //STATEMENT, PREPARED, CALLABLE,默认为PREPARED
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    //解析parameterMap属性,不写的话会通过TypeHandler类型处理器来映射
    String parameterMap = context.getStringAttribute("parameterMap");
    //解析resultType
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    //解析resultMap
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
        resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    //解析keyProperty,仅适用于增改语句
    String keyProperty = context.getStringAttribute("keyProperty");
    //解析keyColumn,仅适用于增改语句
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    //将解析完的select|insert|update|delete语句添加进MappedStatement对象中,并最终添加进Configuration对象中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

/**
 * XMLMapperBuilder:
 * 第203行代码处:
 */
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 && !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);
            //这里重点看下addMapper方法
            configuration.addMapper(boundType);
        }
    }
}

/**
 * Configuration:
 * 第376行代码处:
 */
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
}

/**
 * MapperRegistry:
 */
public <T> void addMapper(Class<T> type) {
    //判断mapper是否是接口
    if (type.isInterface()) {
        //如果已经添加进缓存了,就抛出异常
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            /*
            创建一个MapperProxyFactory,放进knownMappers缓存中
            等到后面调用sqlSession.getMapper方法时,会调用MapperProxyFactory的newInstance方法来JDK动态代理出来一个实现类(这里也就是使用到了代理模式)
             */
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            //进行解析
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

/**
 * MapperAnnotationBuilder:
 * 第411行代码处:
 */
public void parse() {
    String resource = type.toString();
    //如果没有解析过、放进缓存中的话
    if (!configuration.isResourceLoaded(resource)) {
        //根据mapper接口名获取XML文件并解析(之前已经解析过了,这里会直接跳过)
        loadXmlResource();
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        parseCache();
        parseCacheRef();
        //解析注解的方式(例如@Select注解,这里不进行分析)
        for (Method method : type.getMethods()) {
            if (!canHaveStatement(method)) {
                continue;
            }
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                && method.getAnnotation(ResultMap.class) == null) {
                parseResultMap(method);
            }
            try {
                parseStatement(method);
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}

/**
 * SqlSessionFactoryBuilder:
 * 第13行代码处:
 */
public SqlSessionFactory build(Configuration config) {
    //将Configuration放进DefaultSqlSessionFactory中,后面就能够拿着Configuration中的配置执行了
    return new DefaultSqlSessionFactory(config);
}

2.1 二级缓存

? ? ? ? 在上面第225行代码处的解析二级缓存的逻辑需要特别说明一下,这里使用到了装饰者模式。MyBatis的一级缓存指的是SqlSession级别的缓存,而二级缓存指的是应用级别的缓存。一级缓存后面会讲,这里先讲一下二级缓存。二级缓存有多种实现,他们通过相互装饰,最终变成一个大的缓存集合,放进Configuration的caches属性中,如下图所示:

cea978b9deec4d259a86ba76ec8937cc.png

????????而使用的时候是通过责任链的方式来分别调用每种缓存的能力,后面会看到。现在先来看下二级缓存的实现细节:?

/**
 * XMLMapperBuilder:
 */
private void cacheElement(XNode context) {
    if (context != null) {
        //获取缓存的一些配置
        String type = context.getStringAttribute("type", "PERPETUAL");
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        String eviction = context.getStringAttribute("eviction", "LRU");
        Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        Long flushInterval = context.getLongAttribute("flushInterval");
        Integer size = context.getIntAttribute("size");
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        boolean blocking = context.getBooleanAttribute("blocking", false);
        Properties props = context.getChildrenAsProperties();
        //重点看下这个方法
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
}

/**
 * MapperBuilderAssistant:
 * 第17行代码处:
 */
public Cache useNewCache(Class<? extends Cache> typeClass,
                         Class<? extends Cache> evictionClass,
                         Long flushInterval,
                         Integer size,
                         boolean readWrite,
                         boolean blocking,
                         Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        //二级缓存默认是PerpetualCache(HashMap实现)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        //这里使用到了装饰者模式,装饰了LruCache(LinkedHashMap实现,最近最少使用算法)
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    //最终将缓存存进Configuration对象中
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
}

/**
 * CacheBuilder:
 * 第42行代码处:
 */
public Cache build() {
    //设置默认缓存实现类为PerpetualCache
    setDefaultImplementations();
    //PerpetualCache实例化
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
        for (Class<? extends Cache> decorator : decorators) {
          //获取上面第36行代码处的LruCache并实例化出来,这里有多个的话会一层一层包装
          cache = newCacheDecoratorInstance(decorator, cache);
          setCacheProperties(cache);
        }
        //设置其他的装饰器
        cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
        cache = new LoggingCache(cache);
    }
    return cache;
}

/**
 * 第55行代码处:
 */
private void setDefaultImplementations() {
    if (implementation == null) {
        implementation = PerpetualCache.class;
        if (decorators.isEmpty()) {
            //设置默认缓存实现类为PerpetualCache
            decorators.add(LruCache.class);
        }
    }
}

/**
 * 第63行代码处:
 */
private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {
    //拿到Cache的构造器
    Constructor<? extends Cache> cacheConstructor = getCacheDecoratorConstructor(cacheClass);
    try {
        //调用构造器进行包装
        return cacheConstructor.newInstance(base);
    } catch (Exception e) {
        throw new CacheException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);
    }
}

/**
 * 第67行代码处:
 */
private Cache setStandardDecorators(Cache cache) {
    try {
        MetaObject metaCache = SystemMetaObject.forObject(cache);
        if (size != null && metaCache.hasSetter("size")) {
            metaCache.setValue("size", size);
        }
        if (clearInterval != null) {
            //装饰者模式,将PerpetualCache装饰为ScheduledCache(一小时会清空一次缓存)
            cache = new ScheduledCache(cache);
            ((ScheduledCache) cache).setClearInterval(clearInterval);
        }
        if (readWrite) {
            //装饰者模式,将ScheduledCache装饰为SerializedCache(序列化和反序列化存储)
            cache = new SerializedCache(cache);
        }
        //装饰者模式,将SerializedCache装饰为LoggingCache(打印缓存命中的日志信息)
        cache = new LoggingCache(cache);
        //装饰者模式,将LoggingCache装饰为SynchronizedCache(方法上加上synchronized关键字,防止并发)
        cache = new SynchronizedCache(cache);
        if (blocking) {
            //装饰者模式,将SynchronizedCache装饰为BlockingCache(内部维护了一个同步锁,获取不到锁资源的时候会被阻塞,这样可以用来防止请求被穿透,避免将请求打到数据库上)
            cache = new BlockingCache(cache);
        }
        return cache;
    } catch (Exception e) {
        throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
}

????????那么具体是如何进行装饰的呢?我们可以查看下LruCache的构造器实现:

public class LruCache implements Cache {

    private final Cache delegate;
    //...

    public LruCache(Cache delegate) {
        this.delegate = delegate;
        //...
    }

    //...
}

????????可以看到,LruCache内部含有一个Cache类型的delegate属性,它就是需要被包装的Cache。每一个Cache内部都有这么一个delegate属性,所以通过不断调用构造器,就能将Cache链串联起来。

2.2 SqlNode

????????在上面第2小节的第327行代码处,是解析SQL脚本的地方。这里也有一些设计亮点可以单独拿出来进行讲解(这里不会涉及到具体的解析过程,具体的解析使用到了OGNL表达式,感兴趣的同学可以自行查看)。这里会将SQL脚本解析成一个个的SqlNode,使用到了组合设计模式。如下图所示:

e2893c4ec27e42829d58e9116afadca4.png????????

????????可以看到,一个SQL被解析成了一个SqlNode树,而在后面的执行SQL阶段就会调用每一个SqlNode的apply方法来进行拼接。下面来具体看下MyBatis是如何解析成一个个的SqlNode的:

/**
 * XMLLanguageDriver:
 */
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
}

/**
 * XMLScriptBuilder:
 * 第6行代码处:
 */
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    //重点看下这个方法
    initNodeHandlerMap();
}

/**
 * 第19行代码处:
 * 这里会初始化一些节点处理器,后面会看到使用的地方
 */
private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
}

/**
 * 第7行代码处:
 */
public SqlSource parseScriptNode() {
    //重点看下这个方法
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}

/**
 * 第43行代码处:
 */
protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    NodeList children = node.getNode().getChildNodes();
    //获取子节点并遍历
    for (int i = 0; i < children.getLength(); i++) {
        XNode child = node.newXNode(children.item(i));
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
            //如果当前节点是一个文本节点的话
            String data = child.getStringBody("");
            //如果是文本节点的话,封装成TextSqlNode
            TextSqlNode textSqlNode = new TextSqlNode(data);
            //通过查看SQLNode中是否含有“${}”来判断是否是动态的SQL
            if (textSqlNode.isDynamic()) {
                //如果是动态SQL的话,最终会封装成TextSqlNode
                contents.add(textSqlNode);
                isDynamic = true;
            } else {
                //不是动态SQL,会被封装成StaticTextSqlNode
                contents.add(new StaticTextSqlNode(data));
            }
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            //如果当前节点是一个元素节点的话
            String nodeName = child.getNode().getNodeName();
            //获取节点处理器,节点处理器会在上面的第26行代码处的initNodeHandlerMap方法中被初始化
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            /*
            分别调用不同节点处理器的处理方法来进行解析,最终返回对应的SqlNode
            注意:这里会使用到递归的方式来进行解析,递归调用本方法去解析子节点
             */
            handler.handleNode(child, contents);
            isDynamic = true;
        }
    }
    return new MixedSqlNode(contents);
}

3 操作

3.1 openSession方法

????????通过SqlSessionFactory的openSession方法可以获取到SqlSession:

SqlSession sqlSession = sqlSessionFactory.openSession();

? ? ? ? SqlSession不是真正做事的类,真正做事的是Executor。所以这里也可以说是用到了门面设计模式,SqlSession提供了统一的抽象入口,也就是门面。它内部会通过调用Executor,来完成实际的逻辑操作。下面首先来看一下SqlSession的创建:

/**
 * DefaultSqlSessionFactory:
 */
@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);
        //创建SQL执行器对象
        final Executor executor = configuration.newExecutor(tx, execType);
        //最后创建并返回一个DefaultSqlSession
        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();
    }
}

/**
 * Configuration:
 * 第19行代码处:
 */
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //defaultExecutorType也是ExecutorType.SIMPLE,这里写了两行重复代码,猜测是代码遗留问题
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //根据执行器的类型来进行创建(SIMPLE, REUSE, BATCH)
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        executor = new SimpleExecutor(this, transaction);
    }
    //如果全局开启了缓存,则把执行器包装为CachingExecutor(二级缓存包装一级缓存,这里依然使用到了装饰者模式)
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    /*
    之前在分析配置中的XMLConfigBuilder#parseConfiguration方法中,pluginElement方法是用来添加插件的,该方法会往interceptorChain中添加一个Interceptor
    而这里就是在配置这些插件
     */
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

3.1.1 插件

? ? ? ? MyBatis支持插件机制,我们可以自己实现插件,像分页、读写分离等功能都可以通过插件来实现。之前我们在第2小节的pluginElement方法中已经将插件插入到interceptorChain中了,而在上面的第3.1小节的第55行代码处,会执行pluginAll方法来配置插件。下面来看下其实现:

/**
 * InterceptorChain:
 */
public Object pluginAll(Object target) {
    /*
    可以看到这里就是遍历调用每一个拦截器的plugin方法
    plugin方法的默认实现是会生成一个代理类,所以如果有多个插件的话,这里执行的效果就是代理类又被代理了,这么一直嵌套代理下去
     */
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

/**
 * Interceptor:
 * 第10行代码处:
 */
default Object plugin(Object target) {
    //默认实现,自定义的插件可以覆写
    return Plugin.wrap(target, this);
}

/**
 * Plugin:
 */
public static Object wrap(Object target, Interceptor interceptor) {
    //获取@Intercepts中的@Signature中的type
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    //如果当前代理的类和@Intercepts中的@Signature中的type类型可以配对上
    if (interfaces.length > 0) {
        //则可以进行动态代理,返回代理类。等到具体调用executor的方法时会调用到插件的invoke方法
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap));
    }
    return target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        if (methods != null && methods.contains(method)) {
            /*
            如果当前方法是插件注解上的方法的话,调用插件的intercept方法
            intercept方法内部调用invocation.proceed()方法即可调用到目标方法
             */
            return interceptor.intercept(new Invocation(target, method, args));
        }
        //否则,直接调用目标方法
        return method.invoke(target, args);
    } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
    }
}

? ? ? ? 对于上面第10行代码处的嵌套代理的解释,下面以Executor为例,进行演示。首先需要创建三个插件:

@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyPlugin1 implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("执行方法前111。。。");
        Object proceed = invocation.proceed();
        System.out.println("执行方法后111。。。");
        return proceed;
    }
}

@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyPlugin2 implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("执行方法前222。。。");
        Object proceed = invocation.proceed();
        System.out.println("执行方法后222。。。");
        return proceed;
    }
}

@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyPlugin3 implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("执行方法前333。。。");
        Object proceed = invocation.proceed();
        System.out.println("执行方法后333。。。");
        return proceed;
    }
}

? ? ? ? 可以看到,就是在执行目标方法的前后分别打印了日志。然后需要将插件进行配置:

<plugins>
    <plugin interceptor="org.apache.ibatis.demo.plugin.MyPlugin2"/>
    <plugin interceptor="org.apache.ibatis.demo.plugin.MyPlugin1"/>
    <plugin interceptor="org.apache.ibatis.demo.plugin.MyPlugin3"/>
</plugins>

? ? ? ? 注意:这里的顺序是MyPlugin2->MyPlugin1->MyPlugin3,即封装的顺序。最后我们运行下程序,查看下执行结果:

执行方法前333。。。
执行方法前111。。。
执行方法前222。。。
执行方法后222。。。
执行方法后111。。。
执行方法后333。。。
[UserDO(id=1, name=name1, sex=true, phone=phone1, address=address1), UserDO(id=2, name=name2, sex=true, phone=phone2, address=address2), UserDO(id=3, name=name3, sex=true, phone=phone3, address=address3), UserDO(id=4, name=name4, sex=true, phone=phone4, address=address4), UserDO(id=5, name=name5, sex=true, phone=phone5, address=address5), UserDO(id=6, name=name6, sex=true, phone=phone6, address=address6), UserDO(id=7, name=name7, sex=true, phone=phone7, address=address7), UserDO(id=8, name=name8, sex=true, phone=phone8, address=address8), UserDO(id=9, name=name9, sex=true, phone=phone9, address=address9), UserDO(id=10, name=name10, sex=true, phone=phone10, address=address10), UserDO(id=11, name=name11, sex=true, phone=phone11, address=address11), UserDO(id=12, name=name12, sex=true, phone=phone12, address=address12), UserDO(id=13, name=name13, sex=true, phone=phone13, address=address13)]

? ? ? ? 其实看到这个执行的顺序就能联想到栈了。下面的图展示了具体的执行原理。

d0bd11b7fec44aa48de7035047a47b0d.png

? ? ? ? 这里需要说明的一点是:实际目标方法的打印顺序和图中所画有些区别,这是因为我这里的目标方法是条select查询语句,而我手动打印查询结果是在程序的最后执行的,也就是在Executor执行之后打印的(SqlSession执行之后,还记得吗?SqlSession是Executor的门面),这个时候插件都已经运行完了,所以目标方法会放在最后才打印:

List<UserDO> list = session.selectList("org.apache.ibatis.demo.dao.UserDAO.list", UserDO.class);
System.out.println(list);

? ? ? ? 还有一点需要说明:上面的例子是针对同一type的多个插件的执行顺序的研究。而不同type的执行顺序是在代码里写死的,后面会看到具体写死的地方:

执行方法前Executor。。。
执行方法前StatementHandler。。。
执行方法后StatementHandler。。。
执行方法前ParameterHandler。。。
执行方法后ParameterHandler。。。
执行方法前ResultSetHandler。。。
执行方法后ResultSetHandler。。。
执行方法后Executor。。。

3.2?selectOne方法

????????数据操作以selectOne方法为例:

UserDO userDO = session.selectOne("org.apache.ibatis.demo.dao.UserDAO.getById", 1L);

????????查看其实现:

/**
 * DefaultSqlSession:
 */
@Override
public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    //可以看到selectOne方法是通过selectList方法来实现的,只不过是只截取第一条数据而已
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}

/**
 * 第8行代码处:
 */
@Override
public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
        //从Configuration中获取mappedStatements,也就是之前第2.2小节解析的SqlNode
        MappedStatement ms = configuration.getMappedStatement(statement);
        //可以看到是通过执行器来执行的sql
        return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

/**
 * 第36行代码处:
 */
private Object wrapCollection(final Object object) {
    return ParamNameResolver.wrapToMapIfCollection(object, null);
}

/**
 * ParamNameResolver:
 * 如果参数是collection/list/array的话,就包装成map形式返回(这样就可以使用“array[0]”的方式来动态传参)
 */
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
    if (object instanceof Collection) {
        ParamMap<Object> map = new ParamMap<>();
        map.put("collection", object);
        if (object instanceof List) {
            map.put("list", object);
        }
        Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
        return map;
    } else if (object != null && object.getClass().isArray()) {
        ParamMap<Object> map = new ParamMap<>();
        map.put("array", object);
        Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
        return map;
    }
    return object;
}

/**
 * CachingExecutor:
 * 第36行代码处:
 */
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //解析sql
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //生成缓存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

/**
 * MappedStatement:
 * 第80行代码处:
 */
public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
        boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
        String rmId = pm.getResultMapId();
        if (rmId != null) {
            ResultMap rm = configuration.getResultMap(rmId);
            if (rm != null) {
                hasNestedResultMaps |= rm.hasNestedResultMaps();
            }
        }
    }

    return boundSql;
}

/**
 * DynamicSqlSource:
 * 第91行代码处:
 */
@Override
public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //之前在第2.2小节讲了sqlNode,而这里就是通过责任链和递归的方式来执行每一个sqlNode的apply方法,最终会拼接成需要执行的sql
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    //替换动态参数,将“#{}”替换成“?”,将参数封装为parameterMappings
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
}

/**
 * CachingExecutor:
 * 第83行代码处:
 */
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
    //是否开启二级缓存
    Cache cache = ms.getCache();
    if (cache != null) {
        //是否需要刷新缓存
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            //之前的第2.1小节说过,二级缓存使用了装饰者模式,一个包装一个。所以这里就是在一个一个调用它们的getObject方法(装饰者+责任链)
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                //如果二级缓存中找不到的话,就从一级缓存/数据库中查找
                list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                //加入到二级缓存中
                tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
        }
    }
    //没有开启二级缓存,就直接去一级缓存/数据库中查找
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

/**
 * TransactionalCacheManager:
 * 第145行代码处:
 */
public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
}

private TransactionalCache getTransactionalCache(Cache cache) {
    /*
    这里的作用是将Cache最外面再包一层TransactionalCache
    TransactionalCache内部会暂存所有的二级缓存,其内部的putObject方法并不会直接去调用二级缓存的putObject方法,
    而是等到调用commit方法时才会去调用二级缓存的putObject方法;当调用rollback方法时会调用二级缓存的removeObject方法
     */
    return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
}

/**
 * BaseExecutor:
 * 第148行代码处:
 */
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    //如果queryStack为0,并且需要删除一级缓存的话,就删除它
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        /*
        从一级缓存中查找
        这里的localCache会在commit或rollback方法中被清空,也就体现了一级缓存是SqlSession级别的缓存
         */
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
            //找到的话,则处理本地缓存的输出参数
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            //一级缓存中也找不到的话,就从数据库中查找
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }
    if (queryStack == 0) {
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

/**
 * 第204行代码处:
 */
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //先占位(没搞懂?并发考虑?)
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    //查询结果放入到一级缓存中
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

/**
 * SimpleExecutor:
 * 第231行代码处:
 */
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        //拿到Connection和Statement
        stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

/**
 * Configuration:
 * 第252行代码处:
 */
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    //StatementHandler的插件会在此处执行
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

/**
 * RoutingStatementHandler:
 * 第266行代码处:
 */
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
        case STATEMENT:
            delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case PREPARED:
            //默认情况下会走这里,创建PreparedStatementHandler
            delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case CALLABLE:
            delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}

/**
 * PreparedStatementHandler:
 * 第284行代码处:
 */
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}

/**
 * BaseStatementHandler:
 */
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
        generateKeys(parameterObject);
        boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    //创建一个ParameterHandler(ParameterHandler的插件会在此处执行)
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    //创建一个ResultSetHandler(ResultSetHandler的插件会在此处执行)
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
    /*
    注:在上面小节的最后提到了不同type的插件的执行顺序,从这里也就能看到了。会按照Executor->StatementHandler->ParameterHandler->ResultSetHandler
    的执行顺序执行(Executor的插件会在sqlSessionFactory.openSession方法中提前执行)

    另外,通过全局搜索MyBatis的源码发现,插件的执行时机只在上面的四个对象中有介入。这也就意味着只能对上面的四个对象做插件上的增强,其它类型的插件即使写了,也不会生效
     */
}

/**
 * SimpleExecutor:
 * 第254行代码处:
 */
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取连接
    Connection connection = getConnection(statementLog);
    //获取statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    //处理参数
    handler.parameterize(stmt);
    return stmt;
}

/**
 * RoutingStatementHandler:
 * 第255行代码处:
 */
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
}

/**
 * PreparedStatementHandler:
 */
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //这里就会调用底层的JDBC去查询数据库了
    ps.execute();
    //处理结果集(内部会使用到动态代理来创建目标对象,TypeHandler来赋值对象)
    return resultSetHandler.handleResultSets(ps);
}

3.3 getMapper方法

????????通过SqlSession的getMapper方法可以直接获取到mapper接口,从而可以调用到相关业务方法。这种方式比直接调用SqlSession的相关方法要更好一些,避免了硬编码:

UserDAO mapper = session.getMapper(UserDAO.class);
UserDO userDO = mapper.getById(1L);

????????getMapper方法的实现就很简单了,下面来看下其实现:

/**
 * DefaultSqlSession:
 */
@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

/**
 * Configuration:
 */
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

/**
 * MapperRegistry:
 */
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    /*
    获取缓存中的值
    还记得之前在第2小节中调用的addMapper方法吗?在其中会往knownMappers里添加MapperProxyFactory。
    而这里就是从其中拿出mapper接口
     */
    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);
    }
}

/**
 * MapperProxyFactory:
 * 第32行代码处:
 */
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
    //从这里就可以看到,MyBatis就是通过JDK动态代理生成的Mapper实现类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}

? ? ? ? getMapper方法其实只做了一件事,就是动态代理出来mapper接口的实现类,类型为MapperProxy。之后就可以直接拿着代理类调用业务方法就行了。因为代理类是MapperProxy,所以调用业务方法时会调用到其invoke方法:

/**
 * MapperProxy:
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

/**
 * PlainMethodInvoker:
 * 第10行代码处:
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
}

/**
 * MapperMethod:
 */
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //下面就是在根据不同的SQL语句进行不同的处理
    switch (command.getType()) {
        case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        case SELECT:
            //拿SELECT语句来举例,通过下面的逻辑可以看到,这里最终就是在调用sqlSession的相关方法进行查询
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            } else {
                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;
        case FLUSH:
            result = sqlSession.flushStatements();
            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;
}

? ? ? ? 上面的第61行代码处可以看到(SELECT语句的单条查询),业务方法的执行底层仍然是在调用sqlSession的相关方法。


4 与Spring集成

????????在我们使用MyBatis时,大部分情况下都不是单独使用的,都是需要和Spring进行集成后再使用。那么接下来就来看下MyBatis是如何与Spring进行集成的(注:MyBatis与Spring集成的代码是写在单独的mybatis-spring项目中的,不在原有的mybatis项目中)。

4.1 SqlSessionFactoryBean

????????之前在单独使用MyBatis的时候,我们是通过SqlSessionFactoryBuilder的build方法生成的SqlSessionFactory,而在和Spring集成后,是通过SqlSessionFactoryBean来生成的:

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    factoryBean.setMapperLocations(new ClassPathResource("org/mybatis/spring/demo/xml/UserMapper.xml"));
    return factoryBean.getObject();
}

? ? ? ? 可以看到,这里将数据源和mapper文件传给SqlSessionFactoryBean了。SqlSessionFactoryBean本身是个FactoryBean,熟悉Spring源码的同学应该知道,FactoryBean的getObject方法的返回值会被做成bean放进IoC容器中(我之前有写过对Spring源码的解析《较真儿学源码系列-Spring IoC核心流程源码分析》,感兴趣的话可以查看)。那么下面就来看下其getObject方法的实现:

/**
 * SqlSessionFactoryBean:
 */
@Override
public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        afterPropertiesSet();
    }

    return this.sqlSessionFactory;
}

/**
 * 第7行代码处:
 */
@Override
public void afterPropertiesSet() throws Exception {
    //...

    if (xmlConfigBuilder != null) {
        try {
            //之前第2小节已经分析过了,通过XMLConfigBuilder的parse方法来解析配置文件的内容,放到Configuration里
            xmlConfigBuilder.parse();
            LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
        } catch (Exception ex) {
            throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    /*
    这里会把SpringManagedTransactionFactory赋值给transactionFactory,所以最终用到的Transaction会是Spring事务管理器创建的Transaction;
    最终用到的connection会是事务管理器创建好的连接
     */
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    if (this.mapperLocations != null) {
        if (this.mapperLocations.length == 0) {
            LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
        } else {
            for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                    continue;
                }
                try {
                    //之前也说过,创建XMLMapperBuilder并调用其parse方法,会对mapper文件进行解析
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                    targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                    ErrorContext.instance().reset();
                }
                LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
            }
        }
    } else {
        LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

    //调用build方法,将Configuration放进SqlSessionFactoryBuilder中
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

????????至此就明白了SqlSessionFactoryBean的作用就是将MyBatis的配置信息放到Configuration对象中。

4.2 Spring如何去管理Mapper接口

4.2.1?MapperFactoryBean

?????????如果想把一个类交给Spring容器来管理的话,我们只需要在这个类上加上@Component或@Service或@Controller注解即可。但是在MyBatis中我们却不能在这样做,因为mapper是接口。如果是接口或者抽象类的话,是不能被Spring所实例化的:

/**
 * ClassPathScanningCandidateComponentProvider:
 */
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    AnnotationMetadata metadata = beanDefinition.getMetadata();
	return (metadata.isIndependent() && (metadata.isConcrete() ||
        (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

/**
 * ClassMetadata:
 * 第6行代码处:
 */
default boolean isConcrete() {
    return !(isInterface() || isAbstract());
}

? ? ? ? 这个时候就需要MapperFactoryBean登场了,它会对mapper接口进行包装:

@Bean
public MapperFactoryBean userMapper() throws Exception {
    MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(UserMapper.class);
    mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory());
    return mapperFactoryBean;
}

????????同理,因为是个FactoryBean,所以查看其getObject方法实现:

/**
 * MapperFactoryBean:
 */
@Override
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}

/**
 * SqlSessionTemplate:
 */
@Override
public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
}

????????可以看到,最终就是通过调用sqlSession的getMapper方法来创建出代理类,之前在第3.3小节已经讲过了。

4.2.2?并发控制

? ? ? ? 这里需要对SqlSessionTemplate单独做一些分析。我们之前对MyBatis源码进行分析的时候,如果细心的话可以发现DefaultSqlSession乃至整个MyBatis源码中都很少有加锁的控制。那么可以认为DefaultSqlSession是线程不安全的(这点其实没毛病,MyBatis的宗旨就是简单轻量化)。那么现在与Spring集成后,就不得不考虑这点了。SqlSessionTemplate是mybatis-spring项目单独封装的类,下面来看下它的构造器:

/**
 * SqlSessionTemplate:
 */
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType,
        new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                          PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    //这里是在动态代理,生成代理类。重点关注下最后一个参数,之后在调用sqlSessionProxy的方法时会调用到SqlSessionInterceptor的invoke方法
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class}, new SqlSessionInterceptor());
}

/**
 * SqlSessionInterceptor:
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //重点看下是如何获取到sqlSession的
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
        //目标方法的调用
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
            // force commit even on non-dirty sessions because some databases require
            // a commit/rollback before calling close()
            sqlSession.commit(true);
        }
        return result;
    } catch (Throwable t) {
        //...
    } finally {
        if (sqlSession != null) {
            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
    }
}

/**
 * SqlSessionUtils:
 * 第33行代码处:
 */
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
                                       PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    /*
    获取同一个线程之前有没有创建过sqlSession
    Spring事务相关的获取资源的方法,这里不详细去看了,底层就是在使用ThreadLocal
     */
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    //通过sqlSessionHolder获取sqlSession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        //如果之前存在sqlSession的话,就直接返回
        return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    //之前不存在sqlSession,说明当前线程是第一次获取,此时通过openSession方法获取到sqlSession
    session = sessionFactory.openSession(executorType);

    //并放进ThreadLocal中(如果事务同步激活的话)
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
}

? ? ? ? 可以看到,Mybatis集成Spring后,是通过ThreadLocal来保证的并发安全。这样的话,每个线程都有自己单独的sqlSession。

? ? ? ? 需要注意的一点是:如果事务没有开启的话,ThreadLocal中是不会放入值的。这样造成的结果就是每次请求(注意这里的措辞,不是同一个线程)都是new出来的新的sqlSession。这样虽然不会有并发问题,但是会使缓存失效。还记得MyBatis的一级缓存是sqlSession级别的吗?所以这也就是为什么没有开启事务,MyBatis一级缓存失效的原因。因为每次请求都会创建一个新的sqlSession,不管在不在同一个线程内。之前sqlSession的缓存都作废了。

? ? ? ? 至于为什么会这么做,为什么事务不开启的时候不往ThreadLocal里存值。猜测是为了Spring事务的概念。如果一个方法里面有多条查询SQL的语句的话,那么可以认为这几条查询语句是相互独立的,而如果在方法上加上@Transactional注解的话,则代表这里面的所有查询SQL是一个整体,所以需要共用同一个sqlSession、共用一级缓存(?我之前有写过对Spring事务的源码解析《较真儿学源码系列-Spring事务管理核心流程源码分析》,感兴趣的话可以查看)。

4.2.3?@MapperScan

? ? ? ? 前面讲解了通过MapperFactoryBean包装的方式来创建mapper接口的bean,但是我们不可能每次创建出一个mapper接口,就手动用MapperFactoryBean包装一下,再注入到Spring容器中。这个时候我们就会用到@MapperScan注解,以此来提供统一的注册mapper接口的方式。查看其实现:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

    //...

}

? ? ? ? @MapperScan注解上会引用到MapperScannerRegistrar类,因其实现了ImportBeanDefinitionRegistrar接口,所以我们只需要看其registerBeanDefinitions方法的实现就行了:

/**
 * MapperScannerRegistrar:
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //获取@MapperScan注解上的配置信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
        registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
            generateBaseBeanName(importingClassMetadata, 0));
    }
}

/**
 * 第10行代码处:
 */
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
                             BeanDefinitionRegistry registry, String beanName) {

    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    //...

    // for spring-native
    builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

    //这里是将MapperScannerConfigurer注册为一个bean定义
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

? ? ? ? 可以看到,最后是注册了MapperScannerConfigurer。MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,那么接下来就来看下其postProcessBeanDefinitionRegistry接口的实现:

/**
 * MapperScannerConfigurer:
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
        processPropertyPlaceHolders();
    }

    //创建扫描器,可以对mapper进行通用的扫描
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    //...
    scanner.registerFilters();
    //进行扫描
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

/**
 * ClassPathMapperScanner:
 * 第13行代码处:
 */
public void registerFilters() {
    boolean acceptAllInterfaces = true;

    //...

    if (acceptAllInterfaces) {
        // default include filter that accepts all classes
        /*
        这里的作用是添加一个includeFilter,使其可以跳过ClassPathScanningCandidateComponentProvider的isCandidateComponent(MetadataReader)方法
        的校验,之后能跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里
         */
        addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // exclude package-info.java
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
    });
}

/**
 * ClassPathBeanDefinitionScanner:
 * 第15行代码处:
 */
public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

/**
  * ClassPathMapperScanner:
  * 第51行代码处:
  */
 @Override
 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用原有的扫描逻辑(在其中会调用到isCandidateComponent方法,也就是会跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里)
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
        LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
            + "' package. Please check your configuration.");
    } else {
        //这里是ClassPathMapperScanner自己改写的内容
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

/**
 * 第68行代码处:
 */
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    //这里就可以看到:ClassPathMapperScanner改写了Spring默认的实现,使mapper接口可以被注册到Spring容器中
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

/**
 * 第75行代码处:
 */
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    //遍历所有的mapper
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (AbstractBeanDefinition) holder.getBeanDefinition();
        boolean scopedProxy = false;
        //...
        String beanClassName = definition.getBeanClassName();
        LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
            + "' mapperInterface");

        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        //给MapperFactoryBean的构造器传参,参数为mapper接口的Class对象
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
        try {
            // for spring-native
            definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
        } catch (ClassNotFoundException ignore) {
            // ignore
        }

        //bean定义的类型需要从原始mapper的类型改写为MapperFactoryBean(本行代码和上面给构造器传参的代码的顺序不能换)
        definition.setBeanClass(this.mapperFactoryBeanClass);

        //...

        if (!explicitFactoryUsed) {
            LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }

        definition.setLazyInit(lazyInitialization);

        if (scopedProxy) {
            continue;
        }

        //...

    }
}

? ? ? ? 上面最终会把类型为MapperFactoryBean的bean定义注册进去,之后的操作就是调用MapperFactoryBean的getObject方法了,之前在第4.2.1小节已经讲过了。


原创不易,未得准许,请勿转载,翻版必究

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 12:26:14  更:2022-05-09 12:30:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:10:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码