美文网首页mybatis
Mybatis源码分析——Mapper接口和XML文件里的SQL

Mybatis源码分析——Mapper接口和XML文件里的SQL

作者: 小波同学 | 来源:发表于2020-12-31 01:38 被阅读0次

    前言

    这是 mybatis 比较常问到的面试题,我自己在以前的面试过程中被问到过,因此自己印象很深刻。

    另外,估计不少同学应该也注意到了,DAO 接口的全路径名和 XML 文件中的 SQL 的 namespace + id 是一样的。其实,这也是建立关联的根本原因。

    正文

    当一个项目中使用了 Spring 和 Mybatis 时,通常会有以下配置。当然现在很多项目应该都是 SpringBoot 了,可能没有以下配置,但是究其底层原理都是类似的,无非是将扫描 bean 等一些工作通过注解来实现。

    <!-- DAO接口所在包名,Spring会自动查找其下的类 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--basePackage指定要扫描的包,在此包之下的映射器都会被搜索到。可指定多个包,包与包之间用逗号或分号分隔-->
        <property name="basePackage" value="com.joonwhee.open.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
     
    <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- 自动扫描mapping.xml文件 -->
        <property name="mapperLocations" value="classpath:config/mapper/*.xml"/>
        <property name="configLocation" value="classpath:config/mybatis/mybatis-config.xml"/>
        <!--Entity package -->
        <property name="typeAliasesPackage" value="com.joonwhee.open.po"/>
    </bean>
     
    <!-- dataSource -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </bean>
    

    通常我们还会有 DAO 类和 对用的 mapper 文件,如下。

    package com.yibo.open.mapper;
     
    import com.yibo.open.po.UserPO;
     
    public interface UserPOMapper {
        UserPO queryByPrimaryKey(Integer id);
    }
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.yibo.open.mapper.UserPOMapper" >
        <resultMap id="BaseResultMap" type="com.yibo.open.po.UserPO">
            <result column="id" property="id" jdbcType="INTEGER" />
            <result column="name" property="name" jdbcType="VARCHAR" />
        </resultMap>
     
        <select id="queryByPrimaryKey" resultMap="BaseResultMap"
                parameterType="java.lang.Integer">
            select id, name
            from user
            where id = #{id,jdbcType=INTEGER}
        </select>
    </mapper>
    

    1、解析 MapperScannerConfigurer

    MapperScannerConfigurer 是一个 BeanDefinitionRegistryPostProcessor,会在 Spring 构建 IoC容器的早期被调用重写的 postProcessBeanDefinitionRegistry 方法。

    public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
            if (this.processPropertyPlaceHolders) {
                this.processPropertyPlaceHolders();
            }
    
            // 新建一个ClassPathMapperScanner,并填充相应属性
            ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
            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);
            // 注册Filter,因为上面构造函数我们没有使用默认的Filter,
            // 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
            scanner.registerFilters();
            
            // 扫描basePackage,basePackage可通过",; \t\n"来填写多个,
            // ClassPathMapperScanner重写了doScan方法
            scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
        }
    }
    

    注册 Filter

    作用:什么类型的Mapper将会留下来。

    public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    
        public void registerFilters() {
            boolean acceptAllInterfaces = true;
            // 1.如果指定了注解,则将注解添加到includeFilters
            if (this.annotationClass != null) {
                this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
                acceptAllInterfaces = false;
            }
    
            // 2.如果指定了标记接口,则将标记接口添加到includeFilters,
            // 但这边重写了matchClassName方法,并返回了false,
            // 相当于忽略了标记接口上的匹配项,所以该参数目前相当于没有任何作用
            if (this.markerInterface != null) {
                this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
                    protected boolean matchClassName(String className) {
                        return false;
                    }
                });
                acceptAllInterfaces = false;
            }
            
            // 3.如果没有指定annotationClass和markerInterface,则
            // 添加默认的includeFilters,直接返回true,接受所有类
            if (acceptAllInterfaces) {
                this.addIncludeFilter(new TypeFilter() {
                    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                        return true;
                    }
                });
            }
            
            // 4.添加默认的excludeFilters,排除以package-info结尾的类
            this.addExcludeFilter(new TypeFilter() {
                public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                    String className = metadataReader.getClassMetadata().getClassName();
                    return className.endsWith("package-info") ? true : metadataReader.getAnnotationMetadata().hasAnnotation("tk.mybatis.mapper.annotation.RegisterMapper");
                }
            });
        }
    }
    

    通常我们都不会指定 annotationClass 和 markerInterface,也就是会添加默认的 Filter,相当于会接受除了 package-info 结尾的所有类。因此,basePackage 包下的类不需要使用 @Component 注解或 XML 中配置 bean 定义,也会被添加到 IoC 容器中。

    扫描 basePackage

    这边会走到 ClassPathBeanDefinitionScanner(ClassPathMapperScanner 的父类),然后在执行 “doScan(basePackages)” 时回到 ClassPathMapperScanner 重写的方法doScan

    public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
    
        private final BeanDefinitionRegistry registry;
    
        public int scan(String... basePackages) {
            int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
            
            //调用同类方法进行扫描,并将basePackages下的class都封装成BeanDefinitionHolder,并注册进Spring容器的BeanDefinition
            doScan(basePackages);
    
            // Register annotation config processors, if necessary.
            if (this.includeAnnotationConfig) {
                AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
            }
    
            return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
        }
        
        protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
            Assert.notEmpty(basePackages, "At least one base package must be specified");
            Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
            //遍历basePackages进行扫描
            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 对象
                        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                        definitionHolder =
                                AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                        beanDefinitions.add(definitionHolder);
                        //将BeanDefinition对象注入spring的BeanDefinitionMap中,后续getBean时,就是从BeanDefinitionMap获取对应的BeanDefinition对象,取出其属性进行实例化Bean
                        registerBeanDefinition(definitionHolder, this.registry);
                    }
                }
            }
            return beanDefinitions;
        }   
    }
    

    我们重点看下doScan方法,获取basePackages下的所有Class,并将其生成BeanDefinition,注入spring的BeanDefinitionMap中,也就是Class的描述类,在调用getBean方法时,获取BeanDefinition进行实例化。此时,所有的Mapper接口已经被生成了BeanDefinition。

    小结,解析 MapperScannerConfigurer 主要是做了几件事:

    • 1、新建扫描器 ClassPathMapperScanner。
    • 2、使用 ClassPathMapperScanner 扫描注册 basePackage 包下的所有 bean。
    • 3、将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean、为 MapperFactoryBean 添加 sqlSessionFactory 和 sqlSessionTemplate属性。

    2、解析 SqlSessionFactoryBean

    对于 SqlSessionFactoryBean 来说,实现了2个接口,InitializingBean 和 FactoryBean,看过Spring 文章的同学应该对这2个接口不会陌生,简单来说:

    • 1、FactoryBean可以自己定义创建实例对象的方法,只需要实现它的 getObject() 方法。
    • 2、InitializingBean则是会在 bean 初始化阶段被调用。

    SqlSessionFactoryBean 重写这两个接口的部分方法代码如下,核心代码就一个方法—— buildSqlSessionFactory()

    public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    
        public SqlSessionFactory getObject() throws Exception {
            if (this.sqlSessionFactory == null) {
                 // 如果之前没有构建,则这边也会调用afterPropertiesSet进行构建操作
                this.afterPropertiesSet();
            }
    
            return this.sqlSessionFactory;
        }
        
        public void afterPropertiesSet() throws Exception {
            Assert.notNull(this.dataSource, "Property 'dataSource' is required");
            Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
            Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
            // 构建sqlSessionFactory
            this.sqlSessionFactory = this.buildSqlSessionFactory();
        }
    }
    

    buildSqlSessionFactory()

    主要做了几件事:

    • 1、对我们配置的参数进行相应解析。
    • 2、使用配置的参数构建一个 Configuration。
    • 3、使用 Configuration 新建一个 DefaultSqlSessionFactory。

    这边的核心内容是对于 mapperLocations 的解析,如下代码。

    public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    
        protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
            
            //省略前面代码......
    
            configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
            // mapper处理(最重要)
            if (!ObjectUtils.isEmpty(this.mapperLocations)) {
                Resource[] var29 = this.mapperLocations;
                var27 = var29.length;
    
                for(var5 = 0; var5 < var27; ++var5) {
                    Resource mapperLocation = var29[var5];
                    if (mapperLocation != null) {
                        try {
                            // 新建XMLMapperBuilder
                            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                            // 解析mapper文件
                            xmlMapperBuilder.parse();
                        } catch (Exception var20) {
                            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
                        } 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");
            }
            // 使用targetConfiguration构建DefaultSqlSessionFactory
            return this.sqlSessionFactoryBuilder.build(configuration);
        }
    }
    

    解析mapper文件

    public class XMLMapperBuilder extends BaseBuilder {
    
        private final XPathParser parser;
    
        private final String resource;
    
        public void parse() {
            // 如果resource没被加载过才进行加载
            if (!configuration.isResourceLoaded(resource)) {
                // 解析mapper文件
                configurationElement(parser.evalNode("/mapper"));
                // 将resource添加到已加载列表
                configuration.addLoadedResource(resource);
                // 绑定namespace的mapper
                bindMapperForNamespace();
            }
    
            parsePendingResultMaps();
            parsePendingCacheRefs();
            parsePendingStatements();
        }
    }
    

    解析mapper文件

    public class XMLMapperBuilder extends BaseBuilder {
    
        private void configurationElement(XNode context) {
            try {
                 // 1.获取namespace属性
                String namespace = context.getStringAttribute("namespace");
                if (namespace == null || namespace.equals("")) {
                    throw new BuilderException("Mapper's namespace cannot be empty");
                }
                // 2.设置currentNamespace属性
                builderAssistant.setCurrentNamespace(namespace);
                // 3.解析parameterMap、resultMap、sql等节点
                cacheRefElement(context.evalNode("cache-ref"));
                cacheElement(context.evalNode("cache"));
                parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                resultMapElements(context.evalNodes("/mapper/resultMap"));
                sqlElement(context.evalNodes("/mapper/sql"));
                // 4.解析增删改查节点,封装成Statement
                buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            } catch (Exception e) {
                throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
            }
        }
        
        private void buildStatementFromContext(List<XNode> list) {
            if (configuration.getDatabaseId() != null) {
                buildStatementFromContext(list, configuration.getDatabaseId());
            }
            // 解析增删改查节点,封装成Statement
            buildStatementFromContext(list, null);
        }
        
        private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
            for (XNode context : list) {
                // 1.构建XMLStatementBuilder
                final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
                try {
                    // 2.解析节点
                    statementParser.parseStatementNode();
                } catch (IncompleteElementException e) {
                    configuration.addIncompleteStatement(statementParser);
                }
            }
        }
    }
    

    这边会一直执行到 statementParser.parseStatementNode();

    这边每个 XNode 都相当于如下的一个 SQL,下面封装的每个 MappedStatement 可以理解就是每个 SQL。

    <select id="queryByPrimaryKey" resultMap="BaseResultMap"
            parameterType="java.lang.Integer">
        select id, name, password, age
        from user
        where id = #{id,jdbcType=INTEGER}
    </select>
    
    statementParser.parseStatementNode()
    public class XMLMapperBuilder extends BaseBuilder {
    
        public void parseStatementNode() {
            // 1 获得 id 属性,编号。
            String id = context.getStringAttribute("id");
            
            // 2 获得 databaseId , 判断 databaseId 是否匹配
            String databaseId = context.getStringAttribute("databaseId");
    
            if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
              return;
            }
            
            // 3 获得各种属性
            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");
            
            // 4 获得 lang 对应的 LanguageDriver 对象
            LanguageDriver langDriver = getLanguageDriver(lang);
            
            // 5 获得 resultType 对应的类
            Class<?> resultTypeClass = resolveClass(resultType);
            
            // 6 获得 resultSet 对应的枚举值
            String resultSetType = context.getStringAttribute("resultSetType");
            StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
            
            // 7 获得 statementType 对应的枚举值
            ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
            
            // 8 获得 SQL 对应的 SqlCommandType 枚举值
            String nodeName = context.getNode().getNodeName();
            SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
            
            // 9 获得各种属性
            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
            // 10 创建 XMLIncludeTransformer 对象,并替换 <include /> 标签相关的内容
            XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
            includeParser.applyIncludes(context.getNode());
    
            // Parse selectKey after includes and remove them.
            // 11 解析 <selectKey /> 标签
            processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
            // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
            // 12 创建 SqlSource
            SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
            // 13 获得 KeyGenerator 对象
            String resultSets = context.getStringAttribute("resultSets");
            String keyProperty = context.getStringAttribute("keyProperty");
            String keyColumn = context.getStringAttribute("keyColumn");
            KeyGenerator keyGenerator;
            // 13.1 优先,从 configuration 中获得 KeyGenerator 对象。如果存在,意味着是 <selectKey /> 标签配置的
            String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
            keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
            if (configuration.hasKeyGenerator(keyStatementId)) {
              keyGenerator = configuration.getKeyGenerator(keyStatementId);
              
            // 13.2 其次,根据标签属性的情况,判断是否使用对应的 Jdbc3KeyGenerator 或者 NoKeyGenerator 对象  
            } else {
              keyGenerator = context.getBooleanAttribute("useGeneratedKeys", // 优先,基于 useGeneratedKeys 属性判断
                  configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) // 其次,基于全局的 useGeneratedKe
                  ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            }
            
            // 创建 MappedStatement 对象
            // 将解析出来的所有参数添加到 mappedStatements 缓存
            builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                resultSetTypeEnum, flushCache, useCache, resultOrdered, 
                keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }
    }
    
    public class MapperBuilderAssistant extends BaseBuilder {
    
      public MappedStatement addMappedStatement(
          String id,
          SqlSource sqlSource,
          StatementType statementType,
          SqlCommandType sqlCommandType,
          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");
        }
    
        // 1.将id填充上namespace,例如:queryByPrimaryKey变成
        // com.yibo.open.mapper.UserPOMapper.queryByPrimaryKey
        id = applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        // 2.使用参数构建MappedStatement.Builder
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
            .resource(resource)
            .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);
        // 3.使用MappedStatement.Builder构建MappedStatement
        ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
        if (statementParameterMap != null) {
          statementBuilder.parameterMap(statementParameterMap);
        }
        // 4.将MappedStatement 添加到缓存
        MappedStatement statement = statementBuilder.build();
        configuration.addMappedStatement(statement);
        return statement;
      }
    }
    
    public class Configuration {
    
        protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
    
        public void addMappedStatement(MappedStatement ms) {
            mappedStatements.put(ms.getId(), ms);
        }
    }
    

    该方法会将节点的属性解析后封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.yibo.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。

    bindMapperForNamespace()

    绑定namespace的mapper

    public class XMLMapperBuilder extends BaseBuilder {
    
        private final MapperBuilderAssistant builderAssistant;
    
        private void bindMapperForNamespace() {
            String namespace = builderAssistant.getCurrentNamespace();
            if (namespace != null) {
                Class<?> boundType = null;
                try {
                    // 1.解析namespace对应的绑定类型
                    boundType = Resources.classForName(namespace);
                } catch (ClassNotFoundException e) {
                    //ignore, bound type is not required
                }
                if (boundType != null) {
                    if (!configuration.hasMapper(boundType)) {
                        // Spring may not know the real resource name so we set a flag
                        // to prevent loading again this resource from the mapper interface
                        // look at MapperAnnotationBuilder#loadXmlResource
                        
                        // 2.boundType不为空,并且configuration还没有添加boundType,
                        // 则将namespace添加到已加载列表,将boundType添加到knownMappers缓存
                        configuration.addLoadedResource("namespace:" + namespace);
                        configuration.addMapper(boundType);
                    }
                }
            }
        }
    }
    
    public class Configuration {
    
        protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    
        public <T> void addMapper(Class<T> type) {
            mapperRegistry.addMapper(type);
        }
    }
    
    public class MapperRegistry {
    
        private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
    
        public <T> void addMapper(Class<T> type) {
            if (type.isInterface()) {
                if (hasMapper(type)) {
                    throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
                }
                boolean loadCompleted = false;
                try {
                    // 将type和以该type为参数构建的MapperProxyFactory作为键值对,
                    // 放到knownMappers缓存中去
                    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.
                    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                    parser.parse();
                    loadCompleted = true;
                } finally {
                    if (!loadCompleted) {
                        knownMappers.remove(type);
                    }
                }
            }
        }
    }
    

    主要是将刚刚解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。

    小结,解析 SqlSessionFactoryBean 主要做了几件事:

    • 1、解析处理所有属性参数构建 Configuration ,使用 Configuration 新建 DefaultSqlSessionFactory;

    • 2、解析 mapperLocations 属性的 mapper 文件,将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.yibo.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。

    • 3、将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。

    3、解析Mapper 文件

    Mapper 文件,也就是 basePackage 指定的包下的文件,也就是上文的 interface UserPOMapper 。

    上文 doScan 中说过,basePackage 包下所有 bean 定义的 beanClass 会被设置成 MapperFactoryBean.class,而 MapperFactoryBean 也是 FactoryBean,因此直接看 MapperFactoryBean 的 getObject 方法。

    public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    
        public T getObject() throws Exception {
            // 1.从父类中拿到sqlSessionTemplate,这边的sqlSessionTemplate也是doScan中添加的属性
            // 2.通过mapperInterface获取mapper
            return this.getSqlSession().getMapper(this.mapperInterface);
        }
    }
    
    public class DefaultSqlSession implements SqlSession {
    
        private final Configuration configuration;
    
        @Override
        public <T> T getMapper(Class<T> type) {
            return configuration.<T>getMapper(type, this);
        }
    }
    
    
    public class Configuration {
    
        protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    
        public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
            return mapperRegistry.getMapper(type, sqlSession);
        }
    }
    
    public class MapperRegistry {
    
        private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
    
    
        public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
            // 1.从knownMappers缓存中获取
            final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
            if (mapperProxyFactory == null) {
                throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
            }
            try {
                // 2.新建实例
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception e) {
                throw new BindingException("Error getting mapper instance. Cause: " + e, e);
            }
        }
    }
    
    public class MapperProxyFactory<T> {
    
        private final Class<T> mapperInterface;
        private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
    
        public T newInstance(SqlSession sqlSession) {
            // 1.构造一个MapperProxy
            final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
            // 2.使用MapperProxy来构建实例对象
            return newInstance(mapperProxy);
        }
        
        protected T newInstance(MapperProxy<T> mapperProxy) {
            // 使用JDK动态代理来代理要创建的实例对象,InvocationHandler为mapperProxy,
            // 因此当我们真正调用时,会走到mapperProxy的invoke方法
            return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
        }
    }
    

    这边代码用到的 sqlSessionTemplate、mapperInterface 等都是之前添加的属性。

    小结,解析 DAO 文件 主要做了几件事:

    • 1、通过 mapperInterface 从 knownMappers 缓存中获取到 MapperProxyFactory 对象。

    • 2、通过 JDK 动态代理创建 MapperProxyFactory 实例对象,InvocationHandler 为 MapperProxy。

    4、Mapper 接口被调用

    当 Mapper 中的接口被调用时,会走到 MapperProxy 的 invoke 方法。

    public class MapperProxy<T> implements InvocationHandler, Serializable {
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                // Object的方法执行
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                } else if (isDefaultMethod(method)) {
                    //JDK8的默认方法执行
                    return invokeDefaultMethod(proxy, method, args);
                }
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
            // 获取MapperMethod
            final MapperMethod mapperMethod = cachedMapperMethod(method);
            // 真正的处理在这里
            return mapperMethod.execute(sqlSession, args);
        }
        
        private final SqlSession sqlSession;
        private final Class<T> mapperInterface;
        private final Map<Method, MapperMethod> methodCache;    
        
        private MapperMethod cachedMapperMethod(Method method) {
            //从缓存中获取mapperMethod
            MapperMethod mapperMethod = methodCache.get(method);
            if (mapperMethod == null) {
                //缓存中没有则构建一个并放入缓存中
                mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
                methodCache.put(method, mapperMethod);
            }
            return mapperMethod;
        }
    }
    

    mapperMethod.execute(sqlSession, args)

    MapperMethod.execute()方法执行

    public class MapperMethod {
    
      private final SqlCommand command;
      private final MethodSignature method;
    
      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        // 根据命令类型执行来进行相应操作
        // 具体的增删改查操作,都有具体的执行
        switch (command.getType()) {
          case INSERT: {
          Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
          }
          case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
          }
          case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
              executeWithResultHandler(sqlSession, args);
              result = null;
            } else if (method.returnsMany()) {
              result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
              result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
              result = executeForCursor(sqlSession, args);
            } else {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = sqlSession.selectOne(command.getName(), param);
            }
            break;
          case FLUSH:
            result = sqlSession.flushStatements();
            break;
          default:
            throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
          throw new BindingException("Mapper method '" + command.getName() 
              + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
      }
    }
    

    这边就比较简单,根据不同的操作类型执行相应的操作,最终将结果返回

    这边的 command 是上文 new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())时创建的。

    增删改查

    public class DefaultSqlSession implements SqlSession {
    
        @Override
        public int insert(String statement, Object parameter) {
            return update(statement, parameter);
        }
        
        @Override
        public int update(String statement, Object parameter) {
            try {
                dirty = true;
                // 从mappedStatements缓存拿到对应的MappedStatement对象,执行更新操作
                MappedStatement ms = configuration.getMappedStatement(statement);
                return executor.update(ms, wrapCollection(parameter));
            } catch (Exception e) {
                throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
            } finally {
                ErrorContext.instance().reset();
            }
        }
        
        @Override
        public int delete(String statement, Object parameter) {
            return update(statement, parameter);
        }
    }
    
    public class MapperMethod {
    
        private final SqlCommand command;
        private final MethodSignature method;
      
        //select,以executeForMany为例
        private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
            List<E> result;
            // 1.参数转换成sql命令参数
            Object param = method.convertArgsToSqlCommandParam(args);
            if (method.hasRowBounds()) {
                RowBounds rowBounds = method.extractRowBounds(args);
                result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
            } else {
                // 2.执行查询操作
                result = sqlSession.<E>selectList(command.getName(), param);
            }
            // issue #510 Collections & arrays support
            // 3.处理返回结果
            if (!method.getReturnType().isAssignableFrom(result.getClass())) {
                if (method.getReturnType().isArray()) {
                    return convertToArray(result);
                } else {
                    return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
                }
            }
            return result;
        }   
    }
    
    public class DefaultSqlSession implements SqlSession {
    
        private final Configuration configuration;
    
        private final Executor executor;
    
        @Override
        public <E> List<E> selectList(String statement) {
            return this.selectList(statement, null);
        }
    
        @Override
        public <E> List<E> selectList(String statement, Object parameter) {
            return this.selectList(statement, parameter, RowBounds.DEFAULT);
        }
    
        @Override
        public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
            try {
                //从mappedStatements缓存中拿到对应的MappedStatement对象,执行查询操作
                MappedStatement ms = configuration.getMappedStatement(statement);
                return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
            } catch (Exception e) {
                throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
            } finally {
                ErrorContext.instance().reset();
            }
        }
    }
    

    可以看出,最终都是从 mappedStatements 缓存中拿到对应的 MappedStatement 对象,执行相应的操作。

    这边的增删改查不是直接调用 SqlSession 中的方法,而是调用 SqlSessionTemplate 中的方法,继而通过 sqlSessionProxy 来调用 SqlSession 中的方法。SqlSessionTemplate 中的方法主要是通过 sqlSessionProxy 做了一层动态代理,基本没差别。

    总结

    整个流程主要是以下几个核心步骤:

    • 1、扫描注册 basePackage 包下的所有 bean,将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean、为 MapperFactoryBean 添加 sqlSessionFactory 和 sqlSessionTemplate属性。

    • 2、解析 mapperLocations 属性的 mapper 文件,将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。并且将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。

    • 3、创建 DAO 的 bean 时,通过 mapperInterface 从 knownMappers 缓存中获取到 MapperProxyFactory 对象,通过 JDK 动态代理创建 MapperProxyFactory 实例对象,InvocationHandler 为 MapperProxy。

    • 4、DAO 中的接口被调用时,通过动态代理,调用 MapperProxy 的 invoke 方法,最终通过 mapperInterface 从 mappedStatements 缓存中拿到对应的 MappedStatement,执行相应的操作。

    参考:
    https://zhuanlan.zhihu.com/p/140087414

    相关文章

      网友评论

        本文标题:Mybatis源码分析——Mapper接口和XML文件里的SQL

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