Mapper接口和Mapper.xml是如何解析的

/**
 * <pre>
 * 总结 非Spring环境下,配置mybatis-config配置文件
 *     <mappers>
 *         <package name="luck.mybatis.mapper"/>
 *         <mapper class="luck.mybatis.mapper.UsersMapper"/>
 *         <mapper resource="mapper/PermissionsMapper.xml"/>
 *     </mappers>
 *
 *
 *     逻辑1. 当配置为class/package: 则将指定Mapper接口或者指定包名下的Mapper接口进行解析
 *        1.1. 最终会调用到{@link Configuration#addMapper(Class)}注册Mapper
 *          1.1.1. 首先会将Mapper接口中的所有方法进行一一解析,因为Mapper接口中,可以使用注解,也可以使用mapper.xml进行SQL映射
 *                 会通过{@link MapperAnnotationBuilder}来解析Mapper接口,只处理可以书写{@link MapperAnnotationBuilder#statementAnnotationTypes}SQL相关的注解
 *
 *          1.1.2. 再通过{@link MapperAnnotationBuilder#parse()}对Mapper接口开始解析,加载Mapper接口的同时会标记该接口已经解析,防止重复加载,因为下面逻辑又会触发Mapper接口加载
 *                 接着又会同时加载该Mapper接口路径下+.xml的映射文件,如果存在该mapper.xml文件,会通过{@link XMLMapperBuilder}这个MapperXML解析器来解析该Mapper.xml文件
 *                 最终调用{@link XMLMapperBuilder#parse()}解析Mapper.xml,在这个过程中,会获取Mapper.xml中的命名空间进行分析
 *                 如果Mapper.xml中写的命名空间是一个全限定名并且是接口,该接口会当做Mapper接口来处理(又回到了1.1这一步),如果不符合这个条件,则会忽略.
 *                 在解析完.xml之后,会添加一个标识,该xml已经被解析
 *
 *         1.1.3. 这样一来我们可以发现,在注册Mapper的时候,处理Mapper接口的时候,会自动处理Mapper接口路径下的Mapper.xml映射文件
 *                在解析Mapper.xml的时候,又会通过Mapper.xml中的命名空间来注册Mapper接口,完成了相互注册逻辑
 *
 *
 *     逻辑2 当配置的是resource,url: 则将指定的mapper.xml文件进行解析
 *          2.1. 会调用{@link XMLMapperBuilder#parse()}解析Mapper.xml,在这个过程中,会添加一个标识,标识该xml已经被解析,同时会获取Mapper.xml中的命名空间进行分析
 *               如果Mapper.xml中写的命名空间是一个全限定名并且是接口,该接口会当做Mapper接口来处理(调用到{@link Configuration#addMapper(Class)}注册Mapper)
 *               接下来的逻辑就和{@see 1.1}完全一样
 *
 *
 * Spring-SpringBoot环境下
 * 可以通过{@link Demo#sqlSessionFactory}来配置Mapper.xml的路径(如果不配置,那么就是按照Mybatis的固定规则,解析Mapper接口下的Mapper.xml文件,我们只需要扫描Mapper接口就行
 * 我们可以通过PathMatchingResourcePatternResolver来对我们自己设置的mapper的路径进行解析,会解析到所有的Mapper.xml文件
 * 解析到mapper.xml文件之后,就回到了Mybatis原生的XML解析步骤{@see 逻辑2}
 *
 * </pre>
 */
public class Demo {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        // 通过解析器,解析该路径下的所有资源文件
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
        return sessionFactory
    }
}

// Spring环境
// 保留Mapper和Mapper接口是如何解析的关键步骤
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ContextRefreshedEvent> {
    // mybatis全局配置文件
    private Resource configLocation;
    // 所有mapper.xml的路径
    private Resource[] mapperLocations;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 初始化sqlSessionFactory
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }

    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        // mybatis的核心配置类对象,因为创建Configuration的方式有很多
        final Configuration targetConfiguration;

        XMLConfigBuilder xmlConfigBuilder = null;
        // 设置了自定义的Configuration对象
        if (this.configuration != null) {
            targetConfiguration = this.configuration;
        }
        // 指定了mybatis全局配置文件,通过配置文件生成Configuration对象
        else if (this.configLocation != null) {
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
            targetConfiguration = xmlConfigBuilder.getConfiguration();
        }
        // 其他情况,直接创建Configuration对象
        else {
            targetConfiguration = new Configuration();
        }

        // 如果使用的是Mybatis的配置文件创建Configuration对象
        if (xmlConfigBuilder != null) {
            // 解析XML配置文件
            xmlConfigBuilder.parse();
        }
        // 如果指定了mapper.xml的路径
        if (this.mapperLocations != null) {
            // 遍历所有的mapper.xml文件
            for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                    continue;
                }
                // 使用Xml的mapper解析器对该xml进行解析
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                xmlMapperBuilder.parse();
            }
            return this.sqlSessionFactoryBuilder.build(targetConfiguration);
        }
    }
}

