原創(chuàng)/朱季謙網(wǎng)上關(guān)于工作流引擎Activiti生成表的機(jī)制大多僅限于四種策略模式,但其底層是如何實(shí)現(xiàn)的,相關(guān)文章還是比較少,因此,覺得擼一擼其生成表機(jī)制的底層原理。 我接觸工作流引擎Activiti已有兩年之久,但一直都只限于熟悉其各類API的使用,對(duì)底層的實(shí)現(xiàn),則存在較大的盲區(qū)。 Activiti這個(gè)開源框架在設(shè)計(jì)上,其實(shí)存在不少值得學(xué)習(xí)和思考的地方,例如,框架用到以命令模式、責(zé)任鏈模式、模板模式等優(yōu)秀的設(shè)計(jì)模式來進(jìn)行框架的設(shè)計(jì)。 故而,是值得好好研究下Activiti這個(gè)框架的底層實(shí)現(xiàn)。 我在工作當(dāng)中現(xiàn)階段用的比較多是Activiti6.0版本,本文就以這個(gè)版本來展開分析。 在使用Activiti工作流引擎過程中,讓我比較好奇的一個(gè)地方,是框架自帶一套數(shù)據(jù)庫表結(jié)構(gòu),在首次啟動(dòng)時(shí),若設(shè)計(jì)了相應(yīng)的建表策略時(shí),將會(huì)自動(dòng)生成28張表,而這些表都是以ACT_開頭。 那么問題來了,您是否與我一樣,曾好奇過這些表都是怎么自動(dòng)生成的呢? 下面,就開始一點(diǎn)點(diǎn)深入研究—— 在工作流Springboot+Activiti6.0集成框架,網(wǎng)上最常見的引擎啟動(dòng)配置教程一般長這樣: 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 //避免發(fā)布的圖片和xml中文出現(xiàn)亂碼11 pro.setActivityFontName("宋體");12 pro.setLabelFontName("宋體");13 pro.setAnnotationFontName("宋體");14 //數(shù)據(jù)庫更更新策略15 pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);16 return pro.buildProcessEngine();17 }18 19 @Bean20 public RepositoryService repositoryService(){21 return processEngine().getRepositoryService();22 }23 24 @Bean25 public RuntimeService runtimeService(){26 return processEngine().getRuntimeService();27 }28 29 @Bean30 public TaskService taskService(){31 return processEngine().getTaskService();32 }33 ......34 35 } 其中,方法pro.setDatabaseSchemaUpdate()可對(duì)工作流引擎自帶的28張表進(jìn)行不同策略的更新。 Activiti6.0版本總共有四種數(shù)據(jù)庫表更新策略。 查看這三種策略的靜態(tài)常量標(biāo)識(shí),分別如下: 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 }
整個(gè)啟動(dòng)更新數(shù)據(jù)庫的過程都是圍繞這四種策略,接下來就以這四種策略為主題,擼一下自動(dòng)更新?lián)毂淼牡讓釉?,這一步驟是在引擎啟動(dòng)時(shí)所執(zhí)行的buildProcessEngine()方法里實(shí)現(xiàn)。 從該buildProcessEngine方法名上便可以看出,這是一個(gè)初始化工作流引擎框架的方法。 從這里開始,一步一步debug去分析源碼實(shí)現(xiàn)。 一.初始化工作流的buildProcessEngine()方法—— processEngineConfiguration.buildProcessEngine()是一個(gè)抽象方法,主要功能是初始化引擎,獲取到工作流的核心API接口:ProcessEngine。通過該API,可獲取到引擎所有的service服務(wù)。 進(jìn)入processEngine接口,可以看到,其涵蓋了Activiti的所有服務(wù)接口: 1 public interface ProcessEngine { 2 3 public static String VERSION = "6.0.0.4"; 4 5 String getName(); 6 7 void close(); 8 //流程運(yùn)行服務(wù)類,用于獲取流程執(zhí)行相關(guān)信息 9 RepositoryService getRepositoryService();10 //流程運(yùn)行服務(wù)類,用于獲取流程執(zhí)行相關(guān)信息11 RuntimeService getRuntimeService();12 //內(nèi)置表單,用于工作流自帶內(nèi)置表單的設(shè)置13 FormService getFormService();14 //任務(wù)服務(wù)類,用戶獲取任務(wù)信息15 TaskService getTaskService();16 //獲取正在運(yùn)行或已經(jīng)完成的流程實(shí)例歷史信息17 HistoryService getHistoryService();18 //創(chuàng)建、更新、刪除、查詢?nèi)航M和用戶19 IdentityService getIdentityService();20 //流程引擎的管理與維護(hù)21 ManagementService getManagementService();22 //提供對(duì)流程定義和部署存儲(chǔ)庫的訪問的服務(wù)。23 DynamicBpmnService getDynamicBpmnService();24 //獲取配置類25 ProcessEngineConfiguration getProcessEngineConfiguration();26 //提供對(duì)內(nèi)置表單存儲(chǔ)庫的訪問的服務(wù)。27 FormRepositoryService getFormEngineRepositoryService();28 29 org.activiti.form.api.FormService getFormEngineFormService();30 } buildProcessEngine()有三個(gè)子類方法的重寫,默認(rèn)是用ProcessEngineConfigurationImpl類繼承重寫buildProcessEngine初始化方法,如下圖所示: 該buildProcessEngine重寫方法如下: 1 @Override 2 public ProcessEngine buildProcessEngine() { 3 //初始化的方法 4 init(); 5 //創(chuàng)建ProcessEngine 6 ProcessEngineImpl processEngine = new ProcessEngineImpl(this); 7 8 // Activiti 5引擎的觸發(fā)裝置 9 if (isActiviti5CompatibilityEnabled && activiti5CompatibilityHandler != null) {10 Context.setProcessEngineConfiguration(processEngine.getProcessEngineConfiguration());11 activiti5CompatibilityHandler.getRawProcessEngine();12 }13 14 postProcessEngineInitialisation();15 16 return processEngine;17 } init()方法里面包含各類需要初始化的方法,涉及到很多東西,這里先暫不一一展開分析,主要先分析與數(shù)據(jù)庫連接初始化相關(guān)的邏輯。Activiti6.0底層是通過mybatis來操作數(shù)據(jù)庫的,下面主要涉及到mybatis的連接池與SqlSessionFactory 的創(chuàng)建。 1.initDataSource():實(shí)現(xiàn)動(dòng)態(tài)配置數(shù)據(jù)庫DataSource源 1 protected boolean usingRelationalDatabase = true;2 if (usingRelationalDatabase) {3 initDataSource();4 } 該數(shù)據(jù)庫連接模式初始化的意義如何理解,這就需要回到最初引擎配置分析,其中里面有這樣一部分代碼: 1 pro.setJdbcDriver("com.mysql.jdbc.Driver");2 pro.setJdbcUrl("xxxx");3 pro.setJdbcUsername("xxxx");4 pro.setJdbcPassword("xxx"); 這部分設(shè)置的東西,都是數(shù)據(jù)庫相關(guān)的參數(shù),它將傳到initDataSource方法里,通過mybatis默認(rèn)的連接池PooledDataSource進(jìn)行設(shè)置,可以說,這個(gè)方法主要是用來創(chuàng)建mybatis連接數(shù)據(jù)庫的連接池,從而生成數(shù)據(jù)源連接。 1 public void initDataSource() { 2 //判斷數(shù)據(jù)源dataSource是否存在 3 if (dataSource == null) { 4 / 5 //判斷是否使用JNDI方式連接數(shù)據(jù)源 6 if (dataSourceJndiName != null) { 7 try { 8 dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName); 9 } catch (Exception e) {10 ......11 }12 //使用非JNDI方式且數(shù)據(jù)庫地址不為空,走下面的設(shè)置13 } else if (jdbcUrl != null) {14 //jdbc驅(qū)動(dòng)為空或者jdbc連接賬戶為空15 if ((jdbcDriver == null) || (jdbcUsername == null)) {16 ......17 }18 19 //創(chuàng)建mybatis默認(rèn)連接池PooledDataSource對(duì)象,這里傳進(jìn)來的,就是上面pro.setJdbcDriver("com.mysql.jdbc.Driver")配置的參數(shù),20 //debug到這里,就可以清晰明白,配置類里設(shè)置的JdbcDriver、JdbcUrl、JdbcUsername、JdbcPassword等,就是為了用來創(chuàng)建連接池需要用到的;21 PooledDataSource pooledDataSource = new PooledDataSource(ReflectUtil.getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);22 23 if (jdbcMaxActiveConnections > 0) {24 //設(shè)置最大活躍連接數(shù)25 pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections);26 }27 if (jdbcMaxIdleConnections > 0) {28 // 設(shè)置最大空閑連接數(shù)29 pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections);30 }31 if (jdbcMaxCheckoutTime > 0) {32 // 最大checkout 時(shí)長33 pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime);34 }35 if (jdbcMaxWaitTime > 0) {36 // 在無法獲取連接時(shí),等待的時(shí)間37 pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime);38 }39 if (jdbcPingEnabled == true) {40 //是否允許發(fā)送測(cè)試SQL語句41 pooledDataSource.setPoolPingEnabled(true);42 43 ......44 45 dataSource = pooledDataSource;46 }47 48 ......49 }50 //設(shè)置數(shù)據(jù)庫類型51 if (databaseType == null) {52 initDatabaseType();53 }54 } initDatabaseType()作用是設(shè)置工作流引擎的數(shù)據(jù)庫類型。在工作流引擎里,自帶的28張表,其實(shí)有區(qū)分不同的數(shù)據(jù)庫,而不同數(shù)據(jù)庫其建表語句存在一定差異。 進(jìn)入到 initDatabaseType()方法看看其是如何設(shè)置數(shù)據(jù)庫類型的—— 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 } 進(jìn)入到databaseMetaData.getDatabaseProductName()方法里,可以看到這是一個(gè)接口定義的方法: String getDatabaseProductName() throws SQLException; 這個(gè)方法在java.sql包中的DatabaseMetData接口里被定義,其作用是搜索并獲取數(shù)據(jù)庫的名稱。這里配置使用的是mysql驅(qū)動(dòng),那么就會(huì)被mysql驅(qū)動(dòng)中的jdbc中的DatabaseMetaData實(shí)現(xiàn),如下代碼所示: 1 package com.mysql.cj.jdbc;2 3 public class DatabaseMetaData implements java.sql.DatabaseMetaData 在該實(shí)現(xiàn)類里,其重寫的方法中,將會(huì)返回mysql驅(qū)動(dòng)對(duì)應(yīng)的類型字符串: 1 @Override2 public String getDatabaseProductName() throws SQLException {3 return "MySQL";4 } 故而,就會(huì)返回“MySql”字符串,并賦值給字符串變量databaseProductName,再將databaseProductName當(dāng)做參數(shù)傳給 databaseTypeMappings.getProperty(databaseProductName),最終會(huì)得到一個(gè) this.databaseType =“MySQL”,也就是意味著,設(shè)置了數(shù)據(jù)庫類型databaseType的值為mysql。注意,這一步很重要,因?yàn)閷⒃诤竺嫔杀磉^程中,會(huì)判斷該databaseType的值究竟是代表什么數(shù)據(jù)庫類型。 1 String databaseProductName = databaseMetaData.getDatabaseProductName();2 this.databaseType = databaseTypeMappings.getProperty(databaseProductName);
1 protected boolean usingRelationalDatabase = true;2 if (usingRelationalDatabase) {3 initSqlSessionFactory();4 } init()主要都是初始化引擎環(huán)境的相關(guān)操作,里面涉及到很多東西,但在本篇文中主要了解到這里面會(huì)創(chuàng)建線程池以及mybatis相關(guān)的初始創(chuàng)建即可。 二、開始進(jìn)行processEngine 的創(chuàng)建 ProcessEngineImpl processEngine = new ProcessEngineImpl(this); 這部分代碼,就是創(chuàng)建Activiti的各服務(wù)類了: 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 } 注意,這里面有一段代碼,整個(gè)引擎更新數(shù)據(jù)庫的相應(yīng)策略是具體實(shí)現(xiàn),就在這里面: 1 if (processEngineConfiguration.isUsingRelationalDatabase() && processEngineConfiguration.getDatabaseSchemaUpdate() != null) {2 commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());3 }
綜上,if()判斷為true,就意味著,將執(zhí)行括號(hào)里的代碼,這塊功能就是根據(jù)策略去對(duì)數(shù)據(jù)庫進(jìn)行相應(yīng)的增刪改查操作。 commandExecutor.execute()是一個(gè)典型的命令模式,先暫時(shí)不深入分析,直接點(diǎn)開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 } 進(jìn)入到dbSqlSession.performSchemaOperationsProcessEngineBuild()方法中,接下來,將會(huì)看到,在這個(gè)方法當(dāng)中,將根據(jù)不同的if判斷,執(zhí)行不同的方法——而這里的不同判斷,正是基于四種數(shù)據(jù)庫更新策略來展開的,換句話說,這個(gè)performSchemaOperationsProcessEngineBuild方法,才是真正去判斷不同策略,從而根據(jù)不同策略來對(duì)數(shù)據(jù)庫進(jìn)行對(duì)應(yīng)操作: 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模式來講解,其他基本都類似的實(shí)現(xiàn)。 1 public String dbSchemaUpdate() { 2 3 String feedback = null; 4 //判斷是否需要更新,默認(rèn)是false 5 boolean isUpgradeNeeded = false; 6 int matchingVersionIndex = -1; 7 //判斷是否需要更新或者創(chuàng)建引擎核心engine表,若isEngineTablePresent()為true,表示需要更新,若為false,則需要新創(chuàng)建 8 if (isEngineTablePresent()) { 9 ......10 } else {11 //創(chuàng)建表方法,稍后會(huì)詳細(xì)分析12 dbSchemaCreateEngine();13 }14 15 //判斷是否需要?jiǎng)?chuàng)建或更新歷史相關(guān)表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 } 這里以判斷是否需要?jiǎng)?chuàng)建engine表為例,分析下isEngineTablePresent()里面是如何做判斷的。其他如歷史表、用戶表,其判斷是否需要?jiǎng)?chuàng)建的邏輯,是類型的。 點(diǎn)擊isEngineTablePresent()進(jìn)去—— 1 public boolean isEngineTablePresent() {2 return isTablePresent("ACT_RU_EXECUTION");3 } 進(jìn)入到isTablePresent("ACT_RU_EXECUTION")方法里,其中有一句最主要的代碼: 1 tables = databaseMetaData.getTables(catalog, schema, tableName, JDBC_METADATA_TABLE_TYPES);2 return tables.next(); 這兩行代碼大概意思是,通過"ACT_RU_EXECUTION"表名去數(shù)據(jù)庫中查詢?cè)揂CT_RU_EXECUTION表是否存在,若不存在,返回false,說明還沒有創(chuàng)建;若存在,返回true。 返回到該方法上層,當(dāng)isEngineTablePresent()返回值是false時(shí),說明還沒有創(chuàng)建Activiti表,故而,將執(zhí)行 dbSchemaCreateEngine()方法來創(chuàng)建28表張工作流表。 1 if (isEngineTablePresent()) {2 ......3 } else {4 //創(chuàng)建表方法5 dbSchemaCreateEngine();6 }7 進(jìn)入到dbSchemaCreateEngine()方法——里面調(diào)用了executeMandatorySchemaResource方法,傳入"create"與 "engine",代表著創(chuàng)建引擎表的意思。 1 protected void dbSchemaCreateEngine() {2 this.executeMandatorySchemaResource("create", "engine");3 } 繼續(xù)進(jìn)入到executeMandatorySchemaResource里面—— 1 public void executeMandatorySchemaResource(String operation, String component) {2 this.executeSchemaResource(operation, component, this.getResourceForDbOperation(operation, operation, component), false);3 } 跳轉(zhuǎn)到這里時(shí),有一個(gè)地方需要注意一下,即調(diào)用的this.getResourceForDbOperation(operation, operation, component)方法,這方法的作用,是為了獲取sql文件所存放的相對(duì)路徑,而這些sql,就是構(gòu)建工作流28張表的數(shù)據(jù)庫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即前邊傳進(jìn)來的"create",databaseType的值就是前面獲取到的“mysql”,而component則是"engine",因此,這字符串拼接起來,就是:"org/activiti/db/create/activiti.mysql.create.engine.sql"。 根據(jù)這個(gè)路徑,我們?nèi)ctiviti源碼里查看,可以看到在org/activiti/db/路徑底下,總共有5個(gè)文件目錄。根據(jù)其名字,可以猜測(cè)出,create目錄下存放的,是生成表的sql語句;drop目錄下,存放的是刪除表是sql語句;mapping目錄下,是mybatis映射xml文件;properties是各類數(shù)據(jù)庫類型在分頁情況下的特殊處理;upgrade目錄下,則是更新數(shù)據(jù)庫表的sql語句。 展開其中的create目錄,可以進(jìn)一步發(fā)現(xiàn),里面根據(jù)名字區(qū)分了不同數(shù)據(jù)庫類型對(duì)應(yīng)的執(zhí)行sql文件,其中,有db2、h2、hsql、mssql、mysql、mysql55、oracle、postgres這八種類型,反過來看,同時(shí)說明了Activiti工作流引擎支持使用這八種數(shù)據(jù)庫。通常使用比較多的是mysql。根據(jù)剛剛的路徑org/activiti/db/create/activiti.mysql.create.engine.sql,可以在下面截圖中,找到該對(duì)應(yīng)路徑下的engine.sql文件—— 點(diǎn)擊進(jìn)去看,會(huì)發(fā)現(xiàn),這不就是我們常見的mysql建表語句嗎!沒錯(cuò),工作流Activiti就是在源碼里內(nèi)置了一套sql文件,若要?jiǎng)?chuàng)建數(shù)據(jù)庫表,就直接去到對(duì)應(yīng)數(shù)據(jù)庫文件目錄下,獲取到相應(yīng)的建表文件,執(zhí)行sql語句建表。這跟平常用sql語句構(gòu)建表結(jié)構(gòu)沒太大區(qū)別,區(qū)別只在于執(zhí)行過程的方式而已,但兩者結(jié)果都是一樣的。 到這里,我們根據(jù)其拼接的sql存放路徑,找到了create表結(jié)構(gòu)的sql文件,那么讓我們回到原來代碼執(zhí)行的方法里: 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文件路徑,接下來,將同其他幾個(gè)參數(shù),一塊傳入到this.executeSchemaResource()方法里,具體如下: 1 public void executeSchemaResource(String operation, String component, String resourceName, boolean isOptional) { 2 InputStream inputStream = null; 3 try { 4 //根據(jù)resourceName路徑字符串,獲取到對(duì)應(yīng)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文件的字節(jié),然后再傳入到 this.executeSchemaResource(operation, component, resourceName, inputStream)方法當(dāng)中,而這個(gè)方法,將是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數(shù)據(jù)庫 7 Connection connection = this.sqlSession.getConnection(); 8 //2、分行讀取resourceName="org/activiti/db/create/activiti.mysql.create.engine.sql"目錄底下的文件數(shù)據(jù) 9 byte[] bytes = IoUtil.readInputStream(inputStream, resourceName);10 //3.將engine.sql文件里的數(shù)據(jù)分行轉(zhuǎn)換成字符串,換行的地方,可以看到字符串用轉(zhuǎn)義符“\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 //若數(shù)據(jù)庫類型是在mysql 5.6版本以下,需要做一些替換,因?yàn)榈陀?.6版本的MySQL是不支持變體時(shí)間戳或毫秒級(jí)的日期,故而需要在這里對(duì)sql語句的字符串做替換。(注意,這里的majorVersion代表主版本,minorVersion代表主版本下的小版本)20 ddlStatements = this.updateDdlForMySqlVersionLowerThan56(ddlStatements);21 }22 }23 } catch (Exception var26) {24 ......25 }26 //4.以字符流形式讀取字符串?dāng)?shù)據(jù)27 BufferedReader reader = new BufferedReader(new StringReader(ddlStatements));28 //5.根據(jù)字符串中的轉(zhuǎn)義符“\n”分行讀取29 String line = this.readNextTrimmedLine(reader);30 //6.循環(huán)每一行31 for(boolean inOraclePlsqlBlock = false; line != null; line = this.readNextTrimmedLine(reader)) {32 33 if (line.startsWith("# ")) {34 ......35 } 36 //7.若下一行l(wèi)ine還有數(shù)據(jù),證明還沒有全部讀取,仍可執(zhí)行讀取37 else if (line.length() > 0) {38 if (this.isOracle() && line.startsWith("begin")) {39 .......40 41 } 42 /**43 8.在沒有拼接夠一個(gè)完整建表語句時(shí),!line.endsWith(";")會(huì)為true,即一直循環(huán)進(jìn)行拼接,當(dāng)遇到";"就跳出該if語句44 **/45 else if ((!line.endsWith(";") || inOraclePlsqlBlock) && (!line.startsWith("/") || !inOraclePlsqlBlock)) {46 sqlStatement = this.addSqlStatementPiece(sqlStatement, line);47 } else {48 /**49 9.循環(huán)拼接中若遇到符號(hào)";",就意味著,已經(jīng)拼接形成一個(gè)完整的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_bin56 這樣,就可以先通過代碼來將該建表語句執(zhí)行到數(shù)據(jù)庫中,實(shí)現(xiàn)如下: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對(duì)象65 Statement jdbcStatement = connection.createStatement();66 try {67 //11.最后,執(zhí)行建表語句到數(shù)據(jù)庫中68 jdbcStatement.execute(sqlStatement);69 jdbcStatement.close();70 } catch (Exception var27) {71 ......72 } finally {73 //12.到這一步,意味著上一條sql建表語句已經(jīng)執(zhí)行結(jié)束,若沒有出現(xiàn)錯(cuò)誤話,這時(shí)已經(jīng)證明第一個(gè)數(shù)據(jù)庫表結(jié)構(gòu)已經(jīng)創(chuàng)建完成,可以開始拼接下一條建表語句,74 sqlStatement = null;75 }76 }77 }78 }79 80 ......81 } catch (Exception var29) {82 ......83 }84 } 以上步驟可以歸納下:
根據(jù)debug過程截圖,可以更為直觀地看到,這里獲取到的ddlStatements字符串,涵蓋了sql文件里的所有sql語句,同時(shí),每一個(gè)完整的creat建表語句,都是以";"結(jié)尾的: 每次執(zhí)行到";"時(shí),都會(huì)得到一個(gè)完整的create建表語句: 執(zhí)行完一個(gè)建表語句,就會(huì)在數(shù)據(jù)庫里同步生成一張數(shù)據(jù)庫表,如上圖執(zhí)行的是ACT_GE_PROPERTY表,數(shù)據(jù)庫里便生成了這張表: 在執(zhí)行完之后,看idea控制臺(tái)打印信息,可以看到,我的數(shù)據(jù)庫是5.7版本,引擎在啟動(dòng)過程中分別執(zhí)行了engine.sql、history.sql、identity.sql三個(gè)sql文件來進(jìn)行數(shù)據(jù)庫表結(jié)構(gòu)的構(gòu)建。 到這一步,引擎整個(gè)生成表的過程就結(jié)束了,以上主要是基于true策略模式,通過對(duì)engine.sql的執(zhí)行,來說明工作流引擎生成表的底層邏輯,其余模式基本都類似,這里就不一一展開分析了。 最后,進(jìn)入到數(shù)據(jù)庫,可以看到,已成功生成28張ACT開頭的工作流自帶表—— 1、jdbc連接mysql數(shù)據(jù)庫 |
|