亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

MyBatis?如何使項目兼容多種數(shù)據(jù)庫的解決方案

 更新時間:2024年05月27日 10:10:22   作者:戰(zhàn)斧  
要想做兼容多種數(shù)據(jù)庫,那毫無疑問,我們首先得明確我們要兼容哪些數(shù)據(jù)庫,他們的數(shù)據(jù)庫產(chǎn)品名稱是什么,本次我們講解了一套使項目兼容多種數(shù)據(jù)庫的方案,對MyBatis項目兼容多種數(shù)據(jù)庫操作方法感興趣的朋友一起看看吧

一、啟用數(shù)據(jù)庫識別

1. 調(diào)查數(shù)據(jù)庫產(chǎn)品名

要想做兼容多種數(shù)據(jù)庫,那毫無疑問,我們首先得明確我們要兼容哪些數(shù)據(jù)庫,他們的數(shù)據(jù)庫產(chǎn)品名稱是什么。得益于SPI設(shè)計,java語言制定了一個DatabaseMetaData接口,要求各個數(shù)據(jù)庫的驅(qū)動都必須提供自己的產(chǎn)品名。因此我們?nèi)绻胍嫒菽硵?shù)據(jù)庫,只要在對應(yīng)的驅(qū)動包中找到其對DatabaseMetaData的實現(xiàn)即可。

比如Mysql的驅(qū)動包 mysql-connector-java 下的 DatabaseMetaData

在這里插入圖片描述

Oracle 的驅(qū)動包 com.oracle.ojdbc6 下的 OracleDatabaseMetaData

在這里插入圖片描述

2. 啟用databaseId

既然各個驅(qū)動都提供了產(chǎn)品名,那么接下來就是讓項目在啟動中能夠識別這些數(shù)據(jù)庫,并賦予以不同數(shù)據(jù)庫不同的id。MyBatis 其實有這項功能,但是這個功能默認(rèn)沒有被啟用,若要啟用我們首先得建立一個配置,即databaseIdProvider,可以在配置類里面加上這個Bean來實現(xiàn)

@Configuration //配置類
public class MyBatisConfig {
    @Bean
    public DatabaseIdProvider getDatabaseIdProvider() {
        DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
        Properties properties = new Properties();
        // Key值(即產(chǎn)品名)來源于數(shù)據(jù)庫,需要提前查清楚 ,
        // value值(即databaseId)可以隨便填,你填“1” "2" "3"也行,但建議有明確意義,像下面這樣
        properties.setProperty("0racle", "oracle");
        properties.setProperty("MySQL", "mysql");
        properties.setProperty("DB2", "db2");
        properties.setProperty("Derby", "derby");
        properties.setProperty("H2", "h2");
        properties.setProperty("HSQL", "hsql");
        properties.setProperty("Informix", "informix");
        properties.setProperty("MS-SQL", "ms-sql");
        properties.setProperty("PostgresqL", "racle");
        properties.setProperty("sybase", "sybase");
        properties.setProperty("Hana", "hana");
        databaseIdProvider.setProperties(properties);
        return databaseIdProvider;
    }
}

完成了上述配置后,我們的項目就能主動去識別數(shù)據(jù)庫類型了。

二、SQL語法鑒別

對于大部分SQL,因為有SQL規(guī)范的限制,它們通常是通用的,一段SQL可以在不同的數(shù)據(jù)庫上跑。但是對于部分復(fù)雜SQL,就得針對不同數(shù)據(jù)庫,來寫不同的SQL了,我們以Mysql 、 Oracle 為例,看一些常見功能的語法差異

1. 分頁查詢

MySQL中使用LIMIT關(guān)鍵字來實現(xiàn)分頁查詢,例如:

SELECT * FROM table_name LIMIT offset, count;

而Oracle中使用ROWNUM關(guān)鍵字來實現(xiàn)分頁查詢,例如:

SELECT * 
FROM (SELECT t.*, ROWNUM AS rn 
      FROM table_name t 
      WHERE ROWNUM <= offset + count) 
