出站閘道器

JPA 入站通道介面卡允許您輪詢資料庫以檢索一個或多個 JPA 實體。檢索到的資料隨後用於啟動一個 Spring Integration 流,該流將檢索到的資料用作訊息載荷。

此外,您可以在流的末尾使用 JPA 出站通道介面卡來持久化資料,這實際上會在持久化操作結束時停止流。

但是,如何在流的中間執行 JPA 持久化操作呢?例如,您可能在 Spring Integration 訊息流中處理業務資料,並且希望將其持久化,但您仍然需要進一步下游使用其他元件。或者,除了使用輪詢器輪詢資料庫外,您還需要執行 JPQL 查詢並主動檢索資料,這些資料隨後在流中的後續元件中進行處理。

這時,JPA 出站閘道器就派上用場了。它們使您能夠持久化資料以及檢索資料。為了方便這些用途,Spring Integration 提供了兩種型別的 JPA 出站閘道器

  • 更新出站閘道器

  • 檢索出站閘道器

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

從資料庫中檢索(選擇)資料時,我們使用檢索出站閘道器。透過檢索出站閘道器,我們可以使用 JPQL、命名查詢(native 或基於 JPQL)或 native 查詢(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(包括頭部)是非常有益的。可選。

更新出站閘道器

以下列表顯示了您可以在更新出站閘道器上設定的所有屬性,並描述了關鍵屬性

<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 表示式,用於確定 EntityManager.find(Class entityClass, Object primaryKey) 方法的 primaryKey 值,將 requestMessage 作為評估上下文的根物件。如果存在 entity-class 屬性,則 entityClass 引數由此確定。否則,由 payload 類確定。如果使用 id-expression,則不允許使用所有其他屬性。可選。
2 一個布林標誌,指示 select 操作期望返回單個結果還是結果的 List。如果此標誌設定為 true,則單個實體作為訊息的載荷傳送。如果返回多個實體,則丟擲異常。如果設定為 false,則實體 List 作為訊息的載荷傳送。預設為 false。可選。
3 此非零、非負整數值告訴介面卡在執行 select 操作時,選擇的行數不超過指定數量。預設情況下,如果未設定此屬性,則給定查詢會選擇所有可能的記錄。此屬性與 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 是訊息 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 介面卡的完整示例應用程式。