進行版本迭代過程中,使用spring jpa來完美解決資料表更新的問題

myskies發表於2018-06-28
提示:本文中,我們只給了部分示例程式碼。
如果你需要完整的程式碼,請點選:https://github.com/mengyunzhi/sampleUpdateTableWithJpa/tree/1

why to do

在版本的迭代中,我們畢然會面臨資料表更新的問題。而這些更新,有些是可以透過spring jpa進行自動更新的,有些更新spring jpa則表式無能無力,所以只能採用手動的方法。

本文將實現以下功能:
假設當前共有3個釋出的版本。分別為1.1,1.2,1.3,每個版本都有對應的應用程式及資料庫。

實現功能1:1.2版本的程式執行在1.1版本的資料庫上時,自動將其更新為1.2版本所對應的資料庫結構。
實現功能2:1.3版本的程式執行在1.2版本的資料庫時,自動將其更新為1.3版本的資料庫。
實現功能3:1.3版本的程式執行在1.1版本的資料庫時,自動將期更新為1.3版本的資料庫。
實現功能4:開發時,我們使用的為H2資料庫,生產環境使用mysql資料庫。當使用h2資料庫時,不做任何資料庫版本的更新。

準備知識

spring boot在系統啟動時,對資料表的操作順序如下(實際順序並不見得如此,只以下流程圖只為實現本文功能)。

clipboard.png

先想明白

由上面的圖,我們得知,如果想實現我們的功能。需要從以下幾方面入手。

  • 需要設定幾個屬性。
  • 需要單獨為mysql來定製指令碼。
  • 需要一張記錄資料庫版本的表。
  • 需要透過sql指令碼,來判斷資料庫版本,然後執行不同的語句。
  • 當資料庫為H2時,不增加任何指令碼。

好了,想明白了上面的問題。其它的事情就顯得簡單了。

實施

準備

準備好mysql,並建立相應的資料庫。在pom.xml中加入適應的依賴。


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mengyunzhi</groupId>
    <artifactId>sample-update-table-with-jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>sample-update-table-with-jpa</name>
    <description>進行版本迭代過程中,使用spring jpa來完美解決資料表更新的問題</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

配置spring boot

spring:
  profiles:
    active: mysql

---
# h2環境
spring:
  profiles: h2
---
# mysql環境
spring:
  profiles: mysql
  datasource:
    url: jdbc:mysql://localhost/sampleUpdateJpa
    username: root
    password:
    platform: mysql
    separator: //
    initialization-mode: always
  jpa:
    hibernate:
      ddl-auto: update

建立測試檔案

resources資料夾下,建立schema-mysql.sql檔案。並隨便寫兩行錯誤的語句,然後啟動應用,得到報錯資訊,說明該檔案併成功執行。比如,我隨便寫了如下幾行。

-- 先於hibernate執行
sdfsdfsdf

組織sql語句

先判斷,是否存在記錄版本的資料表,如果沒有,則建立一個資料表,並加入一條資料,將版本設定為1.1。

-- 重寫 ; 為 // ,在spring中,註釋掉下面一行,應該我們在配置檔案中的 separator: // 便是起的該作用
-- DELIMITER // 
-- 如果存在函式,則先刪除
DROP PROCEDURE IF EXISTS `FUN20180628` //
-- 定義函式FUN20180628
CREATE PROCEDURE `FUN20180628` ()
    BEGIN
        DECLARE hasDataTable INT;
        SELECT count(*) INTO hasDataTable FROM information_schema.tables WHERE (table_schema = 'sampleUpdateJpa') AND (table_name= 'version');
        IF hasDataTable = 0 THEN
            CREATE TABLE `sampleUpdateJpa`.`version` (
                `version` float NOT NULL COMMENT '版本號',
                PRIMARY KEY (`version`)
            ) COMMENT='版本號';
            insert into `sampleUpdateJpa`.`version` ( `version`) values ( '1.1');
        END IF;
    END
