MyBatis 源码阅读之 databaseIdMyBatis 的配置文件所有配置会被 org.apache.ibatis.builder.xml.XMLConfigBuilder 类读取,我们可以通过此类来了解各个配置是如何运作的。而 MyBatis 的映射文件配置会被 org.apache.ibatis.builder.xml.XMLMapperBuilder 类读取。我们可以通过此类来了解映射文件的配置时如何被解析的。databaseIddatabaseId 是用于项目中存在多种数据库 SQL 时区分同一条 SQL 对应的数据库。可以这样认为,在 Mybatis 中 SQL 的 id 和 databaseId 组合才是一条 SQL 的唯一标识。实际上 MyBatis 只会选择性加载指定 databaseId 的 SQL ,还有一些没有指定 databaseId 的 SQL。这里说的有点不是很准确,我们来慢慢分析便可以知晓。databaseId 的配置MyBatis 配置文件中 databaseId 的配置如下:<!– mybatis-config.xml –><databaseIdProvider type=“DB_VENDOR”> <property name=“SQL Server” value=“sqlserver”/> <property name=“DB2” value=“db2”/> <property name=“Oracle” value=“oracle” /></databaseIdProvider>读取的代码如下: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 值对应,如下:<!– mybatis-mapper.xml –><!– 指定 sql 和 select 节点的内容只适用于 oracle 数据库,那么使用 Oracle 的数据库时便会加载这些节点 –><sql id=“column” databaseId=“oracle”> <!– … –></sql><select id=“selectOne” databaseId=“oracle”> <!– … –></select>读取的代码在这,这只是 <sql> 节点加载的代码:private void sqlElement(List<XNode> list) throws Exception { if (configuration.getDatabaseId() != null) { // 加载 DataSource 对应的 databaseId 的 SQL 节点 sqlElement(list, configuration.getDatabaseId()); } // 记载 databaseId 为空的 SQL 节点 sqlElement(list, null);}private void sqlElement(List<XNode> 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 并且与数据源的一致。同样的,<select> 之类的节点解析代码也是类似,不过它们的解析代码在 org.apache.ibatis.builder.xml.XMLStatementBuilder 中。