Mybatis源码之美:2.12.解析`environments`元素,完成`Mybatis`中的多环境配置

原创
2020/06/27 10:27
阅读数 109

解析environments元素,完成Mybatis中的多环境配置

在完成枯燥的基于settings配置Configuration对象的过程之后,就到了解析environments标签,配置Mybatis的多环境的过程了。

Mybatis默认是支持多环境配置的,在Mybatis中有一个Environment的对象,该对象有三个简单的参数:

/**
 *  Mybatis环境容器
 *
 * @author Clinton Begin
 */
public final class Environment {
    // 环境唯一标志
    private final String id;
    // 事务工厂
    private final TransactionFactory transactionFactory;
    // 数据源
    private final DataSource dataSource;
}

其中id是当前环境的唯一标志,属于语义化属性。

transactionFactory属性对应的是TransactionFactory对象,他是一个事务创建工厂,用于创建Transaction对象。

Transaction对象包装了JDBC的Connection,用于处理数据库链接的生命周期,包括链接的:创建,提交/回滚和关闭。

dataSource属性对应的是DataSource对象,指向了一个JDBC数据源。

在Mybatis关于environments的DTD定义是这样的:

<!--ELEMENT environments (environment+)-->
<!--ATTLIST environments
default CDATA #REQUIRED
-->

environments中必须指定default属性的值,他是默认环境的ID,用于指定默认使用的环境配置,同时在environments 中允许出现一个或多个environment子元素。

<!--ELEMENT environment (transactionManager,dataSource)-->
<!--ATTLIST environment
id CDATA #REQUIRED
-->

environment元素有一个必填ID属性,是当前环境配置的唯一标志,同时他下面必须配置一个transactionManagerdataSource子元素。

transactionManager

其中transactionManager用来配置当前环境的事务管理器,其DTD定义如下:

<!--ELEMENT transactionManager (property*)-->
<!--ATTLIST transactionManager
type CDATA #REQUIRED
-->

transactionManager有一个必填的type属性,表示使用的事务管理器的类型,在Mybatis中默认提供了两种类型的事务管 理器:JDBCMANAGED。 这两个简短的名称依托于Mybatis的类型别名机制,他们是在Configuration的无参构造方法中注册的:

public Configuration() {
    // 注册别名

    // 注册JDBC别名
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    // 注册事务管理别名
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    ...
    }

其中JDBC对应的JdbcTransactionFactory创建的JdbcTransaction对象直接使用了默认的JDBC的提交和回滚设置,依赖于从数据源得到的链接来管理事务作用域。

MANAGED对应的ManagedTransactionFactory创建的ManagedTransaction对象是Transaction的实现之一,它会忽略提交和回滚请求,但是会响应关闭连接的请求,不过,我们可以通过参数closeConnection来控制他如何处理关闭连接的请求,当closeConnection的值为false时,他将会忽略掉关闭链接的请求。

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

在上面的示例中,我们可以发现transactionManager也允许配置property标签,用于自定义的事务管理器的参数。

事务管理器

dataSource

dataSource元素用来配置JDBC数据源,他的DTD定义如下:

<!--ELEMENT dataSource (property*)-->
<!--ATTLIST dataSource
type CDATA #REQUIRED
-->

dataSource标签同样有一个必填的type属性,它用于指向JDBC数据源的具体实例,在Mybatis中默认提供了三种类型的数据源: UNPOOLED,POOLEDJNDI

同样,这三个简短的名称也是Mybatis的类型别名,其注册也是在Configuration对象的无参构造中:

public Configuration() {
    // 注册别名
    
    ...
    
    // 注册JNDI别名
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    // 注册池化数据源别名
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    // 注册为池化的数据源
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    
    ...
    
    }

其中JNDI对应的JndiDataSourceFactory的作用是基于JNDI加载一个可用的DataSource

POOLED对应的PooledDataSourceFactory用于获取一个PooledDataSource

PooledDataSource是一个简单的,同步的,线程安全的数据库连接池,通过复用JDBCConnection,避免了重复创建新的链接实例所必须的初始化和认证时间,提高了应用程序对数据库访问的并发能力。

