解析databaseIdProvider元素,配置数据库类型唯一标志生成器
mybatis中定义了一个名为DatabaseIdProvider的接口,该接口的作用是获取不同数据源在mybatis中的唯一标志。
DatabaseIdProvider定义了两个方法,setProperties()方法用于配置自定义属性,getDatabaseId()方法用于获取指定数据源对应的databaseId。
/**
* 在需要使用多数据库特性的时候,可以实现该接口来构建自己的DatabaseIdProvider
* <p>
* @author Eduardo Macarron
*/
public interface DatabaseIdProvider {
// 配置自定义属性
void setProperties(Properties p);
/**
* 获取指定数据源的databaseId
*
* @param dataSource 数据源
*/
String getDatabaseId(DataSource dataSource) throws SQLException;
}
通常来说,setProperties()方法会在getDatabaseId()方法前被调用。
借助于DatabaseIdProvider和映射语句中配置的databaseId属性,mybatis可以在运行时根据数据源的不同来执行不同的SQL语句。
mybatis对生效语句的筛选逻辑是:
MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有
databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。
记着这个筛选逻辑,我们通过一个单元测试来深入了解DatabaseIdProvider。
在mybatis的单元测试包中包含一个名为MultiDbTest的测试类,该类位于org.apache.ibatis.submitted.multidb包下。
> org.apache.ibatis.submitted.multidb包下的类和文件,主要用于测试关于多数据源的功能。
在MultiDbTest中定义了一个setUp()方法,这个方法会在单元测试运行前执行,主要负责初始化SqlSessionFactory对象和初始化数据库信息。
protected static SqlSessionFactory sqlSessionFactory;
@BeforeAll
public static void setUp() throws Exception {
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/multidb/MultiDbConfig.xml")) {
// 初始化sqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
// 初始化数据库
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
"org/apache/ibatis/submitted/multidb/CreateDB.sql");
}
用于初始化SqlSessionFactory对象的MultiDbConfig.xml配置文件比较简单:
<configuration>
<!-- 配置数据源环境-->
<environments default="development">
<environment id="development">
<transactionmanager type="JDBC">
<property name="" value="" />
</transactionmanager>
<datasource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:multidb" />
<property name="username" value="sa" />
</datasource>
</environment>
</environments>
<!-- 配置DatabaseIdProvider实例-->
<databaseidprovider type="DB_VENDOR">
<property name="HSQL Database Engine" value="hsql" />
</databaseidprovider>
<!-- 引入mappers-->
<mappers>
<mapper resource="org/apache/ibatis/submitted/multidb/MultiDbMapper.xml" />
</mappers>
</configuration>
他配置了一个名为development的数据源环境,指定了用于获取databaseId的DatabaseIdProvider实例,并引入了MultiDbMapper对象对应的mapper文件——MultiDbMapper.xml。
development数据源对应的初始化脚本为CreateDB.sql,脚本中创建了common和hsql两个表,并往两张表中各插入了一条id为1的同名数据。
create table common (
id int,
name varchar(20)
);
create table hsql (
id int,
name varchar(20)
);
insert into common (id, name) values(1, 'common');
insert into hsql (id, name) values(1, 'hsql');
我们这次要看的是MultiDbTest中名为shouldExecuteHsqlQuery()的单元测试方法:
@Test
public void shouldExecuteHsqlQuery() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
// 获取MultiDbMapper的代理对象
MultiDbMapper mapper = sqlSession.getMapper(MultiDbMapper.class);
// 执行简单查询
String answer = mapper.select1(1);
assertEquals("hsql", answer);
}
}
这个方法比较简单,调用了MultiDbMapper的select1()方法进行了一次简单的查询操作。
在MultiDbMapper.xml文件中关于select1方法的定义有两个:
<mapper namespace="org.apache.ibatis.submitted.multidb.MultiDbMapper">
<!-- 未指定databaseId,从common表中查询数据 -->
<select id="select1" resulttype="string" parametertype="int">
select
name from common where id=#{value}
</select>
<!-- 指定了databaseId的值为hsql,从hsql表中查询数据-->
<select id="select1" databaseid="hsql" resulttype="string" parametertype="int">
select name from hsql where
id=#{value}
</select>
</mapper>
在单元测试中,我们可以看到MultiDbMapper的select1()方法的返回值是hsql,也就意味着配置了databaseId="hsql"的声明语句生效了。
根据前面我们了解的mybatis筛选有效声明语句的逻辑,可以推断出,当前数据源环境对应的databaseId是hsql。
这个hsql就是使用别名为DB_VENDOR的DatabaseIdProvider实例获取的。
Mybaits在Configuration的构造方法中注册了别名DB_VENDOR,该别名指向了VendorDatabaseIdProvider类型。
public Configuration() {
...
// 注册处理数据库ID的提供者
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
...
}
VendorDatabaseIdProvider是mybatis中惟一一个默认注册的DatabaseIdProvider实例,该实例基于数据库链接元数据DatabaseMetaData对象的getDatabaseProductName()方法来获取指定数据源对应的数据库产品名称。
通常来说数据库产品名称是一个很长的字符串,而且同一数据库的不同版本得到的数据库产品名称可能也不一致,因此,为了提供更好的用户体验,增强代码兼容性和减少代码量,
VendorDatabaseIdProvider允许用户提供一个包含数据库产品名称和databaseId对应关系的Properties对象,并由此推断出合适的databaseId。
private String getDatabaseName(DataSource dataSource) throws SQLException {
// 通过一个连接获取当前数据库的名称
String productName = getDatabaseProductName(dataSource);
if (this.properties != null) {
for (Map.Entry<object, object> property : properties.entrySet()) {
// 主要包含了配置的名称就算匹配
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// no match, return null
return null;
}
// 如果用户不传properties,会直接使用 数据库产品名称 作为当前databaseId
return productName;
}
在当前单元测试中,指定了数据库产品名称中包含字符串HSQL Database Engine的数据源对应的databaseId为hsql。
<!-- 配置DatabaseIdProvider实例-->
<databaseidprovider type="DB_VENDOR">
<property name="HSQL Database Engine" value="hsql" />
</databaseidprovider>
我们当前使用的是数据库驱动是org.hsqldb.jdbcDriver,因此对应着DatabaseMetaData的实例就是org.hsqldb.jdbc.JDBCDatabaseMetaData。
该实例的getDatabaseProductName()方法定义如下:
public String getDatabaseProductName() throws SQLException {
return "HSQL Database Engine";
}
方法返回值刚好和property元素中定义的name属性相匹配,因此该property元素的value值hsql就是最终得到的databaseId。
在了解了DatabaseIdProvider的定义和实现之后,我们来看一下databaseIdProvider元素的DTD定义:
<!--ELEMENT databaseIdProvider (property*)-->
<!--ATTLIST databaseIdProvider
type CDATA #REQUIRED
-->
databaseIdProvider有一个必填的属性type,指定了DatabaseIdProvider的实现类,该参数可以使用Mybatis中配置的别名。
databaseIdProvider还允许定义零个或多个property子元素,这些子元素最终会被转换为Properties对象,借由DatabaseIdProvider的setProperties()方法传递给DatabaseIdProvider的实现类完成进一步的处理。
比如:VendorDatabaseIdProvider中用户指定数据库产品名称和databaseId对应关系,就是通过databaseIdProvider元素的property子元素来实现的:
<databaseidprovider type="DB_VENDOR">
<property name="Apache Derby" value="derby" />
<property name="SQL Server" value="sqlserver" />
<property name="DB2" value="db2" />
<property name="Oracle" value="oracle" />
</databaseidprovider>
databaseIdProvider元素的解析代码也比较简单:
/**
* 解析 databaseIdProvider节点
*
* @param context databaseIdProvider节点
*/
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
// 获取用户定义的数据库类型和databaseId的配置
Properties properties = context.getChildrenAsProperties();
// 获取databaseIdProvider实例
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
// 配置数据库类型和databaseId的对应关系
databaseIdProvider.setProperties(properties);
}
// 获取Environment容器
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
// 获取当前环境的databaseId
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
// 同步configuration#databaseId的值
configuration.setDatabaseId(databaseId);
}
}
首先解析所有的property子元素获取到对应的Properties对象,然后解析出databaseIdProvider的type属性对应的DatabaseIdProvider实例类型,之后通过反射操作获取对应的DatabaseIdProvider实例。
拿到DatabaseIdProvider实例之后,调用该实例的setProperties()方法完成Properties对象的后续处理操作。
到这里DatabaseIdProvider实例的初始化工作就完成了。
之后获取Mybatis当前生效的Environment对象(Configuration#environment)中的数据源,解析出该数据源对应的databaseId,并将其赋值给Configuration的databaseId属性。
到这为止,databaseIdProvider元素的解析也已经完成。
关注我,一起学习更多知识
</object,></p>
来源:oschina
链接:https://my.oschina.net/u/3101282/blog/4326194