MyBatis 初始化总览

MyBatis 简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

简单点来说 MyBatis 做的事情就是在Java POJO 和 数据库之间 完成了 Java POJO 和 数据库语言的转换,相当于一个翻译官。

Mybatis 初始化做了哪些事情

解析主配置文件

主配置文件主要包含内容(MyBatis 官网)

<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

主节点 <configuration 包含了整个mybatis的配置 对应的配置类实例也叫 Configuration

子节点顺序有严格要求就是按照括号里面的排列

解析主配置文件源码

String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

当 new XMLConfigBuilder(inputStream, environment, properties); 的时候mybatis开始填充内部属性

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
  this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }

1.准备初始化XMLConfigBuilder

2.初始化 XMLConfigBuilder 里面的属性对象 XPathParser

3.初始化 XMLMapperEntityResolver 这个对象可以校验mybatis 和 mapper 的xml配置文件

4.通过配置文件的inputStream 流创建一个 Document 对象

5.开始初始化 XMLConfigBuilder

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
  this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}

调用super 实例化 Configuration 对象 初始化了一些属性

  public Configuration() {
    //关于事务 默认jdbc
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    //关于数据源
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    //关于二级缓存策略
    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    //数据库厂商 默认没有
    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    // xml语言驱动 默认 XMLLanguageDriver
    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    //mybatis 日志 引入那个包就是哪一种日志策略
    // <setting name="logImpl" value="STDOUT_LOGGING"/> 可打印sql 执行信息
    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    
    //代理 扫描mapper 配置 package 或者 class 的时候使用的代理
    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    
    // 设置默认的 xml语言驱动
    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    //注册
    languageRegistry.register(RawLanguageDriver.class);
  }

再执行super 

public BaseBuilder(Configuration configuration) {
  this.configuration = configuration;
  this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
  this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}

typeAliasRegistry :别名注册器(初始化内部的别名注册器)

  比如 Spring 中 beanName的别名,简化写法,一次声明,全局使用。不区分大小写

typeHandlerRegistry :类型注册器(初始化内部的类型注册器)

  可以看到有两个属性 javaType 和 jdbcType 

  官网的解释:MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。

主配置文件中的自定义别名注册器和类型注册器配置

  <typeAliases>
    <typeAlias type="org.apache.ibatis.test.User" alias="u"></typeAlias>
  </typeAliases>
  <typeHandlers>
    <typeHandler handler="org.apache.ibatis.test.XxTypeHandler" javaType="java.lang.String" jdbcType="VARCHAR"></typeHandler>
  </typeHandlers>

然后设置了一个属性  parsed = false

至此 XMLConfigBuilder 初始化完成。

使用已经初始化好的 XPathParser 来解析文档对象节点

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

使用boolean 值 来判断 ,如果mybatis 环境还未被初始化完成并且再次初始化就会抛出异常

在 Spring 里面则直接使用的 是 synchronized 关键字控制

解析节点,这个里面的都是configuration的节点信息,解析的顺序也是按照排列的顺序

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

properties 解析配置的数据库信息,解析完成之后设置到 XPathParser 和 Configuration 的variables 属性中

properties 的用法

 两种方式任选一种

<properties resource="jdbc.properties"></properties>
<!-- jdbc.properties
 driver=com.mysql.jdbc.Driver
 url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8
 username=root
 password=root
 -->
<properties>
  <property name="driver" value="com.mysql.jdbc.Driver"></property>
  <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
  <property name="username" value="root"></property>
  <property name="password" value="root"></property>
</properties>

 settings 自定义的setting 信息 可见官网详情节点

一个配置完整的 settings 元素的示例如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

如需要打印sql执行信息

loadCustomLogImpl(settings);

<setting name="logImpl" value="STDOUT_LOGGING"/>

源码 的设置 直接给 configuration 的logImpl 属性设值

  private void loadCustomLogImpl(Properties props) {
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
  }

settingsElement(settings); 会设置剩余的settings信息

  private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

自定义的别名注册器,别名注册器是一个map 所以直接put进去

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

插件 pluginElement(root.evalNode("plugins")); 插件的底层是一个拦截器,内部维护了一个拦截器集合 也是直接add进去 

插件的拦截对象 即mybatis 责任链的四大对象

解析数据库信息

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

 typeHandlerElement(root.evalNode("typeHandlers"));

类型注册器 也是一个内置的 map 直接 put 进去

解析 mapper 节点

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

详情可见官网 mapper 配置

解析 mapper 节点 有两种配置 一种解析xml 一种解析接口

解析xml配置的是 resource 和 url 属性

解析接口的是 class 和 package 属性

解析mapper 先看 resource 和 url 同一种 。resource 和 url 和 class三 种是可以同时存在的,但是命名空间必须唯一。而 package 有且仅有一种。

再次实例化上面的对象重新用来解析mapper文件

源码 XNode 对象就是每一个 resource 和 url 指定的mapper 文件

  private void configurationElement(XNode context) {
    try {
      //命名空间不能为空且全局唯一
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      
      //使用第三方二级缓存
      cacheRefElement(context.evalNode("cache-ref"));
      //该 命名空间的 查询节点 开启二级缓存
      cacheElement(context.evalNode("cache"));
      
      //xxx版本后已废弃 官网已说明
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));

      //配置Java POJO 和 数据库字段的映射节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
       
      //自定义的sql 节点
      sqlElement(context.evalNodes("/mapper/sql"));
       
      //具体的增删改查节点
      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);
    }
  }

最终将每一个select|insert|update|delete 节点生成一个 MappedStatement 对象并add到Configuration 属性中保存起来。

1.如果sql语句中包含了 #{xxx} 这种类型的值会进行预编译。(没有${xxx})

2.只要sql语句中包含了一个${xxx}就不会进行预编译

基于接口的 class 和 package 属性的mapper文件

if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        }


..........

else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          }
Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }
解析每一个方法parseStatement(method); 最后添加到MappedStatement里面去

总结:初始化流程图

下篇:Mybatis 拦截器执行顺序

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值