Activiti工作流學習筆記之自動生成28張數據庫表的底層原理解析

網上關於工作流引擎Activiti生成表的機制大多僅限於四種策略模式,但其底層是如何實現的,相關文章還是比較少,因此,覺得擼一擼其生成表機制的底層原理。

我接觸工作流引擎Activiti已有兩年之久,但一直都隻限於熟悉其各類API的使用,對底層的實現,則存在較大的盲區。

Activiti這個開源框架在設計上,其實存在不少值得學習和思考的地方,例如,框架用到以命令模式、責任鏈模式、模板模式等優秀的設計模式來進行框架的設計。

故而,是值得好好研究下Activiti這個框架的底層實現。

我在工作當中現階段用的比較多是Activiti6.0版本,本文就以這個版本來展開分析。

在使用Activiti工作流引擎過程中,讓我比較好奇的一個地方,是框架自帶一套數據庫表結構,在首次啟動時,若設計瞭相應的建表策略時,將會自動生成28張表,而這些表都是以ACT_開頭。

那麼問題來瞭,您是否與我一樣,曾好奇過這些表都是怎麼自動生成的呢?

下面,就開始一點點深入研究——

在工作流Springboot+Activiti6.0集成框架,網上最常見的引擎啟動配置教程一般長這樣:

@Configuration
public class SpringBootActivitiConfig {
@Bean
public ProcessEngine processEngine(){
   ProcessEngineConfiguration pro=ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
   pro.setJdbcDriver("com.mysql.jdbc.Driver");
   pro.setJdbcUrl("xxxx");
   pro.setJdbcUsername("xxxx");
   pro.setJdbcPassword("xxx");
   //避免發佈的圖片和xml中文出現亂碼
   pro.setActivityFontName("宋體");
   pro.setLabelFontName("宋體");
   pro.setAnnotationFontName("宋體");
   //數據庫更更新策略
   pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
   return pro.buildProcessEngine();
}

   @Bean
   public RepositoryService repositoryService(){
     return processEngine().getRepositoryService();
   }

   @Bean
   public RuntimeService runtimeService(){
     return processEngine().getRuntimeService();
   }

   @Bean
   public TaskService taskService(){
     return processEngine().getTaskService();
   }
   ......

}

其中,方法pro.setDatabaseSchemaUpdate()可對工作流引擎自帶的28張表進行不同策略的更新。

Activiti6.0版本總共有四種數據庫表更新策略。

查看這三種策略的靜態常量標識,分別如下:

public abstract class ProcessEngineConfiguration {
   public static final String DB_SCHEMA_UPDATE_FALSE = "false";
   public static final String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop";
   public static final String DB_SCHEMA_UPDATE_TRUE = "true";
   ......
}

public abstract class ProcessEngineConfigurationImpl extends ProcessEngineConfiguration {
   public static final String DB_SCHEMA_UPDATE_DROP_CREATE = "drop-create";
   ......
}
  • flase:默認值,引擎啟動時,自動檢查數據庫裡是否已有表,或者表版本是否匹配,如果無表或者表版本不對,則拋出異常。(常用在生產環境);
  • true:若表不存在,自動更新;若存在,而表有改動,則自動更新表,若表存在以及表無更新,則該策略不會做任何操作。(一般用在開發環境);
  • create_drop:啟動時自動建表,關閉時就刪除表,有一種臨時表的感覺。(需手動關閉,才會起作用);
  • drop-create:啟動時刪除舊表,再重新建表。(無需手動關閉就能起作用);

整個啟動更新數據庫的過程都是圍繞這四種策略,接下來就以這四種策略為主題,擼一下自動更新據庫表的底層原理,這一步驟是在引擎啟動時所執行的buildProcessEngine()方法裡實現。

從該buildProcessEngine方法名上便可以看出,這是一個初始化工作流引擎框架的方法。

從這裡開始,一步一步debug去分析源碼實現。

一.初始化工作流的buildProcessEngine()方法——

processEngineConfiguration.buildProcessEngine()是一個抽象方法,主要功能是初始化引擎,獲取到工作流的核心API接口:ProcessEngine。通過該API,可獲取到引擎所有的service服務。

