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