【mybatis源码】2、解析、执行器、缓存、动态代理

前言:

  • 这个链接中有mybatis的各个对象说明,一定要结合看:https://blog.csdn.net/hancoder/article/details/111244516

  • 个人源码解析地址:https://blog.csdn.net/hancoder/article/details/110152732

  • mybatis插件分析:https://blog.csdn.net/hancoder/article/details/110914534

  • 本文主要按解析xml、获取session、执行sql等分为几个大章节

  • 有的小章节前面加了【】,是代表面试比较常见的问题,比如@Param、分页和sql注入等问题

  • 设计模式的Builder建造者模式、装饰者模式很重要。插件中也用到了代理模式和责任链模式

  • 动态代理模式也得看会才能看这个,最起码invocationHandler和invoke知道怎么回事

一、xml文件解析

  • XMLConfigBuilder是解析根标签Configuration的
  • XMLMapperBuilder是解析根标签Mapper的
SqlSessionFactoryBuilder
  • build()的方法是传入一个配置文件流参数,返回一个SqlSessionFactor实例对象。在这里就是传入configuration配置,得到一个SqlSessionFactory实例
// SqlSessionFactoryBuilder.java

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

// 开始创建sqlSessionFactory
// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream) {
    //调用了重载方法
    return build(inputStream, null, null);
}

// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        //  XMLConfigBuilder是专门解析mybatis的配置文件的类
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //这里又调用了一个重载方法Configuration。parser.parse()的返回值是Configuration对象
        return build(parser.parse());//XMLConfigBuilder的parse()返回一个Configuration对象 
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } //省略部分代码
}

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

总结:
    两个BuilderXMLConfigBuilder是parser
    parser.parse()就是返回Builder包含的Configuration对象
    sqlSessionFactoryBuilder.build(config)返回都是DefaultSqlSessionFactory对象。
XMLConfigBuilder
  • XMLConfigBuilder中的属性configuration在父类BaseBuilder中
  • 通过parse()方法返回一个Configuration实例
// XMLConfigBuilder
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()),   environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());//BaseBuilder
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;// XML里的成员parser = new XPathParser(reader, true, props, new XMLMapperEntityResolver())
}

// 返回Configuration
public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;//也就是说这个函数只能进来一次
    parseConfiguration(parser.evalNode("/configuration"));//这里进去把config的属性注入好
    return configuration;
}

mybatis-config.xml元素解析

解析mybatis.xml配置文件中的各个标签。

值得注意的是,先解析好properties,然后就可以把设置注入到configuration中了

//------XMLConfigBuilder---------------
private void parseConfiguration(XNode root) {//Xnode是一个XML,properties还没注入
    try {
        // 解析properties
        propertiesElement(root.evalNode("properties")); //issue #117 read properties first // 在这里把配置才拿出来
        typeAliasesElement(root.evalNode("typeAliases"));
        pluginElement(root.evalNode("plugins"));// 解析拦截器插件
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        settingsElement(root.evalNode("settings"));
        environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 //<environments> //函数传入的时候后root.evalNode("environments")整体已经把配置拿到了
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        typeHandlerElement(root.evalNode("typeHandlers"));
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

上面解析的是mybatis.xml配置文件,值得注意的是在解析其中<mappers>标签的时候,会解析指定的Xxxmapper.xml文件

①properties配置解析

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="root"/>
  <property name="password" value="123123"/>
</properties>
propertiesElement

解析properties

//---------------------
//先解析properties把属性设置好 //  propertyConfigurer
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        Properties defaults = context.getChildrenAsProperties();
        // 配置文件的属性有url或者resource
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");
        if (resource != null && url != null) {//不能同时指定
            throw new BuilderException("不能同时指定url和res");
        }
        if (resource != null) {// 如果指定的是resource
            // 得到一个Properties对象,然后放到configuration.variables这个Properties中
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) { // 如果指定的是url
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}

②解析数据源environment、DataSource

会把解析出来的内容放到configuration 属性中,然后用属性解析数据源

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>
environmentsElement
//---------------------
// 解析完properties后就能${}拿到了
private void environmentsElement(XNode context) {//<environments>
    if (context != null) {
        if (environment == null) {
            environment = context.getStringAttribute("default");
        }
        // 循环开发环境、测试环境等
        for (XNode child : context.getChildren()) { // 每个<environment id="development">
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                // 事物 mybatis有两种:JDBC 和 MANAGED, 配置为JDBC则直接使用JDBC的事务,配置为MANAGED则是将事务托管给容器
                // <transactionManager type="JDBC"/>
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                // <dataSource type="POOLED">
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));//看下面函数
                // 得到数据源
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                    .transactionFactory(txFactory)
                    .dataSource(dataSource);
                // 设置environment给configuration 
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}
//---------------------
// 数据源  <dataSource type="POOLED">  <property name="url" value="${url}"/>
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        // 解析多个property标签,数据源的${}肯定从这里面替换
        Properties props = context.getChildrenAsProperties();// 获得属性
        DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();// 获得class对象,然后new
        factory.setProperties(props);//把属性设置到数据源
        return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    // 解析多个property标签
    for (XNode child : getChildren()) {
        String name = child.getStringAttribute("name");
        String value = child.getStringAttribute("value");
        if (name != null && value != null) {
            properties.setProperty(name, value);
        }
    }
    return properties;
}
/**
 * 解析typeAliases 节点
 * <typeAliases>
 *     <!--<package name="com.lpf.entity"></package>-->
 *     <typeAlias alias="UserEntity" type="com.lpf.entity.User"/>
 * </typeAliases>
 * @param parent
 */
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            //如果子节点是package, 那么就获取package节点的name属性, mybatis会扫描指定的package
            if ("package".equals(child.getName())) {
                String typeAliasPackage = child.getStringAttribute("name");
                //TypeAliasRegistry 负责管理别名, 这儿就是通过TypeAliasRegistry 进行别名注册
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                //如果子节点是typeAlias节点,那么就获取alias属性和type的属性值
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}
typeHandlerElement

类型转换器的用处:

  • 在预处理语句(PreparedStatement)中设置参数
  • 从结果集中取出一个值时转换成 Java 类型

注册到typeHandlerRegistry中

private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            //子节点为package时,获取其name属性的值,然后自动扫描package下的自定义typeHandler
            if ("package".equals(child.getName())) {
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                //子节点为typeHandler时, 可以指定javaType属性, 也可以指定jdbcType, 也可两者都指定
                //javaType 是指定java类型
                //jdbcType 是指定jdbc类型(数据库类型: 如varchar)
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                //handler就是我们配置的typeHandler
                String handlerTypeName = child.getStringAttribute("handler");
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                //JdbcType是一个枚举类型,resolveJdbcType方法是在获取枚举类型的值
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);
                //注册typeHandler, typeHandler通过TypeHandlerRegistry这个类管理
                if (javaTypeClass != null) {
                    if (jdbcType == null) {
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}
pluginElement

解析plugins标签

  • StatementHandler
  • ParameterHandler
  • ResultSetHandler
  • Executor
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            // 我们在定义一个interceptor的时候,需要去实现Interceptor
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
            // 向configuration对象中注册拦截器
            configuration.addInterceptor(interceptorInstance);
        }
    }
}
PropertyParser

PropertyParser这个类中包含一个内部私有的静态类VariableTokenHandler。VariableTokenHandler实现了TokenHandler接口,包含了一个Properties类型的属性,在初始化这个类时需指定该属性的值。VariableTokenHandler类对handleToken函数的具体实现如下:

https://blog.csdn.net/u014213453/article/details/40399475

③mapper元素解析

获取sql、加载接口

配置要注册的mapper有四种方式(优先级由高到低)

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
</mappers>

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
</mappers>

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
</mappers>

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
addMapper
mapperElement
// XMLConfigBuilder.java
// 解析configuration标签中的mapper子标签
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            //mappers/ mappers/<package name="">
            // ① 找包下的接口,然后找同名的xml //这时候没有MSC
            if ("package".equals(child.getName())) {
                // 拿到包名
                String mapperPackage = child.getStringAttribute("name");
                // 得到包下所有的类,依次调用addMapper(mapperClass);,而addMapper(mapperClass)第一句就是判断这个类是不是接口,不是直接的话直接跳过
                configuration.addMappers(mapperPackage);
            } else {// mappers/<mapper resource="">
                // 获取resource/url/class属性
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");

                // 上面3个对应下面3个
                // ② resource属性不为空,如XxxMapper.xml
                if (resource != null && url == null && mapperClass == null) { // resource不为空
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // 解析mapper的xml // 传入了configuration参数,那么肯定就设置到configuration里面了
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                    // ③url,和上面的逻辑一样,就是解析xml而已
                } 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();
                    //  ④mapperClass
                } else if (resource == null && url == null && mapperClass != null) {
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    // 这个方法和上面resource里面最终依次调用的方法一样,就是对接口进行操作
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException("不是  url, resource or class报错");
                }
            }
        }
    }
}

// 上面解析mapper标签主要内容为把标签的内容放到XMLMapperBuilder实例中,然后调用parse方法,parse方法的作用是解析另一个xml文件

在上面我们知道:

  • package和class就是扫描接口而已MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);,然后调用parser.parse();
    • config是MapperRegistry 类持有的私有属性
    • MapperRegistry类还有一个属性是knownMappers ,已经加载过的mapper集合,是从接口类到MapperProxyFactory的映射map,在addMapper(接口.class)的时候,第一句就是先knownMappers .put,告诉这个接口加载过了,即每个接口只能加载一次,否则报错
  • 而对于xml:

好吧我们先不管class的问题了,先看看如何把xml的内容解析好然后再调用addMapper了

上面解析mapper标签主要内容为把标签的内容放到XMLMapperBuilder实例中,然后调用parse方法,parse方法的作用是解析另一个xml文件(XxxMapper.xml)

addMappers

MapperXml解析好后,我们接着刚才的addMapper分析

[①]addMapper(包)
// 如果<mappers>标签里指定的是接口包路径的话
public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
}
//MapperRegistry
public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    // 遍历包下的接口
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}
[②]addMapper(接口)与MapperAnnotationBuilder
public <T> void addMapper(Class<T> type) {
    // 如果是接口
    if (type.isInterface()) {
        // 有没有该类型的mapper(用接口全类名作为key查),如果有则报错,每个接口只能解析一遍
        if (hasMapper(type)) {
            throw new BindingException("MapperRegistry中已经有这个类型了,不要重复解析。解析完的接口都会让MapperRegistry知道");
        }
        boolean loadCompleted = false;
        try {
            // 没有被解析过该接口,开始解析该接口,放入已解析的map中,防止后面重复解析
            knownMappers.put(type, new MapperProxyFactory<T>(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.
            // 比如解析接口方法上的@Select(slect * from a)
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                // 如果未完成解析,移除key
                knownMappers.remove(type);
            }
        }
    }
}

虽然还没有学习解析xml,但还是想在这里加几句方便复习。在xml阶段底层还是会走到addMapper(接口)这里。hasMapper(type)这个判断也能倒推出xml的解析并没有解析完,只是把解析的信息放到configuration中,然后调用addMapper(接口),直到解析接口的时候拿到xml的信息,再加载注解的信息。所以注解的信息优先级高。

MapperAnnotationBuilder就是上面这个过程

[③]addMapper(xml)与XMLMapperBuilder

实际上是没有addMapper(xml)这个东西的,只有解析UserMapper.xml的过程

那我们看看如果是xml的话怎么办吧

倒退到解析xml文件

思想:解析xml的<mapper>标签元素,放到对应的接口

private void mapperElement(XNode parent) throws Exception {

    for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
            String mapperPackage = child.getStringAttribute("name");
            // 得到包下所有的类,依次调用addMapper(mapperClass);
            configuration.addMappers(mapperPackage);
        } else {// mappers/<mapper resource="">
            if (resource != null && url == null && mapperClass == null) { // resource不为空
                InputStream inputStream = Resources.getResourceAsStream(resource);
                // 解析mapper的xml // 传入了configuration参数,那么肯定就设置到configuration里面了
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration,
                                                                     resource, configuration.getSqlFragments());
                mapperParser.parse();
                // ③url,和上面的逻辑一样,就是解析xml而已
//XMLConfigBuilder.java
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

//XMLMapperBuilder.java
public void parse() {
    // resourse:"mapper/UserMapper.xml"
    if (!configuration.isResourceLoaded(resource)) {//从这里看出解析mapper.xml时会看有没有被解析过,如果解析过了config会知道,config持有一个Set<String>
        // 1
        /** ★★★解析mapper标签,即解析sql语句,将sql语句的所有信息封装成MappedStatement对象,然后存储在configuration对象中。
    */ 
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);// 解析后加到config的Set<String>中
        // 2
        /* 绑定mapper到接口类,里面有addMapper */
        bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
}

③.1

解析sql加到configuration中,解析XxxMapper.xml中,<mapper>标签下的子标签,如<select>,当然还包括<resultMap>

// XMLMapperBuilder.java
// 解析mapperXML里的configuration
private void configurationElement(XNode context) {//mapper标签里的内容
    try {
        String namespace = context.getStringAttribute("namespace");// 属性
        // 设置当前解析的接口。从这里也可以推测builderAssistant对象是全局单例的
        builderAssistant.setCurrentNamespace(namespace);
        cacheRefElement(context.evalNode("cache-ref"));//Ele才是子标签
        cacheElement(context.evalNode("cache"));
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        sqlElement(context.evalNodes("/mapper/sql"));
        // ★★★解析CRUD标签 // 参数类型为List<XNode> list
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//select子标签
    } catch (Exception e) {
        throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
    }
}

③.1.1 解析mapper.xml中的每个sql

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    // 我们只有一个select标签,所以list.size==1,如果还有别的标签就++
    for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            // 去parse标签内容
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}
解析标签】③.1.1.1 parseStatementNode

解析mapperxml中的sql标签

public void parseStatementNode() {
    // 此时的id还只是方法名,如"selectUser"
    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,
    // in case if IncompleteElementException (issue #291)
    List<XNode> selectKeyNodes = context.evalNodes("selectKey");
    if (configuration.getDatabaseId() != null) {
      parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
    }
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);

    // 动态sql和静态sql  # 动态sql解析完问号都没有转
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    
    
    // 省略一些解析mapper文件里属性的内容
    。。。;
    // ★★★重点代码 // id:"selectUser" resource:"mapper/UserMapper.xml"
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                                        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                                        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
                                        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

③.1.1.1.1

addMappedStatement
public MappedStatement addMappedStatement(
    String id,//"selectUser",一会就拼接上类名了
    SqlSource sqlSource,
    StatementType statementType,// STATEMENT, PREPARED, CALLABLE
    SqlCommandType sqlCommandType,//UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
    Integer fetchSize,
    Integer timeout,
    String parameterMap,
    Class<?> parameterType,
    String resultMap,
    Class<?> resultType,
    ResultSetType resultSetType,
    boolean flushCache,
    boolean useCache,
    boolean resultOrdered,
    KeyGenerator keyGenerator,
    String keyProperty,
    String keyColumn,
    String databaseId,
    LanguageDriver lang,
    String resultSets) {

    if (unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    // 把selectUser加上全类名 "mapper.UserMapper.selectUser"
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)// mapper/UserMapper.xml
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {//为null
        statementBuilder.parameterMap(statementParameterMap);
    }

    // MappedStatement解析,即sql的封装,只是封装了MappedStatement
    MappedStatement statement = statementBuilder.build();
    // 放到configuration.mappedStatements这个map中,key为全类名+方法mapper.UserMapper.select,value为MappedStatement对象 // 一下放了两个key,全类名+方法、方法两个,总之出来的时候map.size已经是2了。可以通过ms.getId()得到key
    configuration.addMappedStatement(statement);
    return statement;
}

到这里解析完了xml到configuration.mappedStatements这个map中

③.2 bindMapperForNamespace():

接着往下执行XMLMapperBuilder.parse()

实际上就是解析该sql对应的class,并把该class放到configuration.mapperRegistry中。实际上mybatis的所有配置信息以及运行时的配置参数全部都保存在configuration对象中。mapperRegistry这个东西我们之前提过,是一个map,管理着接口到MapperProxyFactory的映射

// XMLMapperBuilder.java
private void bindMapperForNamespace() {
    // 我们是在解析每个mapper,设置过当前解析的接口是哪个,所以能拿到接口的namespace
    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) {
            // 如果mapperRegistry中没有该接口类型,mapperRegistry和configuration都是全局唯一的,知道接口有没有被解析过
            if (!configuration.hasMapper(boundType)) {
                // Spring may not know the real resource name so we set a flag
                // to prevent loading again this resource from the mapper interface
                // look at MapperAnnotationBuilder#loadXmlResource
                // 前面我们也执行过一个同样的方法,只不过现在这个加了个namespace前缀放到这个set,现在这个set中有两个,"mapper/UserMapper.xml"和" configuration.addLoadedResource("namespace:" + namespace);
                configuration.addLoadedResource("namespace:" + namespace);
                // 又到了addMapper(接口)的时候了,config.addMapper内部就是调用的是mapperRegistry.addMapper(type);// 不管是config还是mapperRegistry都是唯一的,addMapper互相"重载"
                configuration.addMapper(boundType);
            }
        }
    }
}

