欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Activiti工作流學(xué)習(xí)筆記之自動(dòng)生成28張數(shù)據(jù)庫(kù)表的底層原理解析

 更新時(shí)間:2021年03月16日 10:07:13   作者:朱季謙  
這篇文章主要介紹了Activiti工作流學(xué)習(xí)筆記之自動(dòng)生成28張數(shù)據(jù)庫(kù)表的底層原理解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

網(wǎng)上關(guān)于工作流引擎Activiti生成表的機(jī)制大多僅限于四種策略模式,但其底層是如何實(shí)現(xiàn)的,相關(guān)文章還是比較少,因此,覺(jué)得擼一擼其生成表機(jī)制的底層原理。

我接觸工作流引擎Activiti已有兩年之久,但一直都只限于熟悉其各類API的使用,對(duì)底層的實(shí)現(xiàn),則存在較大的盲區(qū)。

Activiti這個(gè)開(kāi)源框架在設(shè)計(jì)上,其實(shí)存在不少值得學(xué)習(xí)和思考的地方,例如,框架用到以命令模式、責(zé)任鏈模式、模板模式等優(yōu)秀的設(shè)計(jì)模式來(lái)進(jìn)行框架的設(shè)計(jì)。

故而,是值得好好研究下Activiti這個(gè)框架的底層實(shí)現(xiàn)。

我在工作當(dāng)中現(xiàn)階段用的比較多是Activiti6.0版本,本文就以這個(gè)版本來(lái)展開(kāi)分析。

在使用Activiti工作流引擎過(guò)程中,讓我比較好奇的一個(gè)地方,是框架自帶一套數(shù)據(jù)庫(kù)表結(jié)構(gòu),在首次啟動(dòng)時(shí),若設(shè)計(jì)了相應(yīng)的建表策略時(shí),將會(huì)自動(dòng)生成28張表,而這些表都是以ACT_開(kāi)頭。

那么問(wèn)題來(lái)了,您是否與我一樣,曾好奇過(guò)這些表都是怎么自動(dòng)生成的呢?

下面,就開(kāi)始一點(diǎn)點(diǎn)深入研究——

在工作流Springboot+Activiti6.0集成框架,網(wǎng)上最常見(jiàn)的引擎啟動(dòng)配置教程一般長(zhǎng)這樣:

@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");
   //避免發(fā)布的圖片和xml中文出現(xiàn)亂碼
   pro.setActivityFontName("宋體");
   pro.setLabelFontName("宋體");
   pro.setAnnotationFontName("宋體");
   //數(shù)據(jù)庫(kù)更更新策略
   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()可對(duì)工作流引擎自帶的28張表進(jìn)行不同策略的更新。

Activiti6.0版本總共有四種數(shù)據(jù)庫(kù)表更新策略。

查看這三種策略的靜態(tài)常量標(biāo)識(shí),分別如下:

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:默認(rèn)值,引擎啟動(dòng)時(shí),自動(dòng)檢查數(shù)據(jù)庫(kù)里是否已有表,或者表版本是否匹配,如果無(wú)表或者表版本不對(duì),則拋出異常。(常用在生產(chǎn)環(huán)境);
  • true:若表不存在,自動(dòng)更新;若存在,而表有改動(dòng),則自動(dòng)更新表,若表存在以及表無(wú)更新,則該策略不會(huì)做任何操作。(一般用在開(kāi)發(fā)環(huán)境);
  • create_drop:?jiǎn)?dòng)時(shí)自動(dòng)建表,關(guān)閉時(shí)就刪除表,有一種臨時(shí)表的感覺(jué)。(需手動(dòng)關(guān)閉,才會(huì)起作用);
  • drop-create:?jiǎn)?dòng)時(shí)刪除舊表,再重新建表。(無(wú)需手動(dòng)關(guān)閉就能起作用);

整個(gè)啟動(dòng)更新數(shù)據(jù)庫(kù)的過(guò)程都是圍繞這四種策略,接下來(lái)就以這四種策略為主題,擼一下自動(dòng)更新?lián)?kù)表的底層原理,這一步驟是在引擎啟動(dòng)時(shí)所執(zhí)行的buildProcessEngine()方法里實(shí)現(xiàn)。

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