// Mapper.xml构建器,解析器
public class XMLMapperBuilder extends BaseBuilder {
    // 配置类信息
    public final Configuration configuration;
    // XML解析器
    public final XPathParser parser;
    // Mapper.xml/Mapper接口构造者的辅助类
    public final MapperBuilderAssistant builderAssistant;
    // SQL片段
    public final Map<String, XNode> sqlFragments;
    // 该Mapper的资源文件
    public final String resource;

    // 解析该mapper.xml
    public void parse() {
        // 配置类中没有加载过该资源文件
        if (!configuration.isResourceLoaded(resource)) {
            // 解析xml中的所有标签,在这里会解析mapper文件中的所有<insert,select>等等标签,解析为statement信息
            configurationElement(parser.evalNode("/mapper"));
            // 标识该mapper.xml已经被解析过
            configuration.addLoadedResource(resource);
            // 通过xml中写的命名空间来注册Mapper接口(如果存在的命名空间,并且该命名空间是Mapper接口的情况)
            this.bindMapperForNamespace();
        }
    }

    // 解析mapper文件中的所有标签
    public void configurationElement(XNode context) {
        try {
            // 获取命名空间
            String namespace = context.getStringAttribute("namespace");
            // 不存在抛出异常
            if (namespace == null || namespace.isEmpty()) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            // 设置命名空间
            builderAssistant.setCurrentNamespace(namespace);
            // 解析cacheRef标签
            cacheRefElement(context.evalNode("cache-ref"));
            // 解析cache标签
            cacheElement(context.evalNode("cache"));
            // 解析parameterMap标签
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            // 解析resultMap标签
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            // 解析sql标签
            sqlElement(context.evalNodes("/mapper/sql"));
            // 根据<insert,select,delete,update>标签构建对应的statement对象,保存到Configuration对象中
            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);
        }
    }

    // 根据<insert,select,delete,update>标签构建对应的statement对象,并保存到Configuration类中
    public void buildStatementFromContext(List<XNode> list) {
        // 如果设置了使用指定类型的数据库
        if (configuration.getDatabaseId() != null) {
            buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
    }

    // 创建<insert,select,delete,update>元素的映射语句信息对应的Statement对象
    public void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        // 遍历所有的select|insert|delete|update标签
        for (XNode context : list) {
            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                // 解析该select|insert|delete|update标签标签,并将statement保存到Configuration对象中
                statementParser.parseStatementNode();
            } catch (IncompleteElementException e) {
                // 如果出现异常,先将该标签保存起来,标记该标签未解析完成
                configuration.addIncompleteStatement(statementParser);
            }
        }
    }


    // 通过xml中写的命名空间来注册Mapper接口(如果存在的命名空间,并且该命名空间是Mapper接口的情况)
    public void bindMapperForNamespace() {
        // 获取当前命名空间
        String namespace = builderAssistant.getCurrentNamespace();
        // 如果存在命名空间
        if (namespace != null) {
            Class<?> boundType = null;
            try {
                // 获取命名空间指定的Mapper接口
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException e) {
                // 如果不是Mapper接口,忽略
            }
            // 如果是Mapper接口,并且配置类中没有注册该Mapper
            if (boundType != null && !configuration.hasMapper(boundType)) {
                // 将这个命名空间进行标记,标记为已经加载过了,相同的命名空间不需要重复加载
                configuration.addLoadedResource("namespace:" + namespace);
                /**
                 * 注册该Mapper接口,内部同时也会做一件事,就是按照mybatis的固定规则,使用Mapper接口的类名+.xml来找该Mapper接口的XML映射文件
                 * 如果找到了,则会加载该XML配置文件,将该配置文件中的所有<insert,select>标签解析为Statement
                 * {@link MapperAnnotationBuilder#parse()}
                 * {@link MapperAnnotationBuilder#loadXmlResource()}
                 */
                configuration.addMapper(boundType);
            }
        }
    }

}