还有一个小疑问:xml解析后怎么设置到configuration中的,让configuration知道xml的解析信息

分析了一下,原来是并没有把解析出来的xml信息和mapper接口关联,而是xml信息仅仅和configuration关联

然后直接去调用config.addMapper(接口)

[④]殊途同归

通过上面分析,不管是接口还是包还是xml,最终都是调用addMapper(接口),只不过包的话是挨个调用,xml的话是先封装xml的信息放到configuration中,addMapper时候再拿出来

回忆addMapper(接口类)

// MapperRegistry.java
public <T> void addMapper(Class<T> type) {
    // 如果是接口
    if (type.isInterface()) {
        // knownMappers<Class,MapperProxyFactory>中有没有该类型的mapper(用接口全类名作为key查),如果有则报错,每个接口只能解析一遍
        if (hasMapper(type)) {
            throw new BindingException("MapperRegistry中已经有这个类型了,不要重复解析。解析完的接口都会让MapperRegistry知道");
        }
        boolean loadCompleted = false;
        try {
            // 没有被解析过该接口,开始解析该接口,put(接口,创建代理工厂),防止后面重复解析
            knownMappers.put(type, new MapperProxyFactory<T>(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.
            
            // 比如解析接口方法上的@Select(slect * from a)
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

好吧,就是new了个MapperAnnotationBuilder,然后parse

MapperAnnotationBuilder

下面的parse是addMapper的parse,即对接口的parse,把xml的信息和注解信息都放到接口中

接下来依次解析xml和注解形式的sql。注解会覆盖xml的

// MapperAnnotationBuilder.java
public void parse() {
    // type是接口的名字 ,接口的toString方法是形如"interface mapper.UserMapper"
    String resource = type.toString();
    // 判断接口是否被加载过
    if (!configuration.isResourceLoaded(resource)) {
        // 如果这个接口关联着之前的xml解析出来的内容,那么添加一下 // 如果我们在<mappers>标签里指定的是class,那么进入到这里去加载对于的xml,而resource的方式我们已经加载过了,所以进去又跳出来了
        loadXmlResource();
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        parseCache();
        parseCacheRef();
        // 获取接口中的方法
        Method[] methods = type.getMethods();
        // 遍历接口当前的方法看有没有注解 // 理论上接口上的注解会覆盖xml里的,但是实际上是报错
        for (Method method : methods) {
            try {
                // 解析注解,没有注解又跳出来了
                parseStatement(method);//解析xml的时候把sql语句翻译成MappedStatement对象,加到configuration全局对象中//也就是放到configuration.mappedStatement这个StrictMap中(继承hashmap),这个map的特点是第二个put时直接抛异常,也就是避免了xml和注解方式的重复
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}


// 
private void loadXmlResource() {
    // config和MapperRegistry是全局唯一的,他们知道xml是否被解析过
    // 加载一下,而对于resource方式的,在bindmapper阶段,我们已经加过了,所以不进if
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        // 拼凑接口对应的xml地址
        String xmlResource = type.getName().replace('.', '/') + ".xml";
        InputStream inputStream = null;
        try {
            // 看有没有xml文件
            inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e) {
            // ignore, resource is not required
        }
        // 是否存在xml一个文件
        if (inputStream != null) {
            // 又是一个XML Builder,解析配置文件
            XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(),
                                                              xmlResource, // 
                                                              configuration.getSqlFragments(), type.getName());
            xmlParser.parse();
        }
    }
}
@Select

在刚才的解析中,我们说了如果是xml没有相应注解的话直接跳出来了,如果有注解的话会进入parseStatement,他的最终结果也是往configuration里加了个mappedStatement,id和xml的一样,也就是说,注解会覆盖xml的

parseStatement处理接口的方法
void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    // 
    LanguageDriver languageDriver = getLanguageDriver(method);
    // SqlSource是接口方法上的注解,相应方法上没有的话就是null
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    // 如果没有注解的话就拿不到sqlSource
    if (sqlSource != null) {
        Options options = method.getAnnotation(Options.class);
        // 全类名+方法,mapper.UserMapper.insertUser
        final String mappedStatementId = type.getName() + "." + method.getName();
        Integer fetchSize = null;
        Integer timeout = null;
        StatementType statementType = StatementType.PREPARED;
        ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
        SqlCommandType sqlCommandType = getSqlCommandType(method);
        // 是@Select还是@Insert。。。
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        // 如果不是select的话,说明要清空缓存
        boolean flushCache = !isSelect;
        boolean useCache = isSelect;

        KeyGenerator keyGenerator;
        String keyProperty = "id";
        String keyColumn = null;
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // first check for SelectKey annotation - that overrides everything else // 为null
            SelectKey selectKey = method.getAnnotation(SelectKey.class);
            if (selectKey != null) {
                keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
                keyProperty = selectKey.keyProperty();
            } else if (options == null) {
                keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
            } else {
                keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
                keyProperty = options.keyProperty();
                keyColumn = options.keyColumn();
            }
        } else {
            keyGenerator = new NoKeyGenerator();
        }

        // null,不进入
        if (options != null) {
            if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
                flushCache = true;
            } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
                flushCache = false;
            }
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            resultSetType = options.resultSetType();
        }

        String resultMapId = null;
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        // 不进入
        if (resultMapAnnotation != null) {
            String[] resultMaps = resultMapAnnotation.value();
            StringBuilder sb = new StringBuilder();
            for (String resultMap : resultMaps) {
                if (sb.length() > 0) {
                    sb.append(",");
                }
                sb.append(resultMap);
            }
            resultMapId = sb.toString();
            //不是select类型,也不进入,没有ResultMap的事儿
        } else if (isSelect) {
            resultMapId = parseResultMap(method);
        }

        // 注解同样是add一个mappedStatement
        assistant.addMappedStatement(
            mappedStatementId,//mapper.UserMapper.insertUser
            sqlSource,
            statementType,
            sqlCommandType,
            fetchSize,
            timeout,
            // ParameterMapID
            null,
            parameterTypeClass,
            resultMapId,
            getReturnType(method),
            resultSetType,
            flushCache,
            useCache,
            // TODO gcode issue #577
            false,
            keyGenerator,
            keyProperty,
            keyColumn,
            // DatabaseID
            null,
            languageDriver,
            // ResultSets
            options != null ? nullOrEmpty(options.resultSets()) : null);
    }
}

上面解析了如<select>标签和注解,把最终得到的信息都设置到了configuration属性的mappedStatements属性中。是个StrictMap

【静态sql#{}】

要注意一下头几行

void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 从注解获取sql
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    .....
}

private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
        // 获取sql的注解类型
        Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
        Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
        if (sqlAnnotationType != null) {
            if (sqlProviderAnnotationType != null) {
                throw new BindingException("不能同时提供静态SQL和 SqlProvider");
            }
            Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
            final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
            // 进入
            return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
        } else if (sqlProviderAnnotationType != null) {
            Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
            return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
        }
        return null;
    } catch (Exception e) {
        throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
}
// 此处我们只分析带有@Select注解的方法,所有就是进入if条件中进行处理,这里再进入buildSqlSourceFromStrings方法。
private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
    final StringBuilder sql = new StringBuilder();
    for (String fragment : strings) {
        sql.append(fragment);
        sql.append(" ");
    }
    // 进入
    return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    SqlSource source = super.createSqlSource(configuration, script, parameterType);
    checkIsNotDynamic(source);
    return source;
}

@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    // issue #3
    if (script.startsWith("<script>")) {
        XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
        return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
    } else {
        // issue #127
        script = PropertyParser.parse(script, configuration.getVariables());
        TextSqlNode textSqlNode = new TextSqlNode(script);
        if (textSqlNode.isDynamic()) {
            return new DynamicSqlSource(configuration, textSqlNode);
        } else {
            return new RawSqlSource(configuration, script, parameterType);
        }
    }
}

// 构造函数
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}

在两个方法中做了最后的sql包装,在createSqlSource方法中,第一种是处理带有**

public boolean isDynamic() {
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    GenericTokenParser parser = createParser(checker);
    parser.parse(text);
    return checker.isDynamic();
  }

这里实例化了一个DynamicCheckerTokenParser对象,但是进这个实例化方法中可以看到并没有做什么事情,主要是createParser中,转到createParser方法。

private GenericTokenParser createParser(TokenHandler handler) {
    return new GenericTokenParser("${", "}", handler);
}

然后下一步对是否有"${", "}"进行判断,parser.parse(text);,最后返回是否返回是否是动态sql,在这毫无疑问的是我们前面写的方法中,第一个是返回的RawSqlSource对象,第二个返回的是DynamicSqlSource对象

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    // 此处判断sql中是否含有"#{""}",parser.parse(originalSql)中解析源码的过程比较复杂,没有太多可以说的,但是得说下占位符的替换,在替换参数是最终会调用到SqlSource中的handlerToken方法。
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

@Override
public String handleToken(String content) {
    parameterMappings.add(buildParameterMapping(content));
    return "?";
}
此处进行了参数的替换,以及对字段的映射。

然后最终返回StaticSqlSource对象,所以此处RawSqlSource最后还是返回StaticSqlSource对象,然后在StaticSqlSource对象中执行getBoundSql方法,可以进StaticSqlSource的getBoundSql中看看
    
@Override
  public BoundSql getBoundSql(Object parameterObject) {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }

二、创建sqlSession

前面做了什么:为每个接口生成了个mapperProxyFactory

SqlSession是一个接口,它有两个实现类:DefaultSqlSession(默认)和SqlSessionManager(弃用,不做介绍)

SqlSession是MyBatis中用于和数据库交互的顶层类,通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,并且在使用完毕后需要close。

在这里插入图片描述

SqlSession中的两个最重要的参数,configuration与初始化时的相同,Executor为执行器,

2.1 创建sqlSession

解析xml的过程就是创建SqlSessionFactory的过程,我们解析完上面的xml文件后,得到了一个DefaultSqlSessionFactory对象。调用它的openSession方法就得到了一个SqlSession,每个SqlSession对应一种执行器