從這里開(kāi)始,一步一步debug去分析源碼實(shí)現(xiàn)。

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

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

進(jìn)入processEngine接口,可以看到,其涵蓋了Activiti的所有服務(wù)接口:

public interface ProcessEngine {

  public static String VERSION = "6.0.0.4";

  String getName();

 void close();
  //流程運(yùn)行服務(wù)類,用于獲取流程執(zhí)行相關(guān)信息
  RepositoryService getRepositoryService();
  //流程運(yùn)行服務(wù)類,用于獲取流程執(zhí)行相關(guān)信息
  RuntimeService getRuntimeService();
  //內(nèi)置表單,用于工作流自帶內(nèi)置表單的設(shè)置
  FormService getFormService();
  //任務(wù)服務(wù)類,用戶獲取任務(wù)信息
  TaskService getTaskService();
  //獲取正在運(yùn)行或已經(jīng)完成的流程實(shí)例歷史信息
  HistoryService getHistoryService();
  //創(chuàng)建、更新、刪除、查詢?nèi)航M和用戶
  IdentityService getIdentityService();
  //流程引擎的管理與維護(hù)
  ManagementService getManagementService();
  //提供對(duì)流程定義和部署存儲(chǔ)庫(kù)的訪問(wèn)的服務(wù)。
  DynamicBpmnService getDynamicBpmnService();
  //獲取配置類
  ProcessEngineConfiguration getProcessEngineConfiguration();
  //提供對(duì)內(nèi)置表單存儲(chǔ)庫(kù)的訪問(wèn)的服務(wù)。
  FormRepositoryService getFormEngineRepositoryService();

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

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

該buildProcessEngine重寫(xiě)方法如下:

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

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

 postProcessEngineInitialisation();

 return processEngine;
}

init()方法里面包含各類需要初始化的方法,涉及到很多東西,這里先暫不一一展開(kāi)分析,主要先分析與數(shù)據(jù)庫(kù)連接初始化相關(guān)的邏輯。Activiti6.0底層是通過(guò)mybatis來(lái)操作數(shù)據(jù)庫(kù)的,下面主要涉及到mybatis的連接池與SqlSessionFactory 的創(chuàng)建。

1.initDataSource():實(shí)現(xiàn)動(dòng)態(tài)配置數(shù)據(jù)庫(kù)DataSource源

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

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

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

這部分設(shè)置的東西,都是數(shù)據(jù)庫(kù)相關(guān)的參數(shù),它將傳到initDataSource方法里,通過(guò)mybatis默認(rèn)的連接池PooledDataSource進(jìn)行設(shè)置,可以說(shuō),這個(gè)方法主要是用來(lái)創(chuàng)建mybatis連接數(shù)據(jù)庫(kù)的連接池,從而生成數(shù)據(jù)源連接。

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

    //創(chuàng)建mybatis默認(rèn)連接池PooledDataSource對(duì)象,這里傳進(jìn)來(lái)的,就是上面pro.setJdbcDriver("com.mysql.jdbc.Driver")配置的參數(shù),
    //debug到這里,就可以清晰明白,配置類里設(shè)置的JdbcDriver、JdbcUrl、JdbcUsername、JdbcPassword等,就是為了用來(lái)創(chuàng)建連接池需要用到的;
    PooledDataSource pooledDataSource = new PooledDataSource(ReflectUtil.getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);

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

    ......

    dataSource = pooledDataSource;
   }

    ......
  }
  //設(shè)置數(shù)據(jù)庫(kù)類型
  if (databaseType == null) {
   initDatabaseType();
  }
}

initDatabaseType()作用是設(shè)置工作流引擎的數(shù)據(jù)庫(kù)類型。在工作流引擎里,自帶的28張表,其實(shí)有區(qū)分不同的數(shù)據(jù)庫(kù),而不同數(shù)據(jù)庫(kù)其建表語(yǔ)句存在一定差異。

