资讯专栏INFORMATION COLUMN

MyBatis 源码阅读之 databaseId

Donald / 2005人阅读

摘要:源码阅读之的配置文件所有配置会被类读取,我们可以通过此类来了解各个配置是如何运作的。是用于项目中存在多种数据库时区分同一条对应的数据库。可以这样认为,在中的和组合才是一条的唯一标识。如果发现自己的没被正确识别,可以查看方法是否和预期一致。

MyBatis 源码阅读之 databaseId

MyBatis 的配置文件所有配置会被 org.apache.ibatis.builder.xml.XMLConfigBuilder 类读取,我们可以通过此类来了解各个配置是如何运作的。而 MyBatis 的映射文件配置会被 org.apache.ibatis.builder.xml.XMLMapperBuilder 类读取。我们可以通过此类来了解映射文件的配置时如何被解析的。

databaseId

databaseId 是用于项目中存在多种数据库 SQL 时区分同一条 SQL 对应的数据库。可以这样认为,在 Mybatis 中 SQL 的 id 和 databaseId 组合才是一条 SQL 的唯一标识。实际上 MyBatis 只会选择性加载指定 databaseId 的 SQL ,还有一些没有指定 databaseId 的 SQL。这里说的有点不是很准确,我们来慢慢分析便可以知晓。

databaseId 的配置

MyBatis 配置文件中 databaseId 的配置如下:



    
    
    

读取的代码如下:

private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
        String type = context.getStringAttribute("type");
        // 保持向后兼容
        if ("VENDOR".equals(type)) {
            type = "DB_VENDOR";
        }
        // 属性设置
        Properties properties = context.getChildrenAsProperties();
        // 找到 type 配置对应的类
        databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
        databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
        // 通过数据源确定使用的 databaseId ,之后 SQL 也只会加载这种 databaseId 的 SQL ,其他类型都会被忽略
        String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
        configuration.setDatabaseId(databaseId);
    }
}

这里的代码逻辑比较简单:

读取 databaseIdProvider 节点的 type 值与子节点属性值

根据 type 值找到与之匹配的 DatabaseIdProvider 子类,创建相应的实例,将子节点属性设置到实例中

调用 DatabaseIdProvider 实例的 getDatabaseId() 方法获取值设置到 Configuration 实例中

注:

type 为 DB_VENDOR 表示使用 org.apache.ibatis.mapping.VendorDatabaseIdProvider 作为 DatabaseIdProvider 的实现类。这一点可以在 org.apache.ibatis.session.Configuration 的构造方法中找到证据。

如果发现自己的 databaseId 没被正确识别,可以查看 getDatabaseId() 方法是否和预期一致。

databaseId 的使用

databaseId 在映射文件里要和上一节的配置的属性 value 值对应,如下:




    

读取的代码在这,这只是 节点加载的代码:

private void sqlElement(List list) throws Exception {
    if (configuration.getDatabaseId() != null) {
        // 加载 DataSource 对应的 databaseId 的 SQL 节点
        sqlElement(list, configuration.getDatabaseId());
    }
    // 记载 databaseId 为空的 SQL 节点
    sqlElement(list, null);
}

private void sqlElement(List list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
        String databaseId = context.getStringAttribute("databaseId");
        String id = context.getStringAttribute("id");
        id = builderAssistant.applyCurrentNamespace(id, false);
        if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
            sqlFragments.put(id, context);
        }
    }
}

private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
        if (!requiredDatabaseId.equals(databaseId)) {
            // 两个 databaseId 一致才会返回 true,此处不一致
            return false;
        }
    } else {
        // 一个为空,一个不为空,也不一致
        if (databaseId != null) {
            return false;
        }

        // 如果先前已经加载过节点,则不再加载
        // 是否视为同一个节点是由 id 决定
        // 但 id 相同,databaseId 不同 mybatis也可以加载,所以有些地方说,id+databaseId 确定唯一一条 SQL
        if (this.sqlFragments.containsKey(id)) {
            XNode context = this.sqlFragments.get(id);
            if (context.getStringAttribute("databaseId") != null) {
                return false;
            }
        }
    }
    return true;
}

代码上已经有了详细的注释,这里就简单说一下。sqlElement() 方法会被调用两次,第一次用于处理 databaseId 与全局 Configuration 实例的 databaseId 一致的节点;另一次用于处理节点的 databaseId 为 null 的情况,针对同一个 id ,优先选择存在 databaseId 并且与数据源的一致。

同样的,