public class Configuration {
    // 添加Mapper接口,同时会解析Mappe接口中的注解信息,因为@Select...和<select>...效果一样,都要保存该方法对应的statement
    public <T> void addMapper(Class<T> type) {
        // 只处理接口
        if (type.isInterface()) {
            // 如果已经存在,抛出异常
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // 注册mapper以及给定生成该mapper代理对象的工厂
                knownMappers.put(type, new MapperProxyFactory<>(type));
                // 解析该接口的所有方法
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                // 解析mapper接口的所有方法
                parser.parse();
                // 该接口加载完成
                loadCompleted = true;
            } finally {
                // 如果没有加载成功,异常添加的代理对象工厂
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
}

/**
 * Mapper接口的注解构建器
 * 和{@link XMLStatementBuilder}的作用一样
 * 该类是处理Mapper接口方法注解的形式
 */
public class MapperAnnotationBuilder {

    // 增删查改的注解,类似与xml中的<insert,delete...>标签
    public static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream.of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class, InsertProvider.class, DeleteProvider.class).collect(Collectors.toSet());
    // 配置类信息
    public final Configuration configuration;
    // Mapper.xml/Mapper接口构造者的辅助类
    public final MapperBuilderAssistant assistant;
    // Mapper接口类型
    public final Class<?> type;

    // 解析mapper接口的所有方法
    public void parse() {
        // 当前mapper
        String resource = type.toString();
        /**
         * 如果当前mapper接口没有被加载过,才需要加载
         * {@link XMLMapperBuilder#parse()}
         * {@link XMLMapperBuilder#bindMapperForNamespace()}
         */
        if (!configuration.isResourceLoaded(resource)) {
            // 加载Mapper.xml,因为注册和xml可以共存
            this.loadXmlResource();
            // 标记该mapper被加载过,防止重复加载
            configuration.addLoadedResource(resource);
            // 遍历所有的方法
            for (Method method : type.getMethods()) {
                // 方法是否可以有SQL语句
                // 不能是桥接方法并且不能是默认方法,才可以写sql
                if (!canHaveStatement(method)) {
                    continue;
                }
                // 解析Statement,映射语句
                parseStatement(method);
            }
        }
    }


    // 加载Mapper.xml
    public void loadXmlResource() {
        // 防止多次加载
        if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
            // 找到Mapper接口文件下的Mapper.xml文件
            String xmlResource = type.getName().replace('.', '/') + ".xml";
            InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
            if (inputStream == null) {
                inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
            }
            if (inputStream != null) {
                // Mapper.xml构建器,解析器
                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
                // 解析该Mapper.xml
                xmlParser.parse();
            }
        }
    }

    // 解析Statement,映射语句
    public void parseStatement(Method method) {
        // 获取参数类型,如果有参数类型为RowBounds,ResultHandler的话,那么返回ParamMap类型
        final Class<?> parameterTypeClass = getParameterType(method);
        // 获取方法中的@Lang注解的语言驱动信息
        final LanguageDriver languageDriver = getLanguageDriver(method);
        // 获取所有增删查改的注解,类似与xml中的<insert,delete...>标签
        // 就是从该method中,找到注解列表中指定的注解信息,并包装成AnnotationWrapper对象
        Optional<AnnotationWrapper> annotationWrapper = this.getAnnotationWrapper(method, true, statementAnnotationTypes);
        // 如果存在列表中的注解信息,就需要解析该注解
        annotationWrapper.ifPresent(statementAnnotation -> {
            // 构造SQL源,就是通过语言模板对象,解析为指定的SQL源,可以是动态SQl,也可以是静态SQL
            final SqlSource sqlSource = this.buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
            // 获取SQL类型
            final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
            // 获取方法中Options注解的包装信息,如果不存在返回null
            final Options options = this.getAnnotationWrapper(method, false, Options.class).map(x -> (Options) x.getAnnotation()).orElse(null);
            // 获取该语句的statementID
            final String mappedStatementId = type.getName() + "." + method.getName();
            // 主键生成器
            final KeyGenerator keyGenerator;
            // 保存该映射语句的详细信息
            assistant.addMappedStatement(mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
                    null, parameterTypeClass, resultMapId, getReturnType(method, type), resultSetType, flushCache, useCache,
                    false, keyGenerator, keyProperty, keyColumn, statementAnnotation.getDatabaseId(), languageDriver,
                    options != null ? nullOrEmpty(options.resultSets()) : null, statementAnnotation.isDirtySelect());
        });
    }
}

// mapper.xml中<insert,select,delete,update>元素的映射语句信息的构造者
public class XMLStatementBuilder extends BaseBuilder {

    // Mapper.xml/Mapper接口构造者的辅助类
    public final MapperBuilderAssistant builderAssistant;
    // 当前<select|insert|delete|update>标签的节点
    public final XNode context;
    // 是否指定了在指定的数据库中执行
    public final String requiredDatabaseId;


    // 解析该select|insert|delete|update标签标签
    public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
        // 判断该语句是否符合指定数据库的执行条件
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
        }
        // 获取标签名称
        String nodeName = context.getNode().getNodeName();
        // 解析SQL类型
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        // 是否是select标签
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        // 获取标签的属性
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
        // 将解析好的映射语句保存到配置类中
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
                parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
                keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值