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 的配置如下:
<!– 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 中。