Activiti工作流学习笔记(三)——自动生成28张数据库表的底层原理分析

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 网上关于工作流引擎Activiti生成表的机制大多仅限于四种策略模式,但其底层是如何实现的,相关文章还是比较少,因此,觉得撸一撸其生成表机制的底层原理。

原创/朱季谦

网上关于工作流引擎Activiti生成表的机制大多仅限于四种策略模式,但其底层是如何实现的,相关文章还是比较少,因此,觉得撸一撸其生成表机制的底层原理。

 

我接触工作流引擎Activiti已有两年之久,但一直都只限于熟悉其各类API的使用,对底层的实现,则存在较大的盲区。

 

Activiti这个开源框架在设计上,其实存在不少值得学习和思考的地方,例如,框架用到以命令模式、责任链模式、模板模式等优秀的设计模式来进行框架的设计。

 

故而,是值得好好研究下Activiti这个框架的底层实现。

 

我在工作当中现阶段用的比较多是Activiti6.0版本,本文就以这个版本来展开分析。

 

在使用Activiti工作流引擎过程中,让我比较好奇的一个地方,是框架自带一套数据库表结构,在首次启动时,若设计了相应的建表策略时,将会自动生成28张表,而这些表都是以ACT_开头。

 

那么问题来了,您是否与我一样,曾好奇过这些表都是怎么自动生成的呢?

 

下面,就开始一点点深入研究——

在工作流Springboot+Activiti6.0集成框架,网上最常见的引擎启动配置教程一般长这样:


1@Configuration

2publicclass SpringBootActivitiConfig  {

3@Bean

4public 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版本总共有四种数据库表更新策略。

查看这三种策略的静态常量标识,分别如下:


1publicabstractclass ProcessEngineConfiguration {

2      publicstaticfinal String DB_SCHEMA_UPDATE_FALSE = "false";

3      publicstaticfinal String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop";

4      publicstaticfinal String DB_SCHEMA_UPDATE_TRUE = "true";

5     ......

6}

7

8publicabstractclass ProcessEngineConfigurationImpl extends ProcessEngineConfiguration {

9      publicstaticfinal 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的所有服务接口:


1publicinterface ProcessEngine {

2

3    publicstatic 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

2public 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源

1protectedboolean usingRelationalDatabase = true;

2if (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连接数据库的连接池,从而生成数据源连接。

1publicvoid 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      } elseif (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()方法看看其是如何设置数据库类型的——

1publicvoid 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实现,如下代码所示:

1package com.mysql.cj.jdbc;

2

3publicclass DatabaseMetaData implements java.sql.DatabaseMetaData

在该实现类里,其重写的方法中,将会返回mysql驱动对应的类型字符串:

1@Override

2public 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);

  1. 该方法对SqlSessionFactory进行 初始化创建:SqlSessionFactory是mybatis的核心类,简单的讲,创建这个类,接下来就可以进行增删改查与事务操作了。

1protectedboolean usingRelationalDatabase = true;

2if (usingRelationalDatabase) {

3  initSqlSessionFactory();

4 }

init()主要都是初始化引擎环境的相关操作,里面涉及到很多东西,但在本篇文中主要了解到这里面会创建线程池以及mybatis相关的初始创建即可。

二、开始进行processEngine 的创建

ProcessEngineImpl processEngine = new ProcessEngineImpl(this);

这部分代码,就是创建Activiti的各服务类了:

1public 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 }

注意,这里面有一段代码,整个引擎更新数据库的相应策略是具体实现,就在这里面:

1if (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()方法。

1publicfinalclass 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     returnnull;

9  }

10 }

进入到dbSqlSession.performSchemaOperationsProcessEngineBuild()方法中,接下来,将会看到,在这个方法当中,将根据不同的if判断,执行不同的方法——而这里的不同判断,正是基于四种数据库更新策略来展开的,换句话说,这个performSchemaOperationsProcessEngineBuild方法,才是真正去判断不同策略,从而根据不同策略来对数据库进行对应操作:

1publicvoid 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        } elseif ("true".equals(databaseSchemaUpdate)) {

17               //true模式

18             this.dbSchemaUpdate();

19       }

20    } else {

21         //create_drop模式

22         this.dbSchemaCreate();

23   }

24

25 }

这里主要以true模式来讲解,其他基本都类似的实现。

1public 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   } elseif (this.dbSqlSessionFactory.isDbHistoryUsed()) {

21        this.dbSchemaCreateHistory();

22  }

23   //判断是否需要更新群组和用户

24   if (this.isIdentityTablePresent()) {

25        if (isUpgradeNeeded) {

26          this.dbSchemaUpgrade("identity", matchingVersionIndex);

27       }

28   } elseif (this.dbSqlSessionFactory.isDbIdentityUsed()) {

29          this.dbSchemaCreateIdentity();

30       }

31   return feedback;

32 }

这里以判断是否需要创建engine表为例,分析下isEngineTablePresent()里面是如何做判断的。其他如历史表、用户表,其判断是否需要创建的逻辑,是类型的。

点击isEngineTablePresent()进去——

