Mybatis流程分享-II 构建数据库 Environment 信息

序言

通过 章节一 的讨论,我们知道Mybatis内部对于xml的解析过程具体如下:

image-20230919182120319

可以看到,在配置文件的解析过程中其会首先调用SqlSessionFactortyBuilder中的build方法,接着再调用XMLConfigBuilderparse()方法,并最终返回一个Configuraion对象。进一步,又会调用SqlSessionFactortyBuilder重载build方法,返回一个SqlSessionFactory对象。

在这一章节中,我们主要先看一下 Configuraion 和 SqlSessionFactory 这两个类

核心配置 Configuraion 配置管家

在分析这个类之前,我们先来看一下这个类是在何时进行生成的,具体位置如下

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    //Step1.把配置文件输入流转成XMLConfigBuild解析类
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //Step2.解析类并返回一个SQLSessionFactory类
    return build(parser.parse());
  } catch (Exception e) {
  	.....
  }
  
  
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //Step1.1 主动生成一个Configuration类
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

在进行Step1中,也是就把配置文件输入流转成XMLConfigBuild解析类这个步骤,我们可以看到在解析的过程成,会主动生成了一个Configuraion类,并且生成这个类的时候,同时还携带了大量的默认配置信息。

这些配置信息在后续同样扮演了很重要的角色。

public Configuration() {
  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);

  typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
  typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

  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);

  typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
  typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

  languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
  languageRegistry.register(RawLanguageDriver.class);
}

此刻,对于xml配置文件中的信息就已经开始有一个配置管家Configuration 开始进行接管了,但是这个时候的管家还只存在一些默认信息,更多配置信息则需要经过后续步骤的填充

XMLConfigBuilderparse()方法

在前面,我们知道Configuration 的创建是在XMLConfigurationBuild的构建过程中,此时的Configuration只具有一个原始的框架。而如果parse方法中没有对这个Configuration进行配置的补充,则上方的语句完全不需要执行return build(parser.parse()) ,只需要执行 return build(parser.getConfiguration()) 就行,因此,我们可以断定, parser.parse() 一定存在对 Configuration 配置的补充修饰

XMLConfigBuilderr # parseConfiguration

private void parseConfiguration(XNode root) {
  // 暂时忽略对于其他标签的处理,重点关注environments标签
  environmentsElement(root.evalNode("environments"));
}

对于Mybatis配置文件<configuration>标签下的environments通常会有如下的配置信息:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

其中属性的含义如下:

  1. id 配置环境的名称信息
  2. transactionManager 指明数据库事务实现方式
  3. datoSource 配置数据库连接池信息

了解了XML文件中,environments 这个节点配置的信息,我们再来看这个 environmentsElement 就相对简单许多

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
     // 逐个遍历Environments标签下的environment标签
    for (XNode child : context.getChildren()) {
      // 获取environment的id属性信息
      String id = child.getStringAttribute("id");
			// 判断是否存在指定环境&&是否是当前环境
      if (isSpecifiedEnvironment(id)) {
        // 获取transactionManager管理工厂
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        // 获取数据源信息
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

 
  private boolean isSpecifiedEnvironment(String id) {
    if (environment == null) {
      throw new BuilderException("No environment specified.");
    } else if (id == null) {
      throw new BuilderException("Environment requires an id attribute.");
    } else if (environment.equals(id)) {
      return true;
    }
    return false;
  }

当我获取到事务管理器和数据源信息中,会把信息放入Environment 类中,而这个类也被保存在Configuration 中,此时这个配置中也存在了环境信息的配置

TransactionFactory 事务工厂

在解析 transactionManager 标签的时候,我们能够知道我们设置的事务管理器的类型,

  private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    	......
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
  }

 protected <T> Class<? extends T> resolveClass(String alias) {
    .......
    return resolveAlias(alias);
 }


 protected <T> Class<? extends T> resolveAlias(String alias) {
    return typeAliasRegistry.resolveAlias(alias);
  }

如果在xml中我们没有进行自定义其他的配置的话,Mybatis就会通过这个 事务管理器的type类型,从typeAliasRegistry 找到对应的管理器。

而这个 typeAliasRegistry 变量,实际在生成 Configuration 这个类的时候,就已经存在了默认配置

public Configuration() {
  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);
}

从此处看出,在 MyBatis 中有两种类型的事务管理器(也就是 type="[ JDBC|MANAGED]")environments

