深入浅出Mybatis系列(三)Mybatis核心配置解析

在本系列的第二篇文章中小编就说了,Mybatis的配置信息都是由Configuration来保存的,本篇文章我们就重点来看Mybatis的解析过程。在学习完本篇,你会完全掌握对Mybatis配置的认识,是你产生新的认识。

Configuration

本篇的源码就从下面的代码片段中开始。

  @Test
  public void configurationTest() throws Exception {
    //拿到mybatis的配置文件输入流
    InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");

    //SqlSessionFactoryBuilder通过XMLConfigBuilder解析器读取配置信息生成Configuration信息
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream);
    //获取配置文件
    Configuration configuration = sqlSessionFactory.getConfiguration();
  }
  1. 首先拿到配置文件输入流.
  2. 通过SqlSessionFactoryBuilder().build(mapperInputStream);创建
    SqlSessionFactory。而我们看SqlSessionFactory的接口定义中就知道是包含了获取Configuration方法。因此断定Configuration的解析入口一定在SqlSessionFactoryBuilder.build()方法中。

文档解析器SAX解析,这点不做重点研究,一笔带过。

SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

XMLConfigBuildery初始化时候已经生成了Document,Mybatis配置文件的具体实现看XMLConfigBuilder.parse()

XMLConfigBuilder
 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //获取document中需要解析的节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
 private void parseConfiguration(XNode root) {
    try {
      //根据properties的resource属性去填补配置文件中的占位符
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //注册Mybatis别名,Mybatis中的引用,都是先从别名注册器中
      //或取,当获取不到才直接调动ClassLoader加载。
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析Mybatis中的插件,Mybatis插件都是拦截器的原理,所以
      //获取到拦截器,在执行时候执行拦截器方法即可
      pluginElement(root.evalNode("plugins"));
      //将sql执行后的数据库返回结果对象,转换指定类型的Java对象///的工厂类。属于MetaObject组件。在DefaultResultSetHandler//使用
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      //只读取environments节点default属性指定的环境信息
      environmentsElement(root.evalNode("environments"));
      //Mybatis提供对于不同数据库匹配不同语句的能力,下面讲解。
      //获取数据库类型,从Mapper映射文件中读取databaseId对应的数//据库类型配置
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //类型转换器,这个在数据库表字段,转换Java代码时候很重要
      typeHandlerElement(root.evalNode("typeHandlers"));
      //mapper的配置文件是一个重要部分,抽出单独说明。
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

parseConfiguration方法才是真正将XML解析成Java代码的地方。我们看Mybatis都如何去解析自己的配置节点

配置文件

我们以下面的配置文件为例子,看Mybatis如何解析,已经了解他们在Mybatis中的作用

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 指定properties配置文件, 我这里面配置的是数据库相关 -->
    <properties resource="application.properties"></properties>

    <!-- 指定Mybatis使用log4j -->
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>

    <plugins>
        <plugin interceptor="orm.example.interceptor.CustomerInterceptor">
            <property name="初始化key" value="初始化value"></property>
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->
                <property name="driver" value="${spring.datasource.driver-class-name}"/>
                <property name="url" value="${spring.datasource.url}"/>
                <property name="username" value="${spring.datasource.username}"/>
                <property name="password" value="${spring.datasource.password}"/>
            </dataSource>
        </environment>
    </environments>

    <databaseIdProvider type="org.apache.ibatis.mapping.VendorDatabaseIdProvider" >
        <property name="MySQL" value="mysql"/>
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>
    <!-- 映射文件,mybatis精髓, 后面才会细讲 -->
    <mappers>
        <mapper resource="mapper/TUserMapper.xml"/>
    </mappers>


</configuration>

parseConfiguration

  1. propertiesElement(root.evalNode("properties"));

从配置文件中,读取properties标签,生成Properties对象,交给XPathParser保管,在解析配置节点的属性时候
当遇到${}站位符,就从properties指定的文件中读取数值。

这点的处理逻辑在PropertyParser实现,想要深入研究自行学习。

  1. Properties settings = settingsAsProperties(root.evalNode("settings"));

读取settings节点,生成Properties对象,Mybatis会根据其,去用户化配置系统的实现类,或者是默认值。

这点的逻辑逻辑在settingsElement(Properties props)方法中,比如: 上面配置文件中自定义了日志的实现类。logImpl=>LOG4J

  1. typeAliasesElement(root.evalNode("typeAliases"));

细心的同学可能发现,settings中logImpl的实现,配置文件中写的是LOG4J。那Mybatis是如何根据LOG4J找到具体的Class对象的呢?
其实就是typeAliases标签来,配置的,但是我们的配置文件中没有写这个节点呀,Mybatis是如何做的呢? 这里我们就要引入一个类
TypeAliasRegistry。小编叫别名注册器,代码非常简单。

其实就是一个Map集合,key是别名value是类型

  1. pluginElement(root.evalNode("plugins"));

Mybatis插件都是拦截器的原理,读取拦截器实现类,添加到Configuration的拦截链中InterceptorChain,在执行时候执行拦截器的逻辑

configuration.addInterceptor(interceptorInstance);

public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

这里我们主要分析下Mybatis的拦截器接口,以及如何去扩展它


/**
 * @author Clinton Begin
 */
public interface Interceptor {
  //里面实现主要实现拦截逻辑,参数是代理类等
  Object intercept(Invocation invocation) throws Throwable;
  //一般使用Plugin.wrap(target, this);加载当前插件,解析拦截器的声明注解@Intercepts和@Signature, 包装模式,属于结构性设计模式
  Object plugin(Object target);
  //初始化属性  plugins配置节点的子节点plugin的property
  <!--<plugins>-->
  <!--      <plugin interceptor="com.github.pagehelper.PageInterceptor">-->
  <!--          <property name="初始化key" value="初始化value"></property>-->
  <!--      </plugin>-->
  <!--</plugins>-->
  void setProperties(Properties properties);
  
}

自定的拦截器仅仅继承Interceptor是不够的,同时也要用@Intercepts和@Signature来修饰,Plugin.wrap(target, this)时候会解析这两个注解。
那么如何使用这两个注解呢

Type的类型只能是: StatementHandler | ParameterHandler | ResultSetHandler | Executor 类或者子类

自定义一个拦截器,随便执行一个查询操作

    @Test
    public void CustomerInterceptorTest(){
        //拿到mybatis的配置文件输入流
        InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream);
        TUserMapper tUserMapper = sqlSessionFactory.getConfiguration().getMapper(TUserMapper.class, sqlSessionFactory.openSession());
        System.out.println(tUserMapper.selectOne(2));
    }

以上就是Mybatis的拦截器,推荐可以看看最经典的拦截器: 分页插件。它的逻辑就是,生成Page分页对象,在执行查询时候拼装sql的limit信息,有兴趣的可以深入研究。


objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));