①openSession

当执行openSession()操作的时候,就是给当前会话创建一个执行器

//  DefaultSqlSessionFactory.java
public SqlSession openSession(boolean autoCommit) {
    // 传入configuration里的执行器类型,默认是simple
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        //通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
        // 获得environment标签解析出来的内容
        final Environment environment = configuration.getEnvironment();
        // 从Environment配置信息获取事务工厂
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);//new JdbcTransaction(ds, level, autoCommit);
        // 初始化事务, 此时事务持有dataSource对象
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//return new JdbcTransaction(ds, level, autoCommit);
        
        // ****核心代码*****
        // 根据 事务和execType创建Executor对象, Executor通过Transaction间接持有DataSource对象
        // 通过执行器执行查询sql
        final Executor executor = configuration.newExecutor(tx, 
                                                            execType);// enum ExecutorType{ SIMPLE,REUSE,BATCH}
        // 初始化SqlSession对象, SqlSession持有configuration和executor引用
        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();
    }
}

从上面知道,在返回DefaultSqlSession之前,new了个执行器configuration.newExecutor(tx, execType);

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // executorType如果未配置, 默认为Simple
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    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);
    }
    // 未配置默认开启
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    
    
    // 通过拦截器加载plugin, 返回对象为代理对象
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

public Object pluginAll(Object target) {
    // 此处遍历Plugins标签内所有Plugin进行加载
    // 通过代理对象再代理模式, 保证每一个拦截器都能被执行
    // 此处注意点 : 代理对象可以被再次代理
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);//代理对象可以被再次代理
    }
    return target;
  }

2.2、创建执行器

Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。Executor是Mybatis的四大对象之一

Executor会在执行openSession()的时候创建,返回的sqlSession里包含了指定的执行器。执行器分3种:

  • SimpleExecutor(普通的执行器,默认)
  • BatchExecutor(重用语句并执行批量更新)
  • ReuseExecutor(重用预处理语句prepared statements)
img

上层接口是 SimpleExecutor/ReuseExecutor/SimpleExecutor–>BaseExecutor–>Executor

  • BaseExecutor持有一级缓存属性
  • cachingExecutor管理二级缓存,装饰者模式。持有Executor delegate;
  • cachingExecutor–>Executor,这个执行器里面有个类型为Executor的delegate属性(委托/装饰)

https://tech.meituan.com/2018/01/19/mybatis-cache.html

public abstract class BaseExecutor implements Executor {
    // BaseExecutor持有一级缓存localCache,类型是PerpetualCache
    protected PerpetualCache localCache;

    protected Configuration configuration;
    
//=========CachingExecutor.java=============
public class CachingExecutor implements Executor {

    // cache在MappedStatement中持有
  private Executor delegate;
  private boolean autoCommit; // issue #573. No need to call commit() on autoCommit sessions
  private TransactionalCacheManager tcm = new TransactionalCacheManager();

  private boolean dirty;

Cache: MyBatis中的Cache接口,提供了和缓存相关的最基本的操作,如下图所示:

img

有若干个实现类,使用装饰器模式互相组装,提供丰富的操控缓存的能力,部分实现类如下图所示:

img

BaseExecutor成员变量之一的PerpetualCache,是对Cache接口最基本的实现,其实现非常简单,内部持有HashMap,对一级缓存的操作实则是对HashMap的操作。如下代码所示:

public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
②newExecutor
    // Configuration.java // 从openSessionFromDataSource()中过来
public Executor newExecutor(Transaction transaction, 
                            ExecutorType executorType, 
                            boolean autoCommit) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 3种执行器
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);//基本就创建了一个一级缓存map而已
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        executor = new SimpleExecutor(this, transaction);
    }

    // 如果二级缓存开启,是使用CahingExecutor装饰BaseExecutor的子类(装饰者模式)
    if (cacheEnabled) {
        executor = new CachingExecutor(executor, autoCommit);
    }
    // 插件 拦截器,后面讲  // 通过代理对象再代理模式, 保证每一个拦截器都能被执行
    executor = (Executor) interceptorChain.pluginAll(executor);//Configuration.interceptorChain
    return executor;
}
/*
默认情况下会返回一个SimpleExecutor对象。然后SimpleExecutor被封装到DefaultSqlSession。
这里我们需要注意一下,在Executor创建完毕之后,会根据配置是否开启了二级缓存,来决定是否使用CachingExecutor包装一次Executor。最后尝试将executor使用interceptorChain中的每个interceptor包装一次(根据配置),这里是对Mybatis强大的插件开发功能做支持。
*/
new SimpleExecutor
public class SimpleExecutor extends BaseExecutor {
    // 构造器
    public SimpleExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }
    // 父类构造器
    protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.transaction = transaction;
        this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
        this.localCache = new PerpetualCache("LocalCache");
        this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
        this.closed = false;
        this.configuration = configuration;
    }

创建完后用二级缓存重新包装了一下(开启二级缓存情况下),装饰者的体现。

④interceptorChain.pluginAll(executor);

注意这里只是代理执行器

  • Executor 是 openSession() 的 时 候 创 建 的 ,创建之后即调用 InterceptorChain.pluginAll(),返回层层代理后的对象。
  • StatementHandler 是SimpleExecutor.doQuery()创建的,里面包含了处理参数的 ParameterHandler 和处理结果集的 ResultSetHandler 的创建,创建之后即调用 InterceptorChain.pluginAll(),返回层层代理后的对象。
// executor = (Executor) interceptorChain.pluginAll(executor);
public Object pluginAll(Object target) { // 此时目标对象是执行器
    for (Interceptor interceptor : interceptors) {// 解析xml阶段准备好的拦截器
        target = interceptor.plugin(target);// 
    }
    return target;
}

// 自定义的Interceptor实现类
public Object plugin(Object target) { // 此时目标对象是执行器
    return Plugin.wrap(target, this);// 创建代理
}

// Plugin.java
public static Object wrap(Object target,  // 此时目标对象是执行器
Interceptor interceptor) {// xml配置的handler拦截器
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
        // 动态代理
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap));
    }
    return target;
}
@Intercepts({ @Signature(type = Executor.class, 
                         method = "query", 
                         args = { MappedStatement.class,Object.class,RowBounds.class,ResultHanlder.class }) })
public class MyPageInterceptor implements Interceptor{

还要注意到,在返回执行器前,还会执行器用拦截器包装了一下。

Executor 初始化时, 关联加载 Plugin 插件,并生成Executor代理对象,此处注意:代理对象可以被再次代理,而且只是代理了拦截器的插件,别的插件在查询阶段才代理

三、创建代理

流程:

  • 创建sqlSession
  • MapperProxyFactory全局每个类型只有一个
  • 但是每个会话都对应一个MapperProxy(每个类型都有自己的MapperProxy)
①getMapper

我们使用getMapper方法获取接口对应的代理对象。

当我们使用如下代码:

UserMapper mapper = session.getMapper(UserMapper.class);

来获取UserMapper的时候,实际上是从configuration当中的MapperRegistry当中获取UserMapper的代理对象:

// DefaultSqlSession
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);//session转交给configuration
}

// Configuration--------
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);//configuration交给mapperRegistry注册器
}

所以getMapper是在最后获取的

//MapperRegistry--------
// 通过mapper代理工厂得到一个mapper代理
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {//此时的T就相当于比如User
    // 根据传入的接口获取MapperProxyFactory // 工厂生产代理对象。在config中每个接口都有唯一的mapper代理工厂
    // knownMappers属性里面的值,实际上就是我们在mappers扫描与解析的时候放进去的mapper代理工厂。
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
        // 找不到借口对应的mapper代理工厂,就是说在解析阶段没有加载过这个接口
        throw new BindingException("MapperRegistry/config没解析过这个接口");
    try {
        // 动态代理 // 创建接口的动态代理 // 从mapperProxyFactory中创建MapperProxy,然后创建出代理对象
        return mapperProxyFactory.newInstance(sqlSession);//底层就是java动态代理
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}
②MapperProxyFactory

img

public class MapperProxyFactory<T> {
    // 该工厂对应的是哪个接口
    private final Class<T> mapperInterface;
    // Mapper.Statement执行缓存。缓存这MapperMethod
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

    // 构造器
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public T newInstance(SqlSession sqlSession) {
        // 根据sqlSession+接口 获取到MapperProxy,这个就是invocationHandler // 传入methodCache,免得每次调用还得重新代理
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, 
                                                              mapperInterface, 
                                                              methodCache);
        return newInstance(mapperProxy); 
    }


    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        //动态代理我们写的dao接口,获取动态代理实例
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), 
                                          new Class[] { mapperInterface }, 
                                          mapperProxy);
        // 从这里也可以看出mapperProxy肯定实现了InvocationHandler
    }

从上面可以知道,mapper最后就创建了个动态代理,其中的invocationHandler参数传入的是mapperProxy,

③MapperProxy

动态代理其实执行的是super.h.invoke()方法

大家都在说执行invoke方法,那么哪里可以找到呢?其实肉眼是找不到的,因为JVM启动后把代理类生成在了内存中,代理类内部执行对应的方法都是间接调用invoke方法

也就是说,获取到的UserMapper实际上是代理对象MapperProxy,所以我们执行查询语句的时候实际上执行的是MapperProxy的invoke方法:

img

每个MapperProxy对应一个dao接口, 那么咱们在使用的时候,MapperProxy是怎么做的呢

MapperProxy
  • 因为Mapper接口不能直接被实例化,Mybatis利用JDK动态代理,创建MapperProxy间接实例化Mapper对象。
  • MapperProxy还可以缓存MapperMethod对象
// mybatis里执行sql的自定义InvocationHandler
public class MapperProxy<T> implements InvocationHandler, Serializable {
    // 注意有几个属性
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    // 构造函数
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    // invocationHandler的invoke()方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**如果调用的是Object原生的方法,则直接放行*/
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        }
        // 是自定义方法
        // ①通过method从缓存中获取mapperMethod,获取不到就创建,通常都有,全局共用的
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        
        // ②执行方法execute,传入了session
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        // 尝试中缓存中获取
        MapperMethod mapperMethod = methodCache.get(method);
        // 缓存中没空,创建
        if (mapperMethod == null) {
            // 从缓存集合中未获取到Statment, 初始化当前Statement为MapperMethod
            // 并添加初始化后的MapperMethod到缓存集合
            // mapperInterface, sqlSession, methodCache在构造Mapper代理对象时已经通过MapperProxy初始化传递
            // MapperMethod初始化会从Configuration配置中初始化SqlCommand, MethodSignature信息
            // 表示当前Statment的SQL类型和状态等信息
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            // 添加当前Mapper.Statement到缓存中

            methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }
    // 可以看到,先根据方法签名,从方法缓存中获取方法,如果为空,则生成一个MapperMethod放入缓存并返回。
}

所以最终执行查询的是MapperMethod的execute方法:

④MapperMethod
  • 负责解析Mapper接口的方法,并封装成MapperMethod对象
  • 将Sql命令的执行路由到恰当的SqlSesison方法上
public class MapperMethod {

    // 2个属性
    // sql语句,能得到string
    private final SqlCommand command;// 保存了Sql命令的类型和键id
    private final MethodSignature method;// 保存了Mapper接口方法的解析信息
    // 构造器
    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);// 内部类,根据method的信息拿到类、方法 // 找到ms对象
        /*
        name = declaringInterface.getName() + "." + method.getName();//接口+方法拿到ms
        MappedStatement ms = configuration.getMappedStatement(name);
        type = ms.getSqlCommandType();
        */
        // 包装method,MethodSignature里有返回类型是void还是Many/Cursor
        this.method = new MethodSignature(config, mapperInterface, method);
    }
MapperMethod.execute
// MapperMethod.java
// 根据解析结果,路由到恰当的SqlSession方法上
public Object execute(SqlSession sqlSession, //传入session
                      Object[] args) {
    Object result;
    // command.getType()拿到sql是增删改查哪一种
    if (SqlCommandType.INSERT == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        // select查询语句
    } else if (SqlCommandType.SELECT == command.getType()) {
        // 查看该sql的返回值类型。注意这里只是预先设置好返回值怎么处理,并没有执行呢*/
        // 当返回值为空
        if (method.returnsVoid() && method.hasResultHandler()) {
            executeWithResultHandler(sqlSession, args);// 底层也是selectOne方法
            result = null;
            /** 当返回many(list)的时候*/
        } else if (method.returnsMany()) {
            result = executeForMany(sqlSession, args);
            /**当返回值类型为Map时*/
        } else if (method.returnsMap()) {
            // 内部也是调用convertArgsToSqlCommandParam和selectMap
            result = executeForMap(sqlSession, args);
        } else {// 就返回了一个对象
            // 底层也是selectOne方法
            Object param = method.convertArgsToSqlCommandParam(args);
            // 调用SqlSession单条数据查询执行方法
            // SqlSession在初始化SqlSession分析中已经指明, 此处应为DefaultSqlSession
            // 单数据查询, 且查询结果为 VO
            result = sqlSession.selectOne(command.getName(), param);
        }
    } else {
        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;
}

其中的execute()方法,如果是SqlSessionTemplate的话就是this.sqlSessionProxy.<T> selectOne(stmt,mapKey)

executeForMany会解析是否有@Param注解和是否有分页参数

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    // 如果是对个参数的话,返回值是param是一个map
    Object param = method.convertArgsToSqlCommandParam(args);
    // 看参数中有没有rowBound
    if (method.hasRowBounds()) {
        // 如果有分页参数的话,就根据arg数组拿到分页参数
        RowBounds rowBounds = method.extractRowBounds(args);// 抽离出rowBounds是第一个参数,并放回return (hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null);
        // 传入参数:语句、实参、rowBounds 
        result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
        result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
        if (method.getReturnType().isArray()) {
            return convertToArray(result);
        } else {
            return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        }
    }
    return result;
}
@Param】:convertArgsToSqlCommandParam