WHERE rn > offset;

2. 獲取當(dāng)前時間

MySQL中可以使用NOW()函數(shù)來獲取當(dāng)前時間,例如:

SELECT NOW();

而Oracle中可以使用SYSDATE關(guān)鍵字來獲取當(dāng)前時間,例如:

SELECT SYSDATE FROM DUAL;

3. 獲取自增主鍵的值

MySQL中可以使用LAST_INSERT_ID()函數(shù)來獲取最后插入行的自動生成的主鍵值,例如:

INSERT INTO table_name (column1, column2) VALUES(value1, value2);
SELECT LAST_INSERT_ID();

而Oracle中可以使用SEQUENCE和CURRVAL來獲取自增主鍵的值,例如:

INSERT INTO table_name (column1, column2) VALUES(seq.nextval, value2);
SELECT seq.currval from dual;

4. 轉(zhuǎn)換數(shù)據(jù)類型

MySQL 使用 CAST() 或 CONVERT() 函數(shù)轉(zhuǎn)換數(shù)據(jù)類型,例如:

SELECT CAST('123' AS SIGNED) AS converted_value;  
-- 或者  
SELECT CONVERT('123', SIGNED) AS converted_value;

而Oracle使用 TO_NUMBER(), TO_CHAR(), TO_DATE() 等函數(shù)進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換,例如:

INSERT INTO table_name (column1, column2) VALUES(seq.nextval, value2);
SELECT seq.currval from dual;

5. 字符串拼接

MySQL中可以使用CONCAT()函數(shù)來進(jìn)行字符串拼接,例如:

SELECT CONCAT(column1, column2) FROM table_name;

而Oracle中可以使用||運算符來進(jìn)行字符串拼接,例如:

SELECT column1 || column2 FROM table_name;

6. 字符串截取

MySQL 使用 SUBSTRING() 函數(shù),例如:

SELECT SUBSTRING('Hello World', 1, 5) AS substring_result;

而Oracle 使用 SUBSTR() 函數(shù),例如:

SELECT SUBSTR('Hello World', 1, 5) AS substring_result FROM DUAL;

7. 判空函數(shù)

MySQL中可以使用IFNULL()函數(shù)來進(jìn)行字符串拼接,例如:

SELECT IFNULL(column1, "1") FROM table_name;

而Oracle中可以使用NVL()來進(jìn)行字符串拼接,例如:

SELECT NVL(column1, "1") FROM table_name;

8. 正則表達(dá)式

MySQL 使用 REGEXP 或 RLIKE 進(jìn)行正則表達(dá)式匹配,例如:

SELECT 'Hello World' REGEXP '^Hello' AS is_matched;

而Oracle 使用 REGEXP_LIKE, REGEXP_INSTR, REGEXP_SUBSTR, 和 REGEXP_REPLACE 等函數(shù),例如:

SELECT CASE WHEN REGEXP_LIKE('Hello World', '^Hello') THEN 'Matched' ELSE 'Not Matched' END AS is_matched FROM DUAL;

9. 窗口函數(shù)

MySQL 低版本不支持窗口函數(shù),可以使用自連接模擬窗口函數(shù),例如:

SELECT t1.*
FROM table_name t1
LEFT JOIN table_name t2
ON t1.column_name = t2.column_name AND t1.order_column > t2.order_column
WHERE t2.column_name IS NULL;

而Oracle 或 MySQL高版本則可以 使用 窗口函數(shù),例如:

SELECT * 
FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY column_name ORDER BY order_column) as rn 
      FROM table_name) as t
WHERE rn = 1;

三、SQL兼容處理

如果我們的項目有SQL語法不兼容的情況,如上面那些場景,那么我們就需要對這些SQL做特殊處理了,比如一個常用的功能,獲取當(dāng)前數(shù)據(jù)庫時間。我們需要在同一個XML文件中寫兩份,注意兩份SQL的 databaseId 是不同的,而不同數(shù)據(jù)庫的 databaseId 是什么,則依賴我們最開始維護(hù)的databaseIdProvider 里的value值了