進入processEngine接口,可以看到,其涵蓋瞭Activiti的所有服務接口:

public interface ProcessEngine {

  public static String VERSION = "6.0.0.4";

  String getName();

 void close();
  //流程運行服務類,用於獲取流程執行相關信息
  RepositoryService getRepositoryService();
  //流程運行服務類,用於獲取流程執行相關信息
  RuntimeService getRuntimeService();
  //內置表單,用於工作流自帶內置表單的設置
  FormService getFormService();
  //任務服務類,用戶獲取任務信息
  TaskService getTaskService();
  //獲取正在運行或已經完成的流程實例歷史信息
  HistoryService getHistoryService();
  //創建、更新、刪除、查詢群組和用戶
  IdentityService getIdentityService();
  //流程引擎的管理與維護
  ManagementService getManagementService();
  //提供對流程定義和部署存儲庫的訪問的服務。
  DynamicBpmnService getDynamicBpmnService();
  //獲取配置類
  ProcessEngineConfiguration getProcessEngineConfiguration();
  //提供對內置表單存儲庫的訪問的服務。
  FormRepositoryService getFormEngineRepositoryService();

  org.activiti.form.api.FormService getFormEngineFormService();
}

buildProcessEngine()有三個子類方法的重寫,默認是用ProcessEngineConfigurationImpl類繼承重寫buildProcessEngine初始化方法,如下圖所示:

該buildProcessEngine重寫方法如下:

@Override
public ProcessEngine buildProcessEngine() {
  //初始化的方法
  init();
  //創建ProcessEngine
  ProcessEngineImpl processEngine = new ProcessEngineImpl(this);

 // Activiti 5引擎的觸發裝置
  if (isActiviti5CompatibilityEnabled && activiti5CompatibilityHandler != null) {
   Context.setProcessEngineConfiguration(processEngine.getProcessEngineConfiguration());
   activiti5CompatibilityHandler.getRawProcessEngine();
  }

 postProcessEngineInitialisation();

 return processEngine;
}

init()方法裡面包含各類需要初始化的方法,涉及到很多東西,這裡先暫不一一展開分析,主要先分析與數據庫連接初始化相關的邏輯。Activiti6.0底層是通過mybatis來操作數據庫的,下面主要涉及到mybatis的連接池與SqlSessionFactory 的創建。

1.initDataSource():實現動態配置數據庫DataSource源

protected boolean usingRelationalDatabase = true;
if (usingRelationalDatabase) {
  initDataSource();
}

該數據庫連接模式初始化的意義如何理解,這就需要回到最初引擎配置分析,其中裡面有這樣一部分代碼:

pro.setJdbcDriver("com.mysql.jdbc.Driver");
  pro.setJdbcUrl("xxxx");
 pro.setJdbcUsername("xxxx");
  pro.setJdbcPassword("xxx");

這部分設置的東西,都是數據庫相關的參數,它將傳到initDataSource方法裡,通過mybatis默認的連接池PooledDataSource進行設置,可以說,這個方法主要是用來創建mybatis連接數據庫的連接池,從而生成數據源連接。

