有什麼作用
databaseIdProvider和databaseId的作用簡單來說就是讓一個項目支持不同的數據庫。
讓一個項目支持不同的數據庫在企業開發中是一個比較常見的需求。由於不同的數據庫支持的sql語法稍有差別,所以某些功能需要根據數據庫的不同書寫不同的sql語句。對於這種需求,首先能夠想到的解決方案就是針對不同的數據庫維護不同的mapper.xml文件,但是這種方案會嚴重增加開發和維護的成本。因爲不同數據庫支持的語法大部分都是相同的,不同的畢竟是少數,我們希望只重寫不同的部分而重用相同的部分。
針對這種情況,MyBatis提供瞭解決方案,即databaseIdProvider和databaseId。通過MyBatis提供的這種功能,我們就只需要維護一套mapper.xml文件便可。
下面先講解如何配置,然後在從源碼層面對這個功能進行解讀,最後探討一下如何通過自定義來定製這個功能。
如何配置
配置databaseIdProvider(在mybatis的配置文件中配置,比如mybatis-config.xml)
代碼段一:
<databaseIdProvider type="DB_VENDOR">
<property name="DB2" value="db2" />
<property name="Oracle" value="oracle" />
<property name="Adaptive Server Enterprise" value="sybase" />
<property name="MySQL" value="mysql" />
</databaseIdProvider>
上述配置用於決定當前databaseId的名稱。在每一個property標籤中,name代表數據庫的productName(DatabaseMetaData#getDatabaseProductName()),value是用戶自定義的databaseId名稱。mybatis在初始化的時候會根據所使用的數據源得到當前databaseId的名稱,得到的databaseId的名稱供mybatis選擇映射文件中相應的語句。比如我們使用的是Mysql數據庫,則得到的databaseId名稱爲“mysql”。
配置databaseId(在映射文件中配置,比如:BlogMapper.xml)
代碼段二:
<mapper namespace="info.songjie365.mybatis.sample.mapper.BlogMapper">
<select id="selectBlog" resultType="info.songjie365.mybatis.sample.domain.Blog" databaseId="mysql">
select * from blog where id = #{id}
</select>
</mapper>
官方文檔對databaseId的解釋爲:“如果配置了 databaseIdProvider,MyBatis 會加載所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略”。也就是說如果在mybatis的配置文件中沒有配置 databaseIdProvider,則在映射文件中配置的databaseId不會生效。
由於我們使用的是Mysql數據庫,得到的databaseId爲“mysql”,所以上述的映射片段會在生效。
源碼解讀
上面我們講解了如何配置,下面我們從源碼的角度來剖析一下mybatis是如何實現這個功能的。
databaseIdprovider的默認實現是VendorDatabaseIdProvider,其接口爲DatabaseIdProvider。先看DatabaseIdProvider的定義:
代碼片段三:
public interface DatabaseIdProvider {
void setProperties(Properties p);
String getDatabaseId(DataSource dataSource) throws SQLException;
}
接口很簡單,就兩個方法。
void setProperties(Properties p);:該方法會把databaseIdprovider中配置的屬性(見代碼片段一)通過這個方法傳遞進來。
String getDatabaseId(DataSource dataSource):該方法會將返回一個databaseId。
再看一下默認實現VendorDatabaseIdProvider的源碼:
代碼片段四:
public class VendorDatabaseIdProvider implements DatabaseIdProvider {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
private Properties properties;
@Override
public String getDatabaseId(DataSource dataSource) {
if (dataSource == null) {
throw new NullPointerException("dataSource cannot be null");
}
try {
return getDatabaseName(dataSource);
} catch (Exception e) {
log.error("Could not get a databaseId from dataSource", e);
}
return null;
}
@Override
public void setProperties(Properties p) {
this.properties = p;
}
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;
}
return productName;
}
private String getDatabaseProductName(DataSource dataSource) throws SQLException {
Connection con = null;
try {
con = dataSource.getConnection();
DatabaseMetaData metaData = con.getMetaData();
return metaData.getDatabaseProductName();
} finally {
if (con != null) {
try {
con.close();
} catch (SQLException e) {
// ignored
}
}
}
}
}
默認實現就是根據數據庫的productName去匹配在mybatis配置文件中配置的屬性,然後選出databaseId。
至此,我們分析完了databaseIdprovider的源碼,下面接着看一下databaseId是如何發揮作用的。
mybatis在解析映射文件的時候,首先會根據mybatis的配置文件解析出當前數據庫對應的databaseId,然後根據mybatis的映射文件判斷配置了databaseId屬性的語句是否和接卸出的databaseId匹配,核心代碼如下:
代碼片段五:
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
if (databaseId != null) {
return false;
}
// skip this statement if there is a previous one with a not null databaseId
id = builderAssistant.applyCurrentNamespace(id, false);
if (this.configuration.hasStatement(id, false)) {
MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
if (previous.getDatabaseId() != null) {
return false;
}
}
}
return true;
}
自定義
這裏的自定義只是針對databaseIdProvider的自定義,對映射文件中的databaseId不能自定義也沒自定義的必要。
通過上述的分析,可以看出,如果要自定義databaseIdProvider只要實現接口DatabaseIdProvider便可。然後在mybatis的配置文件中將databaseIdProvider的type置爲自定義的實現便可。
自定義的databaseIdProvider爲:
代碼片段六:
public class MyDatavbaseIdProvider implements DatabaseIdProvider {
Properties props = null;
@Override
public void setProperties(Properties p) {
//p代表mybatis中針對databaseIdProvider配置的屬性
props = p;
}
@Override
public String getDatabaseId(DataSource dataSource) throws SQLException {
//根據使用的數據源,返回不同的databaseId。這裏沒有給出具體實現
return null;
}
}
使用自定義的databaseIdProvider,則mybatis配置文件中的配置需要做相應的調整。
代碼片段七:
<databaseIdProvider type="info.songjie365.mybatis.sample.custom.MyDatavbaseIdProvider">
<property name="DB2" value="db2" />
<property name="Oracle" value="oracle" />
<property name="Adaptive Server Enterprise" value="sybase" />
<property name="MySQL" value="mysql" />
</databaseIdProvider>