Activiti工作流學習筆記(三)——自動生成28張資料庫表的底層原理分析
原創/朱季謙
我接觸工作流引擎Activiti已有兩年之久,但一直都只限於熟悉其各類API的使用,對底層的實現,則存在較大的盲區。
Activiti這個開源框架在設計上,其實存在不少值得學習和思考的地方,例如,框架用到以命令模式、責任鏈模式、模板模式等優秀的設計模式來進行框架的設計。
故而,是值得好好研究下Activiti這個框架的底層實現。
我在工作當中現階段用的比較多是Activiti6.0版本,本文就以這個版本來展開分析。
在使用Activiti工作流引擎過程中,讓我比較好奇的一個地方,是框架自帶一套資料庫表結構,在首次啟動時,若設計了相應的建表策略時,將會自動生成28張表,而這些表都是以ACT_開頭。
那麼問題來了,您是否與我一樣,曾好奇過這些表都是怎麼自動生成的呢?
下面,就開始一點點深入研究——
在工作流Springboot+Activiti6.0整合框架,網上最常見的引擎啟動配置教程一般長這樣:
1 @Configuration
2 public class SpringBootActivitiConfig {
3 @Bean
4 public ProcessEngine processEngine(){
5 ProcessEngineConfiguration pro=ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
6 pro.setJdbcDriver("com.mysql.jdbc.Driver");
7 pro.setJdbcUrl("xxxx");
8 pro.setJdbcUsername("xxxx");
9 pro.setJdbcPassword("xxx");
10 //避免釋出的圖片和xml中文出現亂碼
11 pro.setActivityFontName("宋體");
12 pro.setLabelFontName("宋體");
13 pro.setAnnotationFontName("宋體");
14 //資料庫更更新策略
15 pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
16 return pro.buildProcessEngine();
17 }
18
19 @Bean
20 public RepositoryService repositoryService(){
21 return processEngine().getRepositoryService();
22 }
23
24 @Bean
25 public RuntimeService runtimeService(){
26 return processEngine().getRuntimeService();
27 }
28
29 @Bean
30 public TaskService taskService(){
31 return processEngine().getTaskService();
32 }
33 ......
34
35 }
其中,方法pro.setDatabaseSchemaUpdate()可對工作流引擎自帶的28張表進行不同策略的更新。
Activiti6.0版本總共有四種資料庫表更新策略。
檢視這三種策略的靜態常量標識,分別如下:
1 public abstract class ProcessEngineConfiguration {
2 public static final String DB_SCHEMA_UPDATE_FALSE = "false";
3 public static final String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop";
4 public static final String DB_SCHEMA_UPDATE_TRUE = "true";
5 ......
6 }
7
8 public abstract class ProcessEngineConfigurationImpl extends ProcessEngineConfiguration {
9 public static final String DB_SCHEMA_UPDATE_DROP_CREATE = "drop-create";
10 ......
11 }
-
flase:預設值,引擎啟動時,自動檢查資料庫裡是否已有表,或者表版本是否匹配,如果無表或者表版本不對,則丟擲異常。(常用在生產環境);
-
true:若表不存在,自動更新;若存在,而表有改動,則自動更新表,若表存在以及表無更新,則該策略不會做任何操作。(一般用在開發環境);
-
create_drop:啟動時自動建表,關閉時就刪除表,有一種臨時表的感覺。(需手動關閉,才會起作用);
-
drop-create:啟動時刪除舊錶,再重新建表。(無需手動關閉就能起作用);
整個啟動更新資料庫的過程都是圍繞這四種策略,接下來就以這四種策略為主題,擼一下自動更新據庫表的底層原理,這一步驟是在引擎啟動時所執行的buildProcessEngine()方法裡實現。
從該buildProcessEngine方法名上便可以看出,這是一個初始化工作流引擎框架的方法。
從這裡開始,一步一步debug去分析原始碼實現。
一.初始化工作流的buildProcessEngine()方法——
processEngineConfiguration.buildProcessEngine()是一個抽象方法,主要功能是初始化引擎,獲取到工作流的核心API介面:ProcessEngine。通過該API,可獲取到引擎所有的service服務。
進入processEngine介面,可以看到,其涵蓋了Activiti的所有服務介面:
1 public interface ProcessEngine {
2
3 public static String VERSION = "6.0.0.4";
4
5 String getName();
6
7 void close();
8 //流程執行服務類,用於獲取流程執行相關資訊
9 RepositoryService getRepositoryService();
10 //流程執行服務類,用於獲取流程執行相關資訊
11 RuntimeService getRuntimeService();
12 //內建表單,用於工作流自帶內建表單的設定
13 FormService getFormService();
14 //任務服務類,使用者獲取任務資訊
15 TaskService getTaskService();
16 //獲取正在執行或已經完成的流程例項歷史資訊
17 HistoryService getHistoryService();
18 //建立、更新、刪除、查詢群組和使用者
19 IdentityService getIdentityService();
20 //流程引擎的管理與維護
21 ManagementService getManagementService();
22 //提供對流程定義和部署儲存庫的訪問的服務。
23 DynamicBpmnService getDynamicBpmnService();
24 //獲取配置類
25 ProcessEngineConfiguration getProcessEngineConfiguration();
26 //提供對內建表單儲存庫的訪問的服務。
27 FormRepositoryService getFormEngineRepositoryService();
28
29 org.activiti.form.api.FormService getFormEngineFormService();
30 }
buildProcessEngine()有三個子類方法的重寫,預設是用ProcessEngineConfigurationImpl類繼承重寫buildProcessEngine初始化方法,如下圖所示:
該buildProcessEngine重寫方法如下:
1 @Override
2 public ProcessEngine buildProcessEngine() {
3 //初始化的方法
4 init();
5 //建立ProcessEngine
6 ProcessEngineImpl processEngine = new ProcessEngineImpl(this);
7
8 // Activiti 5引擎的觸發裝置
9 if (isActiviti5CompatibilityEnabled && activiti5CompatibilityHandler != null) {
10 Context.setProcessEngineConfiguration(processEngine.getProcessEngineConfiguration());
11 activiti5CompatibilityHandler.getRawProcessEngine();
12 }
13
14 postProcessEngineInitialisation();
15
16 return processEngine;
17 }
init()方法裡面包含各類需要初始化的方法,涉及到很多東西,這裡先暫不一一展開分析,主要先分析與資料庫連線初始化相關的邏輯。Activiti6.0底層是通過mybatis來操作資料庫的,下面主要涉及到mybatis的連線池與SqlSessionFactory 的建立。
1.initDataSource():實現動態配置資料庫DataSource源
1 protected boolean usingRelationalDatabase = true;
2 if (usingRelationalDatabase) {
3 initDataSource();
4 }
該資料庫連線模式初始化的意義如何理解,這就需要回到最初引擎配置分析,其中裡面有這樣一部分程式碼:
1 pro.setJdbcDriver("com.mysql.jdbc.Driver");
2 pro.setJdbcUrl("xxxx");
3 pro.setJdbcUsername("xxxx");
4 pro.setJdbcPassword("xxx");
這部分設定的東西,都是資料庫相關的引數,它將傳到initDataSource方法裡,通過mybatis預設的連線池PooledDataSource進行設定,可以說,這個方法主要是用來建立mybatis連線資料庫的連線池,從而生成資料來源連線。
1 public void initDataSource() {
2 //判斷資料來源dataSource是否存在
3 if (dataSource == null) {
4 /
5 //判斷是否使用JNDI方式連線資料來源
6 if (dataSourceJndiName != null) {
7 try {
8 dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName);
9 } catch (Exception e) {
10 ......
11 }
12 //使用非JNDI方式且資料庫地址不為空,走下面的設定
13 } else if (jdbcUrl != null) {
14 //jdbc驅動為空或者jdbc連線賬戶為空
15 if ((jdbcDriver == null) || (jdbcUsername == null)) {
16 ......
17 }
18
19 //建立mybatis預設連線池PooledDataSource物件,這裡傳進來的,就是上面pro.setJdbcDriver("com.mysql.jdbc.Driver")配置的引數,
20 //debug到這裡,就可以清晰明白,配置類裡設定的JdbcDriver、JdbcUrl、JdbcUsername、JdbcPassword等,就是為了用來建立連線池需要用到的;
21 PooledDataSource pooledDataSource = new PooledDataSource(ReflectUtil.getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);
22
23 if (jdbcMaxActiveConnections > 0) {
24 //設定最大活躍連線數
25 pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections);
26 }
27 if (jdbcMaxIdleConnections > 0) {
28 // 設定最大空閒連線數
29 pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections);
30 }
31 if (jdbcMaxCheckoutTime > 0) {
32 // 最大checkout 時長
33 pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime);
34 }
35 if (jdbcMaxWaitTime > 0) {
36 // 在無法獲取連線時,等待的時間
37 pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime);
38 }
39 if (jdbcPingEnabled == true) {
40 //是否允許傳送測試SQL語句
41 pooledDataSource.setPoolPingEnabled(true);
42
43 ......
44
45 dataSource = pooledDataSource;
46 }
47
48 ......
49 }
50 //設定資料庫型別
51 if (databaseType == null) {
52 initDatabaseType();
53 }
54 }
initDatabaseType()作用是設定工作流引擎的資料庫型別。在工作流引擎裡,自帶的28張表,其實有區分不同的資料庫,而不同資料庫其建表語句存在一定差異。
進入到 initDatabaseType()方法看看其是如何設定資料庫型別的——
1 public void initDatabaseType() {
2 Connection connection = null;
3 try {
4 connection = this.dataSource.getConnection();
5 DatabaseMetaData databaseMetaData = connection.getMetaData();
6 String databaseProductName = databaseMetaData.getDatabaseProductName();
7 this.databaseType = databaseTypeMappings.getProperty(databaseProductName);
8 ......
9 } catch (SQLException var12) {
10 ......
11 } finally {
12 ......
13 }
14 }
進入到databaseMetaData.getDatabaseProductName()方法裡,可以看到這是一個介面定義的方法:
String getDatabaseProductName() throws SQLException;
這個方法在java.sql包中的DatabaseMetData接口裡被定義,其作用是搜尋並獲取資料庫的名稱。這裡配置使用的是mysql驅動,那麼就會被mysql驅動中的jdbc中的DatabaseMetaData實現,如下程式碼所示:
1 package com.mysql.cj.jdbc;
2
3 public class DatabaseMetaData implements java.sql.DatabaseMetaData
在該實現類裡,其重寫的方法中,將會返回mysql驅動對應的型別字串:
1 @Override
2 public String getDatabaseProductName() throws SQLException {
3 return "MySQL";
4 }
故而,就會返回“MySql”字串,並賦值給字串變數databaseProductName,再將databaseProductName當做引數傳給
databaseTypeMappings.getProperty(databaseProductName),最終會得到一個 this.databaseType =“MySQL”,也就是意味著,設定了資料庫型別databaseType的值為mysql。注意,這一步很重要,因為將在後面生成表過程中,會判斷該databaseType的值究竟是代表什麼資料庫型別。
1 String databaseProductName = databaseMetaData.getDatabaseProductName();
2 this.databaseType = databaseTypeMappings.getProperty(databaseProductName);
-
該方法對SqlSessionFactory進行 初始化建立:SqlSessionFactory是mybatis的核心類,簡單的講,建立這個類,接下來就可以進行增刪改查與事務操作了。
1 protected boolean usingRelationalDatabase = true;
2 if (usingRelationalDatabase) {
3 initSqlSessionFactory();
4 }
init()主要都是初始化引擎環境的相關操作,裡面涉及到很多東西,但在本篇文中主要了解到這裡面會建立執行緒池以及mybatis相關的初始建立即可。
二、開始進行processEngine 的建立
ProcessEngineImpl processEngine = new ProcessEngineImpl(this);
這部分程式碼,就是建立Activiti的各服務類了:
1 public ProcessEngineImpl(ProcessEngineConfigurationImpl processEngineConfiguration) {
2 this.processEngineConfiguration = processEngineConfiguration;
3 this.name = processEngineConfiguration.getProcessEngineName();
4 this.repositoryService = processEngineConfiguration.getRepositoryService();
5 this.runtimeService = processEngineConfiguration.getRuntimeService();
6 this.historicDataService = processEngineConfiguration.getHistoryService();
7 this.identityService = processEngineConfiguration.getIdentityService();
8 this.taskService = processEngineConfiguration.getTaskService();
9 this.formService = processEngineConfiguration.getFormService();
10 this.managementService = processEngineConfiguration.getManagementService();
11 this.dynamicBpmnService = processEngineConfiguration.getDynamicBpmnService();
12 this.asyncExecutor = processEngineConfiguration.getAsyncExecutor();
13 this.commandExecutor = processEngineConfiguration.getCommandExecutor();
14 this.sessionFactories = processEngineConfiguration.getSessionFactories();
15 this.transactionContextFactory = processEngineConfiguration.getTransactionContextFactory();
16 this.formEngineRepositoryService = processEngineConfiguration.getFormEngineRepositoryService();
17 this.formEngineFormService = processEngineConfiguration.getFormEngineFormService();
18
19 if (processEngineConfiguration.isUsingRelationalDatabase() && processEngineConfiguration.getDatabaseSchemaUpdate() != null) {
20 commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());
21 }
22 ......
23 }
注意,這裡面有一段程式碼,整個引擎更新資料庫的相應策略是具體實現,就在這裡面:
1 if (processEngineConfiguration.isUsingRelationalDatabase() && processEngineConfiguration.getDatabaseSchemaUpdate() != null) {
2 commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());
3 }
-
processEngineConfiguration.isUsingRelationalDatabase()預設是true,即代表需要對資料庫模式做設定,例如前面初始化的dataSource資料來源,建立SqlSessionFactory等,這些都算是對資料庫模式進行設定;若為false,則不會進行模式設定與驗證,需要額外手動操作,這就意味著,引擎不能驗證模式是否正確。
-
processEngineConfiguration.getDatabaseSchemaUpdate()是使用者對資料庫更新策略的設定,如,前面配置類裡設定了pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE),若設定四種模式當中的任何一種,就意味著,需要對引擎的資料庫進行相應策略操作。
綜上,if()判斷為true,就意味著,將執行括號裡的程式碼,這塊功能就是根據策略去對資料庫進行相應的增刪改查操作。
commandExecutor.execute()是一個典型的命令模式,先暫時不深入分析,直接點開new SchemaOperationsProcessEngineBuild()方法。
1 public final class SchemaOperationsProcessEngineBuild implements Command<Object> {
2
3 public Object execute(CommandContext commandContext) {
4 DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
5 if (dbSqlSession != null) {
6 dbSqlSession.performSchemaOperationsProcessEngineBuild();
7 }
8 return null;
9 }
10 }
進入到dbSqlSession.performSchemaOperationsProcessEngineBuild()方法中,接下來,將會看到,在這個方法當中,將根據不同的if判斷,執行不同的方法——而這裡的不同判斷,正是基於四種資料庫更新策略來展開的,換句話說,這個performSchemaOperationsProcessEngineBuild方法,才是真正去判斷不同策略,從而根據不同策略來對資料庫進行對應操作:
1 public void performSchemaOperationsProcessEngineBuild() {
2 String databaseSchemaUpdate = Context.getProcessEngineConfiguration().getDatabaseSchemaUpdate();
3 log.debug("Executing performSchemaOperationsProcessEngineBuild with setting " + databaseSchemaUpdate);
4 //drop-create模式
5 if ("drop-create".equals(databaseSchemaUpdate)) {
6 try {
7 this.dbSchemaDrop();
8 } catch (RuntimeException var3) {
9 }
10 }
11
12 if (!"create-drop".equals(databaseSchemaUpdate) && !"drop-create".equals(databaseSchemaUpdate) && !"create".equals(databaseSchemaUpdate)) {
13 //false模式
14 if ("false".equals(databaseSchemaUpdate)) {
15 this.dbSchemaCheckVersion();
16 } else if ("true".equals(databaseSchemaUpdate)) {
17 //true模式
18 this.dbSchemaUpdate();
19 }
20 } else {
21 //create_drop模式
22 this.dbSchemaCreate();
23 }
24
25 }
這裡主要以true模式來講解,其他基本都類似的實現。
1 public String dbSchemaUpdate() {
2
3 String feedback = null;
4 //判斷是否需要更新,預設是false
5 boolean isUpgradeNeeded = false;
6 int matchingVersionIndex = -1;
7 //判斷是否需要更新或者建立引擎核心engine表,若isEngineTablePresent()為true,表示需要更新,若為false,則需要新建立
8 if (isEngineTablePresent()) {
9 ......
10 } else {
11 //建立表方法,稍後會詳細分析
12 dbSchemaCreateEngine();
13 }
14
15 //判斷是否需要建立或更新歷史相關表
16 if (this.isHistoryTablePresent()) {
17 if (isUpgradeNeeded) {
18 this.dbSchemaUpgrade("history", matchingVersionIndex);
19 }
20 } else if (this.dbSqlSessionFactory.isDbHistoryUsed()) {
21 this.dbSchemaCreateHistory();
22 }
23 //判斷是否需要更新群組和使用者
24 if (this.isIdentityTablePresent()) {
25 if (isUpgradeNeeded) {
26 this.dbSchemaUpgrade("identity", matchingVersionIndex);
27 }
28 } else if (this.dbSqlSessionFactory.isDbIdentityUsed()) {
29 this.dbSchemaCreateIdentity();
30 }
31 return feedback;
32 }
這裡以判斷是否需要建立engine表為例,分析下isEngineTablePresent()裡面是如何做判斷的。其他如歷史表、使用者表,其判斷是否需要建立的邏輯,是型別的。
點選isEngineTablePresent()進去——
1 public boolean isEngineTablePresent() {
2 return isTablePresent("ACT_RU_EXECUTION");
3 }
進入到isTablePresent("ACT_RU_EXECUTION")方法裡,其中有一句最主要的程式碼:
1 tables = databaseMetaData.getTables(catalog, schema, tableName, JDBC_METADATA_TABLE_TYPES);
2 return tables.next();
這兩行程式碼大概意思是,通過"ACT_RU_EXECUTION"表名去資料庫中查詢該ACT_RU_EXECUTION表是否存在,若不存在,返回false,說明還沒有建立;若存在,返回true。
返回到該方法上層,當isEngineTablePresent()返回值是false時,說明還沒有建立Activiti表,故而,將執行 dbSchemaCreateEngine()方法來建立28表張工作流表。
1 if (isEngineTablePresent()) {
2 ......
3 } else {
4 //建立表方法
5 dbSchemaCreateEngine();
6 }
7
進入到dbSchemaCreateEngine()方法——裡面呼叫了executeMandatorySchemaResource方法,傳入"create"與 "engine",代表著建立引擎表的意思。
1 protected void dbSchemaCreateEngine() {
2 this.executeMandatorySchemaResource("create", "engine");
3 }
繼續進入到executeMandatorySchemaResource裡面——
1 public void executeMandatorySchemaResource(String operation, String component) {
2 this.executeSchemaResource(operation, component, this.getResourceForDbOperation(operation, operation, component), false);
3 }
跳轉到這裡時,有一個地方需要注意一下,即呼叫的this.getResourceForDbOperation(operation, operation, component)方法,這方法的作用,是為了獲取sql檔案所存放的相對路徑,而這些sql,就是構建工作流28張表的資料庫sql。因此,我們先去executeSchemaResource()方法裡看下——
1 public String getResourceForDbOperation(String directory, String operation, String component) {
2 String databaseType = this.dbSqlSessionFactory.getDatabaseType();
3 return "org/activiti/db/" + directory + "/activiti." + databaseType + "." + operation + "." + component + ".sql";
4 }
這裡的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檔案,那麼讓我們回到原來程式碼執行的方法裡:
1 public void executeMandatorySchemaResource(String operation, String component) {
2 this.executeSchemaResource(operation, component, this.getResourceForDbOperation(operation, operation, component), false);
3 }
這裡通過this.getResourceForDbOperation(operation, operation, component), false)拿到 了mysql檔案路徑,接下來,將同其他幾個引數,一塊傳入到this.executeSchemaResource()方法裡,具體如下:
1 public void executeSchemaResource(String operation, String component, String resourceName, boolean isOptional) {
2 InputStream inputStream = null;
3 try {
4 //根據resourceName路徑字串,獲取到對應engine.sql檔案的輸入流inputStream,即讀取engine.sql檔案
5 inputStream = ReflectUtil.getResourceAsStream(resourceName);
6 if (inputStream == null) {
7 ......
8 } else {
9 //將得到的輸入流inputStream傳入該方法
10 this.executeSchemaResource(operation, component, resourceName, inputStream);
11 }
12 } finally {
13 ......
14 }
15 }
這一步主要通過輸入流InputStream讀取engine.sql檔案的位元組,然後再傳入到 this.executeSchemaResource(operation, component, resourceName, inputStream)方法當中,而這個方法,將是Activiti建表過程中的核心所在。
下面刪除多餘程式碼,只留核心程式碼來分析:
1 private void executeSchemaResource(String operation, String component, String resourceName, InputStream inputStream) {
2 //sql語句字串
3 String sqlStatement = null;
4
5 try {
6 //1、jdbc連線mysql資料庫
7 Connection connection = this.sqlSession.getConnection();
8 //2、分行讀取resourceName="org/activiti/db/create/activiti.mysql.create.engine.sql"目錄底下的檔案資料
9 byte[] bytes = IoUtil.readInputStream(inputStream, resourceName);
10 //3.將engine.sql檔案裡的資料分行轉換成字串,換行的地方,可以看到字串用轉義符“\n”來代替
11 String ddlStatements = new String(bytes);
12 try {
13
14 if (this.isMysql()) {
15 DatabaseMetaData databaseMetaData = connection.getMetaData();
16 int majorVersion = databaseMetaData.getDatabaseMajorVersion();
17 int minorVersion = databaseMetaData.getDatabaseMinorVersion();
18 if (majorVersion <= 5 && minorVersion < 6) {
19 //若資料庫型別是在mysql 5.6版本以下,需要做一些替換,因為低於5.6版本的MySQL是不支援變體時間戳或毫秒級的日期,故而需要在這裡對sql語句的字串做替換。(注意,這裡的majorVersion代表主版本,minorVersion代表主版本下的小版本)
20 ddlStatements = this.updateDdlForMySqlVersionLowerThan56(ddlStatements);
21 }
22 }
23 } catch (Exception var26) {
24 ......
25 }
26 //4.以字元流形式讀取字串資料
27 BufferedReader reader = new BufferedReader(new StringReader(ddlStatements));
28 //5.根據字串中的轉義符“\n”分行讀取
29 String line = this.readNextTrimmedLine(reader);
30 //6.迴圈每一行
31 for(boolean inOraclePlsqlBlock = false; line != null; line = this.readNextTrimmedLine(reader)) {
32
33 if (line.startsWith("# ")) {
34 ......
35 }
36 //7.若下一行line還有資料,證明還沒有全部讀取,仍可執行讀取
37 else if (line.length() > 0) {
38 if (this.isOracle() && line.startsWith("begin")) {
39 .......
40
41 }
42 /**
43 8.在沒有拼接夠一個完整建表語句時,!line.endsWith(";")會為true,即一直迴圈進行拼接,當遇到";"就跳出該if語句
44 **/
45 else if ((!line.endsWith(";") || inOraclePlsqlBlock) && (!line.startsWith("/") || !inOraclePlsqlBlock)) {
46 sqlStatement = this.addSqlStatementPiece(sqlStatement, line);
47 } else {
48 /**
49 9.迴圈拼接中若遇到符號";",就意味著,已經拼接形成一個完整的sql建表語句,例如
50 create table ACT_GE_PROPERTY (
51 NAME_ varchar(64),
52 VALUE_ varchar(300),
53 REV_ integer,
54 primary key (NAME_)
55 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin
56 這樣,就可以先通過程式碼來將該建表語句執行到資料庫中,實現如下:
57 **/
58 if (inOraclePlsqlBlock) {
59 inOraclePlsqlBlock = false;
60 } else {
61
62 sqlStatement = this.addSqlStatementPiece(sqlStatement, line.substring(0, line.length() - 1));
63 }
64 //10.將建表語句字串包裝成Statement物件
65 Statement jdbcStatement = connection.createStatement();
66 try {
67 //11.最後,執行建表語句到資料庫中
68 jdbcStatement.execute(sqlStatement);
69 jdbcStatement.close();
70 } catch (Exception var27) {
71 ......
72 } finally {
73 //12.到這一步,意味著上一條sql建表語句已經執行結束,若沒有出現錯誤話,這時已經證明第一個資料庫表結構已經建立完成,可以開始拼接下一條建表語句,
74 sqlStatement = null;
75 }
76 }
77 }
78 }
79
80 ......
81 } catch (Exception var29) {
82 ......
83 }
84 }
根據debug過程截圖,可以更為直觀地看到,這裡獲取到的ddlStatements字串,涵蓋了sql檔案裡的所有sql語句,同時,每一個完整的creat建表語句,都是以";"結尾的:
每次執行到";"時,都會得到一個完整的create建表語句:
執行完一個建表語句,就會在資料庫裡同步生成一張資料庫表,如上圖執行的是ACT_GE_PROPERTY表,資料庫裡便生成了這張表:
在執行完之後,看idea控制檯列印資訊,可以看到,我的資料庫是5.7版本,引擎在啟動過程中分別執行了engine.sql、history.sql、identity.sql三個sql檔案來進行資料庫表結構的構建。
到這一步,引擎整個生成表的過程就結束了,以上主要是基於true策略模式,通過對engine.sql的執行,來說明工作流引擎生成表的底層邏輯,其餘模式基本都類似,這裡就不一一展開分析了。
最後,進入到資料庫,可以看到,已成功生成28張ACT開頭的工作流自帶表——
本文同步分享在 部落格“朱季謙”(CNBlog)。
如有侵權,請聯絡 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。
- 基於Gin Gorm框架搭建MVC模式的Go語言企業級後端系統
- 策略列舉:消除在專案裡大批量使用if-else的優雅姿勢
- 深入Spring Security魔幻山谷-獲取認證機制核心原理講解(新版)
- 實際使用Elasticdump工具對Elasticsearch叢集進行資料備份和資料還原
- Activiti工作流學習筆記(四)——工作流引擎中責任鏈模式的建立與應用原理
- 原創小說:孤島上住著一隻貓
- 策略列舉:消除在專案裡大批量使用if-else的正確姿勢
- visualvm工具遠端對linux伺服器上的JVM虛擬機器進行監控與調優
- Springboot2.x整合lettuce連線redis叢集報超時異常Command timed out after 6 second(s)
- Springboot專案啟動後自動建立多表關聯的資料庫與表的方案
- Activiti工作流學習筆記(三)——自動生成28張資料庫表的底層原理分析
- 前端筆記:React的form表單全部置空或者某個操作框置空的做法
- mybatis-plus的insert方法出現-id' doesn't have a default value問題