public void initDataSource() {
  //判斷數據源dataSource是否存在
  if (dataSource == null) {
    /
    //判斷是否使用JNDI方式連接數據源
   if (dataSourceJndiName != null) {
    try {
     dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName);
    } catch (Exception e) {
     ......
    }
   //使用非JNDI方式且數據庫地址不為空,走下面的設置
   } else if (jdbcUrl != null) {
     //jdbc驅動為空或者jdbc連接賬戶為空
    if ((jdbcDriver == null) || (jdbcUsername == null)) {
    ......
    }

    //創建mybatis默認連接池PooledDataSource對象,這裡傳進來的,就是上面pro.setJdbcDriver("com.mysql.jdbc.Driver")配置的參數,
    //debug到這裡,就可以清晰明白,配置類裡設置的JdbcDriver、JdbcUrl、JdbcUsername、JdbcPassword等,就是為瞭用來創建連接池需要用到的;
    PooledDataSource pooledDataSource = new PooledDataSource(ReflectUtil.getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);

    if (jdbcMaxActiveConnections > 0) {
     //設置最大活躍連接數
     pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections);
    }
    if (jdbcMaxIdleConnections > 0) {
     // 設置最大空閑連接數
     pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections);
    }
    if (jdbcMaxCheckoutTime > 0) {
     // 最大checkout 時長
     pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime);
    }
    if (jdbcMaxWaitTime > 0) {
     // 在無法獲取連接時,等待的時間
     pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime);
    }
    if (jdbcPingEnabled == true) {
     //是否允許發送測試SQL語句
     pooledDataSource.setPoolPingEnabled(true);

    ......

    dataSource = pooledDataSource;
   }

    ......
  }
  //設置數據庫類型
  if (databaseType == null) {
   initDatabaseType();
  }
}

initDatabaseType()作用是設置工作流引擎的數據庫類型。在工作流引擎裡,自帶的28張表,其實有區分不同的數據庫,而不同數據庫其建表語句存在一定差異。

進入到 initDatabaseType()方法看看其是如何設置數據庫類型的——

public void initDatabaseType() {
   Connection connection = null;
   try {
     connection = this.dataSource.getConnection();
     DatabaseMetaData databaseMetaData = connection.getMetaData();
     String databaseProductName = databaseMetaData.getDatabaseProductName();
     this.databaseType = databaseTypeMappings.getProperty(databaseProductName);
     ......
   } catch (SQLException var12) {
   ......
   } finally {
   ......
   }
}

進入到databaseMetaData.getDatabaseProductName()方法裡,可以看到這是一個接口定義的方法:

String getDatabaseProductName() throws SQLException;

這個方法在java.sql包中的DatabaseMetData接口裡被定義,其作用是搜索並獲取數據庫的名稱。這裡配置使用的是mysql驅動,那麼就會被mysql驅動中的jdbc中的DatabaseMetaData實現,如下代碼所示:

package com.mysql.cj.jdbc;

public class DatabaseMetaData implements java.sql.DatabaseMetaData 

在該實現類裡,其重寫的方法中,將會返回mysql驅動對應的類型字符串:

@Override
 public String getDatabaseProductName() throws SQLException {
   return "MySQL";
 }

故而,就會返回“MySql”字符串,並賦值給字符串變量databaseProductName,再將databaseProductName當做參數傳給

databaseTypeMappings.getProperty(databaseProductName),最終會得到一個 this.databaseType =“MySQL”,也就是意味著,設置瞭數據庫類型databaseType的值為mysql。註意,這一步很重要,因為將在後面生成表過程中,會判斷該databaseType的值究竟是代表什麼數據庫類型。

 String databaseProductName = databaseMetaData.getDatabaseProductName();
 this.databaseType = databaseTypeMappings.getProperty(databaseProductName);

該方法對SqlSessionFactory進行 初始化創建:SqlSessionFactory是mybatis的核心類,簡單的講,創建這個類,接下來就可以進行增刪改查與事務操作瞭。

 protected boolean usingRelationalDatabase = true;
 if (usingRelationalDatabase) {
 initSqlSessionFactory();
}

init()主要都是初始化引擎環境的相關操作,裡面涉及到很多東西,但在本篇文中主要瞭解到這裡面會創建線程池以及mybatis相關的初始創建即可。

二、開始進行processEngine 的創建

ProcessEngineImpl processEngine = new ProcessEngineImpl(this);

這部分代碼,就是創建Activiti的各服務類瞭:

public ProcessEngineImpl(ProcessEngineConfigurationImpl processEngineConfiguration) {
 this.processEngineConfiguration = processEngineConfiguration;
 this.name = processEngineConfiguration.getProcessEngineName();
 this.repositoryService = processEngineConfiguration.getRepositoryService();
 this.runtimeService = processEngineConfiguration.getRuntimeService();
 this.historicDataService = processEngineConfiguration.getHistoryService();
 this.identityService = processEngineConfiguration.getIdentityService();
 this.taskService = processEngineConfiguration.getTaskService();
 this.formService = processEngineConfiguration.getFormService();
 this.managementService = processEngineConfiguration.getManagementService();
 this.dynamicBpmnService = processEngineConfiguration.getDynamicBpmnService();
 this.asyncExecutor = processEngineConfiguration.getAsyncExecutor();
 this.commandExecutor = processEngineConfiguration.getCommandExecutor();
 this.sessionFactories = processEngineConfiguration.getSessionFactories();
 this.transactionContextFactory = processEngineConfiguration.getTransactionContextFactory();
 this.formEngineRepositoryService = processEngineConfiguration.getFormEngineRepositoryService();
 this.formEngineFormService = processEngineConfiguration.getFormEngineFormService();
​
 if (processEngineConfiguration.isUsingRelationalDatabase() && processEngineConfiguration.getDatabaseSchemaUpdate() != null) {
  commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());
 }
 ......
}

註意,這裡面有一段代碼,整個引擎更新數據庫的相應策略是具體實現,就在這裡面:

if (processEngineConfiguration.isUsingRelationalDatabase() && processEngineConfiguration.getDatabaseSchemaUpdate() != null) {
  commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());
 }
  • processEngineConfiguration.isUsingRelationalDatabase()默認是true,即代表需要對數據庫模式做設置,例如前面初始化的dataSource數據源,創建SqlSessionFactory等,這些都算是對數據庫模式進行設置;若為false,則不會進行模式設置與驗證,需要額外手動操作,這就意味著,引擎不能驗證模式是否正確。
  • processEngineConfiguration.getDatabaseSchemaUpdate()是用戶對數據庫更新策略的設置,如,前面配置類裡設置瞭pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE),若設置四種模式當中的任何一種,就意味著,需要對引擎的數據庫進行相應策略操作。

綜上,if()判斷為true,就意味著,將執行括號裡的代碼,這塊功能就是根據策略去對數據庫進行相應的增刪改查操作。

commandExecutor.execute()是一個典型的命令模式,先暫時不深入分析,直接點開new SchemaOperationsProcessEngineBuild()方法。

public final class SchemaOperationsProcessEngineBuild implements Command<Object> {
​
 public Object execute(CommandContext commandContext) {
  DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
  if (dbSqlSession != null) {
   dbSqlSession.performSchemaOperationsProcessEngineBuild();
  }
  return null;
 }
}

進入到dbSqlSession.performSchemaOperationsProcessEngineBuild()方法中,接下來,將會看到,在這個方法當中,將根據不同的if判斷,執行不同的方法——而這裡的不同判斷,正是基於四種數據庫更新策略來展開的,換句話說,這個performSchemaOperationsProcessEngineBuild方法,才是真正去判斷不同策略,從而根據不同策略來對數據庫進行對應操作:

public void performSchemaOperationsProcessEngineBuild() {
  String databaseSchemaUpdate = Context.getProcessEngineConfiguration().getDatabaseSchemaUpdate();
  log.debug("Executing performSchemaOperationsProcessEngineBuild with setting " + databaseSchemaUpdate);
   //drop-create模式
  if ("drop-create".equals(databaseSchemaUpdate)) {
    try {
      this.dbSchemaDrop();
    } catch (RuntimeException var3) {
    }
  }

  if (!"create-drop".equals(databaseSchemaUpdate) && !"drop-create".equals(databaseSchemaUpdate) && !"create".equals(databaseSchemaUpdate)) {
     //false模式
    if ("false".equals(databaseSchemaUpdate)) {
      this.dbSchemaCheckVersion();
    } else if ("true".equals(databaseSchemaUpdate)) {
       //true模式
      this.dbSchemaUpdate();
    }
  } else {
    //create_drop模式
    this.dbSchemaCreate();
  }
​
}

這裡主要以true模式來講解,其他基本都類似的實現。

