1. 項目從 MySQL 切換 PostgreSQL,踩了太多的坑!!!

        共 10611字,需瀏覽 22分鐘

         ·

        2024-08-03 07:40

        關(guān)注我們,設(shè)為星標,每天7:40不見不散,架構(gòu)路上與您共享

        回復(fù)架構(gòu)師獲取資源


        大家好,我是你們的朋友架構(gòu)君,一個會寫代碼吟詩的架構(gòu)師。

        0、前言

        原項目框架 SpringBoot + MybatisPlus + Mysql

        1、切換流程

        1.1、項目引入postgresql驅(qū)動包

        由于我們要連接新的數(shù)據(jù)庫,理所當(dāng)然的要引入該數(shù)據(jù)庫的驅(qū)動包,這與mysql驅(qū)動包類似

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

        1.2、修改jdbc連接信息

        之前用的是mysql協(xié)議,現(xiàn)在改成postgresql連接協(xié)議

        spring:
          datasource:
            # 修改驅(qū)動類
            driver-class-name: org.postgresql.Driver
            # 修改連接地址
            url: jdbc:postgresql://數(shù)據(jù)庫地址/數(shù)據(jù)庫名?currentSchema=模式名&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false

        postgres相比mysql多了一層模式的概念, 一個數(shù)據(jù)庫下可以有多個模式。 這里的模型名等價于以前的mysql的數(shù)據(jù)庫名。如果不指定默認是public。

        這時切換流程基本就改造完了,無非就是代碼修改下連接信息。但是你以為到這就結(jié)束了?

        一堆坑還在后面呢,畢竟是兩個完全不同數(shù)據(jù)庫在語法層面還有很多差別,接下來就是修改代碼里的sql語法踩坑

        2、踩坑記錄

        2.1、TIMESTAMPTZ類型與LocalDateTime不匹配

        異常信息:

        PSQLException: Cannot convert the column of type TIMESTAMPTZ to requested type java.time.LocalDateTime.

        如果postgres表的字段類型是TIMESTAMPTZ ,但是java對象的字段類型是LocalDateTime, 這時會無法轉(zhuǎn)換映射上。postgres表字段類型應(yīng)該用timestamp 或者 java字段類型用Date

        2.2、參數(shù)值不能用雙引號

        錯誤例子:

         WHERE name = "jay"   ===>    WHERE name = 'jay'

        這里參數(shù)值"jay" 應(yīng)該改成單引號 'jay'

        2.3、字段不能用``包起來

        錯誤例子

         WHERE `name` = 'jay'  ==>    WHERE name = 'jay'

        這里的字段名name不能用``選取

        2.4、json字段處理語法不同

        -- mysql語法: 
        WHERE keywords_json->'$.name' LIKE CONCAT('%', ?, '%')

        -- postgreSQL語法:
        WHERE keywords_json ->>'name' LIKE CONCAT('%', ?, '%')

        獲取json字段子屬性的值mysql是用 -> '$.xxx'的語法去選取的, 而 postgreSQL 得用 ->>'xx' 語法選擇屬性

        2.5、convert函數(shù)不存在

        postgreSQL沒有convert函數(shù),用CAST函數(shù)替換

        -- mysql語法: 
        select convert(nameDECIMAL(202))

        -- postgreSQL語法:
        select CAST(name as DECIMAL(202))

        2.6、force index 語法不存在

        -- mysql語法
        select xx FROM user force index(idx_audit_time)

        mysql可以使用force index強制走索引, postgres沒有,建議去掉

        2.7、ifnull 函數(shù)不存在

        postgreSQL沒有ifnull函數(shù),用COALESCE函數(shù)替換

        異常信息

        cause: org.postgresql.util.PSQLException: ERROR: function ifnull(numeric, numeric) does not exist

        2.8、date_format 函數(shù)不存在

        異常信息

        Cause: org.postgresql.util.PSQLException: ERROR: function date_format(timestamp without time zone, unknown) does not exist

        postgreSQL沒有date_format函數(shù),用to_char函數(shù)替換

        替換例子:

        // %Y => YYYY 
        // %m  =>   MM
        // %d   =>  DD
        // %H => HH24
        // %i => MI
        // %s => SS
        to_char(time,'YYYY-MM-DD') => DATE_FORMAT(time,'%Y-%m-%d')
        to_char(time,'YYYY-MM') => DATE_FORMAT(time,'%Y-%m')
        to_char(time,'YYYYMMDDHH24MISS') => DATE_FORMAT(time,'%Y%m%d%H%i%s')

        2.9、group by語法問題

        異常信息

        Cause: org.postgresql.util.PSQLException: ERROR: column  "r.name" must appear in the GROUP BY clause or be used in an  aggregate function

        postgreSQL 的 selectd的字段必須是group by的字段里的 或者使用了聚合函數(shù)。mysql則沒有這個要求,非聚合列會隨機取值

        錯誤例子

        select name, age, count(*)
        from user 
        group by age, score

        這時 select name 是錯誤的, 應(yīng)為group by里沒有這個字段,要么加上,要么變成select min(name)

        2.10、事務(wù)異常問題

        異常信息

        # Cause: org.postgresql.util.PSQLException: ERROR: current transaction is aborted, commands ignored until end of transaction block

        ; uncategorized SQLException; SQL state [25P02]; error code [0]; ERROR: current transaction is aborted, commands ignored until end of transaction block; nested exception is org.postgresql.util.PSQLException: ERROR: current transaction is aborted, commands ignored until end of transaction block

        Postgres數(shù)據(jù)庫中,同一事務(wù)中如果某次數(shù)據(jù)庫操作中出錯的話,那這個事務(wù)以后的數(shù)據(jù)庫操作都會出錯。正常來說不會有這種情況,但是如果有人去捕獲了事務(wù)異常后又去執(zhí)行數(shù)據(jù)庫操作就會導(dǎo)致這個問題。mysql貌似不會有這個問題

        下面就是錯誤的代碼例子:靠異常去走邏輯。解決辦法就是不要靠數(shù)據(jù)庫的異常去控制邏輯,手動判斷。

        2.11 類型轉(zhuǎn)換異常 (大頭)

        這個可以說是最坑的, 因為mysql是支持自動類型轉(zhuǎn)換的。在表字段類型和參數(shù)值之間如果類型不一樣也會自動進行轉(zhuǎn)換。而postgreSQL是強數(shù)據(jù)類型,字段類型和參數(shù)值類型之間必須一樣否則就會拋出異常。

        這時候解決辦法一般有兩種

        • 手動修改代碼里的字段類型和傳參類型保證 或者 postgreSQL表字段類型,反正保證雙方一一對應(yīng)
        • 添加自動隱式轉(zhuǎn)換函數(shù),達到類似mysql的效果

        布爾值和int類型類型轉(zhuǎn)換錯誤

        1、select查詢時的轉(zhuǎn)換異常信息
        Cause: org.postgresql.util.PSQLException: ERROR: operator does not exist: smallint = boolean
        SELECT   xx fom xx    WHERE   enable = ture

        錯誤原因:enable字段是smallint類型查詢卻傳了一個布爾值類型

        2、update更新時的轉(zhuǎn)換異常信息
        Cause: org.postgresql.util.PSQLException: ERROR: column "name" is of type smallint but expression is of type boolean
        update from xx set name = false  where  name = true

        錯誤原因:在update/insert賦值語句的時候,字段類型是smallint,但是傳參卻是布爾值類型

        解決辦法:

        postgres數(shù)據(jù)庫添加boolean <-> smallint 的自動轉(zhuǎn)換邏輯

        -- 創(chuàng)建函數(shù)1  smallint到boolean到轉(zhuǎn)換函數(shù)
        CREATE OR REPLACE FUNCTION "smallint_to_boolean"("i" int2)
          RETURNS "pg_catalog"."bool" AS $BODY$
         BEGIN
          RETURN (i::int2)::integer::bool;
         END;
         $BODY$
        LANGUAGE plpgsql VOLATILE
        -- 創(chuàng)建賦值轉(zhuǎn)換1
        create cast (SMALLINT as BOOLEANwith function smallint_to_boolean as ASSIGNMENT;

        -- 創(chuàng)建函數(shù)2    boolean到smallint到轉(zhuǎn)換函數(shù)
        CREATE OR REPLACE FUNCTION "boolean_to_smallint"("b" bool)
          RETURNS "pg_catalog"."int2" AS $BODY$
         BEGIN
          RETURN (b::boolean)::bool::int;
         END;
         $BODY$
        LANGUAGE plpgsql VOLATILE
          
        -- 創(chuàng)建隱式轉(zhuǎn)換2
        create cast (BOOLEAN as SMALLINTwith function boolean_to_smallint as implicit;

        如果想重來可以刪除掉上面創(chuàng)建的函數(shù)和轉(zhuǎn)換邏輯

        -- 刪除函數(shù)
        drop function smallint_to_boolean
        -- 刪除轉(zhuǎn)換
        drop  CAST (SMALLINT as BOOLEAN)

        主要不要亂添加隱式轉(zhuǎn)換函數(shù),可能導(dǎo)致   Could not choose a best candidate operator 異常 和 # operator is not unique 異常 就是在操作符比較的時候有多個轉(zhuǎn)換邏輯不知道用哪個了,死循環(huán)了

        3、PostgreSQL輔助腳本

        3.1、批量修改timestamptz腳本

        批量修改表字段類型 timestamptztimestamp, 因為我們說過前者無法與LocalDateTime對應(yīng)上

        ?

        ps:

        • timestamp without time zone 就是 timestamp

        • timestamp with time zone 就是 timestamptz

        ?
        DO $$
        DECLARE
            rec RECORD;
        BEGIN
            FOR rec IN SELECT table_name, column_name,data_type
                       FROM information_schema.columns
                       where table_schema = '要處理的模式名' 
                       AND data_type = 'timestamp with time zone'
            LOOP
                EXECUTE 'ALTER TABLE ' || rec.table_name || ' ALTER COLUMN ' || rec.column_name || ' TYPE timestamp';
            END LOOP;
        END $$;

        3.2、批量設(shè)置時間默認值腳本

        批量修改模式名下的所有字段類型為timestamp的并且字段名為 create_time 或者 update_time的字段的默認值為 CURRENT_TIMESTAMP

        -- 注意 || 號拼接的后面的字符串前面要有一個空格
        DO $$
        DECLARE
            rec RECORD;
        BEGIN
            FOR rec IN SELECT table_name, column_name,data_type
                       FROM information_schema.columns
                       where table_schema = '要處理的模式名' 
                         AND data_type = 'timestamp without time zone'
                         -- 修改的字段名
                  and column_name in ('create_time','update_time')
            LOOP
                 EXECUTE 'ALTER TABLE ' || rec.table_name || ' ALTER COLUMN ' || rec.column_name || ' SET DEFAULT CURRENT_TIMESTAMP;';
            END LOOP;
        END $$;

        4、注意事項

        1、將數(shù)據(jù)表從mysql遷移postgres 要注意字段類型要對應(yīng)不要變更(*

        2、原先是 tinyint的就變samllint類型,不要是bool類型,有時代碼字段類型可能對應(yīng)不上

        3、如果java字段是LocalDateTime原先mysql時間類型到postgres后不要用TIMESTAMPTZ類型

        4、mysql一般用tinyint類型和java的Boolean字段對應(yīng)并且在查詢和更新時支持自動轉(zhuǎn)換,但是postgres是強類型不支持,如果想無縫遷移postgres內(nèi)部就新增自動轉(zhuǎn)換的隱式函數(shù),但是缺點是每次部署postgres后都要去執(zhí)行一次腳本。

        如果不想這樣,只能修改代碼的所有表對象的字段類型和傳參類型保證與postgres數(shù)據(jù)庫的字段類型對應(yīng),但是有些依賴的框架底層自己操作數(shù)據(jù)庫可能就無法修改源碼了,只能修改數(shù)據(jù)庫表字段類型了

        來源:juejin.cn/post/7356108146632163339


        到此文章就結(jié)束了。Java架構(gòu)師必看一個集公眾號、小程序、網(wǎng)站(3合1的文章平臺,給您架構(gòu)路上一臂之力)。如果今天的文章對你在進階架構(gòu)師的路上有新的啟發(fā)和進步,歡迎轉(zhuǎn)發(fā)給更多人。歡迎加入架構(gòu)師社區(qū)技術(shù)交流群,眾多大咖帶你進階架構(gòu)師,在后臺回復(fù)“加群”即可入群。



        這些年小編給你分享過的干貨


        0.ChatGPT 4o 國內(nèi)直接用 ?。?!

        1.idea2024.1.4永久激活碼(親測可用)

        2.優(yōu)質(zhì)ERP系統(tǒng)帶進銷存財務(wù)生產(chǎn)功能(附源碼)

        3.優(yōu)質(zhì)SpringBoot帶工作流管理項目(附源碼)

        4.最好用的OA系統(tǒng),拿來即用(附源碼)

        5.SBoot+Vue外賣系統(tǒng)前后端都有(附源碼

        6.SBoot+Vue可視化大屏拖拽項目(附源碼)


        轉(zhuǎn)發(fā)在看就是最大的支持??

        瀏覽 80
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 欧美成人网站在线观看 | 出差的丰满新婚少妇 | 伦理《法国护士长》电影完整版 | 国产成人啪免费观看 | 豆花无码视频一区二区 |