<select id = "getSysDateTime" databaseId="oracle">
	select
			TO_CHAR (sysdate, 'yyyyMMdd') sys_date,
			TO_CHAR (sysdate, 'HH24miss') sys_time
	from dual
</select>
<select id = "getSysDateTime" databaseId="mysql">
	select
			date_format (now(), '%Y%m%d') sys_date,
			date_format (now(), '%H%i%s') sys_time
	from dual
</select>

而一些可以跑在所有平臺的SQL,則不需要改造,即databaseId不要填,如

<select id = "getUserInfo" resultType = "UserInfo">
	select user_name, user_age
	from USERINFO
</select>

四、運行原理

做完上述步驟后,我們的項目就能在多種數(shù)據(jù)庫環(huán)境運行了,而其內(nèi)部原理,其實也非常簡答

1. 配置載入

在項目啟動的時候,MyBatis 需要創(chuàng)建會話工廠,其中就有如下代碼,他的意義很明確,就是找到當(dāng)前連接的數(shù)據(jù)庫,對應(yīng)的是什么databaseId。并且將這個值保存進(jìn)配置中。

// SqlSessionFactoryBean
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
	// 省略無關(guān)代碼
    if (this.databaseIdProvider != null) {
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }
    // 省略無關(guān)代碼
}

2. SQL選擇

我們在 Mybatis之動態(tài)SQL使用小結(jié)(全網(wǎng)最新) 中介紹過MyBatis的啟動流程,其中就有對xml文件的解析,而我們現(xiàn)在在一個xml中寫了多個id相同的SQL,MyBatis會怎么做呢?

// XMLMapperBuilder
  private void buildStatementFromContext(List<XNode> list) {
    // 如果當(dāng)前環(huán)境有DatabaseId,則以這個DatabaseId去加載對應(yīng)的SQL
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    // 兜底,把某些沒有指明DatabaseId的SQL加載進(jìn)來
    buildStatementFromContext(list, null);
  }
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

可以看到對于一個XML文件的解析,會先后以指定databaseId 和無指定databaseId 兩種情況去解析

// XMLStatementBuilder
  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    // 省略無關(guān)代碼
}
  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      return requiredDatabaseId.equals(databaseId);
    }
    if (databaseId != null) {
      return false;
    }
    id = builderAssistant.applyCurrentNamespace(id, false);
    if (!this.configuration.hasStatement(id, false)) {
      return true;
    }
    // skip this statement if there is a previous one with a not null databaseId
    MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
    return previous.getDatabaseId() == null;
  }

可以看到,在讀取每一段SQL塊的時候,會判斷SQL上標(biāo)注的databaseId是否符合當(dāng)前數(shù)據(jù)庫環(huán)境,只有符合的才會被解析。

五、坑點

1. 避免歧義

不難發(fā)現(xiàn),因為兜底邏輯的存在,有時可能會存在歧義,假設(shè)我們在mysql環(huán)境,我們寫下這樣的代碼,是不是會把兩段都解析掉?

<select id = "getSysDateTime" databaseId="mysql">
	select
			date_format (now(), '%Y%m%d') sys_date,
			date_format (now(), '%H%i%s') sys_time
	from dual
</select>
<select id = "getSysDateTime">
	select
			TO_CHAR (sysdate, 'yyyyMMdd') sys_date,
			TO_CHAR (sysdate, 'HH24miss') sys_time
	from dual
</select>

其實是不會的,因為在解析完后我們會把解析的結(jié)果存入一個map中,它的key值就是每一塊的id,因為這個map是個內(nèi)部定義的StrictMap,如下

在這里插入圖片描述

    @Override
    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
      if (containsKey(key)) {
        throw new IllegalArgumentException(name + " already contains value for " + key
            + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
      }
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          super.put(shortKey, value);
        } else {
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      return super.put(key, value);
    }

