SpringBoot系列之資料庫初始化-datasource配置方式

小灰灰Blog發表於2022-12-28

【DB系列】資料庫初始化-datasource配置方式 | 一灰灰Blog

在我們的日常業務開發過程中,如果有db的相關操作,通常我們是直接建立好對應的庫表結構,並初始化對應的資料,即更常見的情況下是我們在已有表結構基礎之下,進行開發;
但是當我們是以專案形式工作時,更常見的做法是所有的庫表結構變更、資料的初始、更新等都需要持有對應的sql變更,並儲存在專案工程中,這也是使用liqubase的一個重要場景;
將上面的問題進行簡單的翻譯一下,就是如何實現在專案啟動之後執行相應的sql,實現資料庫表的初始化?

本文將作為初始化方式的第一篇:基於SpringBoot的配置方式實現的資料初始化

<!-- more -->

I. 專案搭建

1. 依賴

首先搭建一個標準的SpringBoot專案工程,相關版本以及依賴如下

本專案藉助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA進行開發

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
</dependencies>

本文使用MySql資料庫, 版本8.0.31

2. 配置

注意實現初始化資料庫表操作的核心配置就在下面,重點關注

配置檔案: resources/application.yml

# 預設的資料庫名
database:
  name: story

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/${database.name}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password:
    initialization-mode: always
    platform: mysql
    separator: ;
    data: classpath:config-data.sql
    #data-username: root
    #data-password:
    schema: classpath:config-schema.sql # schema必須也存在,若只存在data,data中的sql也不會被執行

# springboot 2.7+ 版本使用下面這個
#  sql:
#    init:
#      mode: always
#      data-location: classpath:config-data.sql
#      schema-location: classpath:init-schema.sql
logging:
  level:
    root: info
    org:
      springframework:
        jdbc:
          core: debug

上面的配置中,相比較於普通的資料庫連結配置,多了幾個配置項

  • spring.datasource.initialization-mode: 取值為 always,改成其他的會導致sql不會被執行
  • spring.datasource.platform: mysql
  • spring.datasource.seprator: ; 這個表示sql之間的分隔符
  • spring.datasource.data: classpath:config-data.sql 取值可以是陣列,這裡存的是初始化資料的sql檔案地址
  • spring.datasource.data-username: 上面data對應的sql檔案執行使用者名稱
  • spring.datasource.data-password: 上面data對應的sql檔案執行使用者密碼
  • spring.datasource.schema: classpath:config-schema.sql 取值也可以是陣列,這裡存的是初始化表結構的sql檔案地址

3. 初始化sql

上面指定了兩個sql,一個是用於建表的ddl,一個是用於初始化資料的dml

resources/config-schema.sql 檔案對應的內容如下

CREATE TABLE `user2`
(
    `id`               int unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
    `third_account_id` varchar(128) NOT NULL DEFAULT '' COMMENT '第三方使用者ID',
    `user_name`        varchar(64)  NOT NULL DEFAULT '' COMMENT '使用者名稱',
    `password`         varchar(128) NOT NULL DEFAULT '' COMMENT '密碼',
    `login_type`       tinyint      NOT NULL DEFAULT '0' COMMENT '登入方式: 0-微信登入,1-賬號密碼登入',
    `deleted`          tinyint      NOT NULL DEFAULT '0' COMMENT '是否刪除',
    `create_time`      timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
    `update_time`      timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最後更新時間',
    PRIMARY KEY (`id`),
    KEY                `key_third_account_id` (`third_account_id`),
    KEY                `user_name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4  COMMENT='使用者登入表';

resources/config-data.sql 檔案對應的內容如下

INSERT INTO `user2` (id, third_account_id, `user_name`, `password`, login_type, deleted)
VALUES (2, '222222-0f85-4dd5-845c-7c5df3746e92', 'admin2', 'admin2', 0, 0);

II. 示例

1. 驗證demo

接下來上面的工作準備完畢之後,在我們啟動專案之後,正常就會執行上面的兩個sql,我們寫一個簡單的驗證demo

@Slf4j
@SpringBootApplication
public class Application implements ApplicationRunner {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List list = jdbcTemplate.queryForList("select * from user2 limit 2");
        log.info("啟動成功,初始化資料: {}\n{}", list.size(), list);
    }
}

2. 問題記錄

從上面的過程走下來,看起來很簡單,但是在實際的使用過程中,很容易遇到不生效的問題,下面記錄一下

2.1 只有初始化資料data.sql,沒有schema.sql時,不生效

當庫表已經存在時,此時我們可能並沒有上文中的config-schema.sql檔案,此時對應的配置可能是


spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/${database.name}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password:
    initialization-mode: always
    platform: mysql
    separator: ; # 預設為 ;
    data: classpath:config-data.sql
    #data-username: root
    #data-password:
    #schema: classpath:config-schema.sql # schema必須也存在,若只存在data,data中的sql也不會被執行

如上面所示,當我們只指定了data時,會發現data對應的sql檔案也不會被執行;即要求schema對應的sql檔案也必須同時存在

針對上面這種情況,可以考慮將data.sql中的語句,解除安裝schema.sql中

2.2 版本問題導致配置不生效

在SpringBoot2.5+版本,使用 spring.sql.init 代替上面的配置項

# springboot 2.5+ 版本使用下面這個
spring:
  sql:
    init:
      mode: always
      data-location: classpath:config-data.sql
      schema-location: classpath:init-schema.sql

相關的配置引數說明如下

  • spring.sql.init.enabled:是否啟動初始化的開關,預設是true。如果不想執行初始化指令碼,設定為false即可。透過-D的命令列引數會更容易控制。
  • spring.sql.init.usernamespring.sql.init.password:配置執行初始化指令碼的使用者名稱與密碼。這個非常有必要,因為安全管理要求,通常給業務應用分配的使用者對一些建表刪表等命令沒有許可權。這樣就可以與datasource中的使用者分開管理。
  • spring.sql.init.schema-locations:配置與schema變更相關的sql指令碼,可配置多個(預設用;分割)
  • spring.sql.init.data-locations:用來配置與資料相關的sql指令碼,可配置多個(預設用;分割)
  • spring.sql.init.encoding:配置指令碼檔案的編碼
  • spring.sql.init.separator:配置多個sql檔案的分隔符,預設是;
  • spring.sql.init.continue-on-error:如果執行指令碼過程中碰到錯誤是否繼續,預設是false`

2.3 mode配置不對導致不生效

當配置完之後發,發現sql沒有按照預期的執行,可以檢查一下spring.datasource.initialization-mode配置是否存在,且值為always

2.4 重複啟動之後,報錯

同樣上面的專案,在第一次啟動時,會執行schema對應的sql檔案,建立表結構;執行data對應的sql檔案,初始化資料;但是再次執行之後就會報錯了,會提示表已經存在

即初始化是一次性的,第一次執行完畢之後,請將spring.datasource.initialization-mode設定為none

3. 小結

本文主要介紹了專案啟動時,資料庫的初始化方式,當然除了本文中介紹的spring.datasource配置之外,還有spring.jpa的配置方式

對於配置方式不太友好的地方則在於不好自適應控制,若表存在則不執行;若不存在則執行;後面將介紹如何使用DataSourceInitializer來實現自主可控的資料初始化,以及更現代化一些的基於liquibase的資料庫版本管理記錄

III. 不能錯過的原始碼和相關知識點

0. 專案

1. 微信公眾號: 一灰灰Blog

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

一灰灰blog

相關文章