ObjectFactory、ObjectWrapperFactory、ReflectorFactory这三个对象都属于MetaObject的组件,从名字上看就是生成对象的,主要用做
数据库返回结果,通过这三个类,生成Java中对应的类。

  1. environmentsElement(root.evalNode("environments"));

比较容易解释了,读取environments节点default属性指定的环境信息。

  1. databaseIdProviderElement(root.evalNode("databaseIdProvider"));

Mybatis是支持多种数据库的,在使用Mybatis编写Mapper的时候,可以为同一个方法编写多种数据库的描述,Mybatis在启动时候会自动读取数据库类型,然后根据类型去查询Mapper配置中databaseId对应的sql信息。

主要Mysql数据库名字是MySQL,Oracle的名字是Oracle,配置文件中的value要与Mapper配置中的databaseId相对应。一般开发很少这样去写。虽然Mybatis提供这样的能力。

  <select id="SelectTime"   resultType="String" databaseId="mysql">
   SELECT  NOW() FROM dual 
  </select>

  <select id="SelectTime"   resultType="String" databaseId="oracle">
   SELECT  'oralce'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss')  FROM dual 
  </select>
  1. typeHandlerElement(root.evalNode("typeHandlers"));

这个也比较容易理解,类的设计很简单和TypeAliasRegistry很类似具体实现类是TypeHandlerRegistry。就是负责数据库类型和Java类型的转换。

  1. mapperElement(root.evalNode("mappers"));

