解析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