public String dbSchemaUpdate() {
​
 String feedback = null;
 //判斷是否需要更新,默認是false
 boolean isUpgradeNeeded = false;
 int matchingVersionIndex = -1;
 //判斷是否需要更新或者創建引擎核心engine表,若isEngineTablePresent()為true,表示需要更新,若為false,則需要新創建
 if (isEngineTablePresent()) {
 ......
 } else {
   //創建表方法,稍後會詳細分析
  dbSchemaCreateEngine();
 }

 //判斷是否需要創建或更新歷史相關表
 if (this.isHistoryTablePresent()) {
   if (isUpgradeNeeded) {
      this.dbSchemaUpgrade("history", matchingVersionIndex);
    }
 } else if (this.dbSqlSessionFactory.isDbHistoryUsed()) {
    this.dbSchemaCreateHistory();
 }
 //判斷是否需要更新群組和用戶
 if (this.isIdentityTablePresent()) {
    if (isUpgradeNeeded) {
     this.dbSchemaUpgrade("identity", matchingVersionIndex);
    }
 } else if (this.dbSqlSessionFactory.isDbIdentityUsed()) {
     this.dbSchemaCreateIdentity();
    }
 return feedback;
}

這裡以判斷是否需要創建engine表為例,分析下isEngineTablePresent()裡面是如何做判斷的。其他如歷史表、用戶表,其判斷是否需要創建的邏輯,是類型的。

點擊isEngineTablePresent()進去——

public boolean isEngineTablePresent() {
  return isTablePresent("ACT_RU_EXECUTION");
}

進入到isTablePresent(”ACT_RU_EXECUTION”)方法裡,其中有一句最主要的代碼:

tables = databaseMetaData.getTables(catalog, schema, tableName, JDBC_METADATA_TABLE_TYPES);
return tables.next();

這兩行代碼大概意思是,通過”ACT_RU_EXECUTION”表名去數據庫中查詢該ACT_RU_EXECUTION表是否存在,若不存在,返回false,說明還沒有創建;若存在,返回true。

返回到該方法上層,當isEngineTablePresent()返回值是false時,說明還沒有創建Activiti表,故而,將執行 dbSchemaCreateEngine()方法來創建28表張工作流表。

if (isEngineTablePresent()) {
 ......
 } else {
   //創建表方法
  dbSchemaCreateEngine();
 }
​

進入到dbSchemaCreateEngine()方法——裡面調用瞭executeMandatorySchemaResource方法,傳入”create”與 “engine”,代表著創建引擎表的意思。

protected void dbSchemaCreateEngine() {
  this.executeMandatorySchemaResource("create", "engine");
}

繼續進入到executeMandatorySchemaResource裡面——

public void executeMandatorySchemaResource(String operation, String component) {
  this.executeSchemaResource(operation, component, this.getResourceForDbOperation(operation, operation, component), false);
}

跳轉到這裡時,有一個地方需要註意一下,即調用的this.getResourceForDbOperation(operation, operation, component)方法,這方法的作用,是為瞭獲取sql文件所存放的相對路徑,而這些sql,就是構建工作流28張表的數據庫sql。因此,我們先去executeSchemaResource()方法裡看下——

public String getResourceForDbOperation(String directory, String operation, String component) {
   String databaseType = this.dbSqlSessionFactory.getDatabaseType();
  return "org/activiti/db/" + directory + "/activiti." + databaseType + "." + operation + "." + component + ".sql";
}

這裡的directory即前邊傳進來的”create”,databaseType的值就是前面獲取到的“mysql”,而component則是”engine”,因此,這字符串拼接起來,就是:”org/activiti/db/create/activiti.mysql.create.engine.sql”。

根據這個路徑,我們去Activiti源碼裡查看,可以看到在org/activiti/db/路徑底下,總共有5個文件目錄。根據其名字,可以猜測出,create目錄下存放的,是生成表的sql語句;drop目錄下,存放的是刪除表是sql語句;mapping目錄下,是mybatis映射xml文件;properties是各類數據庫類型在分頁情況下的特殊處理;upgrade目錄下,則是更新數據庫表的sql語句。

