工作流中的資料持久化詳解!Activiti框架中JPA的使用分析

攻城獅Chova發表於2021-06-08

Activiti中JPA簡介

  • 可以使用JPA實體作為流程變數, 並進行操作:
    • 基於流程變數更新已有的JPA實體,可以在使用者任務的表單中填寫或者由服務任務生成
    • 重用已有的領域模型,不需要編寫顯示的服務獲取實體或者更新實體的值
    • 根據已有實體的屬性做出判斷(閘道器即分支聚合)

JPA實體要求

  • Activiti中JPA只支援符合以下要求的實體:
    • 實體應該使用JPA註解進行配置, 支援欄位和屬性訪問兩種方式.@MappedSuperclass也要能夠被使用
    • 實體中應該有一個使用@Id註解的主鍵,不支援複合主鍵@EmbeddedId 和 @IdClass:
      • Id欄位或者屬性能夠使用JPA規範支援的任意型別:
        • 原生態資料型別和他們的包裝型別(Boolean除外)
        • String
        • BigInteger
        • BigDecimal
        • java.util.Date
        • java.sql.Date

JPA配置

  • 引擎必須有一個對EntityManagerFactory的引用才能夠使用JPA的實體,這樣可以通過配置引用或者提供一個持久化單元名稱
  • 作為變數的JPA實體將會被自動檢測並進行相應的處理
  • 使用jpaPersistenceUnitName配置:
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">

    <!-- 資料庫的配置 -->
    <property name="databaseSchemaUpdate" value="true" />
    <property name="jdbcUrl" value="jdbc:h2:mem:JpaVariableTest;DB_CLOSE_DELAY=1000" />

    <property name="jpaPersistenceUnitName" value="activiti-jpa-pu" />
    <property name="jpaHandleTransaction" value="true" />
    <property name="jpaCloseEntityManager" value="true" />

    <!-- job executor configurations -->
    <property name="jobExecutorActivate" value="false" />

    <!-- mail server configurations -->
    <property name="mailServerPort" value="5025" />
</bean>
  • 配置一個自定義的EntityManagerFactory,
    • 這裡使用了OpenJPA實體管理器
    • 該程式碼片段僅僅包含與例子相關的beans,去掉了其他beans.
    • OpenJPA實體管理的完整並可以使用的例子可以在activiti-spring-examples(/activiti-spring/src/test/java/org/activiti/spring/test/jpa/JPASpringTest.java) 中找到
<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.activiti.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="jobExecutorActivate" value="false" />
</bean>
  • 也可以在程式設計式建立一個引擎時完成配置:
ProcessEngine processEngine = ProcessEngineConfiguration
  .createProcessEngineConfigurationFromResourceDefault()
  .setJpaPersistenceUnitName("activiti-pu")
  .buildProcessEngine();

配置的屬性有:

  • jpaPersistenceUnitName: 使用持久化單元的名稱:
    • 要確保該持久化單元在類路徑下是可用的,預設的路徑是 /META-INF/persistence.xml
    • 要麼使用jpaEntityManagerFactory要麼或者是jpaPersistenceUnitName
  • jpaEntityManagerFactory: 一個實現了javax.persistence.EntityManagerFactorybean的引用:
    • 將被用來載入實體並且重新整理更新
    • 要麼使用jpaEntityManagerFactory要麼或者是jpaPersistenceUnitName
  • jpaHandleTransaction: 在被使用的EntityManager例項上,該標記表示流程引擎是否需要開始和提交或者回滾事務:
    • 當使用Java事務API(JTA) 時,設定為false
  • jpaCloseEntityManager: 該標記表示流程引擎是否應該關閉從 EntityManagerFactory獲取的EntityManager的例項:
    • EntityManager是由容器管理的時候需要設定為false: 當使用並不是單一事務作用域的擴充套件持久化上下文的時候

JPA用法

簡單示例

  • 首先,需要建立一個基於META-INF/persistence.xmlEntityManagerFactory作為持久化單元:包含持久化單元中所有的類和一些供應商特定的配置
  • 使用一個簡單的實體作為測試,其中包含有一個idString型別的value屬性,也將會被持久化
  • 在測試之前,建立一個實體並且儲存:
@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;
  }
}
  • 啟動一個新的流程例項,新增一個實體作為變數. 其他的變數,將會被儲存在流程引擎的持久化資料庫中.下一次獲取該變數的時候,將會根據該類和儲存IdEntityManager中載入:
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("entityToUpdate", entityToUpdate);

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("UpdateJPAValuesProcess", variables);
  • 流程定義中的第一個節點是一個服務任務,將會呼叫entityToUpdate上的setValue方法,其實就是之前在啟動流程例項時候設定的JPA變數並且將會從當前流程引擎的上下文關聯的EntityManager中載入:
<serviceTask id='theTask' name='updateJPAEntityTask' activiti:expression="${entityToUpdate.setValue('updatedValue')}" />
  • 當完成服務任務時,流程例項將會停留在流程定義中定義的使用者任務環節上:
    • 可以檢視該流程例項
    • EntityManager已經被重新整理了並且改變的實體已經被儲存進資料庫中
    • 獲取entityToUpdate的變數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())

查詢JPA流程變數

  • 以查詢某一JPA實體作為變數的ProcessInstancesExecutions
  • ProcessInstanceQueryExecutionQuery查詢中僅僅variableValueEquals(name, entity) 支援JPA實體變數:
    • [variableValueNotEquals],[variableValueGreaterThan],[variableValueGreaterThanOrEqual],[variableValueLessThan],[variableValueLessThanOrEqual]不被支援並且傳遞JPA實體值的時候會丟擲一個ActivitiException
ProcessInstance result = runtimeService.createProcessInstanceQuery().variableValueEquals("entityToQuery", entityToQuery).singleResult();   

使用Spring beans和JPA結合

  • JPASpringTest, 在activiti-spring-examples中:
    • 已經存在了一個使用JPA實體的Spring-bean, 用來儲存貸款申請
    • 使用Activiti,可以通過已經存在的bean獲取已經使用的實體,並使用它作為變數用於流程中
  • 流程定義步驟:
    • 服務任務:
      • 建立一個新的貸款申請,使用已經存在的LoanRequestBean接受啟動流程時候的變數(來自流程啟動時候的表單)
      • 使用activiti:resultVariable(作為一個變數對錶達式返回的結果進行儲存)將建立出來的實體作為變數進行儲存
    • 使用者任務:
      • 允許經理檢視貸款申請,並填入審批意見(同意/不同意)
      • 審批意見將作為一個boolean變數approvedByManager進行儲存
    • 服務任務:
      • 更新貸款申請實體,因此該實體與流程保持同步
    • 根據貸款申請實體變數approved的值,將利用唯一閘道器自動決定下一步該選擇那一條路徑:
      • 當申請批准,流程結束
      • 否則,一個額外的任務將會使用(傳送拒絕信),這樣就可以傳送拒絕信手動通知客戶
        在這裡插入圖片描述
<?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:activiti="http://activiti.org/bpmn"
  targetNamespace="org.activiti.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'
      activiti:expression="${loanRequestBean.newLoanRequest(customerName, amount)}"
      activiti: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'
      activiti: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>

上面的例子展示了JPA結合Spring和引數化方法表示式的強大優勢 :所有的流程就不需要自定義java程式碼(Spring bean除外),大幅度的加快了流程部署

相關文章