輸出閘道

JPA 輸入通道配接器可讓您輪詢資料庫以擷取一個或多個 JPA 實體。擷取的資料隨後用於啟動 Spring Integration 流程,該流程使用擷取的資料作為訊息有效負載。

此外,您可以在流程結束時使用 JPA 輸出通道配接器,以便持久化資料,基本上在持久化操作結束時停止流程。

然而,如何在流程中執行 JPA 持久化操作?例如,您可能有正在 Spring Integration 訊息流程中處理的業務資料,並且您想要持久化這些資料,但您仍然需要使用下游的其他元件。或者,您需要執行 JPQL 查詢並主動擷取資料,而不是使用輪詢器輪詢資料庫,然後在流程中的後續元件中處理這些資料。

這就是 JPA 輸出閘道發揮作用的地方。它們讓您能夠持久化資料以及擷取資料。為了方便這些用途,Spring Integration 提供了兩種 JPA 輸出閘道

  • 更新輸出閘道

  • 擷取輸出閘道

每當輸出閘道用於執行儲存、更新或僅刪除資料庫中某些記錄的動作時,您需要使用更新輸出閘道。例如,如果您使用 entity 來持久化它,則會傳回合併和持久化的實體作為結果。在其他情況下,會傳回受影響的記錄數(已更新或已刪除)。

當從資料庫擷取(選取)資料時,我們使用擷取輸出閘道。使用擷取輸出閘道,我們可以將 JPQL、具名查詢(原生或基於 JPQL)或原生查詢 (SQL) 用於選取資料和擷取結果。

更新輸出閘道在功能上與輸出通道配接器類似,只是更新輸出閘道會在執行 JPA 操作後將結果傳送到閘道的回覆通道。

擷取輸出閘道與輸入通道配接器類似。

我們建議您先閱讀本章稍早的輸出通道配接器章節和輸入通道配接器章節,因為其中說明了大多數常見概念。

這種相似性是使用中央 JpaExecutor 類別來盡可能統一常見功能的 главним 因素。

所有 JPA 輸出閘道通用的,且與 outbound-channel-adapter 類似,我們可以將其用於執行各種 JPA 操作

  • 實體類別

  • JPA 查詢語言 (JPQL)

  • 原生查詢

  • 具名查詢

如需設定範例,請參閱JPA 輸出閘道範例

通用設定參數

JPA 輸出閘道始終可以存取 Spring Integration Message 作為輸入。因此,可以使用以下參數

parameter-source-factory

o.s.i.jpa.support.parametersource.ParameterSourceFactory 的執行個體,用於取得 o.s.i.jpa.support.parametersource.ParameterSource 的執行個體。ParameterSource 用於解析查詢中提供的參數值。如果您使用 JPA 實體執行操作,則會忽略 parameter-source-factory 屬性。parameter 子元素與 parameter-source-factory 互斥,並且必須在提供的 ParameterSourceFactory 上設定。選用。

use-payload-as-parameter-source

如果設定為 true,則 Message 的有效負載會用作參數的來源。如果設定為 false,則整個 Message 可用作參數的來源。如果未傳入 JPA 參數,則此屬性預設為 true。這表示,如果您使用預設的 BeanPropertyParameterSourceFactory,則有效負載的 bean 屬性會用作 JPA 查詢的參數值來源。但是,如果傳入 JPA 參數,則此屬性預設會評估為 false。原因是 JPA 參數可讓您提供 SpEL 運算式。因此,能夠存取整個 Message(包括標頭)非常有利。選用。

更新輸出閘道

以下清單顯示您可以在 updating-outbound-gateway 上設定的所有屬性,並說明了主要屬性

<int-jpa:updating-outbound-gateway request-channel=""  (1)
    auto-startup="true"
    entity-class=""
    entity-manager=""
    entity-manager-factory=""
    id=""
    jpa-operations=""
    jpa-query=""
    named-query=""
    native-query=""
    order=""
    parameter-source-factory=""
    persist-mode="MERGE"
    reply-channel=""  (2)
    reply-timeout=""  (3)
    use-payload-as-parameter-source="true">

    <int:poller/>
    <int-jpa:transactional/>

    <int-jpa:parameter name="" type="" value=""/>
    <int-jpa:parameter name="" expression=""/>
</int-jpa:updating-outbound-gateway>
1 輸出閘道從中接收訊息以執行所需操作的通道。此屬性類似於 outbound-channel-adapterchannel 屬性。選用。
2 閘道在執行必要的 JPA 操作後將回應傳送至的通道。如果未定義此屬性,則請求訊息必須具有 replyChannel 標頭。選用。
3 指定閘道等待將結果傳送至回覆通道的時間。僅當回覆通道本身可能會封鎖傳送操作時適用(例如,目前已滿的有界 QueueChannel)。值以毫秒為單位指定。選用。

其餘屬性已在本章稍早說明。請參閱設定參數參考設定參數參考

使用 Java 設定進行設定

以下 Spring Boot 應用程式顯示如何使用 Java 設定輸出配接器的範例

