Mybatis源码解析二:DataSource数据源负责创建连接以及Transaction的事物管理

简介

对于一个成熟的ORM框架来说,数据源的管理以及事务的管理一定是不可或缺的组成,对于Mybatis来说,为了使用方便以及扩展简单也是做了一系列的封装,这一篇主要介绍mybatis是如何管理数据源以及事务的。

数据源DataSource

DataSource的分类

mybatis提供了三种数据源实现:
在这里插入图片描述

  • UNPOOLED:不使用连接池,每次请求都创建和关闭连接
  • POOLED: 使用连接池,避免每次请求创建何关闭连接,浪费资源
  • JNDI: 此实现旨在与EJB或应用程序服务器等容器一起使用,这些容器可以集中或外部配置DataSource,并在JNDI上下文中对其进行引用。(可以了解一下JNDI和JDBC的区别)

DataSource的创建过程

要创建哪种类型的DataSource是在mybatis的配置文件中决定的,指定对应的type类型:

<environments default="development">
    <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>

MyBatis在初始化时,根据type属性来创建相应类型的的数据源DataSource,看一下解析dataSource的源码(在XMLConfigBuilder类中的environmentsElement方法中):

private void environmentsElement(XNode context) throws Exception {
  for (XNode child : context.getChildren()) {
    String id = child.getStringAttribute("id");
    if (isSpecifiedEnvironment(id)) {
      // Transaction标签的解析
      TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
      // DataSource标签的解析
      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 DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      // 获取type属性值
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      // 根据type创建DataSourceFactory
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      factory.setProperties(props);
      return factory;
    }
}

在上面的代码中,可以看到mybatis根据配置的type值创建对应的DataSourceFactory,再创建对应的DataSource,mybatis中一共有三种DataSourceFactory,正好对应数据源的类型:
在这里插入图片描述

创建DataSourceFactory的代码如下:

protected <T> Class<? extends T> resolveClass(String alias) {
    if (alias == null) {
      return null;
    }
    try {
      // alias就是传进来的type的值
      return resolveAlias(alias);
    } catch (Exception e) {
      throw new BuilderException("Error resolving class. Cause: " + e, e);
    }
}
protected <T> Class<? extends T> resolveAlias(String alias) {
    // 在别名注册器中查找
    return typeAliasRegistry.resolveAlias(alias);
}
public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
    
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      // 别名注册器中查找,存在则直接返回对应的类型
      if (typeAliases.containsKey(key)) {
        value = (Class<T>) typeAliases.get(key);
      } else {
        // 直接返回type的值
        value = (Class<T>) Resources.classForName(string);
      }
      return value;
    } catch (ClassNotFoundException e) {
      throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
    }
}

别名注册器在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);

}

到这里数据源的创建过程就已经分析完了,主要就是解析DatsSource标签,再创建DatsSourceFactory,最后再创建对应DataSource

DataSource在何时被使用

在使用mybatis的时候,需要先获取SqlSessionFactory,再获取SqlSession,通过SqlSession创建mapper接口来执行sql操作,实际上执行逻辑的是mybatis中的执行器Executor,数据源的使用也正是在Executor执行中用来创建连接的,分析一下这段逻辑:

// SqlSession的创建逻辑
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 根据事物工厂创建事物(需要数据源,事物隔离级别以及是否自动提交的参数)
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 创建Executor
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
// Executor执行的查询逻辑
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 根据StatementHandler创建Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

StatementHandler是mybatis中一个重要的组件,这里先不展开,再创建完StatementHandler之后,需要创建具体的Statement来处理sql语句,数据源的使用也正是在prepareStatement方法中:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
    // 从数据源中获取连接
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      // 创建代理的Connection对象
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
}

在创建Connection连接时,是通过transaction对象来完成的,transaction对象中拥有初始化好的DataSource以及事物隔离级别或者事务是否提交的属性,到这里Connection对象就已经创建完成了,接下来看一下mybatis中的事物

事务Transaction

事务分类

MyBatis包含两种TransactionManager类型(即type="[JDBC|MANAGED]”)
在这里插入图片描述

  • JDBC: JDBC-此配置只是直接使用JDBC提交和回滚。它依赖于从dataSource检索的连接来管理事务的范围。默认情况下,它在关闭连接时启用自动提交,以便与某些驱动程序兼容。然而,对于一些驱动程序来说,启用自动提交不仅是不必要的,而且是一项昂贵的操作。因此,自3.5.10版本以来,可以通过将“skipSetAutoCommitOnClose”属性设置为true来跳过此步骤。


  • MANAGED
    这种配置几乎什么都不做。它永远不会提交或回滚连接。相反,它允许容器管理事务的整个生命周期(例如JEE应用程序服务器上下文)。

Transaction的创建逻辑与DataSource是类似的,就不再分析了

Transaction的使用

在Executor执行的时候,创建连接是通过Transaction来构造的,直看创建连接的代码:

protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
}

Transaction在创建连接的时候会指定事务的隔离级别以及是否自动提交

总结

  • 数据源创建通过数据源工厂指定,工厂类型是在mybatis配置文件中指定的;数据源获取连接是在Executor执行器获取Statement时调用的
  • 事务创建也是通过事务工厂指定,同样是在mybatis配置文件中指定的,Transaction在获取到连接后,再进行设置事物的隔离级别以及是否自定提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值