最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

解決Springboot項(xiàng)目啟動后自動創(chuàng)建多表關(guān)聯(lián)的數(shù)據(jù)庫與表的方案

 更新時(shí)間:2021年03月16日 10:19:26   作者:朱季謙  
這篇文章主要介紹了解決Springboot項(xiàng)目啟動后自動創(chuàng)建多表關(guān)聯(lián)的數(shù)據(jù)庫與表的方案,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

熬夜寫完,尚有不足,但仍在努力學(xué)習(xí)與總結(jié)中,而您的點(diǎn)贊與關(guān)注,是對我最大的鼓勵(lì)!

在一些本地化項(xiàng)目開發(fā)當(dāng)中,存在這樣一種需求,即開發(fā)完成的項(xiàng)目,在第一次部署啟動時(shí),需能自行構(gòu)建系統(tǒng)需要的數(shù)據(jù)庫及其對應(yīng)的數(shù)據(jù)庫表。

若要解決這類需求,其實(shí)現(xiàn)在已有不少開源框架都能實(shí)現(xiàn)自動生成數(shù)據(jù)庫表,如mybatis plus、spring JPA等,但您是否有想過,若要自行構(gòu)建一套更為復(fù)雜的表結(jié)構(gòu)時(shí),這種開源框架是否也能滿足呢,若滿足不了話,又該如何才能實(shí)現(xiàn)呢?

我在前面寫過一篇 Activiti工作流學(xué)習(xí)筆記(三)——自動生成28張數(shù)據(jù)庫表的底層原理分析 ,里面分析過工作流Activiti自動構(gòu)建28數(shù)據(jù)庫表的底層原理。在我看來,學(xué)習(xí)開源框架的底層原理,其中一個(gè)原因是,須從中學(xué)到能為我所用的東西。故而,在分析理解完工作流自動構(gòu)建28數(shù)據(jù)庫表的底層原理之后,我決定也寫一個(gè)基于Springboot框架的自行創(chuàng)建數(shù)據(jù)庫與表的demo。我參考了工作流Activiti6.0版本的底層建表實(shí)現(xiàn)的邏輯,基于Springboot框架,實(shí)現(xiàn)項(xiàng)目在第一次啟動時(shí)可自動構(gòu)建各種復(fù)雜如多表關(guān)聯(lián)等形式的數(shù)據(jù)庫與表的。

整體實(shí)現(xiàn)思路并不復(fù)雜,大概是這樣:先設(shè)計(jì)一套完整創(chuàng)建多表關(guān)聯(lián)的數(shù)據(jù)庫sql腳本,放到resource里,在springboot啟動過程中,自動執(zhí)行sql腳本。

首先,先一次性設(shè)計(jì)一套可行的多表關(guān)聯(lián)數(shù)據(jù)庫腳本,這里我主要參考使用Activiti自帶的表做實(shí)現(xiàn)案例,因?yàn)樗鼉?nèi)部設(shè)計(jì)了眾多表關(guān)聯(lián),就不額外設(shè)計(jì)了。

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ā)能自動建庫與建表邏輯的功能。

目前,我已將開發(fā)的demo上傳到了我的github,感興趣的童鞋,可自行下載,目前能直接下下來在本地環(huán)境運(yùn)行,可根據(jù)自己的實(shí)際需求針對性參考使用。

首先,在解決這類需求時(shí),第一個(gè)先要解決的地方是,Springboot啟動后如何實(shí)現(xiàn)只執(zhí)行一次建表方法。

這里需要用到一個(gè)CommandLineRunner接口,這是Springboot自帶的,實(shí)現(xiàn)該接口的類,其重寫的run方法,會在Springboot啟動完成后自動執(zhí)行,該接口源碼如下:

@FunctionalInterface
public interface CommandLineRunner {

 /**
 *用于運(yùn)行bean的回調(diào)
 */
 void run(String... args) throws Exception;

}

擴(kuò)展一下,在Springboot中,可以定義多個(gè)實(shí)現(xiàn)CommandLineRunner接口類,并且可以對這些實(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í)行");
 }
}

控制臺打印的信息如下:

第一個(gè)Command執(zhí)行
第二個(gè)Command執(zhí)行

根據(jù)以上的驗(yàn)證,因此,我們可以通過實(shí)現(xiàn)CommandLineRunner的接口,重寫其run()的bean回調(diào)方法,用于在Springboot啟動后實(shí)現(xiàn)只執(zhí)行一次建表方法。實(shí)現(xiàn)項(xiàng)目啟動建表的功能,可能還需實(shí)現(xiàn)判斷是否已經(jīng)有相應(yīng)數(shù)據(jù)庫,若無,則應(yīng)先新建一個(gè)數(shù)據(jù)庫,同時(shí),得考慮還沒有對應(yīng)數(shù)據(jù)庫的情況,因此,我們通過jdbc第一次連接MySQL時(shí),應(yīng)連接一個(gè)原有自帶存在的庫。每個(gè)MySql安裝成功后,都會有一個(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對象,該對象是jdbc中可用于執(zhí)行靜態(tài) SQL 語句并返回它所生成結(jié)果的對象,這里可以使用它來執(zhí)行查找?guī)炫c創(chuàng)建庫的作用。

