Flowable 引擎 JPA 集成实战:将 JPA 实体作为流程变量使用
【免费下载链接】flowable-engineA compact and highly efficient workflow and Business Process Management (BPM) platform for developers, system admins and business users.项目地址: https://gitcode.com/GitHub_Trending/fl/flowable-engine
Flowable 工作流引擎允许开发者将 JPA(Jakarta Persistence)实体直接作为流程变量使用,从而在用户任务表单与 Service 任务之间复用现有领域模型,无需为每次读写实体编写专门的查询服务。本文基于 Flowable 官方文档《JPA》一章,结合当前仓库中的源码与测试用例,系统讲解 JPA 实体的支持范围、引擎配置、变量读写原理、流程查询限制,以及结合 Spring Bean 的完整实战案例。
JPA 实体作为流程变量的能力与价值
在 Flowable 中,流程变量(Process Variable)是流程实例运行时的数据载体。除了字符串、数字、日期、序列化对象等常规类型,JPA 实体也可以直接存入变量中,并带来三类核心能力:
- 更新既有 JPA 实体:实体可以作为流程变量被用户任务表单回填、被 Service 任务中的表达式修改,改动会自动同步回数据库;
- 复用现有领域模型:不需要为每个实体编写显式的获取与更新服务,实体本身即可在流程中流转;
- 基于实体属性做决策:排他网关(Exclusive Gateway)的条件表达式可以直接读取实体属性,驱动流程分支走向。
从实现角度看,Flowable 会在变量写入时解析实体的类名与主键值并持久化引用信息;下次读取变量时,再从与引擎关联的EntityManager中按“类 + 主键”重新加载实体。这意味着引擎中保存的并非实体对象的序列化副本,而是指向数据库行的引用,读取时总是拿到数据库中的最新状态。
支持范围与前置要求
并非所有 JPA 实体都能作为流程变量,只有满足以下条件的实体才被支持(对应测试见 JPAVariableTest.java):
- 注解方式:实体必须使用 JPA 注解配置,字段访问(field access)与属性访问(property access)两种方式均受支持,同时支持映射超类(Mapped Superclass)中定义的主键;
- 主键要求:实体必须具有标注
@Id的主键。复合主键不受支持(@EmbeddedId与@IdClass均不支持); - 主键类型:
@Id字段/属性的类型须属于 JPA 规范支持的类型:基本类型及其包装类(boolean 除外)、String、BigInteger、BigDecimal、java.util.Date和java.sql.Date。
测试代码对这些约束有非常详尽的验证:testStoreJPAEntityAsVariable用例逐一使用byte、short、int、long、float、double、char、String、Date、SQLDate、BigDecimal、BigInteger共 12 种主键类型启动流程实例并回读断言;testIllegalEntities用例则验证了四类非法场景会抛出异常:
- 使用复合主键(
@EmbeddedId)的实体 → 抛出FlowableException,提示 “only single-valued primary keys are supported on JPA-entities”; - 主键值为
null→ 抛出FlowableIllegalArgumentException,提示 “Value of primary key for JPA-Entity cannot be null”; - 主键类型非法(如
Calendar)→ 抛出FlowableIllegalArgumentException,提示 “Unsupported Primary key type for JPA-Entity”; - 实体有主键但尚未持久化(数据库中不存在该记录)→ 读取变量时抛出
FlowableException,提示 “Entity does not exist”。
引擎配置:两种接入 EntityManagerFactory 的方式
要让引擎识别并处理 JPA 实体变量,必须为引擎提供EntityManagerFactory引用,有两种途径:指定 persistence-unit 名称(引擎自行创建工厂)或注入现成的EntityManagerFactory实例。JPA 实体一旦作为变量使用会被自动检测并处理,无需额外开关。
方式一:通过 jpaPersistenceUnitName 指定持久化单元
以下配置基于StandaloneInMemProcessEngineConfiguration,直接给出 persistence-unit 名称,引擎会在启动时通过JpaHelper.createEntityManagerFactory(...)调用jakarta.persistence.Persistence.createEntityManagerFactory(persistenceUnitName)创建工厂(见 JpaHelper.java):
<bean id="processEngineConfiguration" class="org.flowable.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration"> <!-- Database configurations --> <property name="databaseSchemaUpdate" value="true" /> <property name="jdbcUrl" value="jdbc:h2:mem:JpaVariableTest;DB_CLOSE_DELAY=1000" /> <property name="jpaPersistenceUnitName" value="flowable-jpa-pu" /> <property name="jpaHandleTransaction" value="true" /> <property name="jpaCloseEntityManager" value="true" /> <!-- job executor configurations --> <property name="asyncExecutorActivate" value="false" /> <!-- mail server configurations --> <property name="mailServerPort" value="5025" /> </bean>注意:persistence-unit 必须位于 classpath 中,按照 JPA 规范其默认位置为/META-INF/persistence.xml。flowable-jpa-pu这个名字在仓库的测试中对应org/flowable/standalone/jpa/META-INF/persistence.xml,其中声明了参与持久化单元的实体类与厂商相关配置。
方式二:通过 jpaEntityManagerFactory 注入自定义工厂
若引擎运行在 Spring 容器内,更常见的做法是把自己创建的EntityManagerFactoryBean 注入给引擎。下面示例使用 Spring 的LocalContainerEntityManagerFactoryBean配合 OpenJPA 厂商适配器(示例仅展示与 JPA 相关的 Bean,其余省略;完整可运行示例见 JPASpringTest.java 所在的 flowable-spring 测试模块):
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitManager" ref="pum"/> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"> <property name="databasePlatform" value="org.apache.openjpa.jdbc.sql.H2Dictionary" /> </bean> </property> </bean> <bean id="processEngineConfiguration" class="org.flowable.spring.SpringProcessEngineConfiguration"> <property name="dataSource" ref="dataSource" /> <property name="transactionManager" ref="transactionManager" /> <property name="databaseSchemaUpdate" value="true" /> <property name="jpaEntityManagerFactory" ref="entityManagerFactory" /> <property name="jpaHandleTransaction" value="true" /> <property name="jpaCloseEntityManager" value="true" /> <property name="asyncExecutorActivate" value="false" /> </bean>jpaEntityManagerFactory接受任何实现了jakarta.persistence.EntityManagerFactory的对象,因此无论是 OpenJPA、Hibernate 还是 EclipseLink 的工厂实例都可以接入。
方式三:编程式构建引擎
不使用 Spring/XML 时,可以通过ProcessEngineConfiguration的 setter 方法在代码中完成同样配置:
ProcessEngine processEngine = ProcessEngineConfiguration .createProcessEngineConfigurationFromResourceDefault() .setJpaPersistenceUnitName("flowable-pu") .buildProcessEngine();四个核心配置属性说明
| 属性 | 类型 | 说明 |
|---|---|---|
jpaPersistenceUnitName | String | 要使用的 persistence-unit 名称,persistence-unit 需在 classpath 上(默认位置/META-INF/persistence.xml)。与jpaEntityManagerFactory二选一 |
jpaEntityManagerFactory | jakarta.persistence.EntityManagerFactory | 由外部创建并注入的EntityManagerFactory引用,用于加载实体与刷新(flush)更新。与jpaPersistenceUnitName二选一 |
jpaHandleTransaction | boolean | 指示引擎是否在使用的EntityManager实例上自行 begin/commit/rollback 事务。使用 JTA(Java Transaction API)时应设为 false |
jpaCloseEntityManager | boolean | 指示引擎是否关闭从EntityManagerFactory获取的EntityManager实例。当EntityManager由容器管理时(例如使用不受单个事务作用域限制的 Extended Persistence Context)应设为 false |
这四个属性在引擎初始化阶段被装配进EntityManagerSessionFactory(见 ProcessEngineConfigurationImpl.java):当jpaPersistenceUnitName非空时先用它创建工厂,随后将工厂连同jpaHandleTransaction、jpaCloseEntityManager一起注册为引擎的EntityManagerSession会话工厂,供变量存取环节按需获取EntityManager。
使用示例:一步步理解 JPA 变量读写
官方文档以JPAVariableTest.testUpdateJPAEntityValues为例(完整源码见 JPAVariableTest.java),演示实体变量的完整生命周期。这里按步骤拆解。
第一步:准备实体类与持久化单元
测试使用的实体非常简单,包含一个id和一个字符串value属性,两者都持久化。注意其@Id注解标注在字段上(字段访问方式):
@Entity(name = "JPA_ENTITY_FIELD") public class FieldAccessJPAEntity { @Id @Column(name = "ID_") private Long id; private String value; public FieldAccessJPAEntity() { // Empty constructor needed for JPA } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }测试启动前会先创建EntityManagerFactory(基于META-INF/persistence.xml中的 persistence-unit,其中声明了要纳入持久化单元的实体类与厂商配置),并提前在数据库中持久化一个entityToUpdate实例(id=3)。
第二步:将实体作为变量启动流程实例
启动流程时把实体放入变量 Map。与其他变量一样,实体引用信息会存入引擎自身的持久化存储中;当变量再次被请求时,引擎会根据存储的类名和主键从EntityManager中重新加载实体:
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("entityToUpdate", entityToUpdate); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey( "UpdateJPAValuesProcess", variables);第三步:在 ServiceTask 中通过表达式修改实体
流程定义的第一个节点是一个 ServiceTask,通过flowable:expression调用变量上的方法setValue。这里的entityToUpdate会解析为启动流程时设置的 JPA 变量,并从引擎上下文关联的EntityManager中加载:
<serviceTask id='theTask' name='updateJPAEntityTask' flowable:expression="${entityToUpdate.setValue('updatedValue')}" />第四步:flush 后读取最新实体
ServiceTask 完成后,流程实例会在一个 userTask 处等待。此时EntityManager已被 flush,实体的修改已推送到数据库。随后通过runtimeService.getVariable再次读取变量时,实体被重新加载,value属性已经是updatedValue:
// Servicetask in process 'UpdateJPAValuesProcess' should have set value on entityToUpdate. Object updatedEntity = runtimeService.getVariable(processInstance.getId(), "entityToUpdate"); assertTrue(updatedEntity instanceof FieldAccessJPAEntity); assertEquals("updatedValue", ((FieldAccessJPAEntity)updatedEntity).getValue());值得补充的是,仓库测试还覆盖了更多变量操作边界:testStoreJPAEntityAsVariable验证了实体变量可被置为null后再恢复、以及 12 种主键类型全部可用;testStoreJPAEntityListAsVariable验证了实体列表也可以作为变量存取;testReplaceExistingJPAEntityWithAnotherOfSameType验证了同一变量可被替换为同类型的另一实体。列表变量的内部实现会将每个元素按实体引用解析,因此列表必须是“纯 JPA 实体列表”,混入不可序列化的非 JPA 对象会抛出FlowableException。
查询带 JPA 实体变量的流程实例
可以通过ProcessInstanceQuery和ExecutionQuery查询“某个变量的值等于指定 JPA 实体”的流程实例或执行实例:
ProcessInstance result = runtimeService.createProcessInstanceQuery() .variableValueEquals("entityToQuery", entityToQuery).singleResult();需要注意限制:对 JPA 实体变量,查询仅支持variableValueEquals(name, entity)一种操作符。variableValueNotEquals、variableValueGreaterThan、variableValueGreaterThanOrEqual、variableValueLessThan、variableValueLessThanOrEqual均不支持——当传入 JPA 实体作为值时,这些方法会抛出FlowableException。
这一点在 JPAVariableTest.java 的testQueryJPAVariable用例中得到完整验证:同类型不同主键的实体查询不到结果;五种不支持的比较操作符全部抛出FlowableIllegalArgumentException,消息为 “JPA entity variables can only be used in 'variableValueEquals'”。
高级示例:Spring Bean + JPA 的贷款审批流程
JPASpringTest展示了 JPA 与 Spring Bean 组合的更复杂场景,其对应源码位于 flowable-spring 测试模块(见 LoanRequestBean.java、LoanRequest.java、JpaTest.java)。场景如下:
- 已存在一个使用 JPA 实体存储贷款申请(LoanRequest)的 Spring Bean;
- 借助 Flowable,可以直接把通过既有 Bean 获取的实体作为流程变量使用,整个流程由以下步骤构成:
- ServiceTask 创建贷款申请:调用既有的
LoanRequestBean,使用启动流程时传入的变量(例如来自启动表单的customerName、amount),并通过flowable:resultVariable把表达式结果存为流程变量loanRequest; - UserTask 人工审批:经理审查申请并批准/拒绝,结果存为布尔变量
approvedByManager; - ServiceTask 同步实体状态:把审批结果写回贷款申请实体,使实体与流程状态保持一致;
- 排他网关决策:根据实体属性
approved决定后续路径——批准则流程结束,否则多出一个“发送拒信”的人工任务,以便人工通知客户。
- ServiceTask 创建贷款申请:调用既有的
需要说明的是,该流程仅用于单元测试,因此没有包含任何表单。完整流程定义如下:
<?xml version="1.0" encoding="UTF-8"?> <definitions id="taskAssigneeExample" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="org.flowable.examples"> <process id="LoanRequestProcess" name="Process creating and handling loan request"> <startEvent id='theStart' /> <sequenceFlow id='flow1' sourceRef='theStart' targetRef='createLoanRequest' /> <serviceTask id='createLoanRequest' name='Create loan request' flowable:expression="${loanRequestBean.newLoanRequest(customerName, amount)}" flowable:resultVariable="loanRequest"/> <sequenceFlow id='flow2' sourceRef='createLoanRequest' targetRef='approveTask' /> <userTask id="approveTask" name="Approve request" /> <sequenceFlow id='flow3' sourceRef='approveTask' targetRef='approveOrDissaprove' /> <serviceTask id='approveOrDissaprove' name='Store decision' flowable:expression="${loanRequest.setApproved(approvedByManager)}" /> <sequenceFlow id='flow4' sourceRef='approveOrDissaprove' targetRef='exclusiveGw' /> <exclusiveGateway id="exclusiveGw" name="Exclusive Gateway approval" /> <sequenceFlow id="endFlow1" sourceRef="exclusiveGw" targetRef="theEnd"> <conditionExpression xsi:type="tFormalExpression">${loanRequest.approved}</conditionExpression> </sequenceFlow> <sequenceFlow id="endFlow2" sourceRef="exclusiveGw" targetRef="sendRejectionLetter"> <conditionExpression xsi:type="tFormalExpression">${!loanRequest.approved}</conditionExpression> </sequenceFlow> <userTask id="sendRejectionLetter" name="Send rejection letter" /> <sequenceFlow id='flow5' sourceRef='sendRejectionLetter' targetRef='theOtherEnd' /> <endEvent id='theEnd' /> <endEvent id='theOtherEnd' /> </process> </definitions>对应的 Spring Bean 实现(节选)如下,它通过@PersistenceContext注入EntityManager,并在@Transactional方法中创建并持久化贷款申请:
public class LoanRequestBean { @PersistenceContext private EntityManager entityManager; @Transactional public LoanRequest newLoanRequest(String customerName, Long amount) { LoanRequest lr = new LoanRequest(); lr.setCustomerName(customerName); lr.setAmount(amount); lr.setApproved(false); entityManager.persist(lr); return lr; } public LoanRequest getLoanRequest(Long id) { return entityManager.find(LoanRequest.class, id); } }这个例子虽然简单,却充分展示了 JPA + Spring + 带参数方法表达式组合的威力:整个流程除既有 Spring Bean 外不需要编写任何自定义 Java 代码,实体创建、状态同步、网关决策全部通过 BPMN 表达式完成,可以极大加速业务流程的开发。
小结与最佳实践
- JPA 实体变量让“流程编排”与“领域模型”直接打通,实体引用 + 主键的存储方式保证了读取时总能拿到数据库最新状态;
- 配置时务必在
jpaPersistenceUnitName与jpaEntityManagerFactory之间二选一;jpaHandleTransaction与jpaCloseEntityManager的取值要依据事务边界与容器管理方式(JTA / Extended Persistence Context)谨慎决定; - 实体主键必须是
@Id标注的单值主键,且类型须在规范允许范围内;布尔型主键与复合主键会直接报错; - 对 JPA 实体变量的查询仅支持
variableValueEquals,其余比较操作符会抛出异常; - 在生产中建议优先使用 Spring 注入的
EntityManagerFactory,将事务交由 Spring 事务管理器统一管理,jpaHandleTransaction设为false,以获得一致的事务语义。
相关参考资源:官方文档原文见 ch09-JPA.md;实体变量全部用例见 JPAVariableTest.java;Spring 集成用例见 JpaTest.java 及同目录下的LoanRequest.java、LoanRequestBean.java。
【免费下载链接】flowable-engineA compact and highly efficient workflow and Business Process Management (BPM) platform for developers, system admins and business users.项目地址: https://gitcode.com/GitHub_Trending/fl/flowable-engine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考