展開其中的create目錄,可以進一步發現,裡面根據名字區分瞭不同數據庫類型對應的執行sql文件,其中,有db2、h2、hsql、mssql、mysql、mysql55、oracle、postgres這八種類型,反過來看,同時說明瞭Activiti工作流引擎支持使用這八種數據庫。通常使用比較多的是mysql。根據剛剛的路徑org/activiti/db/create/activiti.mysql.create.engine.sql,可以在下面截圖中,找到該對應路徑下的engine.sql文件——

點擊進去看,會發現,這不就是我們常見的mysql建表語句嗎!沒錯,工作流Activiti就是在源碼裡內置瞭一套sql文件,若要創建數據庫表,就直接去到對應數據庫文件目錄下,獲取到相應的建表文件,執行sql語句建表。這跟平常用sql語句構建表結構沒太大區別,區別隻在於執行過程的方式而已,但兩者結果都是一樣的。

到這裡,我們根據其拼接的sql存放路徑,找到瞭create表結構的sql文件,那麼讓我們回到原來代碼執行的方法裡:

public void executeMandatorySchemaResource(String operation, String component) {
   this.executeSchemaResource(operation, component, this.getResourceForDbOperation(operation, operation, component), false);
 }

這裡通過this.getResourceForDbOperation(operation, operation, component), false)拿到 瞭mysql文件路徑,接下來,將同其他幾個參數,一塊傳入到this.executeSchemaResource()方法裡,具體如下:

public void executeSchemaResource(String operation, String component, String resourceName, boolean isOptional) {
   InputStream inputStream = null;
   try {
     //根據resourceName路徑字符串,獲取到對應engine.sql文件的輸入流inputStream,即讀取engine.sql文件
     inputStream = ReflectUtil.getResourceAsStream(resourceName);
     if (inputStream == null) {
     ......
     } else {
       //將得到的輸入流inputStream傳入該方法
       this.executeSchemaResource(operation, component, resourceName, inputStream);
     }
   } finally {
    ......
   }
}

這一步主要通過輸入流InputStream讀取engine.sql文件的字節,然後再傳入到 this.executeSchemaResource(operation, component, resourceName, inputStream)方法當中,而這個方法,將是Activiti建表過程中的核心所在。

下面刪除多餘代碼,隻留核心代碼來分析:

private void executeSchemaResource(String operation, String component, String resourceName, InputStream inputStream) {
  //sql語句字符串
  String sqlStatement = null;

  try {
 //1、jdbc連接mysql數據庫
    Connection connection = this.sqlSession.getConnection();
 //2、分行讀取resourceName="org/activiti/db/create/activiti.mysql.create.engine.sql"目錄底下的文件數據
    byte[] bytes = IoUtil.readInputStream(inputStream, resourceName);
 //3.將engine.sql文件裡的數據分行轉換成字符串,換行的地方,可以看到字符串用轉義符“\n”來代替
    String ddlStatements = new String(bytes);
    try {

      if (this.isMysql()) {
        DatabaseMetaData databaseMetaData = connection.getMetaData();
        int majorVersion = databaseMetaData.getDatabaseMajorVersion();
        int minorVersion = databaseMetaData.getDatabaseMinorVersion();
        if (majorVersion <= 5 && minorVersion < 6) {
          //若數據庫類型是在mysql 5.6版本以下,需要做一些替換,因為低於5.6版本的MySQL是不支持變體時間戳或毫秒級的日期,故而需要在這裡對sql語句的字符串做替換。(註意,這裡的majorVersion代表主版本,minorVersion代表主版本下的小版本)
          ddlStatements = this.updateDdlForMySqlVersionLowerThan56(ddlStatements);
        }
      }
    } catch (Exception var26) {
      ......
    }
    //4.以字符流形式讀取字符串數據
    BufferedReader reader = new BufferedReader(new StringReader(ddlStatements));
    //5.根據字符串中的轉義符“\n”分行讀取
    String line = this.readNextTrimmedLine(reader);
    //6.循環每一行
    for(boolean inOraclePlsqlBlock = false; line != null; line = this.readNextTrimmedLine(reader)) {

      if (line.startsWith("# ")) {
      ......
      }
      //7.若下一行line還有數據,證明還沒有全部讀取,仍可執行讀取
    else if (line.length() > 0) {
          if (this.isOracle() && line.startsWith("begin")) {
           .......

          }
      /**
      8.在沒有拼接夠一個完整建表語句時,!line.endsWith(";")會為true,即一直循環進行拼接,當遇到";"就跳出該if語句
      **/
      else if ((!line.endsWith(";") || inOraclePlsqlBlock) && (!line.startsWith("/") || !inOraclePlsqlBlock)) {
            sqlStatement = this.addSqlStatementPiece(sqlStatement, line);
          } else {
        /**
       9.循環拼接中若遇到符號";",就意味著,已經拼接形成一個完整的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
      這樣,就可以先通過代碼來將該建表語句執行到數據庫中,實現如下:
        **/
            if (inOraclePlsqlBlock) {
              inOraclePlsqlBlock = false;
            } else {

              sqlStatement = this.addSqlStatementPiece(sqlStatement, line.substring(0, line.length() - 1));
            }
          //10.將建表語句字符串包裝成Statement對象
            Statement jdbcStatement = connection.createStatement();
            try {
          //11.最後,執行建表語句到數據庫中
              jdbcStatement.execute(sqlStatement);
              jdbcStatement.close();
            } catch (Exception var27) {
            ......
            } finally {
          //12.到這一步,意味著上一條sql建表語句已經執行結束,若沒有出現錯誤話,這時已經證明第一個數據庫表結構已經創建完成,可以開始拼接下一條建表語句,
              sqlStatement = null;
            }
          }
        }
      }
​
      ......
    } catch (Exception var29) {
      ......
    }
  }

