您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)啟動(dòng)Springboot項(xiàng)目后怎么實(shí)現(xiàn)自動(dòng)創(chuàng)建多表關(guān)聯(lián)的數(shù)據(jù)庫,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
sql腳本的語句就是平常的create建表語句,類似如下:
create table ACT_PROCDEF_INFO ( ID_ varchar(64) not null, PROC_DEF_ID_ varchar(64) not null, REV_ integer, INFO_JSON_ID_ varchar(64), primary key (ID_) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin;
增加外部主鍵、索引——
create index ACT_IDX_INFO_PROCDEF on ACT_PROCDEF_INFO(PROC_DEF_ID_); alter table ACT_PROCDEF_INFO add constraint ACT_FK_INFO_JSON_BA foreign key (INFO_JSON_ID_) references ACT_GE_BYTEARRAY (ID_); alter table ACT_PROCDEF_INFO add constraint ACT_FK_INFO_PROCDEF foreign key (PROC_DEF_ID_) references ACT_RE_PROCDEF (ID_); alter table ACT_PROCDEF_INFO add constraint ACT_UNIQ_INFO_PROCDEF unique (PROC_DEF_ID_);
整體就是設(shè)計(jì)一套符合符合需求場景的sql語句,保存在.sql的腳本文件里,最后統(tǒng)一存放在resource目錄下,類似如下:
接下來,就是實(shí)現(xiàn)CommandLineRunner的接口,重寫其run()的bean回調(diào)方法,在run方法里開發(fā)能自動(dòng)建庫與建表邏輯的功能。
目前,我已將開發(fā)的demo上傳到了我的github,感興趣的童鞋,可自行下載,目前能直接下下來在本地環(huán)境運(yùn)行,可根據(jù)自己的實(shí)際需求針對(duì)性參考使用。
首先,在解決這類需求時(shí),第一個(gè)先要解決的地方是,Springboot啟動(dòng)后如何實(shí)現(xiàn)只執(zhí)行一次建表方法。
這里需要用到一個(gè)CommandLineRunner接口,這是Springboot自帶的,實(shí)現(xiàn)該接口的類,其重寫的run方法,會(huì)在Springboot啟動(dòng)完成后自動(dòng)執(zhí)行,該接口源碼如下:
@FunctionalInterface public interface CommandLineRunner { /** *用于運(yùn)行bean的回調(diào) */ void run(String... args) throws Exception; }
擴(kuò)展一下,在Springboot中,可以定義多個(gè)實(shí)現(xiàn)CommandLineRunner接口類,并且可以對(duì)這些實(shí)現(xiàn)類中進(jìn)行排序,只需要增加@Order,其重寫的run方法就可以按照順序執(zhí)行,代碼案例驗(yàn)證:
@Component @Order(value=1) public class WatchStartCommandSqlRunnerImpl implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("第一個(gè)Command執(zhí)行"); } @Component @Order(value = 2) public class WatchStartCommandSqlRunnerImpl2 implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("第二個(gè)Command執(zhí)行"); } }
控制臺(tái)打印的信息如下:
第一個(gè)Command執(zhí)行
第二個(gè)Command執(zhí)行
根據(jù)以上的驗(yàn)證,因此,我們可以通過實(shí)現(xiàn)CommandLineRunner的接口,重寫其run()的bean回調(diào)方法,用于在Springboot啟動(dòng)后實(shí)現(xiàn)只執(zhí)行一次建表方法。實(shí)現(xiàn)項(xiàng)目啟動(dòng)建表的功能,可能還需實(shí)現(xiàn)判斷是否已經(jīng)有相應(yīng)數(shù)據(jù)庫,若無,則應(yīng)先新建一個(gè)數(shù)據(jù)庫,同時(shí),得考慮還沒有對(duì)應(yīng)數(shù)據(jù)庫的情況,因此,我們通過jdbc第一次連接MySQL時(shí),應(yīng)連接一個(gè)原有自帶存在的庫。每個(gè)MySql安裝成功后,都會(huì)有一個(gè)mysql庫,在第一次建立jdbc連接時(shí),可以先連接它。
代碼如下:
Class.forName("com.mysql.jdbc.Driver"); String url="jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=UTF-8&ueSSL=false&serverTimezone=GMT%2B8"; Connection conn= DriverManager.getConnection(url,"root","root");
建立與MySql軟件連接后,先創(chuàng)建一個(gè)Statement對(duì)象,該對(duì)象是jdbc中可用于執(zhí)行靜態(tài) SQL 語句并返回它所生成結(jié)果的對(duì)象,這里可以使用它來執(zhí)行查找?guī)炫c創(chuàng)建庫的作用。
//創(chuàng)建Statement對(duì)象 Statement statment=conn.createStatement(); /** 使用statment的查詢方法executeQuery("show databases like \"fte\"") 檢查MySql是否有fte這個(gè)數(shù)據(jù)庫 **/ ResultSet resultSet=statment.executeQuery("show databases like \"fte\""); //若resultSet.next()為true,證明已存在; //若false,證明還沒有該庫,則執(zhí)行statment.executeUpdate("create database fte")創(chuàng)建庫 if(resultSet.next()){ log.info("數(shù)據(jù)庫已經(jīng)存在"); }else { log.info("數(shù)據(jù)庫未存在,先創(chuàng)建fte數(shù)據(jù)庫"); if(statment.executeUpdate("create database fte")==1){ log.info("新建數(shù)據(jù)庫成功"); } }
在數(shù)據(jù)庫fte自動(dòng)創(chuàng)建完成后,就可以在該fte庫里去做建表的操作了。
我將建表的相關(guān)方法都封裝到SqlSessionFactory類里,相關(guān)建表方法同樣需要用到j(luò)dbc的Connection連接到數(shù)據(jù)庫,因此,需要把已連接的Connection引用變量當(dāng)做參數(shù)傳給SqlSessionFactory的初始構(gòu)造函數(shù):
public void createTable(Connection conn,Statement stat) throws SQLException { try { String url="jdbc:mysql://127.0.0.1:3306/fte?useUnicode=true&characterEncoding=UTF-8&ueSSL=false&serverTimezone=GMT%2B8"; conn=DriverManager.getConnection(url,"root","root"); SqlSessionFactory sqlSessionFactory=new SqlSessionFactory(conn); sqlSessionFactory.schemaOperationsBuild("create"); } catch (SQLException e) { e.printStackTrace(); }finally { stat.close(); conn.close(); } }
初始化new SqlSessionFactory(conn)后,就可以在該對(duì)象里使用已進(jìn)行連接操作的Connection對(duì)象了。
public class SqlSessionFactory{ private Connection connection ; public SqlSessionFactory(Connection connection) { this.connection = connection; } ...... }
這里傳參可以有兩種情況,即“create”代表創(chuàng)建表結(jié)構(gòu)的功能,“drop”代表刪除表結(jié)構(gòu)的功能:
sqlSessionFactory.schemaOperationsBuild("create");
進(jìn)入到這個(gè)方法里,會(huì)先做一個(gè)判斷——
public void schemaOperationsBuild(String type) { switch (type){ case "drop": this.dbSchemaDrop();break; case "create": this.dbSchemaCreate();break; } }
若是this.dbSchemaCreate(),執(zhí)行建表操作:
/** * 新增數(shù)據(jù)庫表 */ public void dbSchemaCreate() { if (!this.isTablePresent()) { log.info("開始執(zhí)行create操作"); this.executeResource("create", "act"); log.info("執(zhí)行create完成"); } }
this.executeResource("create", "act")代表創(chuàng)建表名為act的數(shù)據(jù)庫表——
public void executeResource(String operation, String component) { this.executeSchemaResource(operation, component, this.getDbResource(operation, operation, component), false); }
其中 this.getDbResource(operation, operation, component)是獲取sql腳本的路徑,進(jìn)入到方法里,可見——
public String getDbResource(String directory, String operation, String component) { return "static/db/" + directory + "/mysql." + operation + "." + component + ".sql"; }
接下來,讀取路徑下的sql腳本,生成輸入流字節(jié)流:
public void executeSchemaResource(String operation, String component, String resourceName, boolean isOptional) { InputStream inputStream = null; try { //讀取sql腳本數(shù)據(jù) inputStream = IoUtil.getResourceAsStream(resourceName); if (inputStream == null) { if (!isOptional) { log.error("resource '" + resourceName + "' is not available"); return; } } else { this.executeSchemaResource(operation, component, resourceName, inputStream); } } finally { IoUtil.closeSilently(inputStream); } }
最后,整個(gè)執(zhí)行sql腳本的核心實(shí)現(xiàn)在this.executeSchemaResource(operation, component, resourceName, inputStream)方法里——
/** * 執(zhí)行sql腳本 * @param operation * @param component * @param resourceName * @param inputStream */ private void executeSchemaResource(String operation, String component, String resourceName, InputStream inputStream) { //sql語句拼接字符串 String sqlStatement = null; Object exceptionSqlStatement = null; try { /** * 1.jdbc連接mysql數(shù)據(jù)庫 */ Connection connection = this.connection; Exception exception = null; /** * 2、分行讀取"static/db/create/mysql.create.act.sql"里的sql腳本數(shù)據(jù) */ byte[] bytes = IoUtil.readInputStream(inputStream, resourceName); /** * 3.將sql文件里數(shù)據(jù)分行轉(zhuǎn)換成字符串,換行的地方,用轉(zhuǎn)義符“\n”來代替 */ String ddlStatements = new String(bytes); /** * 4.以字符流形式讀取字符串?dāng)?shù)據(jù) */ BufferedReader reader = new BufferedReader(new StringReader(ddlStatements)); /** * 5.根據(jù)字符串中的轉(zhuǎn)義符“\n”分行讀取 */ String line = IoUtil.readNextTrimmedLine(reader); /** * 6.循環(huán)讀取的每一行 */ for(boolean inOraclePlsqlBlock = false; line != null; line = IoUtil.readNextTrimmedLine(reader)) { /** * 7.若下一行l(wèi)ine還有數(shù)據(jù),證明還沒有全部讀取,仍可執(zhí)行讀取 */ if (line.length() > 0) { /** 8.在沒有拼接夠一個(gè)完整建表語句時(shí),!line.endsWith(";")會(huì)為true, 即一直循環(huán)進(jìn)行拼接,當(dāng)遇到";"就跳出該if語句 **/ if ((!line.endsWith(";") || inOraclePlsqlBlock) && (!line.startsWith("/") || !inOraclePlsqlBlock)) { sqlStatement = this.addSqlStatementPiece(sqlStatement, line); } else { /** 9.循環(huán)拼接中若遇到符號(hào)";",就意味著,已經(jīng)拼接形成一個(gè)完整的sql建表語句,例如 create table ACT_GE_PROPERTY ( NAME_ varchar(64), VALUE_ varchar(300), REV_ integer, primary key (NAME_) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin 這樣,就可以先通過代碼來將該建表語句執(zhí)行到數(shù)據(jù)庫中,實(shí)現(xiàn)如下: **/ if (inOraclePlsqlBlock) { inOraclePlsqlBlock = false; } else { sqlStatement = this.addSqlStatementPiece(sqlStatement, line.substring(0, line.length() - 1)); } /** * 10.將建表語句字符串包裝成Statement對(duì)象 */ Statement jdbcStatement = connection.createStatement(); try { /** * 11.最后,執(zhí)行建表語句到數(shù)據(jù)庫中 */ log.info("SQL: {}", sqlStatement); jdbcStatement.execute(sqlStatement); jdbcStatement.close(); } catch (Exception var27) { log.error("problem during schema {}, statement {}", new Object[]{operation, sqlStatement, var27}); } finally { /** * 12.到這一步,意味著上一條sql建表語句已經(jīng)執(zhí)行結(jié)束, * 若沒有出現(xiàn)錯(cuò)誤話,這時(shí)已經(jīng)證明第一個(gè)數(shù)據(jù)庫表結(jié)構(gòu)已經(jīng)創(chuàng)建完成, * 可以開始拼接下一條建表語句, */ sqlStatement = null; } } } } if (exception != null) { throw exception; } } catch (Exception var29) { log.error("couldn't " + operation + " db schema: " + exceptionSqlStatement, var29); } }
這部分代碼主要功能是,先用字節(jié)流形式讀取sql腳本里的數(shù)據(jù),轉(zhuǎn)換成字符串,其中有換行的地方用轉(zhuǎn)義符“/n”來代替。接著把字符串轉(zhuǎn)換成字符流BufferedReader形式讀取,按照“/n”符合來劃分每一行的讀取,循環(huán)將讀取的每行字符串進(jìn)行拼接,當(dāng)循環(huán)到某一行遇到“;”時(shí),就意味著已經(jīng)拼接成一個(gè)完整的create建表語句,類似這樣形式——
create table ACT_PROCDEF_INFO ( ID_ varchar(64) not null, PROC_DEF_ID_ varchar(64) not null, REV_ integer, INFO_JSON_ID_ varchar(64), primary key (ID_) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin;
這時(shí),就可以先將拼接好的create建表字符串,通過 jdbcStatement.execute(sqlStatement)語句來執(zhí)行入庫了。當(dāng)執(zhí)行成功時(shí),該ACT_PROCDEF_INFO表就意味著已經(jīng)創(chuàng)建成功,接著以BufferedReader字符流形式繼續(xù)讀取下一行,進(jìn)行下一個(gè)數(shù)據(jù)庫表結(jié)構(gòu)的構(gòu)建。
整個(gè)過程大概就是這個(gè)邏輯,可以在此基礎(chǔ)上,針對(duì)更為復(fù)雜的建表結(jié)構(gòu)sql語句進(jìn)行設(shè)計(jì),在項(xiàng)目啟動(dòng)時(shí),自行執(zhí)行相應(yīng)的sql語句,來進(jìn)行建表。
關(guān)于啟動(dòng)Springboot項(xiàng)目后怎么實(shí)現(xiàn)自動(dòng)創(chuàng)建多表關(guān)聯(lián)的數(shù)據(jù)庫就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。