zoukankan      html  css  js  c++  java
  • Mybatis 流程解析 之 配置加载

    Mybatis加载配置的流程
    我们知道mybatis在ORM框架中具有举足轻重的地位,接下来几篇博客文章将对mybatis的原理和设计理念进行解析,会发现很多很多令人惊喜的设计和想法。
    在mybatis的源码分析流程中,我大概分成四个模块:1、配置加载;2、映射绑定;3、执行操作,封装结果;4、插件开发。
    这篇文章的首先介绍配置加载模块。
     
    首先,我们提出这样的一个问题,就是mybatis是怎样将SQL语句,配置数据源,配置缓存,在mybatis-config.xml文件配置,我们就可以实现自动注入的呢?这种配置化的编程方式,方便了程序员的开发,
    也屏蔽掉了传统JDBC的各种设置,究竟是怎样做的?
     
    问题提出来了,我们需要回答这个问题的时候,首先想到的是,我们从配置文件中加载的这些东西,应该是有一个地方去存储他们吧,想到这个,mybatis的核心配置类  Configuration 就被设计出来了,
    首先看下Configuration主要包含哪些东西。这里我只是贴了一些Configuration中的成员变量,并加了注释。至于其他构造方法等,等用到的时候我们在详细解释。
    /**
     * @author Clinton Begin
     */
    public class Configuration {
    
        protected Environment environment;
    
        /* 是否启用行内嵌套语句**/
        protected boolean safeRowBoundsEnabled;
        protected boolean safeResultHandlerEnabled = true;
        /* 是否启用数据组A_column自动映射到Java类中的驼峰命名的属性**/
        protected boolean mapUnderscoreToCamelCase;
    
        /*当对象使用延迟加载时 属性的加载取决于能被引用到的那些延迟属性,否则,按需加载(需要的是时候才去加载)**/
        protected boolean aggressiveLazyLoading;
    
        /*是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true **/
        protected boolean multipleResultSetsEnabled = true;
    
        /*-允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false**/
        protected boolean useGeneratedKeys;
    
        /* 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。**/
        protected boolean useColumnLabel = true;
    
        /*配置全局性的cache开关,默认为true**/
        protected boolean cacheEnabled = true;
        protected boolean callSettersOnNulls;
        protected boolean useActualParamName = true;
        protected boolean returnInstanceForEmptyRow;
    
        /* 日志打印所有的前缀 **/
        protected String logPrefix;
    
        /* 指定 MyBatis 所用日志的具体实现,未指定时将自动查找**/
        protected Class<? extends Log> logImpl;
        protected Class<? extends VFS> vfsImpl;
        /* 设置本地缓存范围,session:就会有数据的共享,statement:语句范围,这样不会有数据的共享**/
        protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
        /* 设置但JDBC类型为空时,某些驱动程序 要指定值**/
        protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
    
        /* 设置触发延迟加载的方法**/
        protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
    
        /* 设置驱动等待数据响应超时数**/
        protected Integer defaultStatementTimeout;
    
        /* 设置驱动返回结果数的大小**/
        protected Integer defaultFetchSize;
    
        /* 执行类型,有simple、resue及batch**/
        protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    
        /*指定 MyBatis 应如何自动映射列到字段或属性*/
        protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
        protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
    
        protected Properties variables = new Properties();
    
        protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    
        /*MyBatis每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建POJO*/
        protected ObjectFactory objectFactory = new DefaultObjectFactory();
        protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    
        /*延迟加载的全局开关*/
        protected boolean lazyLoadingEnabled = false;
    
        /*指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具*/
        protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
    
        protected String databaseId;
        /**
         * Configuration factory class.
         * Used to create Configuration for loading deserialized unread properties.
         *
         * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
         */
        protected Class<?> configurationFactory;
    
        /*插件集合*/
        protected final InterceptorChain interceptorChain = new InterceptorChain();
    
        /*TypeHandler注册中心*/
        protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    
        /*TypeAlias注册中心*/
        protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
        protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
        //-------------------------------------------------------------
    
        /*mapper接口的动态代理注册中心*/
        protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    
        /*mapper文件中增删改查操作的注册中心*/
        protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
    
        /*mapper文件中配置cache节点的 二级缓存*/
        protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
    
        /*mapper文件中配置的所有resultMap对象  key为命名空间+ID*/
        protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
        protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
    
        /*mapper文件中配置KeyGenerator的insert和update节点,key为命名空间+ID*/
        protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
    
        /*加载到的所有*mapper.xml文件*/
        protected final Set<String> loadedResources = new HashSet<>();
    
        /*mapper文件中配置的sql元素,key为命名空间+ID*/
        protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
    
        protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
        protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
        protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
        protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
    
        /*
         * A map holds cache-ref relationship. The key is the namespace that
         * references a cache bound to another namespace and the value is the
         * namespace which the actual cache is bound to.
         */
        protected final Map<String, String> cacheRefMap = new HashMap<>();
    
        public Configuration(Environment environment) {
            this();
            this.environment = environment;
        }
    }

    从设计代码上,我们不难发现,Configuration 对象 是真的牛啊,在这里回答一个mybatis关于二级缓存的知识点,二级缓存的生命周期是什么时候?回答:是mybatis的整个生命周期,因为如果二级缓存的生命周期不是mybatis的生命周期,

    那么我们在操作二级缓存的时候,我们就没有必要将二级缓存的对象设置在Configuration对象中,当然二级缓存除了生命周期是整个mybatis的生命周期之外,他本身的作用于是namaspace范围的,这个我们后面会详细讲,这里暂时先说个结论。

    从配置文件中的设置的各种属性我们已经找到了存储的地方,那么mybatis是在什么时候,将这些数据怎么放进去的呢?也就是解决when和how的问题,

    接下来就看下继承于BaseBuilder的三个好儿子了,分别是XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder。

    我们首先看下大儿子 XMLConfigBuilder 干了些什么事

    private void parseConfiguration(XNode root) {
            try {
                //issue #117 read properties first
                //解析<properties>节点
                propertiesElement(root.evalNode("properties"));
                //解析<settings>节点
                Properties settings = settingsAsProperties(root.evalNode("settings"));
                loadCustomVfs(settings);
                //解析<typeAliases>节点
                typeAliasesElement(root.evalNode("typeAliases"));
                //解析<plugins>节点
                pluginElement(root.evalNode("plugins"));
                //解析<objectFactory>节点
                objectFactoryElement(root.evalNode("objectFactory"));
                //解析<objectWrapperFactory>节点
                objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
                //解析<reflectorFactory>节点
                reflectorFactoryElement(root.evalNode("reflectorFactory"));
                settingsElement(settings);//将settings填充到configuration
                // read it after objectFactory and objectWrapperFactory issue #631
                //解析<environments>节点
                environmentsElement(root.evalNode("environments"));
                //解析<databaseIdProvider>节点
                databaseIdProviderElement(root.evalNode("databaseIdProvider"));
                //解析<typeHandlers>节点
                typeHandlerElement(root.evalNode("typeHandlers"));
                //解析<mappers>节点
                mapperElement(root.evalNode("mappers"));
            } catch (Exception e) {
                throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
            }
        }

    代码中,我们可以看到,XMLConfigBuilder 大儿子主要解析的是mybatis-config.xml中的一级节点,他主要就是解析了之后,将数据add到Configuration对象中,问题,XMLConfigBuilder大儿子是怎么拿到Configuration对象的呢?

    后来我们发现,这件事让XMLConfigBuilder大儿子的爸爸BaseBuilder给做了,在SqlSessionFactoryBuilder build SqlSessionFactory的时候,我们可以看到他会通过大儿子的实例化方法将大儿子实例化出来,在大儿子的实例化方法中又

    会 执行他老爸的 BaseBuilder方法,所以,这里解决了三个儿子为什么会持有 Configuration 对象的问题。

    大儿子只负责一级标签的解析,又把其他任务扔给了老二 XMLMapperBuilder ,从老二的名字可以看出,老二的主要职责是解析 mapper文件的,主要核心方法是parse()->configurationElement()

     private void configurationElement(XNode context) {
            try {
                //获取mapper节点的namespace属性
                String namespace = context.getStringAttribute("namespace");
                if (namespace == null || namespace.equals("")) {
                    throw new BuilderException("Mapper's namespace cannot be empty");
                }
                //设置builderAssistant的namespace属性
                builderAssistant.setCurrentNamespace(namespace);
                //解析cache-ref节点
                cacheRefElement(context.evalNode("cache-ref"));
                //重点分析 :解析cache节点----------------1-------------------
                cacheElement(context.evalNode("cache"));
                //解析parameterMap节点(已废弃)
                parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                //重点分析 :解析resultMap节点(基于数据结果去理解)----------------2-------------------
                resultMapElements(context.evalNodes("/mapper/resultMap"));
                //解析sql节点
                sqlElement(context.evalNodes("/mapper/sql"));
                //重点分析 :解析select、insert、update、delete节点 ----------------3-------------------
                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);
            }
        }

    其中我们需要重点去了解的方法是 cacheRefElement、resultMapElements、buildStatementFromContext 这三个方法。

    cacheRefElement 方法主要是应对二级缓存而存在的方法。

    private void cacheElement(XNode context) throws Exception {
            if (context != null) {
                //获取cache节点的type属性,默认为PERPETUAL
                String type = context.getStringAttribute("type", "PERPETUAL");
                //根据type对应的cache接口的实现
                Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
                //读取eviction属性,既缓存的淘汰策略,默认LRU
                String eviction = context.getStringAttribute("eviction", "LRU");
                //根据eviction属性,找到装饰器
                Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
                //读取flushInterval属性,既缓存的刷新周期
                Long flushInterval = context.getLongAttribute("flushInterval");
                //读取size属性,既缓存的容量大小
                Integer size = context.getIntAttribute("size");
                //读取readOnly属性,既缓存的是否只读
                boolean readWrite = !context.getBooleanAttribute("readOnly", false);
                //读取blocking属性,既缓存的是否阻塞
                boolean blocking = context.getBooleanAttribute("blocking", false);
                Properties props = context.getChildrenAsProperties();
                //通过builderAssistant创建缓存对象,并添加至configuration
                builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
            }
        }
    
    
    //通过builderAssistant创建缓存对象,并添加至configuration
      public Cache useNewCache(Class<? extends Cache> typeClass,
          Class<? extends Cache> evictionClass,
          Long flushInterval,
          Integer size,
          boolean readWrite,
          boolean blocking,
          Properties props) {
        //经典的建造起模式,创建一个cache对象
        Cache cache = new CacheBuilder(currentNamespace)
            .implementation(valueOrDefault(typeClass, PerpetualCache.class))
            .addDecorator(valueOrDefault(evictionClass, LruCache.class))
            .clearInterval(flushInterval)
            .size(size)
            .readWrite(readWrite)
            .blocking(blocking)
            .properties(props)
            .build();
        //将缓存添加至configuration,注意二级缓存以命名空间为单位进行划分
        configuration.addCache(cache);
        currentCache = cache;
        return cache;
      }

    resultMapElements 主要作用是为了解析mapper文件中的ResultMap标签,这里面就是具体的解析出来返回的Result结果,具体可以去参照代码

    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
            ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
            //获取resultmap节点的id属性
            String id = resultMapNode.getStringAttribute("id",
                    resultMapNode.getValueBasedIdentifier());
            //获取resultmap节点的type属性
            String type = resultMapNode.getStringAttribute("type",
                    resultMapNode.getStringAttribute("ofType",
                            resultMapNode.getStringAttribute("resultType",
                                    resultMapNode.getStringAttribute("javaType"))));
            //获取resultmap节点的extends属性,描述继承关系
            String extend = resultMapNode.getStringAttribute("extends");
            //获取resultmap节点的autoMapping属性,是否开启自动映射
            Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
            //从别名注册中心获取entity的class对象
            Class<?> typeClass = resolveClass(type);
            Discriminator discriminator = null;
            //记录子节点中的映射结果集合
            List<ResultMapping> resultMappings = new ArrayList<>();
            resultMappings.addAll(additionalResultMappings);
            //从xml文件中获取当前resultmap中的所有子节点,并开始遍历
            List<XNode> resultChildren = resultMapNode.getChildren();
            for (XNode resultChild : resultChildren) {
                if ("constructor".equals(resultChild.getName())) {//处理<constructor>节点
                    processConstructorElement(resultChild, typeClass, resultMappings);
                } else if ("discriminator".equals(resultChild.getName())) {//处理<discriminator>节点
                    discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
                } else {//处理<id> <result> <association> <collection>节点
                    List<ResultFlag> flags = new ArrayList<>();
                    if ("id".equals(resultChild.getName())) {
                        flags.add(ResultFlag.ID);//如果是id节点,向flags中添加元素
                    }
                    //创建ResultMapping对象并加入resultMappings集合中
                    resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
                }
            }
            //实例化resultMap解析器
            ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
            try {
                //通过resultMap解析器实例化resultMap并将其注册到configuration对象
                return resultMapResolver.resolve();
            } catch (IncompleteElementException e) {
                configuration.addIncompleteResultMap(resultMapResolver);
                throw e;
            }
        }

    buildStatementFromContext 方法中主要是牵扯到了BaseBuilder的第三个儿子,即XMLStatementBuilder,这第三个儿子做的事,就是解析SQL语句的select、update、delete、insert语句,也就是BaseBuilder和XMLConfigBuilder、XMLMapperBuilder将准备工作都做好了,现在该小儿子去分析具体的SQL语句了。

    public void parseStatementNode() {
            //获取sql节点的id
            String id = context.getStringAttribute("id");
            String databaseId = context.getStringAttribute("databaseId");
    
            if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
                return;
            }
            /*获取sql节点的各种属性*/
            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);
    
    
            //根据sql节点的名称获取SqlCommandType(INSERT, UPDATE, DELETE, SELECT)
            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
            //在解析sql语句之前先解析<include>节点
            XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
            includeParser.applyIncludes(context.getNode());
    
            // Parse selectKey after includes and remove them.
            //在解析sql语句之前,处理<selectKey>子节点,并在xml节点中删除
            processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
            // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
            //解析sql语句是解析mapper.xml的核心,实例化sqlSource,使用sqlSource封装sql语句
            SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
            String resultSets = context.getStringAttribute("resultSets");//获取resultSets属性
            String keyProperty = context.getStringAttribute("keyProperty");//获取主键信息keyProperty
            String keyColumn = context.getStringAttribute("keyColumn");///获取主键信息keyColumn
    
            //根据<selectKey>获取对应的SelectKeyGenerator的id
            KeyGenerator keyGenerator;
            String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
            keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    
    
            //获取keyGenerator对象,如果是insert类型的sql语句,会使用KeyGenerator接口获取数据库生产的id;
            if (configuration.hasKeyGenerator(keyStatementId)) {
                keyGenerator = configuration.getKeyGenerator(keyStatementId);
            } else {
                keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            }
    
            //通过builderAssistant实例化MappedStatement,并注册至configuration对象
            builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                    fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                    resultSetTypeEnum, flushCache, useCache, resultOrdered,
                    keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }

    我们可以看到,二儿子和三儿子都是使用builderAssistant对象将自身解析出来的数据来放到Configuration对象中,这样设计的目的是什么?为什么大儿子没有使用builderAssistant去进行将数据加载到Configuration对象中呢?原因可能是大

    儿子解析过程相对简单,并没有那么复杂,向Configuration中添加的时候,也不会很复杂,而儿子和小儿子就不一样了,一个需要处理缓存,mapper中的ResultMap,一个需要处理具体的增删改查的语句,其中牵扯到的点也是比较多,所以二儿子和小

    儿子就找了个秘书,让秘书完成他们不关心的事。

    小儿子将 mapper文件中的 namespace + 语句id 作为key value值为 MappedStatement对象 存储在Configuration的mappedStatements变量中。

    至此,mybatis的初始化加载数据的流程到此告一段落,第一阶段中,mybatis将我们需要用到的所有数据全数存储到Configuration对象中了,为下一阶段的映射绑定打下了坚实的基础。

    具体流程图可以参考我画的一个,有点拙劣,请见谅。

     

     
  • 相关阅读:
    Linux 软件安装到哪里合适,目录详解
    python如何判断1个列表中所有的数据都是相等的?
    web接口开发基础知识-什么是web接口?
    MIME TYPE是什么?
    jenkins展示html测试报告(不使用html publisher)
    【转】Java虚拟机的JVM垃圾回收机制
    Map 排序
    sql in 和 exist的区别
    distinct和group by 去掉重复数据分析
    sql执行机制
  • 原文地址:https://www.cnblogs.com/monco-sxy/p/13609414.html
Copyright © 2011-2022 走看看