Learning Notes on Flowable
Reference Documentation:
l https://www.freesion.com/article/50381339562/ (Adding custom properties to FLOWABLE, future considerations may include adding the name of selected users)
l https://tkjohn.github.io/flowable-userguide/
l https://www.cnblogs.com/phyger/p/14067201.html (Explanation of Flowable table structure and data dictionary)
l http://www.blackzs.com/archives/1557
(flowable model design tool)
All Flowable database tables are prefixed with ACT_. The second part is a two-character identifier that indicates the table’s function. The naming convention for service APIs also follows this pattern.
ACT_RE_*: 'RE' stands for Repository. Tables with this prefix store static data, such as process definitions and process resources (e.g., images, rules, etc.).
ACT_RU_*: 'RU' stands for Runtime. These tables store runtime data, such as process instances, user tasks, variables, and jobs. Flowable retains runtime data only during the execution of a process instance and removes it once the instance completes, ensuring that runtime tables remain small and efficient.
ACT_HI_*: 'HI' stands for History. These tables store historical data, such as completed process instances, tasks, and variables.
ACT_GE_*: General data used in multiple parts of the system.
a. General Data (2)
ACT_GE_BYTEARRAY – Stores general process definitions and resources
ACT_GE_PROPERTY – System-wide properties
b. Process History (8)
ACT_HI_ACTINST – Historical process activity instances
ACT_HI_ATTACHMENT – Historical process attachments
ACT_HI_COMMENT – Historical comments
ACT_HI_DETAIL – Detailed information about process execution
ACT_HI_IDENTITYLINK – User relationships in historical process executions
ACT_HI_PROCINST – Historical process instances
ACT_HI_TASKINST – Historical task instances
ACT_HI_VARINST – Historical variable data from process executions
c. User and Group Tables (9)
ACT_ID_BYTEARRAY – Stores binary data related to users and groups
ACT_ID_GROUP – Information about user groups
ACT_ID_INFO – Detailed user information
ACT_ID_MEMBERSHIP – Relationships between users and groups
ACT_ID_PRIV – User and group permissions
ACT_ID_PRIV_MAPPING – Mapping of permissions to users or groups
ACT_ID_PROPERTY – Properties related to users and groups
ACT_ID_TOKEN – System login logs
ACT_ID_USER – User information
d. Process Definition Tables (3)
ACT_RE_DEPLOYMENT Deployment unit information
ACT_RE_MODEL Model information
ACT_RE_PROCDEF Deployed process definitions
e. Runtime Instance Tables (10)
ACT_RU_DEADLETTER_JOB Running task table
ACT_RU_EVENT_SUBSCR Runtime events
ACT_RU_EXECUTION Runtime process execution instance
ACT_RU_HISTORY_JOB Historical job table
ACT_RU_IDENTITYLINK Runtime user relationship information
ACT_RU_JOB Runtime job table
ACT_RU_SUSPENDED_JOB Suspended job table
ACT_RU_TASK Runtime task table
ACT_RU_TIMER_JOB Timer job table
ACT_RU_VARIABLE Runtime variable table
f. Other Tables (2)
ACT_EVT_LOG - Event log table
ACT_PROCDEF_INFO - Process definition information
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();
When ProcessEngines.getDefaultProcessEngine() is called for the first time, the process engine is initialized and built. Subsequent calls return the same instance. You can create a process engine using ProcessEngines.init() and destroy it using ProcessEngines.destroy().
ProcessEngines scans the flowable.cfg.xml and flowable-context.xml configuration files. For flowable.cfg.xml, the process engine is built in the standard Flowable way: ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(inputStream).buildProcessEngine(). For flowable-context.xml, the engine is built using Spring: the Spring application context is first created, and the process engine is retrieved from it.
All services in Flowable are stateless, which means you can run Flowable on multiple nodes in a cluster using a shared database without worrying about where the previous call was executed. Any call to any service is idempotent, regardless of which node it is executed on.
The RepositoryService is typically the first service used when working with Flowable. It provides functionality for managing and controlling deployments and process definitions. A process definition is a Java object that represents a BPMN 2.0 process, defining the structure and behavior of each step. A deployment is a packaging unit in Flowable, which may include multiple BPMN 2.0 XML files and other resources. Developers can decide what to include in a deployment, such as a single process or multiple processes with related resources (e.g., a ‘hr-processes’ deployment may contain all HR-related resources). The RepositoryService allows you to deploy such packages. Deployment means uploading the package to the engine, which will analyze and validate all processes before storing them in the database. After deployment, the package can be used in the system, and all processes in the package can be started.
In addition, this service supports:
Querying existing deployments and process definitions.
Pausing or activating specific processes or the entire deployment.
Retrieving resources, such as files stored in the deployment or automatically generated process diagrams.
Retrieving a POJO version of the process definition, which can be viewed in Java rather than XML.
In contrast to the RepositoryService, which provides static information, the RuntimeService is used to start new process instances from a process definition. A process instance is the actual execution of a process definition. At any time, a process definition may have multiple running instances. The RuntimeService is also used to read and store process variables, which are data elements within a process instance that can be used in various parts of the process (e.g., exclusive gateways often use variables to determine the next step). It can also be used to query process instances and executions, where an execution represents the token in BPMN 2.0, pointing to the current position of the process instance. Finally, it allows notifying the process instance when it is waiting for an external trigger, enabling it to continue.
For a BPM engine like Flowable, human interaction is central. All task-related operations are managed by the TaskService, including:
Querying tasks assigned to users or groups.
Creating standalone tasks that are not associated with a process instance.
Assigning a task to a specific user or associating a user with the task.
Claiming and completing tasks. Claiming means a user takes ownership of the task, and completing means the task is completed.
The IdentityService is simple and is used to manage users and groups (create, update, delete, query, etc.). Note that Flowable does not perform user validation at runtime. For example, a task can be assigned to any user, and the engine does not verify whether the user exists in the system. This is because Flowable often integrates with external systems like LDAP or Active Directory.
The FormService is optional. Flowable can function without it, and no features are lost. This service introduces the concepts of start forms (shown before a process instance starts) and task forms (shown when a user completes a task). Forms can be defined in the BPMN 2.0 process definition, and the service exposes them in a simple way. It should be noted that forms do not have to be embedded in the process definition, making this service optional.
The HistoryService exposes all historical data collected by the Flowable engine. This includes information such as the start time of a process instance, who executed which task, the time taken to complete a task, and the execution path of each process instance. This service is primarily used to query this historical data.
The ManagementService is generally not used when developing user applications with Flowable. It allows querying and managing database tables and jobs, which are used in many parts of Flowable, such as timers, asynchronous continuation, and delayed suspension/activation. These will be discussed in more detail later.
The DynamicBpmnService allows modifying parts of a process definition without redeploying it. For example, you can change the assignee of a user task or update the class name of a service task.
The Process has multiple deployment versions, and the model also has multiple deployment versions. This benefit allows the old and new processes to run simultaneously without interfering with each other.
Flowable only has three concepts of assignees: assignee, candidate, and candidate group. You can dynamically set them by setting ${userid}, but this is not very convenient, because for the system, there are many flexible assignees, such as leaders, specifying the next role, department, or assignee (multiple people, multiple departments, multiple roles, etc.). To achieve high flexibility, it is necessary to dynamically set the assignee, candidate, and candidate group in the code. However, due to the complexity of Flowable processes (main process, sub-process, various tasks (usertask is just one of them)), it is very difficult to find the next node. However, by limiting the use of sub-processes and other tasks during the design phase, it can reduce a lot of complexity.
Also, keep in mind that all services in Flowable are mostly executed asynchronously in multiple threads. This means that after the code completes a task, the database data may not have started to change yet. At this time, if you retrieve the new task, you may get old data or empty data. Currently, this problem is solved by sleeping for 2 seconds, but this approach is not ideal. It can be replaced by two front-end requests, but this would be more performance-consuming.
Flowable has its own expression judgment method. Through the command method, you can use Flowable’s built-in expression calculation. This function has already been implemented in this project.
Concepts such as process, deployment, model, task, instance, historical instance, multi-instance, listener, assignee, variable, initiator, suspension, activation, jump condition, skip condition, notes, etc., should all be familiar, otherwise it is difficult to understand the operation mechanism of Flowable.
There are two ways to query data from the engine: the Query API and the native query. The Query API can use a chainable API, and it allows type-safe queries through programming. You can add various conditions to the query (all conditions are used as AND logic), and you can also explicitly specify the sorting method. Here is an example code:
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("kermit")
.processVariableValueEquals("orderId", "0815")
.orderByDueDate().asc()
.list();
Sometimes more complex queries are needed, such as using the OR operator or when the Query API cannot meet the query conditions. For such needs, we provide a native query that allows you to write your own SQL queries. The return type is determined by the query object used, and the data will be mapped to the correct object (such as Task, ProcessInstance, Execution, etc.). The query is performed in the database, so it requires the use of the table names and column names defined in the database. This requires understanding the internal data structure, so it is recommended to use native queries with caution. The database table names can be read through the API, which minimizes dependencies.
List<Task> tasks = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) +
" T WHERE T.NAME_ = #{taskName}")
.parameter("taskName", "gonzoTask")
.list();
long count = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, " +
managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")
.count();
During the execution of a process instance, data is required at various stages. In Flowable, this data is referred to as variables, which are stored in the database. Variables can be used in expressions (e.g., to determine the correct outgoing path in an exclusive gateway), or in Java service tasks to invoke external services (e.g., to provide input parameters or store results).
A process instance can hold process variables, and both user tasks and executions (pointers to the current active node in the process) can also hold variables. A process instance can contain an arbitrary number of variables, with each variable stored as a row in the ACT_RU_VARIABLE database table.
By simply adding dependencies to the classpath and using the @SpringBootApplication annotation, a number of automated configurations are performed behind the scenes:
An in-memory database (such as H2) is automatically created and configured for the Flowable process engine.
The ProcessEngine, CmmnEngine, DmnEngine, FormEngine, ContentEngine, and IdmEngine beans are created and exposed as Spring beans.
All Flowable services are registered as Spring beans.
A Spring Job Executor is created to manage asynchronous tasks.
Additionally:
Any BPMN 2.0 process definitions in the processes directory are automatically deployed.
Any CMMN 1.1 case definitions in the cases directory are automatically deployed.
Any form definitions in the forms directory are automatically deployed.
To start a process, a process definition (i.e., a ProcessDefinition) must first be deployed. Once deployed, the process instance can be started using the RuntimeService:
runtimeService.startProcessInstanceByKey("processKey", variables);
After the instance is started, an execution is created, which serves as the pointer to the current active node in the process. Subsequent process flow is driven by this execution.
A Process represents the overall definition of a workflow, which may include multiple process diagrams, sub-processes, and decision models. It can be packaged and deployed in Flowable. After deployment, a Deployment ID is generated for information association.
A Model refers to a process model designed using Flowable’s built-in modeler. A newly created Model only stores basic metadata in the ACT_RE_MODEL table. Once the model is designed and saved, the corresponding ACT_GE_BYTEARRAY table stores the XML and PNG data. A Process can be converted into a Model for reuse.
Deployment is the process of publishing a model so that it can be executed. After deployment, the related data is stored in the ACT_RE_DEPLOYMENT table.
act_re_deployment:This table stores deployment metadata.
Each deployment generates one record, and its primary key is used as a foreign key in act_re_procdef and act_ge_bytearray.
act_re_procdef:This table stores process definition information.
A single deployment may include multiple BPMN files (e.g., in a ZIP/BAR package). In such cases, act_re_deployment has one record, while act_re_procdef has multiple records (one per process definition). The DEPLOYMENT_ID_ field in this table is used to link to act_re_deployment.
act_ge_bytearray:This table stores the actual binary content of process resources.
Each deployment generates two records: one for the BPMN file content (stored in the BYTES field), and one for the image (stored as binary data).
Note: After deployment, BPMN content can be parsed to automatically generate the process diagram, enabling visual tracking of the workflow.
act_re_model:This table was previously used to store model metadata, but it is no longer used in XML deployment. Flowable now uses act_de_model to store process model information.
Reference:
https://blog.csdn.net/weixin_39604685/article/details/111947153
In transaction usage, the most commonly used propagation behavior is REQUIRES, which is suitable for most MIS systems, where a transaction can be wrapped around the entire business layer to meet requirements.
However, Spring provides more than just this. For complex business logic, Spring offers various transaction propagation behaviors to meet different needs.
The transaction propagation behaviors in Spring are as follows:
REQUIRES: Supports the current transaction; if there is no transaction, a new one is created. This is the most commonly used behavior.
SUPPORTS: Supports the current transaction; if there is no transaction, it is executed in a non-transactional manner.
MANDATORY: Requires the current transaction; if there is no transaction, an exception is thrown.
REQUIRES_NEW: Creates a new transaction; if a transaction already exists, it is suspended.
NOT_SUPPORTED: Executes the operation in a non-transactional manner; if a transaction exists, it is suspended.
NEVER: Executes the operation in a non-transactional manner; if a transaction exists, an exception is thrown.
NESTED: Creates a new transaction; if a transaction exists, it is suspended. Unlike REQUIRES_NEW, it is associated with the parent transaction and uses a savepoint.
REQUIRES_NEW: When a REQUIRES method A calls a REQUIRES_NEW method B, method B will start a new transaction, which is independent of the transaction of method A. That is, if B fails and rolls back, it will not affect A. Similarly, if B has already been committed and A fails, B will not roll back.
NESTED: This behavior is different from REQUIRES_NEW in that B’s transaction is related to A’s transaction. Only when A’s transaction is committed will B’s transaction also be committed. If A fails, both A and B transactions are rolled back. If B fails, only B is rolled back, and A is rolled back to the savepoint.
After discussing transaction propagation behavior, we now turn to transaction isolation levels. The purpose of isolation levels is to balance performance and data consistency. It is not always better to have a higher level; the best choice is the one that is appropriate for the use case.
The transaction isolation levels are as follows:
SERIALIZABLE: The strictest level, where transactions are executed sequentially, resulting in the highest resource consumption.
REPEATABLE READ: Ensures that a transaction will not modify data that has been read but not yet committed by another transaction.
READ COMMITTED: The default isolation level in most mainstream databases. It ensures that a transaction will not read data that has been modified but not yet committed by a parallel transaction. It is suitable for most systems.
READ UNCOMMITTED: Ensures that no invalid data is read during the read process.
To understand these four levels, it’s important to be aware of three common issues:
dirty reads:Occur when a transaction reads uncommitted data from another transaction. If the first transaction rolls back, the second transaction will have read invalid data.
non-repeatable reads:Occur when a transaction reads the same data twice, and the data changes between the two reads, causing inconsistency in the transaction.
phantom read:Similar to non-repeatable reads, but in this case, the inconsistency is due to changes in the condition set rather than the data itself. For example, a query like SELECT id WHERE name = "ppgogo*" may return 6 rows the first time, but after a change in the condition (e.g., a name update), it may return 7 rows.
Flowable uses a transaction interceptor to manage transactions, and the propagation behavior used is REQUIRES.
扫码关注不迷路!!!
郑州升龙商业广场B座25层
service@iqiqiqi.cn
联系电话:187-0363-0315
联系电话:199-3777-5101