mybatis配置文件解析过程分析

MyBati简介

MyBatis 是一种半自动化的 Java 持久层框架(persistenceframework),其通过注解或 XML的方式将对象和 SQL 关联起来。之所以说它是半自动的,是因为和 Hibernate 等一些可自动生成 SQL 的 ORM(ObjectRelationalMapping)框架相比,使用 MyBatis 需要用户自行维护 SQL。维护 SQL 的工作比较繁琐,但也有好处。比如我们可控制 SQL 逻辑,可对其进行优化,以提高效率。
mbatis-plus是mybatis的增强工具,他完美的兼容了mybatis的原有功能,又提供很多简化开发的功能,提高了开发效率。mp的出现使得mybatis在一些简单的curd场景上趋于自动化。
在实际开发中,我们常常把mybatis和spring整合在一起使用。这样的话,我们可以使用bean的方式便捷的操作dao接口。mybatis和spring是两个不相关的框架,使用了mybatis-spring中间框架,将两者整合起来,该框架一方面解析和加载mybatis相关信息,另一方面将dao接口和相关对象放入到bean工厂中。
下面就是将 MyBatis 整合到 Spring 中所需的一些配置:

 <!--配置数据源-->
 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
          destroy-method="close">
        <property name="driverClassName" value="${mysql.driver}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
        <property name="initialSize" value="${initialSize}"/>
        <property name="maxActive" value="${maxActive}"/>
        <property name="minIdle" value="${minIdle}"/>
        <property name="maxWait" value="${maxWait}"/>
        <property name="filters" value="stat"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--mapping.xml 文件 -->
        <property name="mapperLocations" value="classpath:mapper/test/*.xml"/>
         <!--配置文件 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--插件-拦截器-->
        <property name="plugins">
            <array>
                <bean id="mybatisInterceptor" class="SQLExecInterceptor.class"/>
            </array>
        </property>
    </bean>

    <!-- mapper接口所在包名 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.demo.mapper.test"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
配置⽂件解析过程分析

我们在使用 MyBatis 框架时,通常会进行一定的设置,使其能更好的满足我们的需求。我们在使用 MyBatis 时,第一步要做的事情一般是根据配置文件构建 SqlSessionFactory对象。我们首先会使用 MyBatis 提供的工具类 Resources 加载配置文件,得到一个输入流。然后再通过SqlSessionFactoryBuilder 对象的 build 方法构建 SqlSessionFactory
对象。这里的 build 方法是我们分析配置文件解析过程的入口方法。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
           // 创建配置文件解析器
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 通过parser.parse()方法获取到一个Configuration 对象
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }

通过分析parser.parse()方法找到了配置文件的入口方法parseConfiguration(XNode root),传入配置文件的< configuration>根节点,遍历子节点来完成配置文件的解析过程。

private void parseConfiguration(XNode root) {
        try {
            // 解析properties配置
            this.propertiesElement(root.evalNode("properties"));
            // 解析settings配置并将其转换成Properties对象,加载配置
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            // 解析别名配置
            this.typeAliasesElement(root.evalNode("typeAliases"));
            // 解析插件配置
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析类型转换器配置
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析mapper配置
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

到此,一个完整的配置解析过程就呈现出来了,每个节点的的解析逻辑均封装在了相应的方法中,接下来具体分析下常用的几个配置。

1.解析< properties >节点

< properties >节点常用配置:

<properties resource="jdbc.properties">
	<property name="jdbc.username" value="coolblog"/>
	<property name="hello" value="world"/>
</properties>

propertiesElement(properties节点对象)源码:

 private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
            // 获取properties节点下的所有子节点
            Properties defaults = context.getChildrenAsProperties();
            // 获取properties节点中的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) {
                defaults.putAll(Resources.getUrlAsProperties(url));
            }

            Properties vars = this.configuration.getVariables();
            if (vars != null) {
                defaults.putAll(vars);
            }

            this.parser.setVariables(defaults);
            // 将属性值设置到 configuration 中
            this.configuration.setVariables(defaults);
        }

    }
/**
   遍历properties节点所有子节点
*/
 public Properties getChildrenAsProperties() {
        Properties properties = new Properties();
        // 获取所有子节点的迭代器
        Iterator var2 = this.getChildren().iterator();

        while(var2.hasNext()) {
            XNode child = (XNode)var2.next();
            // 获取 property 节点的 name 和 value 属性
            String name = child.getStringAttribute("name");
            String value = child.getStringAttribute("value");
            if (name != null && value != null) {
                properties.setProperty(name, value);
            }
        }

        return properties;
    }
  
  /**
  *  获取子节点列表, 将节点对象封装到 XNode 中,并将 XNode 对象放入 children 列表中
  */  
  public List<XNode> getChildren() {
        List<XNode> children = new ArrayList();
        NodeList nodeList = this.node.getChildNodes();
        if (nodeList != null) {
            int i = 0;

            for(int n = nodeList.getLength(); i < n; ++i) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == 1) {
                    children.add(new XNode(this.xpathParser, node, this.variables));
                }
            }
        }

        return children;
    }