以上步驟可以歸納下:

  1. jdbc連接mysql數據庫;
  2. 分行讀取resourceName=”org/activiti/db/create/activiti.mysql.create.engine.sql”目錄底下的sql文件數據;
  3. 將整個engine.sql文件數據分行轉換成字符串ddlStatements,有換行的地方,用轉義符“\n”來代替;
  4. 以BufferedReader字符流形式讀取字符串ddlStatements數據;
  5. 循環字符流裡的每一行,拼接成sqlStatement字符串,若讀取到該行結尾有“;”符號,意味著已經拼接成一個完整的create建表語句,這時,跳出該次拼接,直接包裝成成Statement對象;值得註意一點是,Statement 是 Java 執行數據庫操作的一個重要接口,用於在已經建立數據庫連接的基礎上,向數據庫發送要執行的SQL語句。Statement對象是用於執行不帶參數的簡單SQL語句,例如本次的create建表語句。
  6. 最後,執行jdbcStatement.execute(sqlStatement),將create建表語句執行進數據庫中;
  7. 生成對應的數據庫表;

根據debug過程截圖,可以更為直觀地看到,這裡獲取到的ddlStatements字符串,涵蓋瞭sql文件裡的所有sql語句,同時,每一個完整的creat建表語句,都是以”;”結尾的:

每次執行到”;”時,都會得到一個完整的create建表語句:

執行完一個建表語句,就會在數據庫裡同步生成一張數據庫表,如上圖執行的是ACT_GE_PROPERTY表,數據庫裡便生成瞭這張表:

在執行完之後,看idea控制臺打印信息,可以看到,我的數據庫是5.7版本,引擎在啟動過程中分別執行瞭engine.sql、history.sql、identity.sql三個sql文件來進行數據庫表結構的構建。

到這一步,引擎整個生成表的過程就結束瞭,以上主要是基於true策略模式,通過對engine.sql的執行,來說明工作流引擎生成表的底層邏輯,其餘模式基本都類似,這裡就不一一展開分析瞭。

最後,進入到數據庫,可以看到,已成功生成28張ACT開頭的工作流自帶表——

jdbc連接mysql數據庫

到此這篇關於Activiti工作流學習筆記之自動生成28張數據庫表的底層原理解析的文章就介紹到這瞭,更多相關Activiti工作流自動生成28張數據庫表內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: