Activiti中JPA簡介
- 可以使用JPA實體作為流程變數, 並進行操作:
- 基於流程變數更新已有的JPA實體,可以在使用者任務的表單中填寫或者由服務任務生成
- 重用已有的領域模型,不需要編寫顯示的服務獲取實體或者更新實體的值
- 根據已有實體的屬性做出判斷(閘道器即分支聚合)
JPA實體要求
- Activiti中JPA只支援符合以下要求的實體:
- 實體應該使用JPA註解進行配置, 支援欄位和屬性訪問兩種方式.@MappedSuperclass也要能夠被使用
- 實體中應該有一個使用@Id註解的主鍵,不支援複合主鍵@EmbeddedId 和 @IdClass:
- Id欄位或者屬性能夠使用JPA規範支援的任意型別:
- 原生態資料型別和他們的包裝型別(Boolean除外)
- String
- BigInteger
- BigDecimal
- java.util.Date
- java.sql.Date
- Id欄位或者屬性能夠使用JPA規範支援的任意型別:
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.EntityManagerFactory的bean的引用:
- 將被用來載入實體並且重新整理更新
- 要麼使用jpaEntityManagerFactory要麼或者是jpaPersistenceUnitName
- jpaHandleTransaction: 在被使用的EntityManager例項上,該標記表示流程引擎是否需要開始和提交或者回滾事務:
- 當使用Java事務API(JTA) 時,設定為false
- jpaCloseEntityManager: 該標記表示流程引擎是否應該關閉從 EntityManagerFactory獲取的EntityManager的例項:
- 當EntityManager是由容器管理的時候需要設定為false: 當使用並不是單一事務作用域的擴充套件持久化上下文的時候
JPA用法
簡單示例
- 首先,需要建立一個基於META-INF/persistence.xml的EntityManagerFactory作為持久化單元:包含持久化單元中所有的類和一些供應商特定的配置
- 使用一個簡單的實體作為測試,其中包含有一個id和String型別的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;
}
}
- 啟動一個新的流程例項,新增一個實體作為變數. 其他的變數,將會被儲存在流程引擎的持久化資料庫中.下一次獲取該變數的時候,將會根據該類和儲存Id從EntityManager中載入:
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實體作為變數的ProcessInstances和Executions
- 在ProcessInstanceQuery和ExecutionQuery查詢中僅僅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除外),大幅度的加快了流程部署