進(jìn)入到 initDatabaseType()方法看看其是如何設(shè)置數(shù)據(jù)庫(kù)類型的——

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 {
   ......
   }
}

進(jìn)入到databaseMetaData.getDatabaseProductName()方法里,可以看到這是一個(gè)接口定義的方法:

String getDatabaseProductName() throws SQLException;

這個(gè)方法在java.sql包中的DatabaseMetData接口里被定義,其作用是搜索并獲取數(shù)據(jù)庫(kù)的名稱。這里配置使用的是mysql驅(qū)動(dòng),那么就會(huì)被mysql驅(qū)動(dòng)中的jdbc中的DatabaseMetaData實(shí)現(xiàn),如下代碼所示:

package com.mysql.cj.jdbc;

public class DatabaseMetaData implements java.sql.DatabaseMetaData 

在該實(shí)現(xiàn)類里,其重寫(xiě)的方法中,將會(huì)返回mysql驅(qū)動(dòng)對(duì)應(yīng)的類型字符串:

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

故而,就會(huì)返回“MySql”字符串,并賦值給字符串變量databaseProductName,再將databaseProductName當(dāng)做參數(shù)傳給

databaseTypeMappings.getProperty(databaseProductName),最終會(huì)得到一個(gè) this.databaseType =“MySQL”,也就是意味著,設(shè)置了數(shù)據(jù)庫(kù)類型databaseType的值為mysql。注意,這一步很重要,因?yàn)閷⒃诤竺嫔杀磉^(guò)程中,會(huì)判斷該databaseType的值究竟是代表什么數(shù)據(jù)庫(kù)類型。

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

該方法對(duì)SqlSessionFactory進(jìn)行 初始化創(chuàng)建:SqlSessionFactory是mybatis的核心類,簡(jiǎn)單的講,創(chuàng)建這個(gè)類,接下來(lái)就可以進(jìn)行增刪改查與事務(wù)操作了。

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

init()主要都是初始化引擎環(huán)境的相關(guān)操作,里面涉及到很多東西,但在本篇文中主要了解到這里面會(huì)創(chuàng)建線程池以及mybatis相關(guān)的初始創(chuàng)建即可。

二、開(kāi)始進(jìn)行processEngine 的創(chuàng)建

ProcessEngineImpl processEngine = new ProcessEngineImpl(this);

這部分代碼,就是創(chuàng)建Activiti的各服務(wù)類了:

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());
 }
 ......
}

注意,這里面有一段代碼,整個(gè)引擎更新數(shù)據(jù)庫(kù)的相應(yīng)策略是具體實(shí)現(xiàn),就在這里面:

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

綜上,if()判斷為true,就意味著,將執(zhí)行括號(hào)里的代碼,這塊功能就是根據(jù)策略去對(duì)數(shù)據(jù)庫(kù)進(jìn)行相應(yīng)的增刪改查操作。

commandExecutor.execute()是一個(gè)典型的命令模式,先暫時(shí)不深入分析,直接點(diǎn)開(kāi)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;
 }
}

進(jìn)入到dbSqlSession.performSchemaOperationsProcessEngineBuild()方法中,接下來(lái),將會(huì)看到,在這個(gè)方法當(dāng)中,將根據(jù)不同的if判斷,執(zhí)行不同的方法——而這里的不同判斷,正是基于四種數(shù)據(jù)庫(kù)更新策略來(lái)展開(kāi)的,換句話說(shuō),這個(gè)performSchemaOperationsProcessEngineBuild方法,才是真正去判斷不同策略,從而根據(jù)不同策略來(lái)對(duì)數(shù)據(jù)庫(kù)進(jìn)行對(duì)應(yīng)操作:

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模式來(lái)講解,其他基本都類似的實(shí)現(xiàn)。

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

 //判斷是否需要?jiǎng)?chuàng)建或更新歷史相關(guān)表
 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;
}

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

點(diǎn)擊isEngineTablePresent()進(jìn)去——

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

進(jìn)入到isTablePresent("ACT_RU_EXECUTION")方法里,其中有一句最主要的代碼:

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