终于读取到Mapper信息了,这个是Mybatis,对象关系映射核心的处理逻辑。
sql的配置信息BoundSql对象,这里面涉及到占位符和变量符的问题。这点在<<深入浅出Mybatis系列(二)Mybatis核心配置篇>>已经讲过了。
而Mapper类在Mybatis最终会转换成方法签名MethodSignature
什么是方法签名,就是对一个方法的描述。比如: 返回值类型是Void,是集合还是实体类。以及获取@Param注解标记的入参的别名。
以及在执行数据库交互时候,获取接口的入参。这部分逻辑在MapperMethod中,MethodSignature.convertArgsToSqlCommandParam()方法用来处理@Param注解。如下图所示。

上面我们其实已经讲了,Mybatis是如何拿到Mapper类的参数了,以及Mapper类在Java中信息保存的载体。那么如何拿到BoundSql呢? 其实就在MappedStatement中,MappedStatement即包含了MethodSignature也包含了BoundSql。而MappedStatement是在应用启动时候读取mappers配置节点时候就生成的并保存在Configuration


public class Configuration {
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  ...
}

MapperBuilderAssistant是具体生成MappedStatement的。而调用MapperBuilderAssistant有两种实现

  1. XMLMapperBuilder 读取xml配置来生成
  2. MapperAnnotationBuilder 读取注解来生成配合使用的注解
    configuration.addMappers(mapperPackage)>MapperRegistry.addMapper()>MapperAnnotationBuilder.parse()。

到这里mappers节点也已经读取完了,Mapper类方法签名信息保存在MethodSignature,Sql信息保存在BoundSql。在执行sql时候,会将参数与sql拼装起来使用,当拼装好的sql发送到数据库服务器,并受到服务器返回后,Mybatis会将数据库对象,转行成Java类数据对象。这部分逻辑我们下一篇主要来讲解。另外这里涉及到BoundSql的有两个知识点: 1.占位符# 2.变量符$ 在本系列第二篇已经说过了,这里不再赘述。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis是一个流行的Java持久化框架,它提供了一种简单且灵活的方式来访问数据库。MyBatis配置文件对整个框架的使用产生深远的影响,因此我们需要认真学习它。[1]配置文件中包含了数据库连接信息、映射器配置、SQL语句等重要内容,通过配置文件可以实现对数据库的增删改查操作。 除了配置文件,MyBatis最强大的工具之一是映射器。映射器是用于定义SQL语句和Java方法之间映射关系的工具,我们在使用MyBatis时会经常使用到它。[1]映射器可以将数据库表的字段映射到Java对象的属性上,使得我们可以方便地进行对象与数据库之间的转换。 在实际工作中,我们经常会遇到一些特殊的场景,需要灵活运用MyBatis来解决问题。比如处理数据库的BLOB字段的读写、批量更新、调用存储过程、分页、使用参数作为列名、分表等等。[2]这些场景都是通过实战总结出来的,具有较强的实用价值,可以帮助我们更好地应对实际开发中的需求。 此外,MyBatis和Spring框架的结合也是非常常见的。Spring框架是Java世界最流行的IOC和AOP框架之一,而MyBatis和Spring的结合可以构建高性能的大型网站。[3]通过使用Spring MVC和MyBatis,我们可以充分发挥它们的优势,实现灵活可配置的SQL操作,从而构建高性能的Java互联网应用。 总结起来,深入浅出地学习MyBatis的技术原理和实战经验,可以帮助我们更好地理解和应用这个持久化框架,提高开发效率和代码质量。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西魏陶渊明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值