//

-- 呼叫函式
CALL FUN20180628() //
-- 恢復重寫的;,以免影響其它的function
-- DELIMITER ;

我們在上面只所以要先刪除原來的同名函式,主要目的是為了這個函式的隨時升級。
相信有了上面的語句基礎,實現當版本為1時,升級為2;為2時,升級為3,就簡單了。

如果你只在意解決方案,那麼本節內容到此結束。

我是如何做到的?

前面,我們已經給出了實現的方法,如果你還對“我是如何做到的”感興趣,請繼續閱讀以下內容。

閱讀官方文件

spring官方網站,我們找到共當家花旦spring-boot , 然後在介紹的下方,點選參考手冊

然後我們將得到一個很長很長,當然也是很有用很有用的手冊,我期待自己能夠有充分的時間,可以在假期的時候嘗試從頭到尾的讀一遍。來到第82章 -- Database Initialization

按我們的需求,簡單的記錄下,我們所要的資料:
82.1 說,有兩種方式控制開與關,分別是:spring.jpa.generate-ddl(boolean),及spring.jpa.hibernate.ddl-auto(enum)

結論:我們並不需要在程式啟動時,執行相關的import語句。

82.2介紹了使用Hibernate初始化資料庫的過程,並對spring.jpa.hibernate.ddl-auto的幾種屬性和預設屬性做了說明。

然後接著又說:在系統啟動時,如果ddl-auto設定的為createcreate-drop,那麼一個名存在於classpath的名為import.sql,將會被自動執行。並指定,這對我們提供DOME非常有幫助,誰說不是呢?我們完成在系統完成後,新增dome資料,並將其匯出為import.sql檔案,以使得在程式部署其它dome程式時被執行,不是嗎?

結論:上面的資訊,對我們本次的任務沒有幫助。

82.3 Initialize a Database初始化一個資料庫。

大概講了:
spring boot可以分別由classpath中的schema.sqldata.sql中載入資料。然後又指出,如果存在schema-${platform}.sqldata-${platform}.sql時,spring boot也會執行。其中,platform是指在spring.datasource.platform設定的值,比如:我們將spring.datasource.platform的值設定為:hsqldb, h2, oracle, mysql, postgresql或者其它的。

然後又給出3點,第一點說Spring Boot會自動為我們建立一個內建的資料庫,在啟動內建資料庫時,預設會載入sql指令碼(意思是其它的資料庫則不會),我們可以使用spring.datasource.initialization-mode來定義它的屬性,以使得在使用其它資料庫時,也預設載入這些腳。又說:在程式啟時,如果執行sql scripts出錯,那麼程式就報錯退出來了,然後我們可以透過更改spring.datasource.continue-on-error的值來改變。最後點說:我們可以選擇是讓Hiberante為我們自己動生成資料表還是執行schema.sql來生成資料表,但是不能二者全部選擇,如果我們想使用schema.sql,那麼就要禁用spring.jpa.hibernate.ddl-auto。(在82.2中,指出了設定為create或是create-drop為啟動,其它為禁用)

迭代式開發

我們很難一次的將sql的函式寫正確,如果你直接在schema.sql中寫,無異於自己挖坑。

  1. 在建立sql函式的過程中,我們首先找到navicat,連線資料庫後,新建函式,並儲存測試,直至該函式可以透過,並且實現我們的功能。
  2. 有了函式,我們再把這個函式的內容複製到查詢中,在詢中,對;進行轉義。比如,我們轉義為\\。然後執行查詢,也確認查詢沒有錯誤。
  3. 查詢測試完了,最後我們把sql的指令碼才正式的放到schema.sql中,並且註釋到轉義的部分。

進階

spring的官方文件還給出了進階的方法。即使用第三方庫來進行更加人性化,簡單的版本升級工作。請參閱:82.5 Use a Higher-level Database Migration Tool

相關文章