UNPOOLED对应的UnpooledDataSourceFactory用于获取一个UnpooledDataSourceUnpooledDataSource是一个简单的数据源,他对每一次获取链接的请求都会打开一个新的链接。

dataSource

dataSource标签下允许出现多个property子标签,这些property将会转换成Properties用于初始化和配置对应的DataSource. 在了解了每一个元素标签的作用之后,我们继续回到解析environments的代码上来。

调用解析的入口(XmlConfigBuilder):

private void parseConfiguration(XNode root) {
    // ...
    // 加载多环境源配置,寻找当前环境(默认为default)对应的事务管理器和数据源
    environmentsElement(root.evalNode("environments"));
   // ...
}

解析并构建MybatisEnvironment对象:

/**
 * 解析environments节点
 *
 * @param context environments节点
 */
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
            // 配置默认环境
            environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren()) {
            // 获取环境唯一标志
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                // 配置默认数据源
                // 创建事务工厂
                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);

                // 配置Mybatis当前的环境容器
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}

解析environments标签的过程并不是很复杂,他首先通过environmentsdefault属性获取用户指定的默认环境标志。

然后遍历environments下的所有environment节点,通过environment节点的id属性获取待处理环境environment的唯一标志,

如果当前标志和用户指定的默认环境标志一致的话,则处理该environment节点,否则跳过处理。

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节点的操作,主要是通过解析元素transactionManagerdataSource,获取对应的TransactionFactory 对象以及DataSourceFactory对象。

dataSource元素的解析过程和transactionManager近乎一致,都是先解析出type属性对应的实际类型,然后通过反射获取对象实例,最后调用实例对象的setProperties()方法完成自定义的参数的同步工作。

关于transactionManager的解析过程:

    /**
     * 根据environments&gt;environment&gt;transactionManager节点配置事务管理机制
     *
     * @param context environments&gt;environment&gt;transactionManager节点内容
     */
    private TransactionFactory transactionManagerElement(XNode context) throws Exception {
        if (context != null) {
            // 获取事务类型
            String type = context.getStringAttribute("type");
            // 获取事务参数配置
            Properties props = context.getChildrenAsProperties();
            // 创建事务工厂
            TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
            // 设置定制参数
            factory.setProperties(props);
            return factory;
        }
        throw new BuilderException("Environment declaration requires a TransactionFactory.");
    }

关于dataSource的解析过程:

    /**
     * 解析dataSource元素
     */
    private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
            // 获取dataSource的type属性指向的DataSourceFactory别名。
            String type = context.getStringAttribute("type");
            // 获取用户定义配置的参数集合
            Properties props = context.getChildrenAsProperties();
            // 解析别名对应的具体类型,并反射获取实体。
            DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
            // 设值
            factory.setProperties(props);
            return factory;
        }
        throw new BuilderException("Environment declaration requires a DataSourceFactory.");
    }

不同于TransactionFactory,在DataSourceFactory被初始化之后,Mybatis会调用DataSourceFactorygetDataSource()方法获取具体的DataSource实例。

到这里,配置Environment对象的必备元素已经全部得到,接下来通过使用Environment对象的构建器来构建一个 Environment对象,并将该Environment实例同步到Mybatis的配置类Configuration中的environment属性中。

至此,整个environments元素的解析工作也已经完成。

在继续解析后续元素前,让我们先来了解一下TransactionFactoryDataSourceFactory相关的信息。

首先我们看一下TransactionFactory

/**
 * 用于创建Transaction对象的实例
 *
 * @author Clinton Begin
 */
public interface TransactionFactory {

    /**
     * 配置事务工厂的自定义属性
     * @param props 自定义属性
     */
    void setProperties(Properties props);

    /**
     * 通过已有的链接创建一个事务
     * @param conn 现有的数据库链接
     * @return Transaction 事务
     * @since 3.1.0
     */
    Transaction newTransaction(Connection conn);