这个方法的作用就是解析@Param注解

@Param注解的用法:一个参数不用,多个参数必须用注解指定,否则默认的是param1/arg0

// 转化参数
public Object convertArgsToSqlCommandParam(Object[] args) {
    final int paramCount = params.size();
    if (args == null || paramCount == 0) {
        return null;
        /**参数没有标注@Param注解,并且参数个数为一个*/
    } else if (!hasNamedParameters && paramCount == 1) {
        return args[params.keySet().iterator().next()];
        /**否则执行这个分支*/
        // 这里参数解析如果判断参数一个只有一个(一个单一参数或者是一个集合参数),并且没有标注`@Param`注解,那么直接返回这个参数的值,否则会被封装为一个Map,然后再返回。
    } else {
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : params.entrySet()) {
            param.put(entry.getValue(), args[entry.getKey()]);
            // issue #71, add param names as param1, param2...but ensure backward compatibility
            final String genericParamName = "param" + String.valueOf(i + 1);
            if (!param.containsKey(genericParamName)) {
                param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
        }
        return param;
    }
}

解释参数问题:

例1:

/**接口为*/
User selectByNameSex(String name, int sex);
/**我们用如下格式调用*/
userMapper.selectByNameSex("张三",0);
/**参数会被封装为如下格式:*/
0 ---> 张三
1 ---> 0
param1 ---> 张三
param2 ---> 0

在这里插入图片描述

例2:

/**接口为*/
User selectByNameSex(@Param("name") String name, int sex);
/**我们用如下格式调用*/
userMapper.selectByNameSex("张三",0);
/**参数会被封装为如下格式:*/
name ---> 张三
1 ---> 0
param1 ---> 张三
param2 ---> 0

在这里插入图片描述

为什么arg和param是重复的内容还要重复写:这个jdk有关,这个机制让 jdk8之后不写@Param也能找到对应参数

四、开始查询

(1)使用mapper接口对象在spring中的Bean是mapper类的代理类 MapperProxy实例,调用对象查询方法selectABC。

(2)实际上会调用MapperProxy的invoke反射的方法。

(3)在invoke方法中会最终调用DefaultSqlSession类中的select方法,此方法中configuration对象根据selectABC方法的唯一id,获得MappedStatement元数据信息。并用BaseExecutor(SqlSession中已经注入executor)执行器执执行query并传入 MappedStatement。

(4)接着通过configuration.newStatementHandler会生成一个StatementHandler对象 ,并在构造器中调用生成parameterHandler和resultSetHandler,同时存储在StatementHandler对象中

(5)然后用handler生成一个java.sql.statement

(6)最后试用statemenet执行sql。

③selectOne

与spring整合的mapper的底层也是selectOne等方法,我们先了解selectOne

通过configuration.mappedStatement取得之前解析xml时准备好的sql

img

缓存:https://tech.meituan.com/2018/01/19/mybatis-cache.html

public <T> T selectOne(String statement) {
    return this.<T>selectOne(statement, null);//无参
}
public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    // 底层都是selectList()
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {// 如果只有一个元素,无需返回list,直接返回该元素
        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;
    }
}
public <E> List<E> selectList(String statement) {
    return this.<E>selectList(statement, null);// 无参
}
public <E> List<E> selectList(String statement, Object parameter) {
    return this.<E>selectList(statement, 
                              parameter, 
                              RowBounds.DEFAULT);// static :new RowBounds();
}
// select底层都是调用的这个
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 从config中获取我们之前解析xml时(还有注解的)准备好的mappedStatement
        MappedStatement ms = configuration.getMappedStatement(statement);
        
        
        // 执行器去执行
        // Executor初始化阶段已经被包装过
        // 如果有二级缓存,那么就被包装为CachingExecutor
        // 如果只开启了一级缓存的话,首先会进入BaseExecutor的query方法,实现类可能就是SimleExecutor
        List<E> result = executor.<E>query(ms, // 解析xml阶段封装好的mappedStatement
                                           wrapCollection(parameter),  // 包装参数成map
                                           rowBounds,  // 分页参数
                                           Executor.NO_RESULT_HANDLER);
        
        return result;
        
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}
//在执行query时,传入了一个wrapCollection
private Object wrapCollection(final Object object) {
    // 判断传入的参数是什么类型的,list / array
    if (object instanceof List) {
        StrictMap<Object> map = new StrictMap<Object>();
        map.put("list", object);
        return map;
    } else if (object != null && object.getClass().isArray()) {
        StrictMap<Object> map = new StrictMap<Object>();
        map.put("array", object);
        return map;
    }
    return object;
}
④CachingExecutor.query之CacheKey

接下来执行query()方法,如果开启了二级缓存,执行的就是CachingExecutor.query(),他只是看看缓存中有没有与sql一样的查询结果,有的话直接返回,没有的话再由普通的执行器会查询。二级缓存的map在MappedStatement中维护着,也就是我们通常说的二级缓存是namespace级别的缓存

// CachingExecutor:执行第一层 query() 方法
public <E> List<E> query(MappedStatement ms, 
                         Object parameter,// 包装的参数,实参 
                         RowBounds rowBounds, 
                         ResultHandler resultHandler) throws SQLException {
    
    // 获取MappedStatement的boundSql,得到sql对象, 包含Sql, 参数, 返回结果等信息
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建缓存数据 // 将MappedStatement的Id、SQL的offset、SQL的limit、SQL本身以及SQL中的参数传入了CacheKey这个类,最终构成CacheKey。
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 继续进行数据查询
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/*
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
//后面是update了sql中带的参数
cacheKey.update(value);
*/

// 如果只开启了一级缓存的话,首先会进入BaseExecutor的query方法。

在上述代码中,会先根据传入的参数生成CacheKey,进入该方法查看CacheKey是如何生成的,代码如下所示:

// new一个Cache,然后把起始偏移、limit等设置进行
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
//后面是update了sql中带的参数
cacheKey.update(value);

在上述的代码中,将MappedStatement的Id、SQL的offset、SQL的limit、SQL本身以及SQL中的参数传入了CacheKey这个类,最终构成CacheKey。以下是这个类的内部结构:

private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;

private int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;

public CacheKey() {
 this.hashcode = DEFAULT_HASHCODE;
 this.multiplier = DEFAULT_MULTIPLYER;
 this.count = 0;
 this.updateList = new ArrayList<Object>();
}

首先是成员变量和构造函数,有一个初始的hachcode和乘数,同时维护了一个内部的updatelist。在CacheKeyupdate方法中,会进行一个hashcodechecksum的计算,同时把传入的参数添加进updatelist中。如下代码所示:

public void update(Object object) {
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 
    count++;
    checksum += baseHashCode;
    baseHashCode *= count;
    hashcode = multiplier * hashcode + baseHashCode;

    updateList.add(object);
}

同时重写了CacheKeyequals方法,代码如下所示:

@Override
public boolean equals(Object object) {
    .............;
    for (int i = 0; i < updateList.size(); i++) {
        Object thisObject = updateList.get(i);
        Object thatObject = cacheKey.updateList.get(i);
        if (!ArrayUtil.equals(thisObject, thatObject)) {
            return false;
        }
    }
    return true;
}

除去hashcode、checksum和count的比较外,只要updatelist中的元素一一对应相等,那么就可以认为是CacheKey相等。只要两条SQL的下列五个值相同,即可以认为是相同的SQL。

Statement Id + Offset + Limmit + Sql + Params

BaseExecutor的query方法继续往下走,代码如下所示:

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

如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。

query方法执行的最后,会判断一级缓存级别是否是STATEMENT级别,如果是的话,就清空缓存,这也就是STATEMENT级别的一级缓存无法共享localCache的原因。代码如下所示:

if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache();
}

在源码分析的最后,我们确认一下,如果是insert/delete/update方法,缓存就会刷新的原因。

SqlSessioninsert方法和delete方法,都会统一走update的流程,代码如下所示:

@Override
public int insert(String statement, Object parameter) {
    return update(statement, parameter);
}
@Override
public int delete(String statement) {
    return update(statement, null);
}

update方法也是委托给了Executor执行。BaseExecutor的执行方法如下所示:

@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 清空缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
}

每次执行update前都会清空localCache

至此,一级缓存的工作流程讲解以及源码分析完毕。

CachingExecutor.query()
// CachingExecutor:执行第二层 query() 方法
public <E> List<E> query(MappedStatement ms, 
                         Object parameterObject, 
                         RowBounds rowBounds, 
                         ResultHandler resultHandler, 
                         CacheKey key, 
                         BoundSql boundSql) throws SQLException {
    // useCache属性 // select的二级缓存 // <setting name="cacheEnabled" value="true"/> // userCache是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询
    Cache cache = ms.getCache();
    if (cache != null) {
        flushCacheIfRequired(ms);// 是否清空缓存?如果是update的话
        if (ms.isUseCache() && resultHandler == null) { 
            ensureNoOutParams(ms, parameterObject, boundSql);
            if (!dirty) {
                cache.getReadWriteLock().readLock().lock();
                try {
                    @SuppressWarnings("unchecked")
                    List<E> cachedList = (List<E>) cache.getObject(key);
                    if (cachedList != null) return cachedList;
                } finally {
                    cache.getReadWriteLock().readLock().unlock();
                }
            }
            
            // 执行器的delegate
            List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
            tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
            return list;
        }
    }
    // 最常用的是派给simple
    // 缓存中没有数据, 装饰者模式, 经过BaseExecutor转发到SimpleExecutor继续进行数据查询
    return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
【sql注入】getBound

解析完xml后:

  • 静态sql:#变成?
  • 动态sql:#和$都不会变

执行sql时:执行到CachingExecutor.query的ms.GetBoundSql时

  • 对于静态sql:无变化
  • 对于动态sql:#变成?,而$变成具体语句了

?会用指定的XXXTypeHandler处理

总结:

  • $:在执行sql语句之前就要赋值
  • #:执行sql语句时用jdbc的set

jdbc的set会将?变成"",比如我们有如下java语句

PreparedStatement preparedStatement = connection.PreparedStatement("select * from stu where name=? and passwd=? ");
preparedStatement.setString(1,"zs");
preparedStatement.setString(2,"123");
// 解析完会变成
"select * from stu where name='zs' and passwd='123' "

所以比如我们在select自己写了个?:

PreparedStatement preparedStatement = connection.PreparedStatement("select ? from stu where name=? and passwd=? ");
preparedStatement.setString(1,"id");
preparedStatement.setString(2,"zs");
preparedStatement.setString(3,"123");
// 解析完会变成
select "id"。。。

显然是查不到想要的结果的,不能加""

二级缓存介绍

一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。

img

二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。

当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

BaseExecutor.query

BaseExecutor:装饰者模式,转发到 BaseExecutor 继续执行 query() 方法

// simple // BaseExecutor.java
@SuppressWarnings("unchecked")
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.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        // 清空数据缓存
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        // 从本地一级缓存中获取
        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();
        }
        deferredLoads.clear(); // issue #601
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache(); // issue #482
        }
    }
    return list;
}
queryFromDatabase

缓存未加载到,从数据库中获取有效数据

// BaseExecutor.java
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
     // 添加特殊表示, 标识当前Statemnt存在语句正在执行
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 执行器调用doQuery // Query是父类的方法,doQuery是子类的方法
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        // 执行完成后移除该标识
        localCache.removeObject(key);
    }
    // 执行后将结果添加到一级缓存
    localCache.putObject(key, list);
    // 在query方法执行的最后,会判断一级缓存级别是否是STATEMENT级别,如果是的话,就清空缓存,这也就是STATEMENT级别的一级缓存无法共享localCache的原因。
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

img

只有第一次真正查询了数据库,后续的查询使用了一级缓存。

在修改操作后执行的相同查询,查询了数据库,一级缓存失效

一级缓存只在数据库会话内部共享。

SimpleExecutor.doQuery()【创建stmt处理器,处理stmt,执行,获取结果】

执行器有多种,StatementHandler也有多种

接着调用doQuery方法:

// SimpleExecutor.java
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();
        
        // 1
        /**这里出现了Mybatis四大对象中的StatementHandler   */
        // 获取StatementHandler, 生成已经被Plugin代理的执行器 // RoutingStatementHandler 
        // 此时他顺便创建了四大对象的newStatementHandler、newResultSetHandler、newResultSetHandler
        // 在里面已经用过JDBCConnection了
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        
        // 2
        // 获取数据库连接, 并注册相关事务 
        // jdbc的setString在这里面发生
        stmt = prepareStatement(handler, ms.getStatementLog());
        
        // 3 执行器里的.doQuery里调用StatementHandler.query
        // StatementHandler数据执行
        return handler.<E>query(stmt, resultHandler);//StatementHandler先去执行,然后处理结果
    } finally {
        closeStatement(stmt);
    }
}

可见Executor委托给StatementHandler执行查询,在此之前有一个预编译的过程(prepareStatement方法),StatementHandler接口的实现类:CallableStatementHandlerPreparedStatementHandler,SimpleStatementHandler对应JDBC中的CallableStatement,PreparedStatementStatement,分别的执行方法:
img

img

img

1 newStatementHandler【创建stmt处理器】

初始化 StatementHandler 处理器