@SpringBootApplication
@EntityScan(basePackageClasses = StudentDomain.class)
@IntegrationComponentScan
public class JpaJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(JpaJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @MessagingGateway
    interface JpaGateway {

       @Gateway(requestChannel = "jpaUpdateChannel")
       @Transactional
       void updateStudent(StudentDomain payload);

    }

    @Bean
    @ServiceActivator(channel = "jpaUpdateChannel")
    public MessageHandler jpaOutbound() {
        JpaOutboundGateway adapter =
               new JpaOutboundGateway(new JpaExecutor(this.entityManagerFactory));
        adapter.setOutputChannelName("updateResults");
        return adapter;
    }

}

使用 Java DSL 進行設定

以下 Spring Boot 應用程式顯示如何使用 Java DSL 設定輸出配接器的範例

@SpringBootApplication
@EntityScan(basePackageClasses = StudentDomain.class)
public class JpaJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(JpaJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @Bean
    public IntegrationFlow updatingGatewayFlow() {
        return f -> f
                .handle(Jpa.updatingGateway(this.entityManagerFactory),
                        e -> e.transactional(true))
                .channel(c -> c.queue("updateResults"));
    }

}

擷取輸出閘道

以下範例示範如何設定擷取輸出閘道

  • Java DSL

  • Kotlin DSL

  • Java

  • XML

@SpringBootApplication
@EntityScan(basePackageClasses = StudentDomain.class)
public class JpaJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(JpaJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @Bean
    public IntegrationFlow retrievingGatewayFlow() {
        return f -> f
                .handle(Jpa.retrievingGateway(this.entityManagerFactory)
                       .jpaQuery("from Student s where s.id = :id")
                       .expectSingleResult(true)
                       .parameterExpression("id", "payload"))
                .channel(c -> c.queue("retrieveResults"));
    }

}
@Bean
fun retrievingGatewayFlow() =
    integrationFlow {
        handle(Jpa.retrievingGateway(this.entityManagerFactory)
                .jpaQuery("from Student s where s.id = :id")
                .expectSingleResult(true)
                .parameterExpression("id", "payload"))
        channel { queue("retrieveResults") }
    }
@SpringBootApplication
@EntityScan(basePackageClasses = StudentDomain.class)
public class JpaJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(JpaJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Autowired
    private EntityManagerFactory entityManagerFactory;


    @Bean
    public JpaExecutor jpaExecutor() {
        JpaExecutor executor = new JpaExecutor(this.entityManagerFactory);
        jpaExecutor.setJpaQuery("from Student s where s.id = :id");
        executor.setJpaParameters(Collections.singletonList(new JpaParameter("id", null, "payload")));
        jpaExecutor.setExpectSingleResult(true);
        return executor;
    }

    @Bean
    @ServiceActivator(channel = "jpaRetrievingChannel")
    public MessageHandler jpaOutbound() {
        JpaOutboundGateway adapter = new JpaOutboundGateway(jpaExecutor());
        adapter.setOutputChannelName("retrieveResults");
        adapter.setGatewayType(OutboundGatewayType.RETRIEVING);
        return adapter;
    }

}
<int-jpa:retrieving-outbound-gateway request-channel=""
    auto-startup="true"
    delete-after-poll="false"
    delete-in-batch="false"
    entity-class=""
    id-expression=""              (1)
    entity-manager=""
    entity-manager-factory=""
    expect-single-result="false"  (2)
    id=""
    jpa-operations=""
    jpa-query=""
    max-results=""                (3)
    max-results-expression=""     (4)
    first-result=""               (5)
    first-result-expression=""    (6)
    named-query=""
    native-query=""
    order=""
    parameter-source-factory=""
    reply-channel=""
    reply-timeout=""
    use-payload-as-parameter-source="true">
    <int:poller></int:poller>
    <int-jpa:transactional/>

    <int-jpa:parameter name="" type="" value=""/>
    <int-jpa:parameter name="" expression=""/>
</int-jpa:retrieving-outbound-gateway>
1 (自 Spring Integration 4.0 起)SpEL 運算式,用於決定針對 requestMessage 作為評估內容的根物件的 EntityManager.find(Class entityClass, Object primaryKey) 方法的 primaryKey 值。entityClass 引數是從 entity-class 屬性判斷的(如果存在)。否則,它是從 payload 類別判斷的。如果您使用 id-expression,則不允許使用所有其他屬性。選用。
2 布林旗標,指示選取操作是否預期傳回單一結果或 List 結果。如果此旗標設定為 true,則會將單一實體作為訊息的有效負載傳送。如果傳回多個實體,則會擲回例外狀況。如果為 false,則會將實體的 List 作為訊息的有效負載傳送。它預設為 false。選用。
3 此非零、非負整數值告知配接器在執行選取操作時不要選取超過指定數量的列。預設情況下,如果未設定此屬性,則會透過給定查詢選取所有可能的記錄。此屬性與 max-results-expression 互斥。選用。
4 可用於尋找結果集中最大結果數的運算式。它與 max-results 互斥。選用。
5 此非零、非負整數值告知配接器要從中擷取結果的第一筆記錄。此屬性與 first-result-expression 互斥。版本 3.0 引入了此屬性。選用。
6 此運算式會針對訊息進行評估,以尋找結果集中第一筆記錄的位置。此屬性與 first-result 互斥。版本 3.0 引入了此屬性。選用。

