原創/朱季謙java
網上關於工做流引擎Activiti生成表的機制大多僅限於四種策略模式,但其底層是如何實現的,相關文章仍是比較少,所以,以爲擼一擼其生成表機制的底層原理。mysql
我接觸工做流引擎Activiti已有兩年之久,但一直都只限於熟悉其各種API的使用,對底層的實現,則存在較大的盲區。sql
Activiti這個開源框架在設計上,其實存在很多值得學習和思考的地方,例如,框架用到以命令模式、責任鏈模式、模板模式等優秀的設計模式來進行框架的設計。數據庫
故而,是值得好好研究下Activiti這個框架的底層實現。設計模式
我在工做當中現階段用的比較可能是Activiti6.0版本,本文就以這個版原本展開分析。api
在使用Activiti工做流引擎過程當中,讓我比較好奇的一個地方,是框架自帶一套數據庫表結構,在首次啓動時,若設計了相應的建表策略時,將會自動生成28張表,而這些表都是以ACT_開頭。session
那麼問題來了,您是否與我同樣,曾好奇過這些表都是怎麼自動生成的呢?mybatis
下面,就開始一點點深刻研究——oracle
在工做流Springboot+Activiti6.0集成框架,網上最多見的引擎啓動配置教程通常長這樣:app
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相關的初始建立便可。
2、開始進行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文件,其中,有db二、h二、hsql、mssql、mysql、mysql5五、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 //一、jdbc鏈接mysql數據庫 7 Connection connection = this.sqlSession.getConnection(); 8 //二、分行讀取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開頭的工做流自帶表——
一、jdbc鏈接mysql數據庫