// Configuration.java
public StatementHandler newStatementHandler(Executor executor, 
                                            MappedStatement mappedStatement, 
                                            Object parameterObject, 
                                            RowBounds rowBounds, 
                                            ResultHandler resultHandler, 
                                            BoundSql boundSql) {

    // RoutingStatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, 
                                                                    mappedStatement, 
                                                                    parameterObject, 
                                                                    rowBounds, 
                                                                    resultHandler, 
                                                                    boundSql);

    // 循环插件生成StatementHandler的代理对象, 此处代理为循环代理
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}
1.1 SimpleStatementHandler
public class RoutingStatementHandler implements StatementHandler {

    // 持有stmtHandler
    private final StatementHandler delegate;
    
    // 构造器
    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {


        // 根据getStatementType()进行内部初始化, 此次初始化为PreparedStatementHandler
        switch (ms.getStatementType()) {
            case STATEMENT:
                // 在这里默认调用了父类BaseStatementHandler的构造函数
                // 他里面有newParameterHandler、newResultSetHandler,即mybatis四大对象new了3个
                delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                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());
        }

    }
public class SimpleStatementHandler extends BaseStatementHandler {
    public SimpleStatementHandler(Executor executor, 
                                  MappedStatement mappedStatement, 
                                  Object parameter, 
                                  RowBounds rowBounds, 
                                  ResultHandler resultHandler, 
                                  BoundSql boundSql) {
        // 调用BaseStatementHandler
        super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
    }

在创建StatementHandler的同时,应用插件功能,同时创建了Mybatis四大对象中的另外两个对象:

protected BaseStatementHandler(Executor executor, 
                               MappedStatement mappedStatement, 
                               Object parameterObject, 
                               RowBounds rowBounds, 
                               ResultHandler resultHandler, 
                               BoundSql boundSql) {
    ……;
    ……;
    ……;
    
    // Mybatis四大对象中的ParameterHandler,也会代理
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, 
                                                              parameterObject, 
                                                              boundSql);
    
    /**Mybatis四大对象中的ResultSetHandler*/ 
    this.resultSetHandler = configuration.newResultSetHandler(executor, 
                                                              mappedStatement, 
                                                              rowBounds, 
                                                              parameterHandler, 
                                                              resultHandler, 
                                                              boundSql);
}

1.2 newParameterHandler

同时在创建的时候,也运用到了插件增强功能:

// =====newParameterHandler=====
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, 
                                            Object parameterObject, 
                                            BoundSql boundSql) {
    
    ParameterHandler parameterHandler = mappedStatement.getLang()
        .createParameterHandler(mappedStatement, parameterObject, boundSql);
    
    // 代理 pluginAll
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}
1.2 newResultSetHandler【创建结果集代理】
//  =====newResultSetHandler=====
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, 
                                            RowBounds rowBounds, 
                                            ParameterHandler parameterHandler,
                                            ResultHandler resultHandler, // resultHandler
                                            boundSql) {
    
    ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? 
        new NestedResultSetHandler(executor, 
                                   mappedStatement, 
                                   parameterHandler, 
                                   resultHandler, 
                                   boundSql,rowBounds) : 
    	new FastResultSetHandler(executor, 
                                 mappedStatement, 
                                 parameterHandler, 
                                 resultHandler, 
                                 boundSql, 
                                 rowBounds);
    
    // 代理 pluginAll
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}


//==============
public FastResultSetHandler(Executor executor, MappedStatement mappedStatement, 
                            ParameterHandler parameterHandler, ResultHandler resultHandler, 
                            BoundSql boundSql, RowBounds rowBounds) {
    this.executor = executor;
    this.configuration = mappedStatement.getConfiguration();
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
    this.parameterHandler = parameterHandler;
    this.boundSql = boundSql;
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();//
    this.resultHandler = resultHandler;//
    this.proxyFactory = configuration.getProxyFactory();//
    this.resultExtractor = new ResultExtractor(configuration, objectFactory);
  }
2 【jdbc.set】prepareStatement

预编译sql产生ps

// SimpleExecutor.java
private Statement prepareStatement(StatementHandler handler, 
                                   Log statementLog) 
    throws SQLException {
    
    Statement stmt;
    // 初始化数据库连接 // 通过JdbcTransaction获取连接
    Connection connection = getConnection(statementLog);
    
    // StatementHandler初始化PreparedStatement预编译
    stmt = handler.prepare(connection, transaction.getTimeout());//handler是RoutingStatementHandler,会装饰者模式转到别的PreparedStatement
    
    // 封装Statement对象到StatementHandler处理器中
    // jdbc的setString等
    handler.parameterize(stmt);
    
    return stmt;
}
2.1 getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
    
    // 从事务中获取连接
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
        return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
        return connection;
    }
}

protected void openConnection() throws SQLException {
    // dataSource已经封装好了数据库连接信息, 获取数据库连接
    connection = dataSource.getConnection();
    if (level != null) {
        connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
}
2.2 prepare
// RoutingStatementHandler.java
private final StatementHandler delegate;

public Statement prepare(Connection connection) throws SQLException {
    return delegate.prepare(connection);
}

// BaseStatementHandler.java
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
        // 从子类实现中获取最终的Statement
        // 此时子类实现为PreparedStatement
        statement = instantiateStatement(connection);
        setStatementTimeout(statement, transactionTimeout);
        setFetchSize(statement);
        return statement;
    } catch (SQLException e) {
        closeStatement(statement);
        throw e;
    } catch (Exception e) {
        closeStatement(statement);
        throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
}
2.3 stmtHandler.parameterize(stmt);
// RoutingStatementHandler.java
public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
}
// PreparedStatementHandler.java
public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
}

// DefaultParameterHandler.java
public void setParameters(PreparedStatement ps) throws SQLException {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 拿到参数映射关系,执行到这里只有?没有其他内容
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
        MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    value = metaObject == null ? null : metaObject.getValue(propertyName);
                }
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
                // 用typeHandler设置参数,i+1轮询,for是0开始,jdbc是1开始 // 底层是jdbc.set
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
            }
        }
    }
}

// BaseTypeHandler.java
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    // 判断参数是否为空
    if (parameter == null) {
        if (jdbcType == null) {
            throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
        }
        try {
            // 
            ps.setNull(i, jdbcType.TYPE_CODE);
        } catch (SQLException e) {
            throw new TypeException("给#i设置null是非法的,他的类型是jdbcType " );
        }
    } else {
        // 参数非空时
        setNonNullParameter(ps, i, parameter, jdbcType);
    }
}

// UnknownTypeHandler.java
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
    throws SQLException {
    // 根据参数和jdbc类型找到TypeHandler
    TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
    handler.setParameter(ps, i, parameter, jdbcType);
}

// StringTypeHandler.java
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
    throws SQLException {
    // jdbc的set方法
    ps.setString(i, parameter);
}
protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      // 通过数据库连接, 获取预编译平台
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

9,StatementHandler 处理器,进行最终结果集获取

​ * 此时的 StatementHandler 对象是被拦截器插件代理后的代理对象,会通过动态代理方式进行执行,生成代理方法如下

public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // 存在有效插件
    if (interfaces.length > 0) {
      // 对target即executor进行代理
      // 外部循环调用, 则此时executor对象可能已经是代理对象
      // 允许对代理对象再次进行代理
      // 最终执行时, 装箱代理, 拆箱调用, 类似装饰者模式一层层进行处理
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

​ * StatementHandler.query()方法调用,会先执行 Plugin.invoke()方法

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)) {
            return interceptor.intercept(new Invocation(target, method, args));
        }
        return method.invoke(target, args);
    } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
    }
}
2.4 typeHandler.setParameter(ps
3 执行handler.query

再回顾一下是怎么从执行器的来到stmtHandler.的

// SimpleExecutor.java
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();
        
        // 1
        // 获取StatementHandler, 生成已经被Plugin代理的执行器 // RoutingStatementHandler 
        // 此时他顺便创建了四大对象的newStatementHandler、newResultSetHandler、newResultSetHandler
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        
        // 2
        // 获取数据库连接, 并注册相关事务 
        // jdbc的setString在这里面发生
        stmt = prepareStatement(handler, ms.getStatementLog());
        
        // 3 执行器里的.doQuery里调用StatementHandler.query
        // StatementHandler数据执行
        return handler.<E>query(stmt, resultHandler);//StatementHandler先去执行,然后处理结果
    } finally {
        closeStatement(stmt);
    }
}
public class RoutingStatementHandler implements StatementHandler {
    
    private final StatementHandler delegate;
    
    // stmtHandler.query
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        return this.delegate.query(statement, resultHandler);
    }

delegateStmtHandler.query

public class SimpleStatementHandler extends BaseStatementHandler {
    
    public <E> List<E> query(Statement statement, 
                             ResultHandler resultHandler) throws SQLException {
        String sql = this.boundSql.getSql();
        statement.execute(sql);
        // resultSetHandler
        return this.resultSetHandler.handleResultSets(statement);
    }
public class PreparedStatementHandler extends BaseStatementHandler {
    
    public <E> List<E> query(Statement statement, 
                             ResultHandler resultHandler) throws SQLException {
        // 转换Statement为JDBC原生的PreparedStatement
        PreparedStatement ps = (PreparedStatement) statement;
        // 通过JDBC底层执行, 并将执行结果封装到PreparedStatement中
        ps.execute();
        // resultSetHandler:结果集处理器, 对查询结果进行ORM映射
        return resultSetHandler.<E> handleResultSets(ps);
    }
// DefaultResultSetHandler
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    // 获取执行结果集, 并封装为ResultSetWrapper
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 获取Statement的结果映射, 后台会自动将resultType解析为一个ResultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
        // 逐个resultMap, 进行结果集解析
        ResultMap resultMap = resultMaps.get(resultSetCount);
        // 处理结果集, 映射数据库查询数据到reslutMap指定VO中
        handleResultSet(rsw, resultMap, multipleResults, null);
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
        while (rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                handleResultSet(rsw, resultMap, null, parentMapping);
            }
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    }
    // 返回处理结果集
    return collapseSingleResultList(multipleResults);
}

private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    ResultSet rs = stmt.getResultSet();
    while (rs == null) {
        // move forward to get the first resultset in case the driver
        // doesn't return the resultset as the first result (HSQLDB 2.1)
        if (stmt.getMoreResults()) {
            rs = stmt.getResultSet();
        } else {
            if (stmt.getUpdateCount() == -1) {
                // no more results. Must be no resultset
                break;
            }
        }
    }
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    // 获取类型映射注册信息
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    // 获取结果集
    this.resultSet = rs;
    // 获取数据库元数据列
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    // 逐行解析元数据列, 获取元数据列基本信息
    for (int i = 1; i <= columnCount; i++) {
      // 数据库列名, 支持别名
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      // 数据库列字段类型
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      // 数据库列字段类型映射的Java数据类型
      classNames.add(metaData.getColumnClassName(i));
    }
  }

数据库结果集解析,完成ORM映射

DefaultResultSetHandler:调用 handleResultSet() 处理结果集

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      // parentMapping 方法传参为null
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        // DefaultSqlSession.selectList方法中, 初始化resultHandler时为null
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          // 映射查询数据到VO中, 并添加最终VO到结果集中
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          // multipleResults结果集添加ORM映射后的数据
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }
// handleRowValues() 处理所有数据行
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
        ensureNoRowBounds();
        checkResultHandler();
        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
        // 不存在嵌套的单条数据查询, 走此方法
        handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    // 此处基于分页处理, 当前不涉及
    skipRows(rsw.getResultSet(), rowBounds);
    // 遍历每一个数据库查询对象, 进行数据ORM映射
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      // 此处应该意为区分不同的resultMap, 当前只存在一个resultMap, 暂时忽略
      // 此时resultMap只存在一个有效属性, 即type为resultType对应的VO
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      // 映射当前数据库数据到JavaBean, 此行结束后, 返回结果即为JavaBean对象
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      // 保存结果集到DefaultResultHandler中, 外部通过该对象获取结果集
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }

// 处理每一行数据映射 getRowValue()
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 初始化JavaBean对象, 初始化为零值, 此时未进行数据赋值
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    // hasTypeHandlerForResultObject : 判断当前结果集在JDBC->java的默认集合中是否存在
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      // 判断是否需要自动映射
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        // 进行自动映射
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
      // 涉及ResultMap映射处理
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      // 返回结果集
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    // 获取还没有建立属性映射的数据库字段
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      // 未建立映射字段不为空, 为当前属性分别set值
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }

保存并获取ORM映射后的结果集

​ * 保存结果集:映射完成后,会调用 DefaultResultSetHandler.storeObject() 方法,保存结果到 DefaultResultHandler.list属性中

// 保存结果集到DefaultResultHandler中, 外部通过该对象获取结果集
// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());



private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    // parentMapping传递为空
    if (parentMapping != null) {
        linkToParents(rs, parentMapping, rowValue);
    } else {
        callResultHandler(resultHandler, resultContext, rowValue);
    }
}

private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
    resultContext.nextResultObject(rowValue);
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}

public void handleResult(ResultContext<? extends Object> context) {
    list.add(context.getResultObject());
}


获取结果集:映射完成后,会从 DefaultResultHandler.list中获取有效结果集

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
multipleResults.add(defaultResultHandler.getResultList());

// org.apache.ibatis.executor.result.DefaultResultHandler#getResultList
  public List<Object> getResultList() {
    return list;
  }