< properties >节点总结:

  1. 解析< properties >节点的子节点
  2. 从文件系统或网络读取配置信息,会导致同名属性覆盖的问题
  3. 将包含属性信息的 Properties 对象设置到XPathParser 和 Configuration中
2.解析< settings >节点

< settings >节点常用配置:

<settings>
	<setting name="cacheEnabled" value="true"/>
	<setting name="lazyLoadingEnabled" value="true"/>
	<setting name="autoMappingBehavior" value="PARTIAL"/>
</settings>

< settings >节点解析源码分析:

 private Properties settingsAsProperties(XNode context) {
        if (context == null) {
            return new Properties();
        } else {
        	// 获取 settings 子节点中的内容
            Properties props = context.getChildrenAsProperties();
            // 创建 Configuration 类的“元信息”对象
            MetaClass metaConfig = MetaClass.forClass(Configuration.class, this.localReflectorFactory);
            Iterator var4 = props.keySet().iterator();

            Object key;
            // 检测 Configuration 中是否存在相关属性,不存在则抛出异常
            do {
                if (!var4.hasNext()) {
                    return props;
                }

                key = var4.next();
            } while(metaConfig.hasSetter(String.valueOf(key)));

            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
    }

< settings >节点总结:

  1. 解析 settings 子节点的内容,并将解析结果转成 Properties 对象
  2. 为 Configuration 创建元信息对象
  3. 通过 MetaClass 检测 Configuration 中是否存在某个属性的 setter 方法,
    不存在则抛异常
  4. 若通过 MetaClass 的检测,则返回 Properties 对象,方法逻辑结束
    MetaClass元对象的构建是个比较复杂的过程,有兴趣的可以翻阅源码研究下。
3.解析< typeAliases >节点

在 MyBatis 中,我们可以为自己写的一些类定义一个别名。这样在使用的时候,只需要输入别名即可,无需再把全限定的类名写出来。在 MyBatis 中,我们有两种方式进行别名配置。第一种是仅配置包名,让 MyBatis 去扫描包中的类型,并根据类型得到相应的别名(全小写类名),可以搭配注解使用,第二种方式是通过手动的方式,明确为某个类型配置别名。
< typeAliases >节点常用配置:

<!-- 第一种方式-->
<typeAliases>
	<package name="xyz.coolblog.chapter2.model1"/>
	<package name="xyz.coolblog.chapter2.model2"/>
</typeAliases>
<!-- 第二种方式-->
<typeAliases>
	<typeAlias alias="article" type="xyz.coolblog.chapter2.model.Article" />
	<typeAlias alias="author" type="xyz.coolblog.chapter2.model.Author" />
</typeAliases>

< typeAliases >节点解析源码分析:

 private void typeAliasesElement(XNode parent) {
        if (parent != null) {
        	// 获取typeAliases子节点
            Iterator var2 = parent.getChildren().iterator();

            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String alias;
                // 第一种方式,从包中解析别名
                if ("package".equals(child.getName())) {
                    alias = child.getStringAttribute("name");
                    this.configuration.getTypeAliasRegistry().registerAliases(alias);
                } else {
                	// 第二种方式,自定义别名
                    alias = child.getStringAttribute("alias");
                    String type = child.getStringAttribute("type");

                    try {
                        Class<?> clazz = Resources.classForName(type);
                        // 别名可以为空,为空则设置为type对应的类名全小写
                        if (alias == null) {
                            this.typeAliasRegistry.registerAlias(clazz);
                        } else {
                            this.typeAliasRegistry.registerAlias(alias, clazz);
                        }
                    } catch (ClassNotFoundException var7) {
                        throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
                    }
                }
            }
        }

    }

第二种方式具体的实现方法:

 public void registerAlias(Class<?> type) {
 		// 获取全路径类名的简称
        String alias = type.getSimpleName();
        // 获取注解中的别名,如果使用注解设置别名,则优先使用注解中的名称
        Alias aliasAnnotation = (Alias)type.getAnnotation(Alias.class);
        if (aliasAnnotation != null) {
            alias = aliasAnnotation.value();
        }
		// 配置别名
        this.registerAlias(alias, type);
    }

    public void registerAlias(String alias, Class<?> value) {
        if (alias == null) {
            throw new TypeException("The parameter alias cannot be null");
        } else {
        	// 别名转换成小写
            String key = alias.toLowerCase(Locale.ENGLISH);
            // 如果在缓存中已经存在该别名,则判断当前类型与缓存中类型是否一致,不一致则报错
            if (this.TYPE_ALIASES.containsKey(key) && this.TYPE_ALIASES.get(key) != null && !((Class)this.TYPE_ALIASES.get(key)).equals(value)) {
                throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + ((Class)this.TYPE_ALIASES.get(key)).getName() + "'.");
            } else {
            	// 缓存别名到类型映射
                this.TYPE_ALIASES.put(key, value);
            }
        }
    }

第一种方式具体实现方法:

 public void registerAliases(String packageName) {
        this.registerAliases(packageName, Object.class);
    }

 public void registerAliases(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
        // 查找某个包下的父类为 superType 的类。从调用栈来看,这里的
		// superType = Object.class,所以 ResolverUtil 将查找所有的类。
		// 查找完成后,查找结果将会被缓存到内部集合中。
        resolverUtil.find(new IsA(superType), packageName);
        Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
        Iterator var5 = typeSet.iterator();
		// 遍历结果,忽略匿名类,接口,内部类,依次调用registerAlias(type)方法设置别名(默认类名简称,注解优先)
        while(var5.hasNext()) {
            Class<?> type = (Class)var5.next();
            if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
                this.registerAlias(type);
            }
        }

    }

< typeAliases >节点总结以及默认配置:

  1. 通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名,
  2. 比如 xyz/coolblog/model/Article.class
  3. 筛选以.class 结尾的文件名
  4. 将路径名转成全限定的类名,通过类加载器加载类名
  5. 对类型进行匹配,若符合匹配规则,则将其放入内部集合中
    总结一些mybatis已经默认创建的类型别名:
 public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

了解这些默认创建的别名可以更好的帮助我们开发,比如为List对象设置别名为list,这样我们在配置标签的 resultType 属性时,就可以使用list来表示返回类型啦~

4.解析< typeHandlers >节点

在向数据库存储或读取数据时,我们需要将数据库字段类型和 Java 类型进行一个转换。比如数据库中有 CHAR 和 VARCHAR 等类型,但 Java 中没有这些类型,不过 Java 有 String类型。所以我们在从数据库中读取 CHAR 和 VARCHAR 类型的数据时,就可以把它们转成String。在 MyBatis 中,数据库类型和 Java 类型之间的转换任务是委托给类型处理器TypeHandler 去处理的。MyBatis 提供了一些常见类型的类型处理器,除此之外,我们还可以自定义类型处理器以非常见类型转换的需求。
< typeHandlers >节点常用配置:

<!-- 自动扫描 -->
<typeHandlers>
	<package name="xyz.coolblog.handlers"/>
</typeHandlers>
<!-- 手动配置 -->
<typeHandlers>
	<typeHandler jdbcType="TINYINT"
javaType="xyz.coolblog.constant.ArticleTypeEnum"
handler="xyz.coolblog.mybatis.ArticleTypeHandler"/>
</typeHandlers>

< typeHandlers >节点解析源码分析:

private void typeHandlerElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String typeHandlerPackage;
                // 从指定的包中注册 TypeHandler
                if ("package".equals(child.getName())) {
                    typeHandlerPackage = child.getStringAttribute("name");
                    // 注册方法
                    this.typeHandlerRegistry.register(typeHandlerPackage);
                } else {
                	// 从 typeHandler 节点中解析别名到类型的映射
                    typeHandlerPackage = child.getStringAttribute("javaType");
                    String jdbcTypeName = child.getStringAttribute("jdbcType");
                    String handlerTypeName = child.getStringAttribute("handler");
                    // 解析上面获取到的属性值
                    Class<?> javaTypeClass = this.resolveClass(typeHandlerPackage);
                    JdbcType jdbcType = this.resolveJdbcType(jdbcTypeName);
                    Class<?> typeHandlerClass = this.resolveClass(handlerTypeName);
                    // 根据 javaTypeClass 和 jdbcType 值的情况进行不同的注册策略
                    if (javaTypeClass != null) {
                        if (jdbcType == null) {
                            this.typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                        } else {
                            this.typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                        }
                    } else {
                        this.typeHandlerRegistry.register(typeHandlerClass);
                    }
                }
            }
        }

    }

上面的代码中调用了 4 个不同的类型处理器注册方法,类型处理器的解析过程不复杂,但是注册过程由于重载方法间相互调用,导致调用路线比较复杂。这里就不展开四个重载方法详细说明了。大致流程为,将JdbcType通过TypeHandler进行映射,返回结果后,将javaType和结果存储到缓存中。当JdbcType或javaType为空时,第一步则是先尝试从注解中获取JdbcType 的值或通过注解的方式或者反射的方式了解析出 javaType 的值。
< typeHandlers >节点一些常见类型的类型处理器:
在这里插入图片描述
在看到这里的时候有个疑问,在公司开发过程中,库中的timestamp类型被默认自动转成java中的string类型,但是在默认的typehandlers中并未找到这个转换,查看代码发现公司配置了拦截器,在拦截器中做了该类型的转换~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值