提示:本文中,我們只給了部分示例程式碼。
如果你需要完整的程式碼,請點選: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
在系統啟動時,對資料表的操作順序如下(實際順序並不見得如此,只以下流程圖只為實現本文功能)。
先想明白
由上面的圖,我們得知,如果想實現我們的功能。需要從以下幾方面入手。
- 需要設定幾個屬性。
- 需要單獨為
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
設定的為create
或create-drop
,那麼一個名存在於classpath的名為import.sql
,將會被自動執行。並指定,這對我們提供DOME
非常有幫助,誰說不是呢?我們完成在系統完成後,新增dome
資料,並將其匯出為import.sql
檔案,以使得在程式部署其它dome
程式時被執行,不是嗎?
結論:上面的資訊,對我們本次的任務沒有幫助。
82.3 Initialize a Database初始化一個資料庫。
大概講了:spring boot
可以分別由classpath
中的schema.sql
和data.sql
中載入資料。然後又指出,如果存在schema-${platform}.sql
和data-${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
中寫,無異於自己挖坑。
- 在建立
sql
函式的過程中,我們首先找到navicat
,連線資料庫後,新建函式,並儲存測試,直至該函式可以透過,並且實現我們的功能。 - 有了函式,我們再把這個函式的內容複製到查詢中,在詢中,對
;
進行轉義。比如,我們轉義為\\
。然後執行查詢,也確認查詢沒有錯誤。 - 查詢測試完了,最後我們把
sql
的指令碼才正式的放到schema.sql
中,並且註釋到轉義的部分。
進階
spring
的官方文件還給出了進階的方法。即使用第三方庫來進行更加人性化,簡單的版本升級工作。請參閱:82.5 Use a Higher-level Database Migration Tool