    /**
     * 从指定的数据源中创建一个事务
     * @param dataSource 数据源
     * @param level 事务级别
     * @param autoCommit 是否自动提交
     * @return Transaction 事务
     * @since 3.1.0
     */
    Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

}

从方法定义上我们可以看出,TransactionFactory是创建Transaction对象的工厂实例,在mybatisTransaction对象封装了原生的Connection对象, 接管了对Connection对象的控制权,包括:创建链接,提交事务,回滚事务,关闭链接以及获取事务超时时间。

/**
 * 包装数据库连接。
 * 处理连接生命周期,包括:创建,准备,提交/回滚和关闭
 *
 * @author Clinton Begin
 */
public interface Transaction {

    /**
     * 获取一个数据库链接
     *
     * @return 数据库链接
     */
    Connection getConnection() throws SQLException;

    /**
     * 提交
     */
    void commit() throws SQLException;

    /**
     * 回滚
     */
    void rollback() throws SQLException;

    /**
     * 关闭链接
     */
    void close() throws SQLException;

    /**
     * 获取事务超时时间
     */
    Integer getTimeout() throws SQLException;

}

TransactionFactory有两种默认实现,分别是:JdbcTransactionFactoryManagedTransactionFactory

JdbcTransactionFactory负责创建JdbcTransaction对象实例,ManagedTransactionFactory负责创建ManagedTransaction对象实例。

TransactionFactory > 像这种工厂和产品皆被抽象出来的工厂定义,通常我们称之为抽象工厂,对应着设计模式中的抽象工厂模式。

JdbcTransactionFactory的实现比较简单,仅仅是调用JdbcTransaction不同的构造方法完成JdbcTransaction对象的初始化工作而已,同时,他不会处理用户传入的自定义属性配置。

/**
 * 创建一个JdbcTransaction实例
 *
 * @author Clinton Begin
 *
 * @see JdbcTransaction
 */
public class JdbcTransactionFactory implements TransactionFactory {

  @Override
  public void setProperties(Properties props) {
  }

  @Override
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

ManagedTransactionFactory略有不同的地方在于,它会读取用户传入的名为closeConnection的自定义属性,并在创建ManagedTransaction对象实例时使用该属性完成ManagedTransaction对象的初始化工作。

/**
 * 创建一个ManagedTransactionFactory实例
 *
 * @author Clinton Begin
 *
 * @see ManagedTransaction
 */
public class ManagedTransactionFactory implements TransactionFactory {

  private boolean closeConnection = true;

  @Override
  public void setProperties(Properties props) {
    if (props != null) {
      String closeConnectionProperty = props.getProperty("closeConnection");
      if (closeConnectionProperty != null) {
        closeConnection = Boolean.valueOf(closeConnectionProperty);
      }
    }
  }

  @Override
  public Transaction newTransaction(Connection conn) {
    return new ManagedTransaction(conn, closeConnection);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    // Silently ignores autocommit and isolation level, as managed transactions are entirely
    // controlled by an external manager.  It's silently ignored so that
    // code remains portable between managed and unmanaged configurations.
    return new ManagedTransaction(ds, level, closeConnection);
  }
}

JdbcTransaction功能的实现依赖于从数据源得到的链接,他会将提交和回滚等请求直接委托给JDBC原生的Connection对象来完成。

ManagedTransaction作为Transaction的另一种实现,不同于JdbcTransaction, 当对链接的操作请求到来时,ManagedTransaction默认会忽略所有的提交回滚请求,同时针对关闭链接的请求,会根据创建ManagedTransaction对象时传入的closeConnection来决定是否受理。

之后我们简单看一下DataSourceFactory相关的类图: DataSourceFactory

在前面我们大概了解了DataSourceFactory不同实现类的大致作用,因为这里涉及到数据库连接池相关的知识,所以更详细的DataSourceFactory相关的信息,将会在扩展数据库连接池相关的知识时给出。

至此,基本完成了environments元素的处理工作。

关注我,一起学习更多知识

关注我

展开阅读全文
打赏
0
0 收藏
分享
加载中
打赏
1 评论
0 收藏
0
分享
返回顶部
顶部