4 获取结果
⑥ RoutingStatementHandler.query()
// FastResultSetHandler
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<Object>();
    final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    int resultSetCount = 0;
    // 获取结果
    ResultSet rs = stmt.getResultSet();

    while (rs == null) {
        // move forward to get the first resultset in case the driver
        // doesn't return the resultset as the first result (HSQLDB 2.1)
        if (stmt.getMoreResults()) {
            rs = stmt.getResultSet();
        } else {
            if (stmt.getUpdateCount() == -1) {
                // no more results.  Must be no resultset
                break;
            }
        }
    }

接下来就是执行正常的JDBC查询功能了,参数设置由ParameterHandler操作,结果集处理由ResultSetHandler处理。至此,整个查询流程结束。

流程

整个查询阶段的时序图可以用如下图表示:

在这里插入图片描述

整个查询流程,可以总结如下图:

在这里插入图片描述

五、插件与拦截器

插件源码级分析写到了另一篇中:https://blog.csdn.net/hancoder/article/details/110914534

插件的构建:

谈原理首先要知道StatementHandler,ParameterHandler,ResultHandler都是代理,他们是Configuration创建,在创建过程中会调用interceptorChain.pluginAll()方法,为四大组件组装插件(再底层是通过Plugin.wrap(target,XX, new Plugin( interceptor))来来创建的)。

插件链是何时构建的:

在执行SqlSession的query或者update方法时,SqlSession会通过Configuration创建Executor代理,在创建过程中就调用interceptor的pluginAll方法组装插件。然后executor在调用doQuery()方法的时候,也会调用Configuration的newStatementHandler方法创建StatemenHandler(和上面描述的一样,这个handler就是个代理,也是通过interceptorChain的pluginAll方法构建插件)

插件如何执行:

以statementhandler的prepare方法的插件为例,正如前面所说,statementHandler是一个proxy,执行他的prepare方法,将调用invokeHandler的invoke方法,而invokeHandler就是Plugin.wrap(target, xxx, new Plugin(interceptor))中的第三个参数,所以很自然invokeHanlder的invoke的方法最终就会调用interceptor对象的intercept方法。

@Intercepts({ @Signature(type = ResultSetHandler.class, 
                         method = "handleResultSets", 
                         args = { Statement.class }) })// 方法也可能被重载了多个,所以要告诉参数,指定是哪个方法
public class PageResultSetInterceptor implements Interceptor {

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        FastResultSetHandler resultSetHandler = (FastResultSetHandler) invocation.getTarget();
        MetaObject metaStatementHandler = MetaObject.forObject(resultSetHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        // 获取rowBounds参数
        RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("rowBounds");

        Object result = invocation.proceed();

        if (rowBounds instanceof Page) {
            metaStatementHandler.setValue("rowBounds.result", result);
        }
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

}
@Intercepts({ @Signature(type = Executor.class, 
                         method = "query", 
                         args = { MappedStatement.class,Object.class,RowBounds.class,ResultHanlder.class }) })
public class MyPageInterceptor implements Interceptor{
    public Object intercept(Invocation invocation) throws Throwable{
        //逻辑分页改为物理分页
        // 修改sql,添加limit
        Object[] args = invocation.getArgs();//query方法的参数
        // 获取MappedStatement
        MappedStatement ms = (MappedStatement) args[0];
        // 获取里面的boundSql,他是对sql和参数的封装
        BoundSql boundSql = ms.getBoundSql(args[1]).;// 需要传参数
        // 拿到原来的sql
        String sql = boundSql.getSql();
        // 拿到rowBounds
        RowBounds rowBounds = (RowBounds)args[2]
        sql =sql + " limit "+rowBounds.getOffset() + ","+rowBounds.getLimit();
        new BoundSql(ms.getConfiguration,sql,boundSql.getParameterMapping(),parameterObject);
        // 被代理的对象
        Executor exec = invocation.getTarget();
        return exec.query();
    }
}

六、缓存问题

在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示。

img

每个SqlSession中持有了Executor,每个Executor中有一个LocalCache(在Executor的父类BaseExecutor中)。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。具体实现类的类关系图如下图所示。

img

一级缓存配置

我们来看看如何使用MyBatis一级缓存。开发者只需在MyBatis的配置文件中,添加如下语句,就可以使用一级缓存。共有两个选项,SESSION或者STATEMENT,默认是SESSION级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。一种是STATEMENT级别,可以理解为缓存只对当前执行的这一个Statement有效。

<setting name="localCacheScope" value="SESSION"/>

一级缓存PerpetualCache

在一级缓存的介绍中提到对Local Cache的查询和写入是在Executor内部完成的。在阅读BaseExecutor的代码后发现Local CacheBaseExecutor内部的一个成员变量,如下代码所示。

public abstract class BaseExecutor implements Executor {
    protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
    // 本地缓存
    protected PerpetualCache localCache;

Cache: MyBatis中的Cache接口,提供了和缓存相关的最基本的操作,如下图所示:

img

有若干个实现类,使用装饰器模式互相组装,提供丰富的操控缓存的能力,部分实现类如下图所示:

img

BaseExecutor成员变量之一的PerpetualCache,是对Cache接口最基本的实现,其实现非常简单,内部持有HashMap,对一级缓存的操作实则是对HashMap的操作。如下代码所示:

public class PerpetualCache implements Cache {
  private String id;
  private Map<Object, Object> cache = new HashMap<Object, Object>();

在阅读相关核心类代码后,从源代码层面对一级缓存工作中涉及到的相关代码,出于篇幅的考虑,对源码做适当删减,读者朋友可以结合本文,后续进行更详细的学习。

二级缓存CachingExecutor

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);//拿到的是带?的sql
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

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, parameterObject, boundSql);
            if (!dirty) {
                cache.getReadWriteLock().readLock().lock();
                try {
                    @SuppressWarnings("unchecked")
                    List<E> cachedList = (List<E>) cache.getObject(key);
                    if (cachedList != null) return cachedList;
                } finally {
                    cache.getReadWriteLock().readLock().unlock();
                }
            }
            List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
            tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
            return list;
        }
    }
    return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);//BaseExecutor
}

MyBatis核心构件

名称作用
SqlSession作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
ExecutorMyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
ParameterHandler负责对用户传递的实参参数转换成JDBC Statement 所需要的参数
ResultSetHandler负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatementMappedStatement维护了一条select
SqlSource负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql表示动态生成的SQL语句以及相应的参数信息
ConfigurationMyBatis所有的配置信息都维持在Configuration对象之中

七、与spring的整合

我们在mybatis-spring官方看看整合有几种方式:http://mybatis.org/spring/zh/getting-started.html

7.1

我们整合时需要注册的bean,

  • SqlSessionFactoryBean:管理数据源,扫描xml文件(我们最终注入到spring中的mapper都是以工厂bean方式存在的)

    • <!-- 用来产生sql会话。sqlSessionFactory全局唯一,与configuration对应。 -->
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
          <!-- 加载数据源 -->
          <property name="dataSource" ref="dataSource"/>
          <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
      </bean>
      
    • SqlSessionFactoryBean他实现了FactoryBean接口,该接口有getObject()方法

  • 接口的动态代理(多选一)

    • MapperScannerConfigurer:负责扫描接口
    • MapperFactoryBean
    • @MapperScan
方式1 XMLbean
一个一个注入。sqlSessionFactory是MapperFactoryBean的属性
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
MapperFactoryBean

MapperFactoryBean是什么:每个对象和一个mapper接口对应

mybatis的每个mapper共用一个MapperFactoryBean对象,但是他的getObject()每次返回的不一样,是根据会话来的,每个sql会话里都有一个,即每个sqlSession里都有一个mapper对象。多会话不共用mapper

public class MapperFactoryBean<T> 
    extends SqlSessionDaoSupport //里面有一个sqlSession = new SqlSessionTemplate(sqlSessionFactory)
    implements FactoryBean<T> {

    private Class<T> mapperInterface;
    public void setMapperInterface(Class<T> mapperInterface) {//把接口通过构造传入
        this.mapperInterface = mapperInterface;
    }

    public T getObject() throws Exception {
        // 接下来就是mybatis的内容了,他内部是java动态代理
        return getSqlSession().// 获取了session的具体实现SqlSessionTemplate
            getMapper(this.mapperInterface);// mybatis的每个mapper共用一个MapperFactoryBean对象,但是他的getObject()每次返回的不一样,是根据会话来的,每个sql会话里都有一个,即每个sqlSession里都有一个mapper对象。多会话不共用mapper
    }
    
public abstract class SqlSessionDaoSupport extends DaoSupport {

    private SqlSession sqlSession;
    // 
    private boolean externalSqlSession;

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if (!this.externalSqlSession) {
            // 创建SqlSessionTemplate
            this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
        }
    }
    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSession = sqlSessionTemplate;
        this.externalSqlSession = true;
    }

    /** 用户使用该方法获取sqlSession去调用stmt方法。
    这个sqlSession被spring管理着,用户不应该commit/rollback/close操作这个sqlSession,因为会自动进行
   * @return 返回一个Spring管理的线程安全的SqlSession
   */
    public SqlSession getSqlSession() {
        return this.sqlSession;
    }

    protected void checkDaoConfig() {
        notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
    }
}
什么是sqlSessionTemplate
public class SqlSessionTemplate implements SqlSession {
    private final SqlSessionFactory sqlSessionFactory;

    private final ExecutorType executorType;

    private final SqlSession sqlSessionProxy;

    private final PersistenceExceptionTranslator exceptionTranslator;

    
    public <T> T selectOne(String statement) {
        return this.sqlSessionProxy.<T> selectOne(statement);
    }


    private class SqlSessionInterceptor implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 一些事务的操作。
        }
    }
}

sqlSessionFactory全局唯一,SqlSessionTemplate是sqlSession的具体实现,他是线程安全的。

方式2 @Bean
@Configuration
public class MyBatisConfig {
    @Bean // 这种方式还是一个一个注入
    public UserMapper userMapper() throws Exception {
        // 产生一个会话
        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());//sqlSessionFactory()方法是传入了容器中的单例bean:sqlSessionFactory
        return sqlSessionTemplate.getMapper(UserMapper.class);
    }
}

@Configuration
public class MyBatisConfig {
    @Bean // 与上面一样
    public MapperFactoryBean<UserMapper> userMapper() throws Exception {
        MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactory());
        return factoryBean;
    }
}
方式3 MapperScannerConfigurer

上面的MapperFactoryBean是和类型意义对应的。

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 指定扫描的包,如果存在多个包使用(逗号,)分割 -->
    <property name="basePackage" value="com.test.bean"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

如果配置了一个MapperScannerConfigurer.setAnnotationClass(Mapper.class)属性的话,那么就必须写@Mapper注解了。

所以我们不写@Mapper,也不要设置setAnnotationClass

方式4 @MapperScan
方式5 <mybatis:scan>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

  <mybatis:scan base-package="org.mybatis.spring.sample.mapper" />

</beans>

7.2 原理讲解

1 MapperScannerConfigurer工厂后置处理器

上面的前两种方式都是一一注入的方式,而345是扫描注入的方式。他们的底层用的都是MapperScannerConfigurer这个类,他是一个工厂后置处理器。而工厂后置处理器的创建时机在单例bean之前。

public class MapperScannerConfigurer 
    implements BeanDefinitionRegistryPostProcessor, //该接口继承了BeanFactoryPostProcessor
                InitializingBean, 
                ApplicationContextAware, 
                BeanNameAware {

看过我spring解读的都知道工厂后置处理器太关键了,他几乎在所有bean实例化前就实例化好了。注解看他实现的方法

// 工厂后置处理器接口 会实现的方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();// 把xml中${XXX}中的XXX替换成属性文件中的相应的值
    }

    // 看对registry执行了上面操作 
    // new ClassPathMapperScanner的时候把工厂传进去了 // 然后调用它的scan()方法扫描出所有的代理bd定义
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);//传入了工厂的bd注册器
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);//
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();//根据配置的属性生成对应的过滤器,然后这些过滤器在扫描的时候会起作用。
    // .scan()进行扫描工作 // 
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, //
                                                   ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
ClassPathMapperScanner

我们在上面看到,创建MapperScannerConfigurer这个bean的时候,因为是工厂后置处理器,所以会执行他的方法,该方法中会new ClassPathMapperScanner(registry),然后执行一个扫描的逻辑

@ComponentScan的底层就是ClassPathMapperScanner

这个类会扫描一个包,将下面的注解都new bd放到spring容器中。但是该类并不能扫描接口,所以我们需要继承该类重写方法isCandidateComponent()

ClassPathMapperScanner这个扫描器的主要的作用有以下几个:

  • 第一扫描basePackage包下面所有的class类

  • 第二将所有的class类(指定范围的)封装成为spring的ScannedGenericBeanDefinition sbd对象

  • 第三过滤sbd对象,只接受接口类,从下面的代码中可以看出。return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();

  • 第四完成sbd对象属性的设置,比如设置sqlSessionFactory、BeanClass等,这个sqlSessionFactory是本文接下来要解析的SqlSessionFactoryBean

    • sbd.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
      sbd.setBeanClass(MapperFactoryBean.class);
      sbd.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      
  • 第五将过滤出来的sbd对象通过这个BeanDefinitionRegistry registry注册器注册到DefaultListableBeanFactory中,这个registry就是方法postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)中的参数。

