Flowable 6.6.0 BPMN使用者指南 - 17 高階用例 - 17.5 執行自定義SQL(Execute custom SQL)

月滿閒庭發表於2021-01-05

《Flowable 6.6.0 BPMN使用者指南》

1. 入門
2. 配置
3 The Flowable API
4 Flowable 6.6.0 BPMN使用者指南 - (4)Spring整合
5 Spring Boot
6 部署
7 BPMN 2.0簡介
8 BPMN 2.0的構造
9 表單(Forms)
10 流程例項遷移
11 JPA
12 歷史(History)
13 身份管理(Identity management)
14 REST API
15 CDI整合
16 LDAP整合
17 高階用例

有關Flowable的更多文件,參見:

《Flowable文件大全》


17.5 執行自定義SQL(Execute custom SQL)

The Flowable API allows for interacting with the database using a high level API. For example, for retrieving data the Query API and the Native Query API are powerful in its usage. However, for some use cases they might not be flexible enough. The following section describes how a completely custom SQL statement (select, insert, update and delete are possible) can be executed against the Flowable data store, but completely within the configured Process Engine (and thus levering the transaction setup for example).

To define custom SQL statements, the Flowable engine leverages the capabilities of its underlying framework, MyBatis. More info can be read in the MyBatis user guide.

Flowable API允許使用高階API(high level API)與資料庫互動。例如,對於檢索資料,Query API和Native Query API的用法非常強大。然而,對於某些用例,它們可能不夠靈活。以下部分描述如何針對可流動資料儲存執行完全自定義的SQL語句(可以查詢、插入、更新和刪除),但完全在配置的流程引擎中執行(例如,還可以利用事務設定)。

為了定義定製的SQL語句,Flowable引擎利用其底層框架MyBatis的功能。更多資訊可在MyBatis使用者指南(in the MyBatis user guide)中閱讀。

17.5.1 基於註解的對映語句(Annotation based Mapped Statements)

The first thing to do when using Annotation based Mapped Statements, is to create a MyBatis mapper class. For example, suppose that for some use case not the whole task data is needed, but only a small subset of it. A Mapper that could do this, looks as follows:

使用基於註解的對映語句(Annotation based Mapped Statements)時,首先要做的是建立一個MyBatis對映器類。例如,假設對於某些用例,不需要整個任務資料,而只需要其中的一小部分。以下Mapper可以做到:

public interface MyTestMapper {

    @Select("SELECT ID_ as id, NAME_ as name, CREATE_TIME_ as createTime FROM ACT_RU_TASK")
    List<Map<String, Object>> selectTasks();

}

This mapper must be provided to the Process Engine configuration as follows:

此對映器必須提供給流程引擎配置,如下所示:

...
<property name="customMybatisMappers">
  <set>
    <value>org.flowable.standalone.cfg.MyTestMapper</value>
  </set>
</property>
...

Notice that this is an interface. The underlying MyBatis framework will make an instance of it that can be used at runtime. Also notice that the return value of the method is not typed, but a list of maps (which corresponds to the list of rows with column values). Typing is possible with the MyBatis mappers if wanted.

To execute the query above, the managementService.executeCustomSql method must be used. This method takes in a CustomSqlExecution instance. This is a wrapper that hides the internal bits of the engine otherwise needed to make it work.

Unfortunately, Java generics make it a bit less readable than it could have been. The two generic types below are the mapper class and the return type class. However, the actual logic is simply to call the mapper method and return its results (if applicable).

注意這是一個介面。底層MyBatis框架將生成一個可以在執行時使用的例項。還要注意,該方法的返回值不是型別化的,而是一個對映列表(對應於具有列值的行的列表)。如果需要的話,可以MyBatis對映器可以型別化(Typing)。

若要執行上述查詢,則必須使用managementService.executeCustomSql 方法。此方法接受一個CustomSqlExecution 例項。這是一個包裝器,它隱藏了使之工作的引擎的內部部分資訊。

不幸的是,Java泛型使得它的可讀性比原來的要低一些。下面的兩個泛型型別是對映器類(mapper class)和返回型別類(return type class)。然而,實際的邏輯只是呼叫對映器方法並返回其結果(如果適用)。

CustomSqlExecution<MyTestMapper, List<Map<String, Object>>> customSqlExecution =
          new AbstractCustomSqlExecution<MyTestMapper, List<Map<String, Object>>>(MyTestMapper.class) {

  public List<Map<String, Object>> execute(MyTestMapper customMapper) {
    return customMapper.selectTasks();
  }

};

List<Map<String, Object>> results = managementService.executeCustomSql(customSqlExecution);

The Map entries in the list above will only contain id, name and create time in this case and not the full task object.

Any SQL is possible when using the approach above. Another more complex example:

在本例中,上面列表中的Map條目只包含id、name和create time,而沒有包含完整的task物件。

使用上述方法時,可以使用任何SQL。另一個更復雜的例子:

    @Select({
        "SELECT task.ID_ as taskId, variable.LONG_ as variableValue FROM ACT_RU_VARIABLE variable",
        "inner join ACT_RU_TASK task on variable.TASK_ID_ = task.ID_",
        "where variable.NAME_ = #{variableName}"
    })
    List<Map<String, Object>> selectTaskWithSpecificVariable(String variableName);

Using this method, the task table will be joined with the variables table. Only where the variable has a certain name is retained, and the task id and the corresponding numerical value is returned.

For a working example on using Annotation based Mapped Statements check the unit test org.flowable.standalone.cfg.CustomMybatisMapperTest and other classes and resources in folders src/test/java/org/flowable/standalone/cfg/ and src/test/resources/org/flowable/standalone/cfg/

使用此方法,任務表將與變數表聯接。只有在變數具有特定名稱的情況下才會保留,並返回任務id和相應的數值。
有關使用基於註解的對映語句的工作示例,請檢查單元測試org.flowable.standalone.cfg.CustomMybatisMapperTest以及src/test/java/org/flowable/standalone/cfg/和src/test/resources/org/flowable/standalone/cfg/資料夾中的其他類和資源

17.5.2 基於XML的對映語句(XML based Mapped Statements)

When using XML based Mapped Statements, statements are defined in XML files. For the use case where not the whole task data is needed, but only a small subset of it. The XML file can look as follows:

使用基於XML的對映語句時,語句是在XML檔案中定義的。對於不需要整個任務資料的用例,只需要其中一小部分資料。XML檔案可以如下所示:

<mapper namespace="org.flowable.standalone.cfg.TaskMapper">

  <resultMap id="customTaskResultMap" type="org.flowable.standalone.cfg.CustomTask">
    <id property="id" column="ID_" jdbcType="VARCHAR"/>
    <result property="name" column="NAME_" jdbcType="VARCHAR"/>
    <result property="createTime" column="CREATE_TIME_" jdbcType="TIMESTAMP" />
  </resultMap>

  <select id="selectCustomTaskList" resultMap="customTaskResultMap">
    select RES.ID_, RES.NAME_, RES.CREATE_TIME_ from ACT_RU_TASK RES
  </select>

</mapper>

Results are mapped to instances of org.flowable.standalone.cfg.CustomTask class which can look as follows:
結果對映到org.flowable.standalone.cfg.CustomTask類,如下所示:


public class CustomTask {

  protected String id;
  protected String name;
  protected Date createTime;

  public String getId() {
    return id;
  }
  public String getName() {
    return name;
  }
  public Date getCreateTime() {
    return createTime;
  }
}

Mapper XML files must be provided to the Process Engine configuration as follows:

對映器XML檔案必須提供給流程引擎配置,如下所示:

...
<property name="customMybatisXMLMappers">
  <set>
    <value>org/flowable/standalone/cfg/custom-mappers/CustomTaskMapper.xml</value>
  </set>
</property>
...

The statement can be executed as follows:

語句的執行方式如下:

List<CustomTask> tasks = managementService.executeCommand(new Command<List<CustomTask>>() {

      @SuppressWarnings("unchecked")
      @Override
      public List<CustomTask> execute(CommandContext commandContext) {
        return (List<CustomTask>) commandContext.getDbSqlSession().selectList("selectCustomTaskList");
      }
    });

For uses cases that require more complicated statements, XML Mapped Statements can be helpful. Since Flowable uses XML Mapped Statements internally, it’s possible to make use of the underlying capabilities.

Suppose that for some use case the ability to query attachments data is required based on id, name, type, userId, etc! To fulfill the use case a query class AttachmentQuery that extends org.flowable.engine.impl.AbstractQuery can be created as follows:

對於需要更復雜語句的用例,XML對映語句可能很有用。由於Flowable在內部使用XML對映語句,所以可以使用底層功能。

假設對於某些用例,需要根據id、name、type、userId等查詢附件資料!為了實現這個用例,查詢類AttachmentQuery 擴充套件org.flowable.engine.impl.AbstractQuery,可以按如下方式建立:


public class AttachmentQuery extends AbstractQuery<AttachmentQuery, Attachment> {

  protected String attachmentId;
  protected String attachmentName;
  protected String attachmentType;
  protected String userId;

  public AttachmentQuery(ManagementService managementService) {
    super(managementService);
  }

  public AttachmentQuery attachmentId(String attachmentId){
    this.attachmentId = attachmentId;
    return this;
  }

  public AttachmentQuery attachmentName(String attachmentName){
    this.attachmentName = attachmentName;
    return this;
  }

  public AttachmentQuery attachmentType(String attachmentType){
    this.attachmentType = attachmentType;
    return this;
  }

  public AttachmentQuery userId(String userId){
    this.userId = userId;
    return this;
  }

  @Override
  public long executeCount(CommandContext commandContext) {
    return (Long) commandContext.getDbSqlSession()
                   .selectOne("selectAttachmentCountByQueryCriteria", this);
  }

  @Override
  public List<Attachment> executeList(CommandContext commandContext, Page page) {
    return commandContext.getDbSqlSession()
            .selectList("selectAttachmentByQueryCriteria", this);
  }

Note that when extending AbstractQuery extended classes should pass an instance of ManagementService to super constructor and methods executeCount and executeList need to be implemented to call the mapped statements.

The XML file containing the mapped statements can look as follows:

請注意,在擴充套件AbstractQuery 時,擴充套件類應該將ManagementService 的例項傳遞給超級建構函式(super constructor),並且需要實現executeCount 和executeList 方法來呼叫對映的語句。
包含對映語句的XML檔案可以如下所示:

<mapper namespace="org.flowable.standalone.cfg.AttachmentMapper">

  <select id="selectAttachmentCountByQueryCriteria" parameterType="org.flowable.standalone.cfg.AttachmentQuery" resultType="long">
    select count(distinct RES.ID_)
    <include refid="selectAttachmentByQueryCriteriaSql"/>
  </select>

  <select id="selectAttachmentByQueryCriteria" parameterType="org.flowable.standalone.cfg.AttachmentQuery" resultMap="org.flowable.engine.impl.persistence.entity.AttachmentEntity.attachmentResultMap">
    ${limitBefore}
    select distinct RES.* ${limitBetween}
    <include refid="selectAttachmentByQueryCriteriaSql"/>
    ${orderBy}
    ${limitAfter}
  </select>

  <sql id="selectAttachmentByQueryCriteriaSql">
  from ${prefix}ACT_HI_ATTACHMENT RES
  <where>
   <if test="attachmentId != null">
     RES.ID_ = #{attachmentId}
   </if>
   <if test="attachmentName != null">
     and RES.NAME_ = #{attachmentName}
   </if>
   <if test="attachmentType != null">
     and RES.TYPE_ = #{attachmentType}
   </if>
   <if test="userId != null">
     and RES.USER_ID_ = #{userId}
   </if>
  </where>
  </sql>
</mapper>

Capabilities such as pagination, ordering, table name prefixing are available and can be used in the statements (since the parameterType is a subclass of AbstractQuery). Note that to map results the predefined org.flowable.engine.impl.persistence.entity.AttachmentEntity.attachmentResultMap resultMap can be used.

Finally, the AttachmentQuery can be used as follows:

分頁、排序、表名字首等功能都是可用的,可以在語句中使用(因為parameterType是AbstractQuery的一個子類)。請注意,要對映結果,可以使用預定義的org.flowable.engine.impl.persistence.entity.AttachmentEntity.attachmentResultMap resultMap。
最後,AttachmentQuery 的用法如下:

....
// Get the total number of attachments
long count = new AttachmentQuery(managementService).count();

// Get attachment with id 10025
Attachment attachment = new AttachmentQuery(managementService).attachmentId("10025").singleResult();

// Get first 10 attachments
List<Attachment> attachments = new AttachmentQuery(managementService).listPage(0, 10);

// Get all attachments uploaded by user kermit
attachments = new AttachmentQuery(managementService).userId("kermit").list();
....

For working examples on using XML Mapped Statements check the unit test org.flowable.standalone.cfg.CustomMybatisXMLMapperTest and other classes and resources in folders src/test/java/org/flowable/standalone/cfg/ and src/test/resources/org/flowable/standalone/cfg/

使用XML對映語句的示例可檢查位於src/test/java/org/flowable/standalone/cfg/和src/test/resources/org/flowable/standalone/cfg/資料夾中的單元測試org.flowable.standalone.cfg.CustomMybatisXMLMapperTest 以及其他類和資源。

相關文章