這兩行代碼大概意思是,通過(guò)"ACT_RU_EXECUTION"表名去數(shù)據(jù)庫(kù)中查詢?cè)揂CT_RU_EXECUTION表是否存在,若不存在,返回false,說(shuō)明還沒(méi)有創(chuàng)建;若存在,返回true。

返回到該方法上層,當(dāng)isEngineTablePresent()返回值是false時(shí),說(shuō)明還沒(méi)有創(chuàng)建Activiti表,故而,將執(zhí)行 dbSchemaCreateEngine()方法來(lái)創(chuàng)建28表張工作流表。

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

進(jìn)入到dbSchemaCreateEngine()方法——里面調(diào)用了executeMandatorySchemaResource方法,傳入"create"與 "engine",代表著創(chuàng)建引擎表的意思。

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

繼續(xù)進(jìn)入到executeMandatorySchemaResource里面——

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

跳轉(zhuǎn)到這里時(shí),有一個(gè)地方需要注意一下,即調(diào)用的this.getResourceForDbOperation(operation, operation, component)方法,這方法的作用,是為了獲取sql文件所存放的相對(duì)路徑,而這些sql,就是構(gòu)建工作流28張表的數(shù)據(jù)庫(kù)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即前邊傳進(jìn)來(lái)的"create",databaseType的值就是前面獲取到的“mysql”,而component則是"engine",因此,這字符串拼接起來(lái),就是:"org/activiti/db/create/activiti.mysql.create.engine.sql"。

根據(jù)這個(gè)路徑,我們?nèi)ctiviti源碼里查看,可以看到在org/activiti/db/路徑底下,總共有5個(gè)文件目錄。根據(jù)其名字,可以猜測(cè)出,create目錄下存放的,是生成表的sql語(yǔ)句;drop目錄下,存放的是刪除表是sql語(yǔ)句;mapping目錄下,是mybatis映射xml文件;properties是各類數(shù)據(jù)庫(kù)類型在分頁(yè)情況下的特殊處理;upgrade目錄下,則是更新數(shù)據(jù)庫(kù)表的sql語(yǔ)句。

展開(kāi)其中的create目錄,可以進(jìn)一步發(fā)現(xiàn),里面根據(jù)名字區(qū)分了不同數(shù)據(jù)庫(kù)類型對(duì)應(yīng)的執(zhí)行sql文件,其中,有db2、h2、hsql、mssql、mysql、mysql55、oracle、postgres這八種類型,反過(guò)來(lái)看,同時(shí)說(shuō)明了Activiti工作流引擎支持使用這八種數(shù)據(jù)庫(kù)。通常使用比較多的是mysql。根據(jù)剛剛的路徑org/activiti/db/create/activiti.mysql.create.engine.sql,可以在下面截圖中,找到該對(duì)應(yīng)路徑下的engine.sql文件——

點(diǎn)擊進(jìn)去看,會(huì)發(fā)現(xiàn),這不就是我們常見(jiàn)的mysql建表語(yǔ)句嗎!沒(méi)錯(cuò),工作流Activiti就是在源碼里內(nèi)置了一套sql文件,若要?jiǎng)?chuàng)建數(shù)據(jù)庫(kù)表,就直接去到對(duì)應(yīng)數(shù)據(jù)庫(kù)文件目錄下,獲取到相應(yīng)的建表文件,執(zhí)行sql語(yǔ)句建表。這跟平常用sql語(yǔ)句構(gòu)建表結(jié)構(gòu)沒(méi)太大區(qū)別,區(qū)別只在于執(zhí)行過(guò)程的方式而已,但兩者結(jié)果都是一樣的。

到這里,我們根據(jù)其拼接的sql存放路徑,找到了create表結(jié)構(gòu)的sql文件,那么讓我們回到原來(lái)代碼執(zhí)行的方法里:

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

這里通過(guò)this.getResourceForDbOperation(operation, operation, component), false)拿到 了mysql文件路徑,接下來(lái),將同其他幾個(gè)參數(shù),一塊傳入到this.executeSchemaResource()方法里,具體如下:

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

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

