MyBatis學習——databaseIdProvider和databaseId

有什麼作用

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>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章