1publicboolean isEngineTablePresent() {

2   return isTablePresent("ACT_RU_EXECUTION");

3 }

进入到isTablePresent("ACT_RU_EXECUTION")方法里,其中有一句最主要的代码:

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

2return 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",代表着创建引擎表的意思。

1protectedvoid dbSchemaCreateEngine() {

2     this.executeMandatorySchemaResource("create", "engine");

3 }

继续进入到executeMandatorySchemaResource里面——

1publicvoid 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()方法里看下——

1public 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文件,那么让我们回到原来代码执行的方法里:

1publicvoid 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()方法里,具体如下:

1publicvoid 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建表过程中的核心所在。

下面删除多余代码,只留核心代码来分析:

1privatevoid 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         elseif (line.length() > 0) {

38                     if (this.isOracle() && line.startsWith("begin")) {

39                     .......

40                        

41                   }

42             /**

43            8.在没有拼接够一个完整建表语句时,!line.endsWith(";")会为true,即一直循环进行拼接,当遇到";"就跳出该if语句

44            **/

45             elseif ((!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    }

以上步骤可以归纳下:

  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开头的工作流自带表——

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
存储 SQL 关系型数据库
Mysql学习笔记(二):数据库命令行代码总结
这篇文章是关于MySQL数据库命令行操作的总结,包括登录、退出、查看时间与版本、数据库和数据表的基本操作(如创建、删除、查看)、数据的增删改查等。它还涉及了如何通过SQL语句进行条件查询、模糊查询、范围查询和限制查询,以及如何进行表结构的修改。这些内容对于初学者来说非常实用,是学习MySQL数据库管理的基础。
131 6
|
3月前
|
关系型数据库 MySQL 分布式数据库
PolarDB 与传统数据库的性能对比分析
【8月更文第27天】随着云计算技术的发展,越来越多的企业开始将数据管理和存储迁移到云端。阿里云的 PolarDB 作为一款兼容 MySQL 和 PostgreSQL 的关系型数据库服务,提供了高性能、高可用和弹性伸缩的能力。本文将从不同角度对比 PolarDB 与本地部署的传统数据库(如 MySQL、PostgreSQL)在性能上的差异。
233 1
|
17天前
|
存储 SQL Apache
Apache Doris 开源最顶级基于MPP架构的高性能实时分析数据库
Apache Doris 是一个基于 MPP 架构的高性能实时分析数据库,以其极高的速度和易用性著称。它支持高并发点查询和复杂分析场景,适用于报表分析、即席查询、数据仓库和数据湖查询加速等。最新发布的 2.0.2 版本在性能、稳定性和多租户支持方面有显著提升。社区活跃,已广泛应用于电商、广告、用户行为分析等领域。
Apache Doris 开源最顶级基于MPP架构的高性能实时分析数据库
|
1月前
|
SQL Ubuntu 关系型数据库
Mysql学习笔记(一):数据库详细介绍以及Navicat简单使用
本文为MySQL学习笔记,介绍了数据库的基本概念,包括行、列、主键等,并解释了C/S和B/S架构以及SQL语言的分类。接着,指导如何在Windows和Ubuntu系统上安装MySQL,并提供了启动、停止和重启服务的命令。文章还涵盖了Navicat的使用,包括安装、登录和新建表格等步骤。最后,介绍了MySQL中的数据类型和字段约束,如主键、外键、非空和唯一等。
71 3
Mysql学习笔记(一):数据库详细介绍以及Navicat简单使用
|
1月前
|
SQL 关系型数据库 MySQL
Vanna使用ollama分析本地数据库
这篇文章详细介绍了如何使用Vanna和Ollama框架来分析本地数据库,实现自然语言查询转换为SQL语句并与数据库交互的过程。
182 7
Vanna使用ollama分析本地数据库
|
1月前
|
缓存 算法 关系型数据库
Mysql(3)—数据库相关概念及工作原理
数据库是一个以某种有组织的方式存储的数据集合。它通常包括一个或多个不同的主题领域或用途的数据表。
52 5
Mysql(3)—数据库相关概念及工作原理
|
20天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
38 2
|
1月前
|
SQL 关系型数据库 数据库
SQL数据库:核心原理与应用实践
随着信息技术的飞速发展,数据库管理系统已成为各类组织和企业中不可或缺的核心组件。在众多数据库管理系统中,SQL(结构化查询语言)数据库以其强大的数据管理能力和灵活性,广泛应用于各类业务场景。本文将深入探讨SQL数据库的基本原理、核心特性以及实际应用。一、SQL数据库概述SQL数据库是一种关系型数据库
59 5
|
1月前
|
存储 分布式计算 数据库
阿里云国际版设置数据库云分析工作负载的 ClickHouse 版
阿里云国际版设置数据库云分析工作负载的 ClickHouse 版
|
1月前
|
SQL 自然语言处理 关系型数据库
Vanna使用ollama分析本地MySQL数据库
这篇文章详细介绍了如何使用Vanna结合Ollama框架来分析本地MySQL数据库,实现自然语言查询功能,包括环境搭建和配置流程。
201 0