//創(chuàng)建Statement對象
 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自動創(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)后,就可以在該對象里使用已進(jìn)行連接操作的Connection對象了。

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è)方法里,會先做一個(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(";")會為true,
   即一直循環(huán)進(jìn)行拼接,當(dāng)遇到";"就跳出該if語句
   **/
   if ((!line.endsWith(";") || inOraclePlsqlBlock) && (!line.startsWith("/") || !inOraclePlsqlBlock)) {
   sqlStatement = this.addSqlStatementPiece(sqlStatement, line);
   } else {
   /**
   9.循環(huán)拼接中若遇到符號";",就意味著,已經(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對象
   */
   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ǔ)上,針對更為復(fù)雜的建表結(jié)構(gòu)sql語句進(jìn)行設(shè)計(jì),在項(xiàng)目啟動時(shí),自行執(zhí)行相應(yīng)的sql語句,來進(jìn)行建表。

該demo代碼已經(jīng)上傳git,可直接下載運(yùn)行:https://github.com/z924931408/Springboot-AutoCreateMySqlTable.git

到此這篇關(guān)于解決Springboot項(xiàng)目啟動后自動創(chuàng)建多表關(guān)聯(lián)的數(shù)據(jù)庫與表的方案的文章就介紹到這了,更多相關(guān)Springboot創(chuàng)建多表關(guān)聯(lián)的數(shù)據(jù)庫與表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • jvm添加自定義dns實(shí)現(xiàn)過程示例

    jvm添加自定義dns實(shí)現(xiàn)過程示例

    這篇文章主要為大家介紹了jvm添加自定義dns實(shí)現(xiàn)過程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • 一篇文章帶你了解Java容器,面板及四大布局管理器應(yīng)用

    一篇文章帶你了解Java容器,面板及四大布局管理器應(yīng)用

    這篇文章主要介紹了JAVA布局管理器與面板組合代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-08-08
  • Java編寫時(shí)間工具類ZTDateTimeUtil的示例代碼

    Java編寫時(shí)間工具類ZTDateTimeUtil的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用Java編寫時(shí)間工具類ZTDateTimeUtil,文中的示例代碼講解詳細(xì),有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-11-11
  • Java中Map和Set練習(xí)項(xiàng)目實(shí)例代碼

    Java中Map和Set練習(xí)項(xiàng)目實(shí)例代碼

    這篇文章主要給大家介紹了關(guān)于Java中Map和Set練習(xí)項(xiàng)目的相關(guān)資料,首先介紹了如何使用map來統(tǒng)計(jì)字符串?dāng)?shù)組中每個(gè)字符串的出現(xiàn)次數(shù),然后討論了如何使用set來找出只出現(xiàn)一次的數(shù)字,最后提出了一個(gè)解決壞鍵盤打字問題的思路,需要的朋友可以參考下
    2024-11-11
  • springboot集成swagger、knife4j及常用注解的使用

    springboot集成swagger、knife4j及常用注解的使用

    這篇文章主要介紹了springboot集成swagger、knife4j及常用注解的使用,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • Spring boot項(xiàng)目集成Camel FTP的方法示例

    Spring boot項(xiàng)目集成Camel FTP的方法示例

    這篇文章主要介紹了Spring boot項(xiàng)目集成Camel FTP的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-10-10
  • java多線程通過CompletableFuture組裝異步計(jì)算單元

    java多線程通過CompletableFuture組裝異步計(jì)算單元

    這篇文章主要為大家介紹了java多線程通過CompletableFuture組裝異步計(jì)算單元,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • 簡單講解Java的Future編程模式

    簡單講解Java的Future編程模式

    這篇文章主要介紹了Java的Future編程模式,包括對異步和并發(fā)的一些設(shè)計(jì)思維,需要的朋友可以參考下
    2015-11-11
  • 修改IDEA代碼左側(cè)折疊線顏色的操作

    修改IDEA代碼左側(cè)折疊線顏色的操作

    這篇文章主要介紹了修改IDEA代碼左側(cè)折疊線顏色的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • Mybatis plus通用字段自動填充的示例

    Mybatis plus通用字段自動填充的示例

    這篇文章主要介紹了Mybatis plus通用字段自動填充的示例,幫助大家更好的理解和使用Mybatis,感興趣的朋友可以了解下
    2021-01-01

最新評論

读书| 双江| 阆中市| 依兰县| 五台县| 潼关县| 开原市| 荆门市| 平塘县| 专栏| 临湘市| 徐汇区| 镇平县| 尉犁县| 克东县| 衡东县| 全州县| 斗六市| 米林县| 武山县| 海丰县| 虞城县| 库伦旗| 沁水县| 永宁县| 开江县| 淄博市| 邢台市| 榕江县| 贵定县| 醴陵市| 银川市| 济南市| 当雄县| 石棉县| 本溪市| 防城港市| 五河县| 仁怀市| 呼伦贝尔市| 临漳县|