當您選擇在擷取時刪除實體,且您已擷取實體集合時,預設情況下,實體是按每個實體刪除的。這可能會導致效能問題。

或者,您可以將屬性 deleteInBatch 設定為 true,以執行批次刪除。但是,這樣做的限制是不支援串聯刪除。

JSR 317:Java™ Persistence 2.0 在第 4.10 章「大量更新和刪除操作」中指出

「刪除操作僅適用於指定類別及其子類別的實體。它不會串聯到相關實體。」

如需詳細資訊,請參閱JSR 317:Java™ Persistence 2.0

從 6.0 版開始,當查詢未傳回任何實體時,Jpa.retrievingGateway() 會傳回空白列表結果。先前,null 會傳回,導致流程結束,或擲回例外狀況,具體取決於 requiresReply。或者,若要還原為先前的行為,請在閘道後方新增 filter 以過濾掉空白列表。這需要在下游邏輯中包含空白列表處理的應用程式中進行額外設定。請參閱分割器捨棄通道,以取得可能的空白列表處理選項。

JPA 輸出閘道範例

本節包含使用更新輸出閘道和擷取輸出閘道的各種範例

使用實體類別更新

在以下範例中,更新輸出閘道透過使用 org.springframework.integration.jpa.test.entity.Student 實體類別作為 JPA 定義參數來持久化

<int-jpa:updating-outbound-gateway request-channel="entityRequestChannel"  (1)
    reply-channel="entityResponseChannel"  (2)
    entity-class="org.springframework.integration.jpa.test.entity.Student"
    entity-manager="em"/>
1 這是輸出閘道的請求通道。它類似於 outbound-channel-adapterchannel 屬性。
2 這是閘道與輸出配接器的不同之處。這是接收 JPA 操作回覆的通道。但是,如果您對收到的回覆不感興趣,而只想執行操作,則使用 JPA outbound-channel-adapter 是適當的選擇。在本範例中,我們使用實體類別,回覆是作為 JPA 操作結果建立或合併的實體物件。

使用 JPQL 更新

以下範例使用 Java Persistence Query Language (JPQL) 更新實體,這需要使用更新輸出閘道

<int-jpa:updating-outbound-gateway request-channel="jpaqlRequestChannel"
  reply-channel="jpaqlResponseChannel"
  jpa-query="update Student s set s.lastName = :lastName where s.rollNumber = :rollNumber"  (1)
  entity-manager="em">
    <int-jpa:parameter name="lastName" expression="payload"/>
    <int-jpa:parameter name="rollNumber" expression="headers['rollNumber']"/>
</int-jpa:updating-outbound-gateway>
1 閘道執行的 JPQL 查詢。由於我們使用了更新輸出閘道,因此只有 updatedelete JPQL 查詢才是合理的選擇。

當您傳送具有 String 有效負載的訊息,其中也包含一個名為 rollNumber 且值為 long 的標頭時,具有指定學號的學生的姓氏會更新為訊息有效負載中的值。使用更新閘道時,傳回值始終為整數值,表示 JPA QL 執行所影響的記錄數。

使用 JPQL 擷取實體

以下範例使用擷取輸出閘道和 JPQL 從資料庫擷取(選取)一個或多個實體

<int-jpa:retrieving-outbound-gateway request-channel="retrievingGatewayReqChannel"
    reply-channel="retrievingGatewayReplyChannel"
    jpa-query="select s from Student s where s.firstName = :firstName and s.lastName = :lastName"
    entity-manager="em">
    <int-jpa:parameter name="firstName" expression="payload"/>
    <int-jpa:parameter name="lastName" expression="headers['lastName']"/>
</int-jpa:outbound-gateway>

使用 id-expression 擷取實體

以下範例使用具有 id-expression 的擷取輸出閘道,從資料庫擷取(尋找)一個且僅一個實體:primaryKeyid-expression 評估的結果。entityClass 是 Message payload 的類別。

<int-jpa:retrieving-outbound-gateway
	request-channel="retrievingGatewayReqChannel"
    reply-channel="retrievingGatewayReplyChannel"
    id-expression="payload.id"
    entity-manager="em"/>

使用具名查詢更新

使用具名查詢基本上與直接使用 JPQL 查詢相同。不同之處在於使用 named-query 屬性,如下列範例所示

<int-jpa:updating-outbound-gateway request-channel="namedQueryRequestChannel"
    reply-channel="namedQueryResponseChannel"
    named-query="updateStudentByRollNumber"
    entity-manager="em">
    <int-jpa:parameter name="lastName" expression="payload"/>
    <int-jpa:parameter name="rollNumber" expression="headers['rollNumber']"/>
</int-jpa:outbound-gateway>
您可以在此處找到使用 Spring Integration JPA 配接器的完整範例應用程式。