zoukankan      html  css  js  c++  java
  • mybatis(一)SqlSessionFactory初始化

    Main方法

    代码版本:3.4.2

        public static void main(String[] args) throws IOException {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //入口
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            try {
                Employee employeeMapper = sqlSession.getMapper(Employee.class);
                List<Employee> all = employeeMapper.getAll();
                for (Employee item : all)
                    System.out.println(item);
            } finally {
                sqlSession.close();
            }
        }
    
    1. 创建一个SqlSessionFactory。
    2. 通过SqlSessionFactory获取一个SqlSession
    3. 从SqlSession获取需要的mapper

    创建SqlSessionFactory

    //    SqlSessionFactoryBuilder
        public SqlSessionFactory build(InputStream inputStream) {
            return this.build((InputStream)inputStream, (String)null, (Properties)null);
        }
        public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
            // 创建配置文件解析器
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //解析配置文件,生成Configuration对象
            //入口
            Configuration configuration = parser.parse();
            //通过Configuration对象生成SqlSessionFactory,
            //就是创建一个SqlSessionFactory,然后设置其Configuration属性
            SqlSessionFactory var5 = this.build(configuration);
            return var5;
        }
    
    1. 创建配置文件解析器
    2. 通过parse方法解析配置文件,将解析结果封装到Configuration
    3. 通过Configuration创建SqlSessionFactory

    接下来看【通过parse方法解析配置文件,将解析结果封装到Configuration】

    //XMLConfigBuilder
    public Configuration parse() {
            if (this.parsed) {
                throw new BuilderException("Each XMLConfigBuilder can only be used once.");
            } else {
                this.parsed = true;
                //入口
                this.parseConfiguration(this.parser.evalNode("/configuration"));
                return this.configuration;
            }
        }
    
    //    XMLConfigBuilder
     private void parseConfiguration(XNode root) {
            try {
                private void parseConfiguration(XNode root) {
                    try {
                        // 解析 properties 配置
                        propertiesElement(root.evalNode("properties"));
    
                        // 解析 settings 配置,并将其转换为 Properties 对象
                        Properties settings = settingsAsProperties(root.evalNode("settings"));
    
                        loadCustomVfs(settings);
    
                        // settings 中的信息设置到 Configuration 对象中
                        settingsElement(settings);
    
                        // 解析 typeAliases 配置
                        typeAliasesElement(root.evalNode("typeAliases"));
    
                        // 解析 plugins 配置
                        pluginElement(root.evalNode("plugins"));
    
                        objectFactoryElement(root.evalNode("objectFactory"));
    
                        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    
                        reflectorFactoryElement(root.evalNode("reflectorFactory"));
    
                        // 解析 environments 配置
                        environmentsElement(root.evalNode("environments"));
    
                        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    
                        typeHandlerElement(root.evalNode("typeHandlers"));
    
                        // 解析 mappers 配置
                        mapperElement(root.evalNode("mappers"));
                    } catch (Exception e) {
                        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
                    }
                }
            } catch (Exception e) {
                throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
            }
        }
    

    定义了一堆标签解析入口:

    1. propertiesElement:解析properties配置
    2. settingsAsProperties:解析settings配置,并将其转换为Properties对象
    3. settingsElement:settings中的信息设置到Configuration对象中
    4. typeAliasesElement:解析typeAliases配置
    5. pluginElement:解析plugins配置
    6. environmentsElement:解析environments配置
    7. mapperElement:解析mappers配置

    返回顶部

    propertiesElement:解析properties配置

    <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </properties>
    
    //XMLConfigBuilder
     private void propertiesElement(XNode context) throws Exception {
            if (context != null) {
                // 解析 propertis 的子节点,并将这些节点内容转换为属性对象 Properties
                //入口
                Properties defaults = context.getChildrenAsProperties();
                // 获取 propertis 节点中的 resource 和 url 属性值
                String resource = context.getStringAttribute("resource");
                String url = context.getStringAttribute("url");
    
                // 两者都不为空,则抛出异常
                if (resource != null && url != null) {
                    throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
                }
                if (resource != null) {
                    // 从文件系统中加载并解析属性文件
                    defaults.putAll(Resources.getResourceAsProperties(resource));
                } else if (url != null) {
                    // 通过 url 加载并解析属性文件
                    defaults.putAll(Resources.getUrlAsProperties(url));
                }
                Properties vars = configuration.getVariables();
                if (vars != null) {
                    defaults.putAll(vars);
                }
                parser.setVariables(defaults);
                // 将属性值设置到 configuration 中
                configuration.setVariables(defaults);
            }
        }
        public Properties getChildrenAsProperties() {
            //创建一个Properties对象
            Properties properties = new Properties();
            // 获取并遍历子节点
            for (XNode child : getChildren()) {
                // 获取 property 节点的 name 和 value 属性
                String name = child.getStringAttribute("name");
                String value = child.getStringAttribute("value");
                if (name != null && value != null) {
                    // 设置属性到属性对象中
                    properties.setProperty(name, value);
                }
            }
            return properties;
        }
    
    1. 解析出property的name和value封装到Properties对象中,然后设置到configuration的属性里
    2. 不只是从property属性加载,还会从文件系统或者网络读取属性配置,这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties子节点中同名的属性和及值

    返回顶部

    settingsAsProperties:解析settings配置,并将其转换为Properties对象

    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
    </settings>
    
    //    XMLConfigBuilder
        private Properties settingsAsProperties(XNode context) {
            if (context == null) {
                return new Properties();
            }
            // 获取 settings 子节点中的内容,解析成Properties,getChildrenAsProperties 方法前面已分析过
            Properties props = context.getChildrenAsProperties();
    
            // 创建 Configuration 类的“元信息”对象
            MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
            for (Object key : props.keySet()) {
                // 检测 Configuration 中是否存在相关属性,不存在则抛出异常
                if (!metaConfig.hasSetter(String.valueOf(key))) {
                    throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
                }
            }
            return props;
        }
    
    1. 遍历所有的setting,取出name、value封装到Properties
    2. 所有setting配置在Configuration类中都要有一个属性与其对应,如果没有则抛出异常

    返回顶部

    settingsElement:settings中的信息设置到Configuration对象中

    //    XMLConfigBuilder
        private void settingsElement(Properties props) throws Exception {
            // 设置 autoMappingBehavior 属性,默认值为 PARTIAL
            configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
            configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
            // 设置 cacheEnabled 属性,默认值为 true
            configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    
            // 解析默认的枚举处理器
            Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
            // 设置默认枚举处理器
            configuration.setDefaultEnumTypeHandler(typeHandler);
            configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
            configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
        }
    
    1. 将解析settings标签获取到的properties中的属性在取出来,直接设置到configuration里。

    返回顶部

    typeAliasesElement:解析typeAliases配置

    在 MyBatis 中,可以为我们自己写的有些类定义一个别名。这样在使用的时候,我们只需要输入别名即可,无需再把全限定的类名写出来。

    有两种方式进行别名配置。第一种是仅配置包名,让MyBatis去扫描包中的类型,并根据类型得到相应的别名

    <typeAliases>
        <package name="com.mybatis.model"/>
    </typeAliases>
    

    第二种方式是通过手动的方式,明确为某个类型配置别名。

    <typeAliases>
        <typeAlias alias="employe" type="com.mybatis.model.Employe" />
        <typeAlias type="com.mybatis.model.User" />//alias不是必须的,没配置的话就是user
    </typeAliases>
    
    //    XMLConfigBuilder
    private void typeAliasesElement(XNode parent) {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                // 第一种方式:自动扫描
                if ("package".equals(child.getName())) {
                    String typeAliasPackage = child.getStringAttribute("name");
                    //入口3
                    configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
    
                } else {//第二种方式:配置别名
                    // 获取 alias 和 type 属性值,alias 不是必填项,可为空
                    String alias = child.getStringAttribute("alias");
                    String type = child.getStringAttribute("type");
                    try {
                        // 加载 type 对应的类型
                        Class<?> clazz = Resources.classForName(type);
    
                        // 注册别名到类型的映射
                        if (alias == null) {
                            //入口1
                            typeAliasRegistry.registerAlias(clazz);
                        } else {
                            //入口2
                            typeAliasRegistry.registerAlias(alias, clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                    }
                }
            }
        }
    }
    
        public void registerAliases(String packageName) {
            registerAliases(packageName, Object.class);
        }
        public void registerAliases(String packageName, Class<?> superType) {
            ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
            resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
            Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
            //查找指定包下的所有类,遍历查找到的类型集合,为每个类型注册别名
            for (Class<?> type : typeSet) {
                // 忽略匿名类,接口,内部类
                if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
                    // 为类型注册别名
                    registerAlias(type);
                }
            }
        }
    
    //TypeAliasRegistry
    private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
        public void registerAlias(Class<?> type) {
            // 获取全路径类名的简称
            String alias = type.getSimpleName();
            Alias aliasAnnotation = type.getAnnotation(Alias.class);
            if (aliasAnnotation != null) {
                // 从注解中取出别名
                alias = aliasAnnotation.value();
            }
            // 调用重载方法注册别名和类型映射
            registerAlias(alias, type);
        }
        public void registerAlias(String alias, Class<?> value) {
            if (alias == null) {
                throw new TypeException("The parameter alias cannot be null");
            }
            // 将别名转成小写
            String key = alias.toLowerCase(Locale.ENGLISH);
            /*
             * 如果 TYPE_ALIASES 中存在了某个类型映射,这里判断当前类型与映射中的类型是否一致,
             * 不一致则抛出异常,不允许一个别名对应两种类型
             */
            if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
                throw new TypeException(
                        "The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
            }
            // 缓存别名到类型映射
            TYPE_ALIASES.put(key, value);
        }
    
    1. 判断是否包含package属性,如果包含,查找包下所有的类,过滤掉接口、内部类等,遍历挨个调用registerAlias
    2. 如果不包含,判断是否指定了alias属性,如果没指定,则从类获取alias注解,如果获取到注解就按照注解设置的别名,如果没获取到注解,则按照类的名称,最后调用registerAlias
    3. 如果指定了alias,直接按照指定到alias调用registerAlias
    4. 最后看registerAlias:
      • 将别名转换成小写
      • 检查别名不能重复,就是一个别名不能对应两种类型
      • 将别名存入到TypeAliasRegistry中。

    返回顶部

    pluginElement:解析plugins配置

    插件是 MyBatis 提供的一个拓展机制,通过插件机制我们可在 SQL 执行过程中的某些点上做一些自定义操作。比如分页插件,在SQL执行之前动态拼接语句

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>
    
    //    XMLConfigBuilder
        private void pluginElement(XNode parent) throws Exception {
            if (parent != null) {
                for (XNode child : parent.getChildren()) {
                    String interceptor = child.getStringAttribute("interceptor");
                    // 获取配置信息
                    Properties properties = child.getChildrenAsProperties();
                    // 解析拦截器的类型,并创建拦截器
                    Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
                    // 设置属性
                    interceptorInstance.setProperties(properties);
                    // 添加拦截器到 Configuration 中
                    configuration.addInterceptor(interceptorInstance);
                }
            }
        }
    //    Configuration
        public void addInterceptor(Interceptor interceptor) {
            this.interceptorChain.addInterceptor(interceptor);
        }
    
    1. 插件其实就是拦截器,对执行的sql进行拦截。
    2. 就是解析出plugin,创建拦截器,然后放入到interceptorChain中。interceptorChain类内部维护了一个拦截器集合

    返回顶部

    environmentsElement:解析environments配置

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    
    //    XMLConfigBuilder
        private void environmentsElement(XNode context) throws Exception {
            if (context != null) {
                if (environment == null) {
                    // 获取 default 属性
                    environment = context.getStringAttribute("default");
                }
                for (XNode child : context.getChildren()) {
                    // 获取 id 属性
                    String id = child.getStringAttribute("id");
                    /*
                     * 检测当前 environment 节点的 id 与其父节点 environments 的属性 default
                     * 内容是否一致,一致则返回 true,否则返回 false
                     * 将其default属性值与子元素environment的id属性值相等的子元素设置为当前使用的Environment对象
                     */
                    if (isSpecifiedEnvironment(id)) {
                        // 将environment中的transactionManager标签转换为TransactionFactory对象
                        //入口1
                        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                        // 将environment中的dataSource标签转换为DataSourceFactory对象
                        //入口2
                        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                        // 创建 DataSource 对象
                        DataSource dataSource = dsFactory.getDataSource();
                        Environment.Builder environmentBuilder = new Environment.Builder(id)
                                .transactionFactory(txFactory)
                                .dataSource(dataSource);
                        // 构建 Environment 对象,并设置到 configuration 中
                        configuration.setEnvironment(environmentBuilder.build());
                    }
                }
            }
        }
    
    private TransactionFactory transactionManagerElement(XNode context) throws Exception {
            if (context != null) {
                String type = context.getStringAttribute("type");
                Properties props = context.getChildrenAsProperties();
                //通过别名获取Class,并实例化
                TransactionFactory factory = (TransactionFactory)this.resolveClass(type).newInstance();
                factory.setProperties(props);
                return factory;
            } else {
                throw new BuilderException("Environment declaration requires a TransactionFactory.");
            }
        }
        private DataSourceFactory dataSourceElement(XNode context) throws Exception {
            if (context != null) {
                String type = context.getStringAttribute("type");
                //通过别名获取Class,并实例化
                Properties props = context.getChildrenAsProperties();
                DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).newInstance();
                factory.setProperties(props);
                return factory;
            } else {
                throw new BuilderException("Environment declaration requires a DataSourceFactory.");
            }
        }
    
    1. environment的id属性和environments的default属性要一致,用来处理多环境
    2. 通过transactionManager标签和dataSource标签,反射实例化TransactionFactory和DataSourceFactory
    3. 创建DataSource
    4. 将DataSource 和TransactionFactory都设置到environment中,并且将environment添加到Configuration
    5. environment是用来存储事务和数据源的。总结一下这步就是通过反射,按照配置实例化事务工厂和数据源

    返回顶部

    mapperElement:解析mappers配置

    常用的配置有三种情况:
    1、接口信息进行配置:这种方式必须保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。因为是通过获取mapper中的class属性,拼接上.xml来读取UserMapper.xml,如果xml文件名不同或者不在同一个包中是无法读取到xml的。

    <mappers>
        <mapper class="org.mybatis.mappers.UserMapper"/>
        <mapper class="org.mybatis.mappers.ProductMapper"/>
        <mapper class="org.mybatis.mappers.ManagerMapper"/>
    </mappers>
    

    2、相对路径进行配置:这种方式不用保证同接口同包同名。但是要保证xml中的namespase和对应的接口名相同。

    <mappers>
        <mapper resource="org/mybatis/mappers/UserMapper.xml"/>
        <mapper resource="org/mybatis/mappers/ProductMapper.xml"/>
        <mapper resource="org/mybatis/mappers/ManagerMapper.xml"/>
    </mappers>
    

    3、接口所在包进行配置:这种方式和第一种方式要求一致,保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。

    <mappers>
        <package name="org.mybatis.mappers"/>
    </mappers>
    
    //    XMLConfigBuilder
        private void mapperElement(XNode parent) throws Exception {
            if (parent != null) {
                for (XNode child : parent.getChildren()) {
                    //包扫描的形式
                    if ("package".equals(child.getName())) {
                        // 获取 <package> 节点中的 name 属性
                        String mapperPackage = child.getStringAttribute("name");
                        // 从指定包中查找 所有的 mapper 接口,并根据 mapper 接口解析映射配置
                        configuration.addMappers(mapperPackage);
                    } else {
                        String resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
    
                        //相对路径的方式
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            InputStream inputStream = Resources.getResourceAsStream(resource);
                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                            // 解析映射文件
                            mapperParser.parse();
                            // 绝对路径方式,略过
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            InputStream inputStream = Resources.getUrlAsStream(url);
                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                            mapperParser.parse();
                        } else if (resource == null && url == null && mapperClass != null) {
                            // 通过 mapperClass 解析映射配置
                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            configuration.addMapper(mapperInterface);
                        } else {
                            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                        }
                    }
                }
            }
        }
    
    //Configuration
        public void addMappers(String packageName) {
            mapperRegistry.addMappers(packageName);
        }
        //MapperRegistry
        public void addMappers(String packageName) {
            this.addMappers(packageName, Object.class);
        }
        public void addMappers(String packageName, Class<?> superType) {
            ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
            resolverUtil.find(new IsA(superType), packageName);
            Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
            Iterator i$ = mapperSet.iterator();
    
            while(i$.hasNext()) {
                Class<?> mapperClass = (Class)i$.next();
                //找出包下的所有mapper,挨个调用这个方法
                //入口
                this.addMapper(mapperClass);
            }
    
        }
        public <T> void addMapper(Class<T> type) {
            if (type.isInterface()) {//mapper是接口
                if (this.hasMapper(type)) {
                    throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
                }
                boolean loadCompleted = false;
    
                try {
                    this.knownMappers.put(type, new MapperProxyFactory(type));
                    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                    parser.parse();//入口
                    loadCompleted = true;
                } finally {
                    if (!loadCompleted) {
                        this.knownMappers.remove(type);
                    }
    
                }
            }
    
        }
    
    //MapperAnnotationBuilder
    public void parse() {
            String resource = this.type.toString();
            if (!this.configuration.isResourceLoaded(resource)) {
                this.loadXmlResource();//入口
                this.configuration.addLoadedResource(resource);
                this.assistant.setCurrentNamespace(this.type.getName());
                this.parseCache();
                this.parseCacheRef();
                Method[] methods = this.type.getMethods();
                Method[] var3 = methods;
                int var4 = methods.length;
    
                for(int var5 = 0; var5 < var4; ++var5) {
                    Method method = var3[var5];
    
                    try {
                        if (!method.isBridge()) {
                            this.parseStatement(method);
                        }
                    } catch (IncompleteElementException var8) {
                        this.configuration.addIncompleteMethod(new MethodResolver(this, method));
                    }
                }
            }
    
            this.parsePendingMethods();
        }
    private void loadXmlResource() {
            if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
                String xmlResource = this.type.getName().replace('.', '/') + ".xml";
                InputStream inputStream = null;
    
                try {
                    inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
                } catch (IOException var4) {
                }
    
                if (inputStream != null) {
                    XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
                    xmlParser.parse();//入口
                }
            }
    
        }
    
    //XMLMapperBuilder
    public void parse() {
        // 检测映射文件是否已经被解析过
        if (!configuration.isResourceLoaded(resource)) {
            //解析mapper的方法入口
            //入口
            configurationElement(parser.evalNode("/mapper"));
            // 添加资源路径到“已解析资源集合”中
            configuration.addLoadedResource(resource);
            // 通过命名空间绑定 Mapper 接口
            bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
    }
    
    1. 按照配置的规则找出所有mapper xml文件(如果配置的是class,则通过名称拼接找到对应的xml)
    2. 通过configurationElement方法挨个对xml文件进行解析
    3. 将解析结果和相应的mapper接口进行绑定

    先看一看xml大致是什么样。

    <?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="mapper.EmployeeMapper">
        <cache/>
        
        <resultMap id="baseMap" type="entity.Employee">
            <result property="id" column="id" jdbcType="INTEGER"></result>
            <result property="name" column="name" jdbcType="VARCHAR"></result>
        </resultMap>
        
        <sql id="table">
            employee
        </sql>
        
        <select id="getAll" resultMap="baseMap">
            select * from  <include refid="table"/>  WHERE id = #{id}
        </select>
        
        <!-- <insert|update|delete/> -->
    </mapper>
    

    看一下【通过configurationElement方法挨个对xml文件进行解析】

    //XMLMapperBuilder
    private void configurationElement(XNode context) {
            try {
                // 获取 mapper 命名空间,如 mapper.EmployeeMapper
                String namespace = context.getStringAttribute("namespace");
                if (namespace == null || namespace.equals("")) {
                    throw new BuilderException("Mapper's namespace cannot be empty");
                }
    
                // 设置命名空间到 builderAssistant 中
                builderAssistant.setCurrentNamespace(namespace);
    
                // 解析 <cache-ref> 节点
                cacheRefElement(context.evalNode("cache-ref"));
    
                // 解析 <cache> 节点
                cacheElement(context.evalNode("cache"));
    
                // 已废弃配置,这里不做分析
                parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    
                // 解析 <resultMap> 节点
                resultMapElements(context.evalNodes("/mapper/resultMap"));
    
                // 解析 <sql> 节点
                sqlElement(context.evalNodes("/mapper/sql"));
    
                // 解析 <select>、<insert>、<update>、<delete> 节点
                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);
            }
        }
    

    定义了一堆标签的解析入口,接下来分别看一下

    返回顶部

    解析cache

    MyBatis 提供了一、二级缓存,其中一级缓存是 SqlSession 级别的,默认为开启状态。二级缓存配置在映射文件中,使用者需要显式配置才能开启。如下:

    <cache/>
    

    也可以使用第三方缓存

    <cache type="org.mybatis.caches.redis.RedisCache"/>
    

    其中有一些属性可以选择

    <cache eviction="LRU"  flushInterval="60000"  size="512" readOnly="true"/>
    
    //XMLMapperBuilder
    private void cacheElement(XNode context) throws Exception {
            if (context != null) {
                // 获取type属性,如果type没有指定就用默认的PERPETUAL(早已经注册过的别名的PerpetualCache)
                String type = context.getStringAttribute("type", "PERPETUAL");
                // 根据type从早已经注册的别名中获取对应的Class,PERPETUAL对应的Class是PerpetualCache.class
                // 如果我们写了type属性,如type="org.mybatis.caches.redis.RedisCache",这里将会得到RedisCache.class
                Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
                //获取淘汰方式,默认为LRU(早已经注册过的别名的LruCache),最近最少使用到的先淘汰
                String eviction = context.getStringAttribute("eviction", "LRU");
                Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
                Long flushInterval = context.getLongAttribute("flushInterval");
                Integer size = context.getIntAttribute("size");
                boolean readWrite = !context.getBooleanAttribute("readOnly", false);
                boolean blocking = context.getBooleanAttribute("blocking", false);
    
                // 获取子节点配置
                Properties props = context.getChildrenAsProperties();
    
                // 构建缓存对象
                //入口
                builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
            }
        }
    
    //MapperBuilderAssistant
    public Cache useNewCache(Class<? extends Cache> typeClass,
                                 Class<? extends Cache> evictionClass,Long flushInterval,
                                 Integer size,boolean readWrite,boolean blocking,Properties props) {
    
            // 使用建造模式构建缓存实例
            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 属性,即当前使用的缓存
            currentCache = cache;
            return cache;
        }
    
    //CacheBuilder
    public Cache build() {
            // 设置默认的缓存类型(PerpetualCache)和淘汰策略(LruCache)
            setDefaultImplementations();//入口1
    
            // 通过反射创建缓存
            //入口2
            Cache cache = newBaseCacheInstance(implementation, id);
            setCacheProperties(cache);
            //接下来是给缓存设置装饰器(缓存的各种属性都封装成一个个装饰器)
            if (PerpetualCache.class.equals(cache.getClass())) {//默认缓存
                // 遍历装饰器集合,应用装饰器
                for (Class<? extends Cache> decorator : decorators) {
                    // 通过反射创建装饰器实例
                    cache = newCacheDecoratorInstance(decorator, cache);
                    // 设置属性值到缓存实例中
                    setCacheProperties(cache);
                }
                // 应用标准的装饰器,比如 LoggingCache、SynchronizedCache
                cache = setStandardDecorators(cache);
            } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {// 具有日志装饰功能的缓存       
                cache = new LoggingCache(cache);
            }
            return cache;
        }
    
    
    
    //CacheBuilder
    private void setDefaultImplementations() {
            if (this.implementation == null) {
                //设置默认缓存类型为PerpetualCache
                this.implementation = PerpetualCache.class;
                //将淘汰策略放入装饰器集合中
                if (this.decorators.isEmpty()) {
                    this.decorators.add(LruCache.class);
                }
            }
        }
    
    
     private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
            //获取构造器
            Constructor cacheConstructor = this.getBaseCacheConstructor(cacheClass);
    
            try {
                //通过构造器实例化Cache
                return (Cache)cacheConstructor.newInstance(id);
            } catch (Exception var5) {
                throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + var5, var5);
            }
        }
    
    1. 获取缓存类型,有配置就取配置的,没配置就取默认的。默认缓存类型PerpetualCache
    2. 获取淘汰策略,有配置就取配置的,没配置就取默认的,默认淘汰策略LruCache,最近最少使用到的先淘汰。
      • 还支持先进先出的淘汰策略
    3. 通过建造者模式创建一个cache对象
      • 将淘汰策略和缓存类型设置cache中。注意:缓存类型是cache的一个属性,而淘汰策略是其一个装饰器
      • 通过反射创建cache
      • 给cache进行装饰,包括淘汰策略、日志、锁等等装饰器
    4. 将cache添加到Configuration中。是一个map,key是缓存id,value是cache对象

    返回顶部

    解析ResultMap

    //XMLMapperBuilder
    private void resultMapElements(List<XNode> list) throws Exception {
            // 遍历 <resultMap> 节点列表
            for (XNode resultMapNode : list) {
                try {
                    // 解析 resultMap 节点
                    //入口
                    resultMapElement(resultMapNode);
                } catch (IncompleteElementException e) {
                }
            }
        }
        private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
            return resultMapElement(resultMapNode, Collections.<ResultMapping>emptyList());
        }
    
    //XMLMapperBuilder
    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
            ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    
            // 获取 id 和 type 属性
            String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
            String type = resultMapNode.getStringAttribute("type",
                    resultMapNode.getStringAttribute("ofType",
                            resultMapNode.getStringAttribute("resultType",
                                    resultMapNode.getStringAttribute("javaType"))));
            // 获取 extends 和 autoMapping
            String extend = resultMapNode.getStringAttribute("extends");
            Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    
            // 获取 type 属性对应的类型
            Class<?> typeClass = resolveClass(type);
            Discriminator discriminator = null;
            //创建ResultMapping集合,用来存储result节点
            List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
            resultMappings.addAll(additionalResultMappings);
    
            // 获取并遍历 <resultMap> 的子节点列表
            List<XNode> resultChildren = resultMapNode.getChildren();
            for (XNode resultChild : resultChildren) {
                if ("constructor".equals(resultChild.getName())) {
                    processConstructorElement(resultChild, typeClass, resultMappings);
                } else if ("discriminator".equals(resultChild.getName())) {
                    discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
                } else {
    
                    List<ResultFlag> flags = new ArrayList<ResultFlag>();
                    if ("id".equals(resultChild.getName())) {
                        // 添加 ID 到 flags 集合中
                        flags.add(ResultFlag.ID);
                    }
                    // 解析 id 和 result 节点,将id或result节点生成相应的 ResultMapping,将ResultMapping添加到resultMappings集合中
                    //入口1
                    resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
                }
            }
            //创建ResultMapResolver对象
            ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend,
                    discriminator, resultMappings, autoMapping);
            try {
                // 根据前面获取到的信息构建 ResultMap 对象
                return resultMapResolver.resolve();
            } catch (IncompleteElementException e) {
                configuration.addIncompleteResultMap(resultMapResolver);
                throw e;
            }
        }
    
    1. 遍历mapper.xml下所有resultMap
    2. 创建ResultMapping集合,用来存储result节点,每一条ResultMapping对应resultMap的一个子元素
    3. 遍历resultMap子元素,子节点有三种类型:id、result、association。association属于嵌套。解析出来也是一个ResultMapping集合。
    4. 最后通过解析出来的ResultMapping构建ResultMap对象

    首先看两个复杂map是什么样

    <resultMap id="articleResult" type="Article">
        <id property="id" column="id"/>
        <result property="title" column="article_title"/>
        <!-- 引用 authorResult,此时为嵌套查询 -->
        <association property="article_author" column="article_author_id" javaType="Author" resultMap="authorResult"/>
        <!-- 引用 authorResult,此时为延迟查询 -->
        <association property="article_author" column="article_author_id" javaType="Author" select="authorResult"/>
    </resultMap>
    <resultMap id="authorResult" type="Author">
        <id property="id" column="author_id"/>
        <result property="name" column="author_name"/>
    </resultMap>
    
    <resultMap id="articleResult" type="Article">
        <id property="id" column="id"/>
        <result property="title" column="article_title"/>
        <!-- resultMap 嵌套 -->
        <association property="article_author" javaType="Author">
            <id property="id" column="author_id"/>
            <result property="name" column="author_name"/>
        </association>
    </resultMap>
    

    只要此节点是association并且select为空,就说明是嵌套查询,那如果select不为空呢?那说明是延迟加载此节点的信息,并不属于嵌套查询,但是有可能有多个association,有一个设置为延迟加载也就是select属性不为空,有一个没有设置延迟加载,那说明resultMap中有嵌套查询的ResultMapping,也有延迟加载的ResultMapping,这个在后面结果集映射时会用到。

    接下来看是怎么解析的。

    //XMLMapperBuilder
     private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
            String property;
            // 根据节点类型获取 name 或 property 属性
            if (flags.contains(ResultFlag.CONSTRUCTOR)) {
                property = context.getStringAttribute("name");
            } else {
                property = context.getStringAttribute("property");
            }
    
            // 获取其他各种属性
            String column = context.getStringAttribute("column");
            String javaType = context.getStringAttribute("javaType");
            String jdbcType = context.getStringAttribute("jdbcType");
            String nestedSelect = context.getStringAttribute("select");
    
            /*
             * 碰到<association>节点
             * 若这个节点不包含 resultMap 属性,则调用 processNestedResultMappings 方法,递归调用resultMapElement解析<association> 和 <collection>的                        
                嵌套节点,生成resultMap,并返回resultMap.getId();(对应第二种嵌套)
    
             * 如果包含resultMap属性,则直接获取其属性值,这个属性值对应一个resultMap节点(对应第一种)
             */
            //入口
            String nestedResultMap = context.getStringAttribute("resultMap", processNestedResultMappings(context, Collections.<ResultMapping>emptyList()));
    
            String notNullColumn = context.getStringAttribute("notNullColumn");
            String columnPrefix = context.getStringAttribute("columnPrefix");
            String typeHandler = context.getStringAttribute("typeHandler");
            String resultSet = context.getStringAttribute("resultSet");
            String foreignColumn = context.getStringAttribute("foreignColumn");
            boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    
            Class<?> javaTypeClass = resolveClass(javaType);
            Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
            JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    
            // 构建 ResultMapping 对象
            //入口2
            return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect,
                    nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
        }
    
    
    private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
            if (("association".equals(context.getName()) || "collection".equals(context.getName()) || "case".equals(context.getName())) && context.getStringAttribute("select") == null) {
                ResultMap resultMap = this.resultMapElement(context, resultMappings);
                return resultMap.getId();
            } else {
                return null;
            }
        }
    
    //MapperBuilderAssistant
    public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,JdbcType jdbcType,
                                                String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,Class<? extends TypeHandler<?>> typeHandler,
                                                List<ResultFlag> flags, String resultSet, String foreignColumn, boolean lazy) {
    
            // resultType:即 <resultMap type="xxx"/> 中的 type 属性
            // property:即 <result property="xxx"/> 中的 property 属性
            //入口1
            Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
    
            TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
    
            List<ResultMapping> composites = parseCompositeColumnName(column);
    
            // 通过建造模式构建 ResultMapping
            return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
                    .jdbcType(jdbcType)
                    .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
                    .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
                    .resultSet(resultSet)
                    .typeHandler(typeHandlerInstance)
                    .flags(flags == null ? new ArrayList<ResultFlag>() : flags)
                    .composites(composites)
                    .notNullColumns(parseMultipleColumnNames(notNullColumn))
                    .columnPrefix(columnPrefix)
                    .foreignColumn(foreignColumn)
                    .lazy(lazy)
                    .build();//入口2
        }
    
        private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
            if (javaType == null && property != null) {
                try {
                    //获取ResultMap中的type属性的元类,如<resultMap id="user" type="java.model.User"/> 中User的元类
                    MetaClass metaResultType = MetaClass.forClass(resultType, this.configuration.getReflectorFactory());
                    //<result property="name" javaType="String"/>,如果result中没有设置javaType,则获取元类属性对那个的类型
                    javaType = metaResultType.getSetterType(property);
                } catch (Exception var5) {
                    ;
                }
            }
    
            if (javaType == null) {
                javaType = Object.class;
            }
    
            return javaType;
        }
    
        public ResultMapping build() {
            resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
            resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
            resolveTypeHandler();
            validate();
            return resultMapping;
        }
    
    1. 遍历节点,如果是id、result就简单解析出来就可以
    2. 如果遇到association,判断是否包含resultMap属性,如果不包含则递归解析子association的子标签
    3. 如果包含resultMap属性,则取出resultMap属性的name
    4. 最后将结果构建到ResultMapping中。ResultMapping的属性和resultMap能对应起来

    看一下ResultMapping长什么样。

    public class ResultMapping {
        private Configuration configuration;
        private String property;
        private String column;
        private Class<?> javaType;
        private JdbcType jdbcType;
        private TypeHandler<?> typeHandler;
        private String nestedResultMapId;//嵌套查询的mapid
        private String nestedQueryId;//延迟查询的mapid
        private Set<String> notNullColumns;
        private String columnPrefix;
        private List<ResultFlag> flags;
        private List<ResultMapping> composites;
        private String resultSet;
        private String foreignColumn;
        private boolean lazy;
    
        ResultMapping() {
        }
    }
    

    最后将resultMapping设置到resultMap中。

    //ResultMapResolver
     public ResultMap resolve() {
            return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
        }
    
        public ResultMap addResultMap(
                String id, Class<?> type, String extend, Discriminator discriminator,
                List<ResultMapping> resultMappings, Boolean autoMapping) {
    
            // 为 ResultMap 的 id 和 extend 属性值拼接命名空间
            id = applyCurrentNamespace(id, false);
            extend = applyCurrentNamespace(extend, true);
    
            if (extend != null) {
                if (!configuration.hasResultMap(extend)) {
                    throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
                }
                ResultMap resultMap = configuration.getResultMap(extend);
                List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
                extendedResultMappings.removeAll(resultMappings);
    
                boolean declaresConstructor = false;
                for (ResultMapping resultMapping : resultMappings) {
                    if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                        declaresConstructor = true;
                        break;
                    }
                }
    
                if (declaresConstructor) {
                    Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
                    while (extendedResultMappingsIter.hasNext()) {
                        if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                            extendedResultMappingsIter.remove();
                        }
                    }
                }
                resultMappings.addAll(extendedResultMappings);
            }
    
            // 构建 ResultMap
            ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
                    .discriminator(discriminator)
                    .build();
            // 将创建好的ResultMap加入configuration中
            configuration.addResultMap(resultMap);
            return resultMap;
        }
    

    我们先看看ResultMap

    public class ResultMap {
        private String id;
        private Class<?> type;
        private List<ResultMapping> resultMappings;
        //用于存储 <id> 节点对应的 ResultMapping 对象
        private List<ResultMapping> idResultMappings;
        private List<ResultMapping> constructorResultMappings;
        //用于存储 <id> 和 <result> 节点对应的 ResultMapping 对象
        private List<ResultMapping> propertyResultMappings;
        //用于存储 所有<id>、<result> 节点 column 属性
        private Set<String> mappedColumns;
        private Discriminator discriminator;
        private boolean hasNestedResultMaps;
        private boolean hasNestedQueries;
        private Boolean autoMapping;
    
        private ResultMap() {
        }
        //略
    }
    

    再来看看通过建造模式构建 ResultMap 实例

    public ResultMap build() {
        if (resultMap.id == null) {
            throw new IllegalArgumentException("ResultMaps must have an id");
        }
        resultMap.mappedColumns = new HashSet<String>();
        resultMap.mappedProperties = new HashSet<String>();
        resultMap.idResultMappings = new ArrayList<ResultMapping>();
        resultMap.constructorResultMappings = new ArrayList<ResultMapping>();
        resultMap.propertyResultMappings = new ArrayList<ResultMapping>();
        final List<String> constructorArgNames = new ArrayList<String>();
    
        for (ResultMapping resultMapping : resultMap.resultMappings) {
            /*
             * resultMapping.getNestedQueryId()不为空,表示当前resultMap是中有需要延迟查询的属性
             * resultMapping.getNestedResultMapId()不为空,表示当前resultMap是一个嵌套查询
             * 有可能当前ResultMapp既是一个嵌套查询,又存在延迟查询的属性
             */
            resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
            resultMap.hasNestedResultMaps =  resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
    
            final String column = resultMapping.getColumn();
            if (column != null) {
                // 将 colum 转换成大写,并添加到 mappedColumns 集合中
                resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
            } else if (resultMapping.isCompositeResult()) {
                for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
                    final String compositeColumn = compositeResultMapping.getColumn();
                    if (compositeColumn != null) {
                        resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
                    }
                }
            }
    
            // 添加属性 property 到 mappedProperties 集合中
            final String property = resultMapping.getProperty();
            if (property != null) {
                resultMap.mappedProperties.add(property);
            }
    
            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                resultMap.constructorResultMappings.add(resultMapping);
                if (resultMapping.getProperty() != null) {
                    constructorArgNames.add(resultMapping.getProperty());
                }
            } else {
                // 添加 resultMapping 到 propertyResultMappings 中
                resultMap.propertyResultMappings.add(resultMapping);
            }
    
            if (resultMapping.getFlags().contains(ResultFlag.ID)) {
                // 添加 resultMapping 到 idResultMappings 中
                resultMap.idResultMappings.add(resultMapping);
            }
        }
        if (resultMap.idResultMappings.isEmpty()) {
            resultMap.idResultMappings.addAll(resultMap.resultMappings);
        }
        if (!constructorArgNames.isEmpty()) {
            final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
            if (actualArgNames == null) {
                throw new BuilderException("Error in result map '" + resultMap.id
                    + "'. Failed to find a constructor in '"
                    + resultMap.getType().getName() + "' by arg names " + constructorArgNames
                    + ". There might be more info in debug log.");
            }
            Collections.sort(resultMap.constructorResultMappings, new Comparator<ResultMapping>() {
                @Override
                public int compare(ResultMapping o1, ResultMapping o2) {
                    int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
                    int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
                    return paramIdx1 - paramIdx2;
                }
            });
        }
    
        // 将以下这些集合变为不可修改集合
        resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
        resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
        resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
        resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
        resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
        return resultMap;
    }
    
    1. 逻辑很简单,resultMap数据结构是我们后续想要的,这里将resultMapping转换到resultmap中
    2. resultmap一条数据对应一个mapper.xml

    返回顶部

    解析sql节点

    这个比较简单,就是将内容解析出来存入到一个缓存里边,供后续语句使用

        private Map<String, XNode> sqlFragments;
    

    节点用来定义一些可重用的 SQL 语句片段,比如表名,或表的列名等。在映射文件中,我们可以通过 节点引用 节点定义的内容。

    <sql id="table">
        user
    </sql>
    
    <select id="findOne" resultType="Article">
        SELECT * FROM <include refid="table"/> WHERE id = #{id}
    </select>
    
    //XMLMapperBuilder
    private void sqlElement(List<XNode> list) throws Exception {
        if (configuration.getDatabaseId() != null) {
            // 调用 sqlElement 解析 <sql> 节点
            sqlElement(list, configuration.getDatabaseId());
        }
    
        // 再次调用 sqlElement,不同的是,这次调用,该方法的第二个参数为 null
        sqlElement(list, null);
    }
    
    private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
        for (XNode context : list) {
            // 获取 id 和 databaseId 属性
            String databaseId = context.getStringAttribute("databaseId");
            String id = context.getStringAttribute("id");
    
            // id = currentNamespace + "." + id
            id = builderAssistant.applyCurrentNamespace(id, false);
    
            // 检测当前 databaseId 和 requiredDatabaseId 是否一致
            if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
                // 将 <id, XNode> 键值对缓存到XMLMapperBuilder对象的 sqlFragments 属性中,以供后面的sql语句使用
                sqlFragments.put(id, context);
            }
        }
    }
    

    返回顶部

    解析sql语句节点

    //XMLMapperBuilder
    private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
            // 调用重载方法构建 Statement
            buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
    }
    
    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
            // 创建 XMLStatementBuilder 建造类
            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                /*
                 * 解析sql节点,将其封装到 Statement 对象中,并将解析结果存储到 configuration 的 mappedStatements 集合中
                 */
                statementParser.parseStatementNode();
            } catch (IncompleteElementException e) {
                configuration.addIncompleteStatement(statementParser);
            }
        }
    }
    
    //XMLStatementBuilder
    public void parseStatementNode() {
        // 获取 id 和 databaseId 属性
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
        }
    
        // 获取各种属性
        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);
    
        // 通过别名解析 resultType 对应的类型
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultSetType = context.getStringAttribute("resultSetType");
        
        // 解析 Statement 类型,默认为 PREPARED
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        
        // 解析 ResultSetType
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
        // 获取节点的名称,比如 <select> 节点名称为 select
        String nodeName = context.getNode().getNodeName();
        // 根据节点名称解析 SqlCommandType
        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> 节点
        //入口1
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
        // 创建SqlSource
        //入口2
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        String resultSets = context.getStringAttribute("resultSets");
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
    
        KeyGenerator keyGenerator;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        if (configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
    
          // 构建 MappedStatement 对象,并将该对象存储到 Configuration 的 mappedStatements 集合中
        //入口3
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
    
    1. 获取各种属性:resultType、Statement、nodeName、SqlCommandType
    2. 通过applyIncludes方法解析include节点
    3. 通过createSqlSource方法创建SqlSource
    4. 构建MappedStatement对象,并将该对象存储到Configuration中(每一条sql语句对应一条记录)

    通过applyIncludes方法解析节点

    <mapper namespace="java.mybaits.dao.UserMapper">
        <sql id="table">
            user
        </sql>
    
        <select id="findOne" resultType="User">
            SELECT  * FROM  <include refid="table"/> WHERE id = #{id}
        </select>
    </mapper>
    
    public void applyIncludes(Node source) {
        Properties variablesContext = new Properties();
        Properties configurationVariables = configuration.getVariables();
        if (configurationVariables != null) {
            variablesContext.putAll(configurationVariables);
        }
        //入口
        applyIncludes(source, variablesContext, false);
    }
    
    //XMLIncludeTransformer
    private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
    
        // 第一个条件分支  
        //<include refid="table"/>会进入这里
        if (source.getNodeName().equals("include")) {
    
            //获取 <sql> 节点。
            Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
    
            Properties toIncludeContext = getVariablesContext(source, variablesContext);
    
            applyIncludes(toInclude, toIncludeContext, true);
    
            if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
                toInclude = source.getOwnerDocument().importNode(toInclude, true);
            }
            // 将 <select>节点中的 <include> 节点替换为 <sql> 节点
            source.getParentNode().replaceChild(toInclude, source);
            while (toInclude.hasChildNodes()) {
                // 将 <sql> 中的内容插入到 <sql> 节点之前
                toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
            }
    
            /*
             * 前面已经将 <sql> 节点的内容插入到 dom 中了,
             * 现在不需要 <sql> 节点了,这里将该节点从 dom 中移除
             */
            toInclude.getParentNode().removeChild(toInclude);
    
        // 第二个条件分支
        //<select id="findOne" resultType="User">会进入这里
        } else if (source.getNodeType() == Node.ELEMENT_NODE) {
            if (included && !variablesContext.isEmpty()) {
                NamedNodeMap attributes = source.getAttributes();
                for (int i = 0; i < attributes.getLength(); i++) {
                    Node attr = attributes.item(i);
                    // 将 source 节点属性中的占位符 ${} 替换成具体的属性值
                    attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
                }
            }
            
            NodeList children = source.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                // 递归调用
                applyIncludes(children.item(i), variablesContext, included);
            }
            
        // 第三个条件分支
        //SELECT  * FROM和 WHERE id = 5会进入这里
        } else if (included && source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {
            // 将文本(text)节点中的属性占位符 ${} 替换成具体的属性值
            source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
        }
    }
    

    观察上面代码有三个分支,这段代码是在做什么?
    这段代码是在解析

        <sql id="table">
            user
        </sql>
    
        <select id="findOne" resultType="User">
            SELECT  * FROM  <include refid="table"/> WHERE id = #{id}
        </select>
    

    这个语句。最终生成语句

    select * from user where id=5
    

    这里边把语句拆分成3个片段

    //片段1, 对应上面代码分支二
    <select id="findOne" resultType="User">
    
    //片段3  对应上面代码分支三
    SELECT  * FROM 和 WHERE id = #{id}
    
    //片段2   对应上面代码分支一
     <include refid="table"/>
    

    流程:
    1、第一次调用遇到片段1,进入分支二,获取到子节点为

    SELECT  * FROM  <include refid="table"/> WHERE id = #{id}
    

    也就是获取到了片段2和片段3
    2、上边获取到的子节点列表可以拆分成3个节点,如下图

    3、第一个节点调用applyIncludes方法,source为 SELECT * FROM 节点,节点类型:TEXT_NODE,进入分支三,没有${},不会替换,则节点一结束返回,什么都没有做。
    4、第二个节点调用applyIncludes方法,此时source为include标签,节点类型:ELEMENT_NODE,进入分支一,找到对应sql,生成语句
    5、第二个节点调用applyIncludes方法,此时source为
    标签,进入分支3,替换变量。

    总结一下该方法:该方法最终目的是将include标签进行替换,同时将占位符进行替换

    通过createSqlSource方法创建SqlSource

    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        return builder.parseScriptNode();
    }
    
    // XMLScriptBuilder
    public SqlSource parseScriptNode() {
        // 解析 SQL 语句节点
        MixedSqlNode rootSqlNode = parseDynamicTags(context);//入口1
        SqlSource sqlSource = null;
        // 根据 isDynamic 状态创建不同的 SqlSource
        if (isDynamic) {
            //ru'k
            sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
            sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
    }
    
    //XMLScriptBuilder
    protected MixedSqlNode parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<SqlNode>();
        NodeList children = node.getNode().getChildNodes();
        // 遍历子节点
        for (int i = 0; i < children.getLength(); i++) {
            XNode child = node.newXNode(children.item(i));
            //如果节点是TEXT_NODE类型
            if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
                // 获取文本内容
                String data = child.getStringBody("");
                TextSqlNode textSqlNode = new TextSqlNode(data);
                // 若文本中包含 ${} 占位符,会被认为是动态节点
                if (textSqlNode.isDynamic()) {
                    contents.add(textSqlNode);
                    // 设置 isDynamic 为 true
                    isDynamic = true;
                } else {
                    // 创建 StaticTextSqlNode
                    contents.add(new StaticTextSqlNode(data));
                }
    
            // child 节点是 ELEMENT_NODE 类型,比如 <if>、<where> 等
            } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
                // 获取节点名称,比如 if、where、trim 等
                String nodeName = child.getNode().getNodeName();
                // 根据节点名称获取 NodeHandler,也就是上面注册的nodeHandlerMap
                NodeHandler handler = nodeHandlerMap.get(nodeName);
                if (handler == null) {
                    throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
                }
                // 处理 child 节点,生成相应的 SqlNode
                handler.handleNode(child, contents);
    
                // 设置 isDynamic 为 true
                isDynamic = true;
            }
        }
        return new MixedSqlNode(contents);
    }
    

    上一步是解析include和占位符,这一步是处理剩下所有的部分,先把sql片段封装到sqlNode集合中,最后将sqlNode封装成sqlSource。

    1. 遍历子节点,如果节点是TEXT_NODE类型,并且包含${} 占位符,则认为是动态节点,直接将片段添加到sqlNode集合
    2. 如果节点是TEXT_NODE类型,不包含${} 占位符,则认为是静态节点,直接创建静态sqlNode,添加到集合
      3、如果节点是ELEMENT_NODE类型,比如 if、where 等,就要获取相应的NodeHandler,然后交给NodeHandler解析。
      4、最后将解析出来是sqlNode转换成sqlSource

    看一下都有哪些NodeHandler。

    /** 该方法用于初始化 nodeHandlerMap 集合,该集合后面会用到 */
    private void initNodeHandlerMap() {
        nodeHandlerMap.put("trim", new TrimHandler());
        nodeHandlerMap.put("where", new WhereHandler());
        nodeHandlerMap.put("set", new SetHandler());
        nodeHandlerMap.put("foreach", new ForEachHandler());
        nodeHandlerMap.put("if", new IfHandler());
        nodeHandlerMap.put("choose", new ChooseHandler());
        nodeHandlerMap.put("when", new IfHandler());
        nodeHandlerMap.put("otherwise", new OtherwiseHandler());
        nodeHandlerMap.put("bind", new BindHandler());
    }
    

    举例看一下WhereHandler。

    /** 定义在 XMLScriptBuilder 中 */
    private class WhereHandler implements NodeHandler {
    
        public WhereHandler() {
        }
    
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
            // 调用 parseDynamicTags 解析 <where> 节点
            MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
            // 创建 WhereSqlNode
            WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
            // 添加到 targetContents
            targetContents.add(where);
        }
    }
    

    构建MappedStatement对象

    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");
        }
      // 拼接上命名空间,如 <select id="findOne" resultType="User">,则id=java.mybaits.dao.UserMapper.findOne
        id = applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    
        // 创建建造器,设置各种属性
        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))
            .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
            .resultSetType(resultSetType).useCache(valueOrDefault(useCache, isSelect))
            .cache(currentCache);//这里用到了前面解析<cache>节点时创建的Cache对象,设置到MappedStatement对象里面的cache属性中
    
        // 获取或创建 ParameterMap
        ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
        if (statementParameterMap != null) {
            statementBuilder.parameterMap(statementParameterMap);
        }
    
        // 构建 MappedStatement
        MappedStatement statement = statementBuilder.build();
        // 添加 MappedStatement 到 configuration 的 mappedStatements 集合中
        // 通过UserMapper代理对象调用findOne方法时,就可以拼接UserMapper接口名java.mybaits.dao.UserMapper和findOne方法找到id=java.mybaits.dao.UserMapper的MappedStatement,然后执行对应的sql语句
        configuration.addMappedStatement(statement);
        return statement;
    }
    
    1. 一条MappedStatement相当于一个sql语句
    2. MappedStatement相当于最终用来存放解析好的sql的集合。通过UserMapper代理对象调用findOne方法时,就可以拼接UserMapper接口名java.mybaits.dao.UserMapper和findOne方法找到id=java.mybaits.dao.UserMapper的MappedStatement,然后执行对应的sql语句
    3. 所有后续会用到的属性都封装到这个对象里了

    总结:1. 首先是替换include和$占位符
    2. 解析sql语句封装成一个一个sqlNode,sqlNode分动态和静态部分,同时像where、if等标签有专门的handler处理。
    3. 将sqlNode封装到sqlSource中,sqlSource可以看出是sqlNode集合
    4. 最后在将sqlSource封装到MappedStatement。

    返回顶部

    Mapper接口绑定

    //XMLMapperBuilder
    private void bindMapperForNamespace() {
        // 获取映射文件的命名空间
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class<?> boundType = null;
            try {
                // 根据命名空间解析 mapper 类型
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException e) {
            }
            if (boundType != null) {
                // 检测当前 mapper 类是否被绑定过
                if (!configuration.hasMapper(boundType)) {
                    configuration.addLoadedResource("namespace:" + namespace);
                    // 绑定 mapper 类
                    configuration.addMapper(boundType);
                }
            }
        }
    }
    
    // Configuration
    public <T> void addMapper(Class<T> type) {
        // 通过 MapperRegistry 绑定 mapper 类
        mapperRegistry.addMapper(type);
    }
    
    // MapperRegistry
    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 和 MapperProxyFactory 进行绑定,MapperProxyFactory 可为 mapper 接口生成代理类
                 */
                knownMappers.put(type, new MapperProxyFactory<T>(type));
                
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                // 解析注解中的信息
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
    
    public class MapperProxyFactory<T> {
        //存放Mapper接口Class
        private final Class<T> mapperInterface;
        private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
    
        public MapperProxyFactory(Class<T> mapperInterface) {
            this.mapperInterface = mapperInterface;
        }
    
        public Class<T> getMapperInterface() {
            return this.mapperInterface;
        }
    
        public Map<Method, MapperMethod> getMethodCache() {
            return this.methodCache;
        }
    
        protected T newInstance(MapperProxy<T> mapperProxy) {
            //生成mapperInterface的代理类
            return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
        }
    
        public T newInstance(SqlSession sqlSession) {
            MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
            return this.newInstance(mapperProxy);
        }
    }
    
    1. 通过命名空间和class找到mapper接口。然后为mapper接口创建一个代理类工厂,将mapper接口和代理类工厂放入到knownMappers。
    2. 那么后续通过mapper接口调用方法的时候就可以通过这个knownMappers找到代理类工厂,然后获取代理类。
    3. 代理类工厂内部是通过jdk动态代理生成代理类的

    返回顶部

  • 相关阅读:
    WindowsXP/2000来帮您自动关机
    ASP程序加密/解密方法大揭密
    教你如何在Windows XP使用定时关机命令
    向左滚动 替代 Marquee
    [转]Subversion的权限控制
    Xplanner的安装
    推荐一个不错的浮动广告代码
    微软称20日验证Windows与Office 盗版将黑屏?!
    Internet Explorer 8 Beta 2十大看点
    [转]Subversion的权限控制
  • 原文地址:https://www.cnblogs.com/yanhui007/p/12601023.html
Copyright © 2011-2022 走看看