欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-24 13:20:28
...

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+{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包结构如下:

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