JDBC – 这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。默认情况下,为了与某些驱动程序兼容,它在关闭连接时启用自动提交。然而,对于某些驱动程序来说,启用自动提交不仅是不必要的,而且是一个代价高昂的操作。因此,从 3.5.10 版本开始,你可以通过将 "skipSetAutoCommitOnClose" 属性设置为 "true" 来跳过这个步骤。例如:

<transactionManager type="JDBC">
  <property name="skipSetAutoCommitOnClose" value="true"/>
</transactionManager>

MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

<transactionManager type="MANAGED">
  <property name="closeConnection" value="false"/>
</transactionManager>

DataSourceFactory 数据源工厂

DataSourceFactory 和 TransactionFactory 有点类似,也是通过获取数据源type类型,然后再获取Configraution中默认的数据源类型。

从Configruation的默认配置中来看,数据源有以下三种 数据源类型信息

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

  • driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。

  • url – 这是数据库的 JDBC URL 地址。

  • username – 登录数据库的用户名。

  • password – 登录数据库的密码。

  • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。

  • defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息。作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:

  • driver.encoding=UTF8

这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8encoding 属性给数据库驱动。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

  • poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
  • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
  • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
  • poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
  • poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnectionspoolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)
  • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
  • poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
  • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:

  • initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
  • data_source – 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

当所有信息全部解析完毕后,其会将配置文件中设定的内容设定到Environment对象中;然后,再将配置好的Environment对象设定到XMLConfigBuilder的成员变量Configuration之中。

事实上,

Configuration 对象是一个核心组件,负责管理 MyBatis 的配置信息和各种资源。例如:

  1. 配置信息管理: Configuration 对象负责管理 MyBatis 的全局配置信息,包括数据库连接信息、缓存配置、插件配置等。
  2. 映射信息管理:MyBatis通过 Configuration 对象管理与数据库表的映射关系。映射信息包括 SQL 语句与方法的映射关系,以及查询结果与 Java 对象之间的映射配置。
  3. Mapper 注册: Configuration 对象负责将 Mapper 接口与对应的映射配置关联起来。通过它,你可以将 Mapper 接口与 xml 文件或注解进行关联,从而实现 SQL 语句的映射。

所以Configuration更像一个 “大管家” 一样,统筹管理着Mybatis的配置信息。 明白了Configuration对象的作用后,我们继续来看parse方法中build(parse.parse())方法的逻辑,方法逻辑如下:

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

述代码逻辑很简单,就是将配置信息configuration传入build方法,然后new一个DefaultSqlSessionFactory进行返回。

接下来,我们继续来看DefaultSqlSessionFactory的究竟又是何物

SqlSessionFactory相关内容

对于DefaultSqlSessionFactory而言,其有如下的类结构关系:

image-20230920173613606

可以看到,DefaultSqlSessionFactory类结构关系非常简单。其只实现了一个SqlSessionFactory 接口。而SqlSessionFactory 接口内容如下所示:

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  // 其他为openSession方法的重载
}

可以看到,SqlSessionFactory作为一个接口,其内部只定义了一个openSession方法,而该方法的返回值一个SqlSession对象。

因此对于SqlSessionFactory而言,只需记住:是一个SqlSession的工厂,通过该工厂可以获取SqlSession对象。

此处实际上是一个工厂模式的典型应用。具体来看,在SqlSessionFactory内部定义了创建SqlSession的创建方式。从而使得SqlSession的创建和使用进行了分析。

(tip:SqlSession相关内容我们会在后续文章中分析,此处可以先选择性的忽视)

DefaultSqlSessionFactory作为SqlSessionFactory的唯一实现。接下来,我们便看看其内部又有哪些逻辑。DefaultSqlSessionFactory的相关内容:

public class DefaultSqlSessionFactory implements SqlSessionFactory {
  // mybatis会将所有配置信息归结到configuration之中
  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }
  /**
   * 获取一个sqlSession信息 
   **/
  @Override
  public SqlSession openSession() {
    // 调用openSessionFromDataSource返回一个SqlSession对象
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
  // .......省略其他无关代码
}

可以看到DefaultSqlSessionFactory中的openSession方法,从而将构建SqlSession的逻辑委托给方法openSessionFromDataSource来完成。

总结

至此,我们对MybaitsConfigurationSqlSessionFactory进行了介绍和分析,总结来看:

  1. Configuration用于保存配置文件的配置信息;
  2. SqlSessionFactory作为SqlSession的工厂,用于SqlSession的创建。

此外,解析配置文件、构建SqlSessinonFactory的整体调用过程如下图所示。而这张图可以看做是本文的核心内容的总结。

image-20230920174203077
posted @ 2024-04-02 23:03  Violet-EV  阅读(28)  评论(0)    收藏  举报
Language: HTML