不難發(fā)現(xiàn),一旦有兩個id沖突(同一個命名空間下)直接就會報錯,所以我們要知道,每一個id實際上只會被存儲一次,我們應(yīng)盡量避免出現(xiàn)歧義的寫法

2. 復(fù)雜數(shù)據(jù)庫場景

對于大部分場景,按照上面的做法就能解決,但是仍有部分場景是需要特殊處理的,比如同一個數(shù)據(jù)庫的不同版本。

比如說都屬于 MySQL 族,但是 MySQL 下又分 5.7 或 8.0,有些語法在低版本上不支持,又或者與Percona 和 Maria-db 等不兼容

此時就需要使用通用性SQL來寫了,一般都是順著低版本來寫,但往往也是性能最差的寫法。

總結(jié)

本次我們講解了一套使項目兼容多種數(shù)據(jù)庫的方案,總體而言還是比較簡單的,主要還是希望大家能學(xué)會原理,從而融會貫通

到此這篇關(guān)于MyBatis 使項目兼容多種數(shù)據(jù)庫的解決方案的文章就介紹到這了,更多相關(guān)MyBatis項目兼容多種數(shù)據(jù)庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java動態(tài)規(guī)劃算法——硬幣找零問題實例分析

    java動態(tài)規(guī)劃算法——硬幣找零問題實例分析

    這篇文章主要介紹了java動態(tài)規(guī)劃算法——硬幣找零問題,結(jié)合實例形式分析了java動態(tài)規(guī)劃算法——硬幣找零問題相關(guān)原理、實現(xiàn)方法與操作注意事項,需要的朋友可以參考下
    2020-05-05
  • Java后臺接口開發(fā)初步實戰(zhàn)教程

    Java后臺接口開發(fā)初步實戰(zhàn)教程

    下面小編就為大家分享一篇 Java后臺接口開發(fā)初步實戰(zhàn)教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • 關(guān)于springcloud集成nacos遇到的問題

    關(guān)于springcloud集成nacos遇到的問題

    這篇文章主要介紹了關(guān)于springcloud集成nacos遇到的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • java 將byte中的有效長度轉(zhuǎn)換為String的實例代碼

    java 將byte中的有效長度轉(zhuǎn)換為String的實例代碼

    下面小編就為大家?guī)硪黄猨ava 將byte中的有效長度轉(zhuǎn)換為String的實例代碼。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-11-11
  • mybatisPlus填坑之邏輯刪除的實現(xiàn)

    mybatisPlus填坑之邏輯刪除的實現(xiàn)

    本文主要介紹了mybatisPlus填坑之邏輯刪除的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • 劍指Offer之Java算法習(xí)題精講數(shù)組與二叉樹

    劍指Offer之Java算法習(xí)題精講數(shù)組與二叉樹

    跟著思路走,之后從簡單題入手,反復(fù)去看,做過之后可能會忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會發(fā)現(xiàn)質(zhì)的變化
    2022-03-03
  • Spring中的set注入方法

    Spring中的set注入方法

    這篇文章主要介紹了Spring中的set注入方法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • SpringCloud Stream消息驅(qū)動實例詳解

    SpringCloud Stream消息驅(qū)動實例詳解

    這篇文章主要介紹了SpringCloud Stream消息驅(qū)動的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • SpringCache的基本使用方法

    SpringCache的基本使用方法

    Spring?Cache利用了AOP,實現(xiàn)了基于注解的緩存功能,并且進(jìn)行了合理的抽象,業(yè)務(wù)代碼不用關(guān)心底層是使用了什么緩存框架,只需要簡單地加一個注解,就能實現(xiàn)緩存功能了,本文介紹SpringCache的基本使用方法,感興趣的朋友一起看看吧
    2024-01-01
  • 概述java虛擬機中類的加載器及類加載過程

    概述java虛擬機中類的加載器及類加載過程

    這篇文章主要介紹了概述java虛擬機中類的加載器及類加載過程,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-04-04

最新評論