scan()
public class ClassPathBeanDefinitionScanner
    extends ClassPathScanningCandidateComponentProvider {

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

doScan()

doScan方法得到bds集合

不能同时使用sqlSessionTemplate and sqlSessionFactory。sqlSessionFactory被忽略

public class ClassPathMapperScanner 
    extends ClassPathBeanDefinitionScanner {

    // 得到bds
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 调用父类的doScan()
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

        if (beanDefinitions.isEmpty()) {//如果为空,日志warn扫描包为空
            logger.warn("在该包里找不到该mapper");
        } else {
            // 遍历
            for (BeanDefinitionHolder holder : beanDefinitions) {
                // new一个bd,然后开始创建某个接口对应的MapperFactoryBean,name为holder.getBeanName()
                GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
                // 给bd设置一些属性
                // mapper的类型是接口的代理,但是在容器中是MapperFactoryBean。添加一个属性
                definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());//先把原始的类型取出来塞到BD的属性里 dao.UserMapper
                // 设置成mapper工厂的类,他可以生产动态代理
                definition.setBeanClass(MapperFactoryBean.class);//然后重新给BD赋予class,这样这个bean的类型就是 MapperFactoryBean 
                definition.getPropertyValues().add("addToConfig", this.addToConfig);

                boolean explicitFactoryUsed = false;
                if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
                    definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
                    explicitFactoryUsed = true;
                } else if (this.sqlSessionFactory != null) {
                    definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
                    explicitFactoryUsed = true;
                }

                if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
                    // 不能同时使用sqlSessionTemplate and sqlSessionFactory。sqlSessionFactory被忽略
                    if (explicitFactoryUsed) {
                        logger.warn("不能同时使用sqlSessionTemplate and sqlSessionFactory。sqlSessionFactory被忽略");
                    }
                    definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
                    explicitFactoryUsed = true;
                } else if (this.sqlSessionTemplate != null) {
                    if (explicitFactoryUsed) {
                        logger.warn("不能同时使用sqlSessionTemplate和sqlSessionFactory。sqlSessionFactory被忽略");
                    }
                    definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
                    explicitFactoryUsed = true;
                }

                if (!explicitFactoryUsed) {
                    // 自动装配MapperFactoryBean
                    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
                }
            }
        }

        return beanDefinitions;
    }


    @Override // 指定要扫描出什么样的类。
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return (beanDefinition.getMetadata().isInterface() && // 得是接口
                beanDefinition.getMetadata().isIndependent());
    }

    @Override
    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
        if (super.checkCandidate(beanName, beanDefinition)) {
            return true;
        } else {
            // 跳过了某个接口的MapperFactoryBean,因为已经有同名定义了
            return false;
        }
    }
public class ClassPathBeanDefinitionScanner 
    extends ClassPathScanningCandidateComponentProvider {

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 至少一个包被指定
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        // 要返回的对象
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {
            // 查找组件
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

以上就是实例化MapperScannerConfigurer类的主要工作,总结起来就是扫描basePackage包下所有的mapper接口类,并将mapper接口类封装成为BeanDefinition对象,注册到spring的BeanFactory容器中。以下时序图不代表实际过程。

img

mapper接口注册之后,在什么地方实例化和使用呢?后面在分析。

2 @MapperScan
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {}

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private ResourceLoader resourceLoader;

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
                                        BeanDefinitionRegistry registry) {
        // 拿到@MapperScan注解中的属性
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        // 又是new ClassPathMapperScanner
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

        if (resourceLoader != null) { // this check is needed in Spring 3.1
            scanner.setResourceLoader(resourceLoader);
        }

        Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
            scanner.setAnnotationClass(annotationClass);
        }

        Class<?> markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
            scanner.setMarkerInterface(markerInterface);
        }

        Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
            scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
        }

        scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
        scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

        List<String> basePackages = new ArrayList<String>();
        for (String pkg : annoAttrs.getStringArray("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (String pkg : annoAttrs.getStringArray("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        
        scanner.registerFilters();
        
        // 调用ClassPathMapperScanner.doScan()注册bean定义
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }
3 SqlSessionFactoryBean 工厂bean

接着看看spring和mybatis整合的另外一个标签。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 加载数据源 -->
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
</bean>
首先看出他是个工厂bean,他是生成sqlSession的
public class SqlSessionFactoryBean 
    implements FactoryBean<SqlSessionFactory>, //工厂bean,关注getObject方法
				InitializingBean, // 那么当bean初始化的时候,spring就会调用该接口的实现类的afterPropertiesSet方法,去实现当spring初始化该Bean 的时候所需要的逻辑。
				ApplicationListener<ApplicationEvent> {//如果注册了该监听的话,那么就可以监听到Spring的一些事件,然后做相应的处理

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

        return this.sqlSessionFactory;//返回属性 SqlSessionFactory
    }

    // 初始化工厂bean的时候会调用此方法
    public void afterPropertiesSet() throws Exception { // sqlSessionFactory做定制的初始化
        // 确保非空之后直接调用buildSqlSessionFactory();
        notNull(dataSource, "Property 'dataSource' is required");
        notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");

        this.sqlSessionFactory = buildSqlSessionFactory();//调用了该管管
    }
buildSqlSessionFactory

SqlSessionFactoryBean.getObject()的时候会来到这里(如果sqlSessionFactory为空的话,第二次访问build方法的时候不会进来,因为已经拿到SqlSessionFactory了)。SqlSessionFactory全局唯一,他可以产生多个sql会话

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    // mybatis的全局config
    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configLocation != null) {
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();
    } else {
        // configLocation没有指定,使用默认的MyBatis Configuration
        configuration = new Configuration();
        configuration.setVariables(this.configurationProperties);
    }

    if (this.objectFactory != null) {
        configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
        configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (hasLength(this.typeAliasesPackage)) {
        String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                                                               ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeAliasPackageArray) {
            //Scanned package
            configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                                                                 typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        }
    }

    if (!isEmpty(this.typeAliases)) {
        for (Class<?> typeAlias : this.typeAliases) {
            configuration.getTypeAliasRegistry().registerAlias(typeAlias);//Registered type alias
        }
    }

    if (!isEmpty(this.plugins)) {
        for (Interceptor plugin : this.plugins) {
            configuration.addInterceptor(plugin);//Registered plugin
        }
    }

    if (hasLength(this.typeHandlersPackage)) {
        String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                                                                  ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeHandlersPackageArray) {
            configuration.getTypeHandlerRegistry().register(packageToScan);//扫描包
        }
    }

    if (!isEmpty(this.typeHandlers)) {
        for (TypeHandler<?> typeHandler : this.typeHandlers) {
            configuration.getTypeHandlerRegistry().register(typeHandler);//Registered type handler
        }
    }

    if (xmlConfigBuilder != null) {
        try {
            xmlConfigBuilder.parse();//解析configuration file:
        } catch (Exception ex) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    if (this.transactionFactory == null) {
        this.transactionFactory = new SpringManagedTransactionFactory();
    }

    // 持有事务和数据源等信息
    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);//设置到config中

    if (this.databaseIdProvider != null) {
        try {
            configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException e) {
            throw new NestedIOException("Failed getting a databaseId", e);
        }
    }

    // 遍历mapper,执行xmlMapperBuilder.parse(),行mapper的代理对象
    if (!isEmpty(this.mapperLocations)) {
        for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
                continue;
            }

            try {
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                                                                         configuration, mapperLocation.toString(), configuration.getSqlFragments());
                // 执行parse()
                xmlMapperBuilder.parse();
            } catch (Exception e) {
                throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
            } finally {
                ErrorContext.instance().reset();
            }

        }
    } else {
        if (logger.isDebugEnabled()) {
            logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
        }
    }
    // 将config中保存好的mapper代理对象肯定要放到ac中
    return this.sqlSessionFactoryBuilder.build(configuration);

调用完上面的afterPropertiesSet方法之后,第二个被调用的就是onApplicationEvent方法,这个方法的调用时机是,spring容器初始化完成之后,该方法是接口ApplicationListener<ApplicationEvent>中的方法。

public void onApplicationEvent(ApplicationEvent event) {
    if (this.failFast && event instanceof ContextRefreshedEvent) {
        this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
}

img

我们原来spring注册bean的时候,方式有<bean>、componentScan、@Service我们没有办法参与

我们mybatis创建完mapper后,mybatis能自己拿到,但怎么让spring拿到?

我们能让spring拿到的方式有

  • @Bean,我们可以在函数里面getMapper返回,但是这不是mybatis选择的方式
  • 或者是创建完后使用ac.getBeanFactory().registerSingleton(beanname,User.class);
  • factoryBean:跟@Bean一样得挨个注册,不实际
  • mybatis选择的方式是beanFactory的API
    • 我们弄完beanFactory后,不能直接@Service,那样spring就直接接管了
    • 利用spring提供的扩展
    • 存到了MutablePropertyValues

在前面介绍Mybatis初始化过程时提到,SqlSessionFactoryBuilder会通过XMLConfigBuilder等对象读取mybatis-config.xml配置文件以及映射配置信息,得到Configuration对象,然后创建SqlSessionFactory对象。而在spring集成式,mybatis中的SqlSessionFactory对象则是由SqlSessionFactoryBean创建的。spring.xml中配置了SqlSessionFactoryBean,其中指定了数据源对象,mybatis-config.xml配置文件的位置等信息。SqlSessionFactoryBean定义了很多mybatis配置相关的字段。

图4引展示的所有字段对应了开发人员可以在applicationContext.xmI配置文件中为SqlSessionFactoryBean配置的配置项,同时,也都可以在Configuration对象中找到相应的字段,其含义就不再重复描述了。

这里重点关注SqlSessionFactoryBean是如何创建SqlSessionFactory对象的,该功能是在SqlSessionFactoryBean.buildSqlSessionFactory()方法中实现的,其中涉及使用XMLConfigBuilder创建Configuration对象、对Configuration对象进行配置、使用XMLMapperBui1der解析映射配置过文件以及Mapper接口等一些列操作,这些操作的原理都在前面介绍过了,方法的具体实现如下:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

    private static final Log logger = LogFactory.getLog(SqlSessionFactoryBean.class);

    private Resource configLocation;

    private Resource[] mapperLocations;

    private DataSource dataSource;

    private TransactionFactory transactionFactory;

    private Properties configurationProperties;

    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

    private SqlSessionFactory sqlSessionFactory;

    private String environment = SqlSessionFactoryBean.class.getSimpleName(); // EnvironmentAware requires spring 3.1

    private boolean failFast;

    private Interceptor[] plugins;

    private TypeHandler<?>[] typeHandlers;

    private String typeHandlersPackage;

    private Class<?>[] typeAliases;

    private String typeAliasesPackage;

    private Class<?> typeAliasesSuperType;

    private DatabaseIdProvider databaseIdProvider; // issue #19. No default provider.

    private ObjectFactory objectFactory;

    private ObjectWrapperFactory objectWrapperFactory;



    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

        Configuration configuration;

        XMLConfigBuilder xmlConfigBuilder = null;
        // //如果Configuration对象存在·则使用指定的configuration对其进行匹配
        if (this.configLocation != null) {
            // 创建XMLConfigBui1der对象,读取指定的配置文件
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
            configuration = xmlConfigBuilder.getConfiguration();
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
            }
            configuration = new Configuration();
            configuration.setVariables(this.configurationProperties);
        }

        // 配置objectFactory
        if (this.objectFactory != null) {
            configuration.setObjectFactory(this.objectFactory);
        }

        // applicationcontext.xml中的配置,设置objectWrapperFactory
        if (this.objectWrapperFactory != null) {
            configuration.setObjectWrapperFactory(this.objectWrapperFactory);
        }

        // 扫描typeAliasespackage指定的包,并为其中的类注册别名
        if (hasLength(this.typeAliasesPackage)) {
            String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                                                                   ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeAliasPackageArray) {
                configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                                                                     typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
                if (logger.isDebugEnabled()) {
                    logger.debug("Scanned package: '" + packageToScan + "' for aliases");
                }
            }
        }

        // 为typeA1iases集合中指定的类注册别名〔略)
        if (!isEmpty(this.typeAliases)) {
            for (Class<?> typeAlias : this.typeAliases) {
                configuration.getTypeAliasRegistry().registerAlias(typeAlias);
                if (logger.isDebugEnabled()) {
                    logger.debug("Registered type alias: '" + typeAlias + "'");
                }
            }
        }

        // 注册plugins集合中指定的插件(以)
        if (!isEmpty(this.plugins)) {
            for (Interceptor plugin : this.plugins) {
                configuration.addInterceptor(plugin);
                if (logger.isDebugEnabled()) {
                    logger.debug("Registered plugin: '" + plugin + "'");
                }
            }
        }
        // 打描typeHand1ergpackage指定的包·并注册其中的TypeHand1er
        if (hasLength(this.typeHandlersPackage)) {
            String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                                                                      ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeHandlersPackageArray) {
                configuration.getTypeHandlerRegistry().register(packageToScan);
                if (logger.isDebugEnabled()) {
                    logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
                }
            }
        }

        // 注册将typeHand1ers集合中指定的TypeHand1er()
        if (!isEmpty(this.typeHandlers)) {
            for (TypeHandler<?> typeHandler : this.typeHandlers) {
                configuration.getTypeHandlerRegistry().register(typeHandler);
                if (logger.isDebugEnabled()) {
                    logger.debug("Registered type handler: '" + typeHandler + "'");
                }
            }
        }

        if (xmlConfigBuilder != null) {
            try {
                xmlConfigBuilder.parse();

                if (logger.isDebugEnabled()) {
                    logger.debug("Parsed configuration file: '" + this.configLocation + "'");
                }
            } catch (Exception ex) {
                throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
            } finally {
                ErrorContext.instance().reset();
            }
        }

        if (this.transactionFactory == null) {
            this.transactionFactory = new SpringManagedTransactionFactory();
        }

        Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
        configuration.setEnvironment(environment);
        //配置databaseIdProvider
        if (this.databaseIdProvider != null) {
            try {
                configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
            } catch (SQLException e) {
                throw new NestedIOException("Failed getting a databaseId", e);
            }
        }

        if (!isEmpty(this.mapperLocations)) {
            for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                    continue;
                }

                try {
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                                                                             configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                    ErrorContext.instance().reset();
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("Parsed mapper file: '" + mapperLocation + "'");
                }
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
            }
        }

        return this.sqlSessionFactoryBuilder.build(configuration);
    }
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

      // 可以看到这里主要是读取了注解上面的一些属性,然后给一个ClassPathMapperScanner对象去设置这些属性值,
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
      // 那么这个ClassPathMapperScanner对象的是什么来的呢?这个对象的父类在spring里面及其重要,因为它的父类就是用来进行包扫描的,把扫描到的类封装成一个个的BeanDefinition放在了spring的BeanDefinitionMap里面,其中的细节是spring的核心知识点了,这里就不展开说了。而上面的代码重点是scanner.doScan,在看这句代码之前我们先来看下ClassPathMapperScanner的类继承结构图:
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    if (resourceLoader != null) { // this check is needed in Spring 3.1
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>
@Configuration
public class MyBatisConfig {
  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    return factoryBean.getObject();
  }
}

