SpringBoot啟動(dòng)并初始化執(zhí)行sql腳本問題
SpringBoot啟動(dòng)并初始化執(zhí)行sql腳本
如果我們想在項(xiàng)目啟動(dòng)的時(shí)候去執(zhí)行一些sql腳本該怎么辦呢,SpringBoot給我們提供了這個(gè)功能,可以在啟動(dòng)SpringBoot的項(xiàng)目時(shí),執(zhí)行腳本,下面我們來看一下。
我們先看一下源碼
boolean createSchema() { //會(huì)從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(); //運(yùn)行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),默認(rèn)為all String platform = this.properties.getPlatform(); List<String> fallbackResources = new ArrayList(); //如果配置文件中沒配置,則會(huì)去類路徑下找名稱為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]; //驗(yàn)證文件是否存在 if (resource.exists()) { resources.add(resource); } else if (validate) { throw new InvalidConfigurationPropertyValueException(propertyName, resource, "The specified resource does not exist."); } } } return resources; }
從源碼觀察,大致知道是什么意思了,SpringBoot默認(rèn)會(huì)從類路徑下去找腳本文件,但是類路徑下只能放規(guī)定名稱為schema或schema-platform的腳本文件,如果我們想要分好多個(gè)腳本文件,那么這種方式就不合適了,那么就需要我們在application.properties或application.yml中去配置腳本列表,那么這個(gè)初始化腳本操作能不能在配置文件中控制呢,可以的,有一個(gè)initialization-mode屬性,可以設(shè)置三個(gè)值,always為始終執(zhí)行初始化,embedded只初始化內(nèi)存數(shù)據(jù)庫(默認(rèn)值),如h2等,never為不執(zhí)行初始化。
spring: datasource: username: root password: liuzhenyu199577 url: jdbc:mysql://localhost:3306/jdbc driver-class-name: com.mysql.cj.jdbc.Driver initialization-mode: always
下面我們驗(yàn)證一下這兩種方式
1.默認(rèn)放置schema或schema-platform的腳本文件
CREATE TABLE IF NOT EXISTS department (ID VARCHAR(40) NOT NULL, NAME VARCHAR(100), PRIMARY KEY (ID));
查看數(shù)據(jù)庫沒有department這個(gè)表,接下來我們啟動(dòng)程序
啟動(dòng)之后,我們再看一下,就執(zhí)行了
2.配置文件中指定多個(gè)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
三個(gè)sql腳本都是插入語句
INSERT INTO department (ID,NAME) VALUES ('1','2') INSERT INTO department (ID,NAME) VALUES ('2','3') INSERT INTO department (ID,NAME) VALUES ('3','4')
現(xiàn)在表中無任何數(shù)據(jù),接下來我們啟動(dòng)程序
啟動(dòng)之后,我們再看一下,表中就有了三條數(shù)據(jù)
就是SpringBoot啟動(dòng)并初始化sql腳本的步驟
SpringBoot項(xiàng)目在啟動(dòng)時(shí)執(zhí)行指定sql文件
1. 啟動(dòng)時(shí)執(zhí)行
當(dāng)有在項(xiàng)目啟動(dòng)時(shí)先執(zhí)行指定的sql語句的需求時(shí),可以在resources文件夾下添加需要執(zhí)行的sql文件,文件中的sql語句可以是DDL腳本或DML腳本,然后在配置加入相應(yīng)的配置即可,如下:
spring: ? datasource: ? ? schema: classpath:schema.sql # schema.sql中一般存放的是DDL腳本,即通常為創(chuàng)建或更新庫表的腳本 data: classpath:data.sql # data.sql中一般是DML腳本,即通常為數(shù)據(jù)插入腳本?
2. 執(zhí)行多個(gè)sql文件
spring.datasource.schema和spring.datasource.data都是支持接收一個(gè)列表,所以當(dāng)需要執(zhí)行多個(gè)sql文件時(shí),可以使用如下配置:
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. 不同運(yùn)行環(huán)境執(zhí)行不同腳本
一般情況下,都會(huì)有多個(gè)運(yùn)行環(huán)境,比如開發(fā)、測試、生產(chǎn)等。而不同運(yùn)行環(huán)境通常需要執(zhí)行的sql會(huì)有所不同。為解決這個(gè)問題,可以使用通配符來實(shí)現(xiàn)。
創(chuàng)建不同環(huán)境的文件夾
在resources文件夾創(chuàng)建不同環(huán)境對應(yīng)的文件夾,如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},其中分號(hào)前是取屬性spring.profiles.active的值,而當(dāng)該屬性的值不存在,則使用分號(hào)后面的值,即dev。
bootstrap.yml
spring: ? profiles: ? ? active: dev # dev/sit/prod等。分別對應(yīng)開發(fā)、測試、生產(chǎn)等不同運(yùn)行環(huán)境。
提醒:spring.profiles.active屬性一般在bootstrap.yml或bootstrap.properties中配置。
4. 支持不同數(shù)據(jù)庫
因?yàn)椴煌瑪?shù)據(jù)庫的語法有所差異,所以要實(shí)現(xiàn)同樣的功能,不同數(shù)據(jù)庫的sql語句可能會(huì)不一樣,所以可能會(huì)有多份不同的sql文件。當(dāng)需要支持不同數(shù)據(jù)庫時(shí),可以使用如下配置:
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屬性的默認(rèn)值是'all',所以當(dāng)有在不同數(shù)據(jù)庫切換的情況下才使用如上配置,因?yàn)槟J(rèn)值的情況下,spring boot會(huì)自動(dòng)檢測當(dāng)前使用的數(shù)據(jù)庫。
注:此時(shí),以dev允許環(huán)境為例,resources/dev/文件夾下必須存在如下文件:schema-mysql.sql和data-mysql.sql。
5. 避坑
5.1 坑
當(dāng)在執(zhí)行的sql文件中存在存儲(chǔ)過程或函數(shù)時(shí),在啟動(dòng)項(xiàng)目時(shí)會(huì)報(bào)錯(cuò)。
比如現(xiàn)在有這樣的需求:項(xiàng)目啟動(dòng)時(shí),掃描某張表,當(dāng)表記錄數(shù)為0時(shí),插入多條記錄;大于0時(shí),跳過。
schema.sql文件腳本如下:
-- 當(dāng)存儲(chǔ)過程`p1`存在時(shí),刪除。 drop procedure if exists p1; ? -- 創(chuàng)建存儲(chǔ)過程`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 ('zhangsan', '123456'); end if; end; -- 調(diào)用存儲(chǔ)過程`p1` call p1(); drop procedure if exists p1;?
啟動(dòng)項(xiàng)目,報(bào)錯(cuò),原因如下:
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'這一句出現(xiàn)語法錯(cuò)誤。剛看到這一句,我一開始是懵逼的,嚇得我趕緊去比對mysql存儲(chǔ)過程的寫法,對了好久都發(fā)現(xiàn)沒錯(cuò),最后看到一篇講解spring boot配置啟動(dòng)時(shí)執(zhí)行sql腳本的文章,發(fā)現(xiàn)其中多了一項(xiàng)配置:spring.datasource.separator=$$。然后看源碼發(fā)現(xiàn),spring boot在解析sql腳本時(shí),默認(rèn)是以';'作為斷句的分隔符的??吹竭@里,不難看出報(bào)錯(cuò)的原因,即:spring boot把'create procedure p1() begin declare row_num int'當(dāng)成是一條普通的sql語句。而我們需要的是創(chuàng)建一個(gè)存儲(chǔ)過程。
5.2 解決方案
修改sql腳本的斷句分隔符。如:spring.datasource.separator=$$。然后把腳本改成:
-- 當(dāng)存儲(chǔ)過程`p1`存在時(shí),刪除。 drop procedure if exists p1;$$ ? -- 創(chuàng)建存儲(chǔ)過程`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 ('zhangsan', '123456'); end if; end;$$ -- 調(diào)用存儲(chǔ)過程`p1` call p1();$$ drop procedure if exists p1;$$?
5.3 不足
因?yàn)閟ql腳本的斷句分隔符從';'變成'$$',所以可能需要在DDL、DML語句的';'后加'$$',不然可能會(huì)出現(xiàn)將整個(gè)腳本當(dāng)成一條sql語句來執(zhí)行的情況。比如:
-- DDL CREATE TABLE `table_name` ( ? -- 字段定義 ? ...? ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;$$ ? -- DML INSERT INTO `table_name` VALUE(...);$$
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Springboot @Configuration與自動(dòng)配置詳解
這篇文章主要介紹了SpringBoot中的@Configuration自動(dòng)配置,在進(jìn)行項(xiàng)目編寫前,我們還需要知道一個(gè)東西,就是SpringBoot對我們的SpringMVC還做了哪些配置,包括如何擴(kuò)展,如何定制,只有把這些都搞清楚了,我們在之后使用才會(huì)更加得心應(yīng)手2022-07-07Java(基于Struts2) 分頁實(shí)現(xiàn)代碼
這篇文章介紹了Java(基于Struts2) 分頁實(shí)現(xiàn)代碼,有需要的朋友可以參考一下2013-10-10Apache?SkyWalking?修復(fù)TTL?timer?失效bug詳解
這篇文章主要為大家介紹了Apache?SkyWalking?修復(fù)TTL?timer?失效bug詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09java中int轉(zhuǎn)string與string轉(zhuǎn)int的效率對比
這篇文章主要介紹了java中int轉(zhuǎn)string與string轉(zhuǎn)int的效率對比,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java 實(shí)現(xiàn)攔截器Interceptor的攔截功能方式
這篇文章主要介紹了Java 實(shí)現(xiàn)攔截器Interceptor的攔截功能方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10