mybatis源码解析(二)-加载过程

mybatis源码解析(一)-开篇
mybatis源码解析(二)-加载过程
mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程
mybatis源码解析(四)-Mapper方法调用过程
mybatis源码解析(五)-mybatis如何实现的事务控制
mybatis源码解析(六)-配合spring-tx实现事务的原理
mybatis源码解析(七)-当mybatis一级缓存遇上spring

转载请标明出处:
http://blog.csdn.net/bingospunky/article/details/79197665
本文出自马彬彬的博客

mybatis加载过程概览

mybatis的加载过程,最重要的就是基于xml配置文件的加载,基于xml配置文件的加载过程主要由org.apache.ibatis.builder.xml.XMLConfigBuilder类来完成,主要代码如下:

Code 1
org.apache.ibatis.builder.xml.XMLConfigBuilder

    private void parseConfiguration(XNode root) {
        try {
            Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));
            this.propertiesElement(root.evalNode("properties"));
            this.loadCustomVfs(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectionFactoryElement(root.evalNode("reflectionFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

上述代码针对mybatis xml主配置文件的内容一一做了解析处理。我们这里主要关注一下mybatis xxxMapper.xml相关的加载过程。

mybatis xxxMapper.xml加载过程

每一个xxxMapper.xml,都对应一个XMLMapperBuilder,由XMLMapperBuilder的parse方法解析,代码如下:

Code 2
org.apache.ibatis.builder.xml.XMLMapperBuilder

    public void parse() {
        if (!this.configuration.isResourceLoaded(this.resource)) {
            this.configurationElement(this.parser.evalNode("/mapper"));
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace();
        }
        this.parsePendingResultMaps();
        this.parsePendingChacheRefs();
        this.parsePendingStatements();
    }

解析xxxMapper.xml文件

Code 2第3行解析标签里的内容,处理过程如下:

Code 3
org.apache.ibatis.builder.xml.XMLMapperBuilder

    private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if (namespace != null && !namespace.equals("")) {
                this.builderAssistant.setCurrentNamespace(namespace);
                this.cacheRefElement(context.evalNode("cache-ref"));
                this.cacheElement(context.evalNode("cache"));
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                this.sqlElement(context.evalNodes("/mapper/sql"));
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            } else {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
        } catch (Exception var3) {
            throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
        }
    }

context对应的内容就是这个xxxMapper.xml的完整内容。第9行,把这个里的resultMap标签的内容都添加到protected final Map

解析xxxMapper.java文件

Code 2第5行的方法,代码如下:

Code 4
org.apache.ibatis.builder.xml.XMLMapperBuilder

    private void bindMapperForNamespace() {
        String namespace = this.builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class boundType = null;
            try {
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException var4) {
                ;
            }
            if (boundType != null && !this.configuration.hasMapper(boundType)) {
                this.configuration.addLoadedResource("namespace:" + namespace);
                this.configuration.addMapper(boundType);
            }
        }
    }

该方法获取xxxMapper.xml里的namespace的值,根据这个字符串获取Class类,如果该类存在,那么就解析这个类。解析这个类时,就是获取它的所有方法,如果某个方法包含特定的注解时,就把这个方法转化为对应的MappedStatement。

特定的注解如下:

Code 5

        this.sqlAnnotationTypes.add(Select.class);
        this.sqlAnnotationTypes.add(Insert.class);
        this.sqlAnnotationTypes.add(Update.class);
        this.sqlAnnotationTypes.add(Delete.class);
        this.sqlProviderAnnotationTypes.add(SelectProvider.class);
        this.sqlProviderAnnotationTypes.add(InsertProvider.class);
        this.sqlProviderAnnotationTypes.add(UpdateProvider.class);
        this.sqlProviderAnnotationTypes.add(DeleteProvider.class);

把某个方法转化为MappedStatement的过程是很复杂的,概括地说就是:对于符合条件的方法,通过方法上的注解生成SqlSource,这个SqlSource主要就包含sql语句和参数,再生成一些其他的信息,比如org.apache.ibatis.mapping.SqlCommandType。通过这些其他信息和SqlSource生成org.apache.ibatis.mapping.MappedStatement.Builder,然后通过Builder创建MappedStatement

最后把MappedStatement添加到org.apache.ibatis.session.Configuration中,key为 package+ p a c k a g e + {className}+${methodName}

当mapper.xml里和mapper.java里都配置一个sql时,出现什么情况?

会抛出如下第3行异常。

Code 6

    public V put(String key, V value) {
            if (this.containsKey(key)) {
                throw new IllegalArgumentException(this.name + " already contains value for " + key);
            } else {
                if (key.contains(".")) {
                    String shortKey = this.getShortName(key);
                    if (super.get(shortKey) == null) {
                        super.put(shortKey, value);
                    } else {
                        super.put(shortKey, new Configuration.StrictMap.Ambiguity(shortKey));
                    }
                }
                return super.put(key, value);
            }
        }

mybatis框架添加了一个StrictMap类,继承自HashMap,当put已经存在的key时,抛出异常。
从上面的过程中我们可以看到,在xml解析的key和方法注解解析出来的key是有可能重复的,如果重复了StrictMap的put方法就会抛出异常。

mybatis加载相关的类一览

mybatis加载相关的类主要都在builder这个包下,我们已经见识了多数的类,builder包结构如下:

你可能感兴趣的:(♚java♚,mybatis源码解析)