下面刪除多余代碼,只留核心代碼來(lái)分析:

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

  try {
 //1、jdbc連接mysql數(shù)據(jù)庫(kù)
    Connection connection = this.sqlSession.getConnection();
 //2、分行讀取resourceName="org/activiti/db/create/activiti.mysql.create.engine.sql"目錄底下的文件數(shù)據(jù)
    byte[] bytes = IoUtil.readInputStream(inputStream, resourceName);
 //3.將engine.sql文件里的數(shù)據(jù)分行轉(zhuǎn)換成字符串,換行的地方,可以看到字符串用轉(zhuǎn)義符“\n”來(lái)代替
    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) {
          //若數(shù)據(jù)庫(kù)類型是在mysql 5.6版本以下,需要做一些替換,因?yàn)榈陀?.6版本的MySQL是不支持變體時(shí)間戳或毫秒級(jí)的日期,故而需要在這里對(duì)sql語(yǔ)句的字符串做替換。(注意,這里的majorVersion代表主版本,minorVersion代表主版本下的小版本)
          ddlStatements = this.updateDdlForMySqlVersionLowerThan56(ddlStatements);
        }
      }
    } catch (Exception var26) {
      ......
    }
    //4.以字符流形式讀取字符串?dāng)?shù)據(jù)
    BufferedReader reader = new BufferedReader(new StringReader(ddlStatements));
    //5.根據(jù)字符串中的轉(zhuǎn)義符“\n”分行讀取
    String line = this.readNextTrimmedLine(reader);
    //6.循環(huán)每一行
    for(boolean inOraclePlsqlBlock = false; line != null; line = this.readNextTrimmedLine(reader)) {

      if (line.startsWith("# ")) {
      ......
      }
      //7.若下一行l(wèi)ine還有數(shù)據(jù),證明還沒(méi)有全部讀取,仍可執(zhí)行讀取
    else if (line.length() > 0) {
          if (this.isOracle() && line.startsWith("begin")) {
           .......

          }
      /**
      8.在沒(méi)有拼接夠一個(gè)完整建表語(yǔ)句時(shí),!line.endsWith(";")會(huì)為true,即一直循環(huán)進(jìn)行拼接,當(dāng)遇到";"就跳出該if語(yǔ)句
      **/
      else if ((!line.endsWith(";") || inOraclePlsqlBlock) && (!line.startsWith("/") || !inOraclePlsqlBlock)) {
            sqlStatement = this.addSqlStatementPiece(sqlStatement, line);
          } else {
        /**
       9.循環(huán)拼接中若遇到符號(hào)";",就意味著,已經(jīng)拼接形成一個(gè)完整的sql建表語(yǔ)句,例如
      create table ACT_GE_PROPERTY (
      NAME_ varchar(64),
      VALUE_ varchar(300),
      REV_ integer,
      primary key (NAME_)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin
      這樣,就可以先通過(guò)代碼來(lái)將該建表語(yǔ)句執(zhí)行到數(shù)據(jù)庫(kù)中,實(shí)現(xiàn)如下:
        **/
            if (inOraclePlsqlBlock) {
              inOraclePlsqlBlock = false;
            } else {

              sqlStatement = this.addSqlStatementPiece(sqlStatement, line.substring(0, line.length() - 1));
            }
          //10.將建表語(yǔ)句字符串包裝成Statement對(duì)象
            Statement jdbcStatement = connection.createStatement();
            try {
          //11.最后,執(zhí)行建表語(yǔ)句到數(shù)據(jù)庫(kù)中
              jdbcStatement.execute(sqlStatement);
              jdbcStatement.close();
            } catch (Exception var27) {
            ......
            } finally {
          //12.到這一步,意味著上一條sql建表語(yǔ)句已經(jīng)執(zhí)行結(jié)束,若沒(méi)有出現(xiàn)錯(cuò)誤話,這時(shí)已經(jīng)證明第一個(gè)數(shù)據(jù)庫(kù)表結(jié)構(gòu)已經(jīng)創(chuàng)建完成,可以開(kāi)始拼接下一條建表語(yǔ)句,
              sqlStatement = null;
            }
          }
        }
      }
