首頁  >  文章  >  Java  >  SpringBoot如何啟動並初始化執行sql腳本

SpringBoot如何啟動並初始化執行sql腳本

WBOY
WBOY轉載
2023-05-13 10:58:122636瀏覽

    SpringBoot啟動並初始化執行sql腳本

    如果我們想在專案啟動的時候去執行一些sql腳本該怎麼辦呢,SpringBoot給我們提供了這個功能,可以在啟動SpringBoot的專案時,執行腳本,下面我們來看看。

    我們先看一下原始碼

    SpringBoot如何啟動並初始化執行sql腳本

    boolean createSchema() {
    	//会从application.properties或application.yml中获取sql脚本列表
    	List<Resource> scripts = this.getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");
        if (!scripts.isEmpty()) {
        	if (!this.isEnabled()) {
            	logger.debug("Initialization disabled (not running DDL scripts)");
                return false;
            }
    
            String username = this.properties.getSchemaUsername();
            String password = this.properties.getSchemaPassword();
            //运行sql脚本
            this.runScripts(scripts, username, password);
        }
        return !scripts.isEmpty();
    }
    private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
    	if (resources != null) {
    		//如果配置文件中配置,则加载配置文件
        	return this.getResources(propertyName, resources, true);
       	} else {
       		//指定schema要使用的Platform(mysql、oracle),默认为all
        	String platform = this.properties.getPlatform();
            List<String> fallbackResources = new ArrayList();
            //如果配置文件中没配置,则会去类路径下找名称为schema或schema-platform的文件
            fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
           	fallbackResources.add("classpath*:" + fallback + ".sql");
           	return this.getResources(propertyName, fallbackResources, false);
        }
    }
    private List<Resource> getResources(String propertyName, List<String> locations, boolean validate) {
    	List<Resource> resources = new ArrayList();
    	Iterator var5 = locations.iterator();
    
        while(var5.hasNext()) {
        	String location = (String)var5.next();
            Resource[] var7 = this.doGetResources(location);
            int var8 = var7.length;
    
            for(int var9 = 0; var9 < var8; ++var9) {
            	Resource resource = var7[var9];
            	//验证文件是否存在
            	if (resource.exists()) {
                	resources.add(resource);
               	} else if (validate) {
                	throw new InvalidConfigurationPropertyValueException(propertyName, resource, "The specified resource does not exist.");
                }
            }
        }
        return resources;
    }

    從原始碼觀察,大致知道是什麼意思了,SpringBoot預設會從類別路徑下去找腳本文件,但是類別路徑下只能放規定名稱為schema或schema-platform的腳本文件,如果我們想要分好多個腳本文件,那麼這種方式就不合適了,那麼就需要我們在application.properties或application.yml中去配置腳本列表,那麼這個初始化腳本操作能不能在設定檔中控制呢,可以的,有一個initialization-mode屬性,可以設定三個值,always為始終執行初始化,embedded只初始化記憶體資料庫(預設值) ,如h3等,never為不執行初始化。

    spring:
      datasource:
        username: root
        password: liuzhenyu199577
        url: jdbc:mysql://localhost:3306/jdbc
        driver-class-name: com.mysql.cj.jdbc.Driver
        initialization-mode: always

    下面我們驗證這兩種方式

    1.預設放置schema或schema-platform的腳本檔案

    SpringBoot如何啟動並初始化執行sql腳本##

    CREATE TABLE IF NOT EXISTS department (ID VARCHAR(40) NOT NULL, NAME VARCHAR(100), PRIMARY KEY (ID));

    查看資料庫沒有department這個表,接下來我們啟動程式

    SpringBoot如何啟動並初始化執行sql腳本

    啟動之後,我們再看一下,就執行了

    SpringBoot如何啟動並初始化執行sql腳本

    2.設定檔中指定多個sql腳本

    spring:
      datasource:
        username: root
        password: liuzhenyu199577
        url: jdbc:mysql://localhost:3306/jdbc
        driver-class-name: com.mysql.cj.jdbc.Driver
        initialization-mode: always
        schema:
          - classpath:department.sql
          - classpath:department2.sql
          - classpath:department3.sql

    SpringBoot如何啟動並初始化執行sql腳本

    三個sql腳本都是插入語句

    INSERT INTO department (ID,NAME) VALUES (&#39;1&#39;,&#39;2&#39;)
    INSERT INTO department (ID,NAME) VALUES (&#39;2&#39;,&#39;3&#39;)
    INSERT INTO department (ID,NAME) VALUES (&#39;3&#39;,&#39;4&#39;)

    現在表中無任何數據,接下來我們啟動程式

    SpringBoot如何啟動並初始化執行sql腳本

    啟動之後,我們再看一下,表中就有了三個資料

    SpringBoot如何啟動並初始化執行sql腳本

    就是SpringBoot啟動並初始化sql腳本的步驟

    SpringBoot專案在啟動時執行指定sql檔

    1. 啟動時執行

    當有在專案啟動時先執行指定的sql語句的需求時,可以在resources資料夾下新增需要執行的sql文件,文件中的sql語句可以是DDL腳本或DML腳本,然後在組態加入對應的組態即可,如下:

    spring:
      datasource:
        schema: classpath:schema.sql # schema.sql中一般存放的是DDL脚本,即通常为创建或更新库表的脚本 data: classpath:data.sql # data.sql中一般是DML脚本,即通常为数据插入脚本

    2. 執行多個sql檔

    spring.datasource.schema和spring.datasource.data都是支援接收一個列表,所以當需要執行多個sql檔時,可以使用以下設定:

    spring:
      datasource:
        schema: classpath:schema_1.sql, classpath:schema_2.sql data: classpath:data_1.sql, classpath:data_2.sql 或 spring: datasource: schema: - classpath:schema_1.sql - classpath:schema_2.sql data: - classpath:data_1.sql - classpath:data_2.sql

    3. 不同運行環境執行不同腳本

    一般情況下,都會有多個運行環境,例如開發、測試、生產等。而不同運行環境通常需要執行的sql會有所不同。為解決這個問題,可以使用通配符來實現。

    建立不同環境的資料夾

    在resources資料夾建立不同環境對應的資料夾,如dev/、sit/、prod/。

    設定

    application.yml

    spring:
      datasource:
        schema: classpath:${spring.profiles.active:dev}/schema.sql 
        data: classpath:${spring.profiles.active:dev}/data.sql

    附註:${}通配符支援預設值。如上面的配置中的${spring.profiles.active:dev},其中分號前是取屬性spring.profiles.active的值,而當該屬性的值不存在,則使用分號後面的值,即dev。

    bootstrap.yml

    spring:
      profiles:
        active: dev # dev/sit/prod等。分别对应开发、测试、生产等不同运行环境。

    提醒:spring.profiles.active屬性一般在bootstrap.yml或bootstrap.properties中配置。

    4. 支援不同資料庫

    因為不同資料庫的語法有所差異,所以要實現同樣的功能,不同資料庫的sql語句可能會不一樣,所以可能會有多份不同的sql檔。當需要支援不同資料庫時,可以使用以下配置:

    spring:
      datasource:
        schema: classpath:${spring.profiles.active:dev}/schema-${spring.datasource.platform}.sql
        data: classpath:${spring.profiles.active:dev}/data-${spring.datasource.platform}.sql
        platform: mysql

    提醒:platform屬性的預設值是'all',所以當有在不同資料庫切換的情況下才使用如上配置,因為預設值的情況下,spring boot會自動偵測目前使用的資料庫。

    附註:此時,以dev允許環境為例,resources/dev/資料夾下必須存在下列檔案:schema-mysql.sql和data-mysql.sql。

    5. 避坑

    5.1 坑

    當在執行的sql檔中存在預存程序或函數時,在啟動專案時會報錯。

    例如現在有這樣的需求:專案啟動時,掃描某張表,當表記錄數為0時,插入多筆記錄;大於0時,跳過。

    schema.sql檔案腳本如下:

    -- 当存储过程`p1`存在时,删除。
    drop procedure if exists p1;
     
    -- 创建存储过程`p1`
    create procedure p1() 
    begin declare row_num int; select count(*) into row_num from `t_user`; if row_num = 0 then INSERT INTO `t_user`(`username`, `password`) VALUES (&#39;zhangsan&#39;, &#39;123456&#39;); end if; end; -- 调用存储过程`p1` call p1(); drop procedure if exists p1;

    啟動項目,報錯,原因如下:

    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'create procedure p1() begin declare row_num int' at line 1

    大致的意思是:'create procedure p1() begin declare row_num int'这一句出现语法错误。刚看到这一句,我一开始是懵逼的,吓得我赶紧去比对mysql存储过程的写法,对了好久都发现没错,最后看到一篇讲解spring boot配置启动时执行sql脚本的文章,发现其中多了一项配置:spring.datasource.separator=$$。然后看源码发现,spring boot在解析sql脚本时,默认是以';'作为断句的分隔符的。看到这里,不难看出报错的原因,即:spring boot把'create procedure p1() begin declare row_num int'当成是一条普通的sql语句。而我们需要的是创建一个存储过程。

    5.2 解决方案

    修改sql脚本的断句分隔符。如:spring.datasource.separator=$$。然后把脚本改成:

    -- 当存储过程`p1`存在时,删除。
    drop procedure if exists p1;$$
     
    -- 创建存储过程`p1`
    create procedure p1() 
    begin declare row_num int; select count(*) into row_num from `t_user`; if row_num = 0 then INSERT INTO `t_user`(`username`, `password`) VALUES (&#39;zhangsan&#39;, &#39;123456&#39;); end if; end;$$ -- 调用存储过程`p1` call p1();$$ drop procedure if exists p1;$$

    5.3 不足

    因为sql脚本的断句分隔符从';'变成'$$',所以可能需要在DDL、DML语句的';'后加'$$',不然可能会出现将整个脚本当成一条sql语句来执行的情况。比如:

    -- DDL
    CREATE TABLE `table_name` (
      -- 字段定义
      ... 
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;$$
     
    -- DML
    INSERT INTO `table_name` VALUE(...);$$

    以上是SpringBoot如何啟動並初始化執行sql腳本的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除