注意:SqlSessionFactory 需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

假设你定义了一个如下的 mapper 接口:

public interface UserMapper {
  @Select("SELECT * FROM users WHERE id = #{userId}")
  User getUser(@Param("userId") String userId);
}

那么可以通过 MapperFactoryBean 将接口加入到 Spring 中:

前面已经有了sqlSessionFactory,接下来要让sqlSessionFactory扫描到接口或xml
下面只是注入了单个mapper
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
    
========等价于===========
@Configuration
public class MyBatisConfig {
  @Bean
  public MapperFactoryBean<UserMapper> userMapper() throws Exception {
    MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
    factoryBean.setSqlSessionFactory(sqlSessionFactory());
    return factoryBean;
  }
}
=======多mapper的情况======
但是我们肯定不能自己挨个注入,所以可以用mybatis-scan的方式
<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />
    =====或者=========
@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
  // ...
}

需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中,通过注解来指定 SQL 语句,但是也可以使用 MyBatis 映射器的 XML 配置文件。

配置好之后,你就可以像 Spring 中普通的 bean 注入方法那样,将映射器注入到你的业务或服务对象中。MapperFactoryBean 将会负责 SqlSession 的创建和关闭。 如果使用了 Spring 的事务功能,那么当事务完成时,session 将会被提交或回滚。最终任何异常都会被转换成 Spring 的 DataAccessException 异常。

使用 Java 代码来配置的方式如下:

@Configuration
public class MyBatisConfig {
  @Bean
  public UserMapper userMapper() throws Exception {
    SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
    return sqlSessionTemplate.getMapper(UserMapper.class);
  }
}

要调用 MyBatis 的数据方法,只需一行代码:

public class FooServiceImpl implements FooService {

  private final UserMapper userMapper;

  public FooServiceImpl(UserMapper userMapper) {
    this.userMapper = userMapper;
  }

  public User doSomeBusinessStuff(String userId) {
    return this.userMapper.getUser(userId);
  }
}

整合后自动装配的mapper对象应该和我们之前分析的mapper代理对象相同。

问题也就是如何转化为bean对象放到spring中

spring中bean是由beanDefinition定义的,我们只需要new beanDefinition(),然后把mybatis的动态代理对象设置进去,然后spring.add(bd)即可。

但是有个问题是动态代理类的名字我们是未知的,同时也不支持getBean(接口)

所以mybatis与spring结合采用的是FactoryBean

spring.get("factoryBean")拿到的是getObject()返回的类型,在这里面执行我们前面的sqlSession.getMapper()即可。

但是此时每个FactoryBean只能产生一个类型的动态代理对象,我们怎么知道到底有多少mybatis的代理对象。

我们在定义FactoryBean的时候通过【构造函数】传入一个【接口】,getObject返回Object类型,这个类就可以重复利用了,不断地new FactoryBean(XXXMapperInterface.class),得到不同的代理对象

然后再利用beanDefinition设置到spring中

list=  new LinkedList<>();
for(Class clazz:list){
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.getGenericBeanDefinition();
    BeanDefinition bd = builder.getBeanDefinition();
    bd.setBeanClassName(LubanFactoryBean.class.getName);
    db.getConstructorArgumentValues().addGenericAugumentValue(clazz);
}

然后利用@Import的BeanDefinitionRegister那个类

然后我们就把上面的逻辑放到该类中即可

public class LubanBeanDefinitionRegister implements BeanDefinitionRegister{
    public void registerBeanDefinitons(importingClassMetaData,registry){
        list=  new LinkedList<>();
        for(Class clazz:list){
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.getGenericBeanDefinition();
            BeanDefinition bd = builder.getBeanDefinition();
            bd.setBeanClassName(LubanFactoryBean.class.getName);
            db.getConstructorArgumentValues().addGenericAugumentValue(clazz);
            registry.registryBeanDefinition("bd名字",bd);
        }
    }
}
  • FactoryBean:负责生成代理对象

  • BeanDefinitionRegister:扫描,根据mapper生成FactoryBean

  • 把这个东西通过@Import加到spring的启动类上

    • 实际上应该加到@MapperScan注解定义类上

    • @Import(LubanFactoryBean.class)
      @Retention(Retention.RUNTIME)
      public @interface MapperScan{
          String value() default "";
      }
      
    • 让通过@MapperScan(“类”)扫描到

    • public void registerBeanDefinitons(importingClassMetaData,registry){
          // 在前面的代码前加
          Map<String,Object> 	annotationAttributes = importingClassMetaData.getAnnotation(MapperScan.class);
          annotationAttributes.get("value");//就能拿到要注册的类
      

八、sql注入

$和#

sqlSource

  • $是动态sql:解析阶段不会进行处理
    • 在执行sql时生成的boundSQL阶段会进行参数的替换,#会替换为?,$会替换为参数值
    • 最终boundSql的?号会在后续JDBC阶段进行赋值
    • 动态SQL会引发SQL注入
  • #是静态sql:语句中没有$就是静态sql
    • 静态sql会在sql解析阶段把#替换为?
    • 静态sql在执行sql时的生成BoundSql阶段不会处理
    • 最终boundSql的?号会在后续JDBC阶段进行赋值

https://www.cnblogs.com/miserable-faith/p/7658550.html

public boolean isDynamic() {
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    GenericTokenParser parser = createParser(checker);//建立GenericTokenParser
    parser.parse(text);//GenericTokenParser解析text
    return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
    return new GenericTokenParser("${",     // openToken
                                  "}",      // closeToken
                                  handler); // TokenHandler
} 

DynamicCheckerTokenParser实现的handleToken方法

// DynamicCheckerTokenParser.java
public String handleToken(String content) {
    this.isDynamic = true;
    return null;
}

ParameterMappingTokenHandler实现的handleToken方法:

// ParameterMappingTokenHandler.java
public String handleToken(String content) {//#的处理方式,返回占位符?
    parameterMappings.add(buildParameterMapping(content));
    return "?";
}

九、参数处理

十、事务

public class SqlSessionTemplate implements SqlSession {

    private final SqlSessionFactory sqlSessionFactory;

    private final ExecutorType executorType;

    private final SqlSession sqlSessionProxy;

    private final PersistenceExceptionTranslator exceptionTranslator;

    // 产生sqlSessionProxy时用的
    private class SqlSessionInterceptor implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            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) {
                Throwable unwrapped = unwrapThrowable(t);
                if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
                    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    sqlSession = null;
                    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            } finally {
                if (sqlSession != null) {
                    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }
            }
        }
    }

梳理

SqlSession下的四大核心组件

Mybatis中SqlSession下的四大核心组件:

  • ParameterHandler 、
  • ResultSetHandler 、
  • StatementHandler 、
  • Executor 。
//ParameterHandler 处理sql的参数对象
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    //包装参数插件
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}

//ResultSetHandler 处理sql的返回结果集
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
                                            ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    //包装返回结果插件
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}

//StatementHandler 数据库的处理对象
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);
    //包装数据库执行sql插件
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

public Executor newExecutor(Transaction transaction) {
    //创建Mybatis的执行器:Executor
    return newExecutor(transaction, defaultExecutorType);
}

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //mybatis支持的三种执行器:batch、reuse、simple,其中默认支持的是simple
    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);
    }
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    //包装执行器插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

一切的执行从MapperProxy开始,MapperProxy是MapperProxyFactory使用SqlSession创建出来的。所以MapperProxy中包含SqlSession。

img

可以看到MapperProxy调用invoke方法,进而调用MapperMethod的execute(),这些MapperMethod就是和你要执行的命令相关,比如执行select语句,则会通过SqlSession的select()方法,最终调用到Executor的query方法。Executor会再协调另外三个核心组件。

  • MapperProxyFactory用来创建MapperProxy,这个factory其实主要就是完成了InvokeHandler的bindTarget的功能。而MapperProxy只需要完成invoke方法的功能。
  • MapperProxy包含SqlSession

执行器与缓存

把缓存抽象到BaseExecutor,即一级缓存放到这个里面,获取连接的操作也抽象到这里。里面有query和update。所以测试的时候要注意一下,是调用的doQuery还是query

二级缓存commit提交之后,缓存中才会有。一级缓存查询完立刻就有了。

流程:mapper注册中心、执行器、stmHandler、resultSetHandler。都可以加拦截器。责任链

  • 一级缓存:kv形式,perpetualCache,hashMap
    • sql和参数必须相同
    • session一样
    • stm一样
    • selectOne和动态代理是互通的
    • RowBounds返回行范围必须相同
    • 没有执行修改、清空操作
    • 改成statement可以屏蔽一级缓存,但只能全局修改,不能只修改mapperedStm,但嵌套查询不能屏蔽
  • 二级缓存

XML与DOM

xml解析常见的方式有3种:DOM、SAX、StAX

  • DOM:基于树形结构的XML解析方式
  • SAX:不需要将整个XML文档加载到内存中,而只需将XML文档的一部分加载到内存中,即可开始解析,在处理过程中不会再内存中记录XML的数据(只能从前到后进行,无法返回),所以占用的资源比较少。
  • StAX
XPath

mybatis在初始化过程中处理mybatis-config.xml配置文件以及映射文件时,使用的是DOM解析方式,并结合使用XPath解析XML配置文件。DOM会将整个XML文档加载到内存中并形成树状数据结构,而XPath是一种为查询XML文档而设计的语言,他可以与DOM解析方式配合使用。

XPath使用路径表达式来选取XML文档中指定的结点或者结点结集合,与常见的URL路径有些类似。

/  从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
@  选取属性。

../ 从当前规则的父级开始匹配
./ 从当前规则开始匹配
/text() 表示从当前规则下匹配所有文本内容
/@name 表示匹配当前规则下的属性的value
[] 中括号中可以写过滤的条件(支持and、or语法),也可以写数组下标(从1开始)
/div[@class="classname"] 表示匹配当前规则下所有class为classname的div对象
/div[contains(@class,"classname")] 表示匹配当前规则下所有class包含classname的div对象
/div[contains(@class,"classname1") or contains(@class,"classname2")] 表示匹配当前规则下所有class包含classname1或者classname2的div对象
/span[text()="text"] 表示匹配当前规则下文本包含text的所有span对象
/a/following-sibling::* 表示匹配当前规则下a标签之后所有的同级节点对象
/a/following-sibling::*[1] 表示匹配当前规则下a标签之后的第一个同级节点对象
//*[name(.)!="i"] 表示排除所有i标签

NamedParameterJdbcTemplate

命名参数jdbc模板

public int update(String sql, // 带有:的
                  SqlParameterSource paramSource,// 
                  KeyHolder generatedKeyHolder,
                  String[] keyColumnNames) throws DataAccessException {
    ParsedSql parsedSql = this.getParsedSql(sql);
    String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
    Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, (List)null);
    // 
    List<SqlParameter> declaredParameters = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource);
    PreparedStatementCreatorFactory pscf = new PreparedStatementCreatorFactory(sqlToUse, declaredParameters);
    if (keyColumnNames != null) {
        pscf.setGeneratedKeysColumnNames(keyColumnNames);
    } else {
        pscf.setReturnGeneratedKeys(true);
    }

    return this.getJdbcOperations().update(pscf.newPreparedStatementCreator(params), generatedKeyHolder);
}
query

参数一般为:

  • sql
  • 参数源,可以是map,可以是SqlParameterSource,它的目的都是将对于的字段转为具体实参
  • 返回值处理器,RowCallBackHandler/RowMapper/Class<T>
query
queryForObject
queryForMap
queryForList,也是可以传入Class
queryForRowSet


update(sql,source,KeyHolder)返回int
// 获取泛型类型
val type = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
// T的class对象
clazz = type as Class<T>
// 构造T类型对应的BeanRowMapper,并添加一个转换器
rowMapper = BeanRowMapper(clazz).apply {
    (conversionService as DefaultConversionService).addConverter(JsonStringToObjectConverter(conversionService))
}
BeanPropertyRowMapper
public class BeanRowMapper<T> extends BeanPropertyRowMapper<T> {
    private static DefaultConversionService conversionService = new DefaultConversionService();

    public BeanRowMapper(Class<T> mappedClass) {
        super(mappedClass);
        this.setConversionService(conversionService);
    }

    static {
        conversionService.removeConvertible(Object.class, Enum.class);
        conversionService.addConverter(new NumberToEnumConverter());
        conversionService.addConverter(new JdbcToObjectConverter());
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值