​
      ......
    } catch (Exception var29) {
      ......
    }
  }

以上步驟可以歸納下:

  1. jdbc連接mysql數(shù)據(jù)庫(kù);
  2. 分行讀取resourceName="org/activiti/db/create/activiti.mysql.create.engine.sql"目錄底下的sql文件數(shù)據(jù);
  3. 將整個(gè)engine.sql文件數(shù)據(jù)分行轉(zhuǎn)換成字符串ddlStatements,有換行的地方,用轉(zhuǎn)義符“\n”來(lái)代替;
  4. 以BufferedReader字符流形式讀取字符串ddlStatements數(shù)據(jù);
  5. 循環(huán)字符流里的每一行,拼接成sqlStatement字符串,若讀取到該行結(jié)尾有“;”符號(hào),意味著已經(jīng)拼接成一個(gè)完整的create建表語(yǔ)句,這時(shí),跳出該次拼接,直接包裝成成Statement對(duì)象;值得注意一點(diǎn)是,Statement 是 Java 執(zhí)行數(shù)據(jù)庫(kù)操作的一個(gè)重要接口,用于在已經(jīng)建立數(shù)據(jù)庫(kù)連接的基礎(chǔ)上,向數(shù)據(jù)庫(kù)發(fā)送要執(zhí)行的SQL語(yǔ)句。Statement對(duì)象是用于執(zhí)行不帶參數(shù)的簡(jiǎn)單SQL語(yǔ)句,例如本次的create建表語(yǔ)句。
  6. 最后,執(zhí)行jdbcStatement.execute(sqlStatement),將create建表語(yǔ)句執(zhí)行進(jìn)數(shù)據(jù)庫(kù)中;
  7. 生成對(duì)應(yīng)的數(shù)據(jù)庫(kù)表;

根據(jù)debug過(guò)程截圖,可以更為直觀地看到,這里獲取到的ddlStatements字符串,涵蓋了sql文件里的所有sql語(yǔ)句,同時(shí),每一個(gè)完整的creat建表語(yǔ)句,都是以";"結(jié)尾的:

每次執(zhí)行到";"時(shí),都會(huì)得到一個(gè)完整的create建表語(yǔ)句:

執(zhí)行完一個(gè)建表語(yǔ)句,就會(huì)在數(shù)據(jù)庫(kù)里同步生成一張數(shù)據(jù)庫(kù)表,如上圖執(zhí)行的是ACT_GE_PROPERTY表,數(shù)據(jù)庫(kù)里便生成了這張表:

在執(zhí)行完之后,看idea控制臺(tái)打印信息,可以看到,我的數(shù)據(jù)庫(kù)是5.7版本,引擎在啟動(dòng)過(guò)程中分別執(zhí)行了engine.sql、history.sql、identity.sql三個(gè)sql文件來(lái)進(jìn)行數(shù)據(jù)庫(kù)表結(jié)構(gòu)的構(gòu)建。

到這一步,引擎整個(gè)生成表的過(guò)程就結(jié)束了,以上主要是基于true策略模式,通過(guò)對(duì)engine.sql的執(zhí)行,來(lái)說(shuō)明工作流引擎生成表的底層邏輯,其余模式基本都類似,這里就不一一展開(kāi)分析了。

最后,進(jìn)入到數(shù)據(jù)庫(kù),可以看到,已成功生成28張ACT開(kāi)頭的工作流自帶表——

jdbc連接mysql數(shù)據(jù)庫(kù)

到此這篇關(guān)于Activiti工作流學(xué)習(xí)筆記之自動(dòng)生成28張數(shù)據(jù)庫(kù)表的底層原理解析的文章就介紹到這了,更多相關(guān)Activiti工作流自動(dòng)生成28張數(shù)據(jù)庫(kù)表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springboot 整合 sa-token簡(jiǎn)介及入門教程

    springboot 整合 sa-token簡(jiǎn)介及入門教程

    Sa-Token 是一個(gè)輕量級(jí) Java 權(quán)限認(rèn)證框架,主要解決:登錄認(rèn)證、權(quán)限認(rèn)證、Session會(huì)話、單點(diǎn)登錄、OAuth2.0、微服務(wù)網(wǎng)關(guān)鑒權(quán) 等一系列權(quán)限相關(guān)問(wèn)題,這篇文章主要介紹了springboot 整合 sa-token簡(jiǎn)介及入門教程,需要的朋友可以參考下
    2023-05-05
  • 帶你輕松了解Modbus協(xié)議

    帶你輕松了解Modbus協(xié)議

    這篇文章主要給大家介紹了關(guān)于Modbus協(xié)議的相關(guān)資料,此協(xié)議定義了一個(gè)控制器能認(rèn)識(shí)使用的消息結(jié)構(gòu),而不管它們是經(jīng)過(guò)何種網(wǎng)絡(luò)進(jìn)行通信的,需要的朋友可以參考下
    2021-11-11
  • spark中使用groupByKey進(jìn)行分組排序的示例代碼

    spark中使用groupByKey進(jìn)行分組排序的示例代碼

    這篇文章主要介紹了spark中使用groupByKey進(jìn)行分組排序的實(shí)例代碼,本文通過(guò)實(shí)例代碼給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • Spring Boot使用Log4j2的實(shí)例代碼

    Spring Boot使用Log4j2的實(shí)例代碼

    這篇文章主要介紹了Spring Boot使用Log4j2的實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-07-07
  • spring boot和spring cloud之間的版本關(guān)系

    spring boot和spring cloud之間的版本關(guān)系

    這篇文章主要介紹了spring boot和spring cloud之間的版本關(guān)系,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • Spring配置文件使用占位符配置方式

    Spring配置文件使用占位符配置方式

    這篇文章主要介紹了Spring配置文件使用占位符配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • 解決cmd執(zhí)行javac報(bào)錯(cuò):不是內(nèi)部或外部命令,也不是可運(yùn)行的程序

    解決cmd執(zhí)行javac報(bào)錯(cuò):不是內(nèi)部或外部命令,也不是可運(yùn)行的程序

    剛接觸JAVA的新手可能就不知道怎么解決'JAVAC'不是內(nèi)部命令或外部命令,這篇文章主要給大家介紹了關(guān)于解決cmd執(zhí)行javac報(bào)錯(cuò):不是內(nèi)部或外部命令,也不是可運(yùn)行的程序的相關(guān)資料,需要的朋友可以參考下
    2023-11-11
  • SpringBoot動(dòng)態(tài)定時(shí)功能實(shí)現(xiàn)方案詳解

    SpringBoot動(dòng)態(tài)定時(shí)功能實(shí)現(xiàn)方案詳解

    在SpringBoot項(xiàng)目中簡(jiǎn)單使用定時(shí)任務(wù),不過(guò)由于要借助cron表達(dá)式且都提前定義好放在配置文件里,不能在項(xiàng)目運(yùn)行中動(dòng)態(tài)修改任務(wù)執(zhí)行時(shí)間,實(shí)在不太靈活?,F(xiàn)在我們就來(lái)實(shí)現(xiàn)可以動(dòng)態(tài)修改cron表達(dá)式的定時(shí)任務(wù),感興趣的可以了解一下
    2022-11-11
  • 關(guān)于Redis的緩存穿透問(wèn)題

    關(guān)于Redis的緩存穿透問(wèn)題

    這篇文章主要介紹了關(guān)于Redis的緩存穿透問(wèn)題,緩存穿透是指客戶端請(qǐng)求的數(shù)據(jù)在緩存中和數(shù)據(jù)庫(kù)中都不存在,這樣緩存永遠(yuǎn)不會(huì)生效,這些請(qǐng)求都會(huì)打到數(shù)據(jù)庫(kù),需要的朋友可以參考下
    2023-08-08
  • 如何基于Autowired對(duì)構(gòu)造函數(shù)進(jìn)行注釋

    如何基于Autowired對(duì)構(gòu)造函數(shù)進(jìn)行注釋

    這篇文章主要介紹了如何基于Autowired對(duì)構(gòu)造函數(shù)進(jìn)行注釋,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10

最新評(píng)論