出站通道适配器

JPA 出站通道适配器允许您通过请求通道接收消息。有效载荷可以用作要持久化的实体,也可以与 JPQL 查询的参数表达式中的消息头一起使用。以下部分介绍了执行这些操作的可能方法。

使用实体类

以下 XML 配置出站通道适配器以将实体持久化到数据库:

<int-jpa:outbound-channel-adapter channel="entityTypeChannel"               [id="CO1-1"]1
    entity-class="org.springframework.integration.jpa.test.entity.Student"  [id="CO1-2"]2
    persist-mode="PERSIST"                                                  [id="CO1-3"]3
    entity-manager="em"/ >                                                  [id="CO1-4"]4
1 通过该通道将有效的 JPA 实体发送到 JPA 出站通道适配器。
2 适配器接受的实体类的完全限定名称,用于持久化到数据库。在大多数情况下,您可以省略此属性,因为适配器可以自动从 Spring Integration 消息有效载荷中确定实体类。
3 适配器要执行的操作。有效值是 PERSISTMERGEDELETE。默认值是 MERGE
4 要使用的 JPA 实体管理器。

outbound-channel-adapter 的这四个属性配置它通过输入通道接受实体,并处理它们以 PERSISTMERGEDELETE 底层数据源中的实体。

从 Spring Integration 3.0 开始,用于 PERSISTMERGE 的有效载荷也可以是 java.lang.Iterable 类型。在这种情况下,Iterable 返回的每个对象都被视为一个实体,并使用底层 EntityManager 进行持久化或合并。迭代器返回的空值将被忽略。

从 5.5.4 版本开始,配置了 PersistMode.DELETEJpaOutboundGateway 可以接受 Iterable 有效载荷来对提供的实体执行批量移除持久化操作。

使用 JPA 查询语言 (JPA QL)

上一节 展示了如何使用实体执行 PERSIST 操作。本节展示了如何将出站通道适配器与 JPA QL 结合使用。

以下 XML 配置出站通道适配器以将实体持久化到数据库:

<int-jpa:outbound-channel-adapter channel="jpaQlChannel"                                      [id="CO2-1"]1
  jpa-query="update Student s set s.firstName = :firstName where s.rollNumber = :rollNumber"  [id="CO2-2"]2
  entity-manager="em">                                                                        [id="CO2-3"]3
    <int-jpa:parameter name="firstName"  expression="payload['firstName']"/>                  [id="CO2-4"]4
    <int-jpa:parameter name="rollNumber" expression="payload['rollNumber']"/>
</int-jpa:outbound-channel-adapter>
1 消息发送到出站通道适配器的输入通道。
2 要执行的 JPA QL。此查询可能包含使用 parameter 元素进行评估的参数。
3 适配器用于执行 JPA 操作的实体管理器。
4 用于定义 query 属性中指定的 JPA QL(上例中的第 2 点)的参数名称值的元素(每个参数一个)。

parameter 元素接受一个属性,其 name 对应于所提供的 JPA QL 中指定的命名参数(上例中的第 2 点)。参数的值可以是静态的,也可以通过表达式派生。静态值和派生值的表达式分别使用 valueexpression 属性指定。这些属性是互斥的。

如果指定了 value 属性,则可以提供一个可选的 type 属性。此属性的值是其值由 value 属性表示的类的完全限定名称。默认情况下,类型假定为 java.lang.String。以下示例显示了如何定义 JPA 参数:

<int-jpa:outbound-channel-adapter ...
>
    <int-jpa:parameter name="level" value="2" type="java.lang.Integer"/>
    <int-jpa:parameter name="name" expression="payload['name']"/>
</int-jpa:outbound-channel-adapter>

如上例所示,您可以在出站通道适配器元素中使用多个 parameter 元素,并使用表达式定义某些参数,而使用静态值定义其他参数。但是,请注意不要多次指定相同的参数名称。您应该为 JPA 查询中指定的每个命名参数提供一个 parameter 元素。例如,我们指定两个参数:levelnamelevel 属性是 java.lang.Integer 类型的静态值,而 name 属性是从消息的有效载荷派生的。

尽管指定 select 对于 JPA QL 是有效的,但这样做没有意义。出站通道适配器不返回任何结果。如果您想选择某些值,请考虑使用出站网关。

使用原生查询

本节介绍如何使用原生查询来执行 JPA 出站通道适配器的操作。使用原生查询类似于使用 JPA QL,不同之处在于查询是原生数据库查询。通过使用原生查询,我们失去了使用 JPA QL 获得的数据库供应商独立性。

我们可以通过使用原生查询实现的一件事是执行数据库插入,这在 JPA QL 中是不可能的。(为了执行插入,我们将 JPA 实体发送到通道适配器,如 前文所述)。下面是一个小的 XML 片段,演示了如何使用原生查询在表中插入值。

命名参数可能不受您的 JPA 提供程序与原生 SQL 查询结合使用的支持。虽然它们与 Hibernate 配合良好,但 OpenJPA 和 EclipseLink 不支持它们。请参阅 [role="bare"][role="bare"]issues.apache.org/jira/browse/OPENJPA-111。JPA 2.0 规范的第 3.8.12 节指出:“对于原生查询,只能可移植地使用位置参数绑定和位置访问结果项。

以下示例配置了一个带有原生查询的出站通道适配器:

<int-jpa:outbound-channel-adapter channel="nativeQlChannel"
  native-query="insert into STUDENT_TABLE(FIRST_NAME,LAST_UPDATED) values (:lastName,:lastUpdated)"  [id="CO3-1"]1
  entity-manager="em">
    <int-jpa:parameter name="lastName" expression="payload['updatedLastName']"/>
    <int-jpa:parameter name="lastUpdated" expression="new java.util.Date()"/>
</int-jpa:outbound-channel-adapter>
<1>  此出站通道适配器执行的原生查询。

请注意,其他属性(例如 channelentity-manager)和 parameter 元素具有与 JPA QL 相同的语义。

使用命名查询

使用命名查询类似于使用 JPA QL原生查询,不同之处在于我们指定一个命名查询而不是一个查询。首先,我们介绍如何定义 JPA 命名查询。然后,我们介绍如何声明出站通道适配器以使用命名查询。如果我们有一个名为 Student 的实体,我们可以使用 Student 类上的注解来定义两个命名查询:selectStudentupdateStudent。以下示例显示了如何执行此操作:

@Entity
@Table(name="Student")
@NamedQueries({
    @NamedQuery(name="selectStudent",
        query="select s from Student s where s.lastName = 'Last One'"),
    @NamedQuery(name="updateStudent",
        query="update Student s set s.lastName = :lastName,
               lastUpdated = :lastUpdated where s.id in (select max(a.id) from Student a)")
})
public class Student {

...
}

或者,您可以使用 orm.xml 定义命名查询,如下例所示:

<entity-mappings ...>
    ...
    <named-query name="selectStudent">
        <query>select s from Student s where s.lastName = 'Last One'</query>
    </named-query>
</entity-mappings>

现在我们已经展示了如何使用注解或 orm.xml 定义命名查询,我们现在展示一个小的 XML 片段,它定义了一个使用命名查询的 outbound-channel-adapter,如下例所示:

<int-jpa:outbound-channel-adapter channel="namedQueryChannel"
            named-query="updateStudent"	 [id="CO4-1"]1
            entity-manager="em">
        <int-jpa:parameter name="lastName" expression="payload['updatedLastName']"/>
        <int-jpa:parameter name="lastUpdated" expression="new java.util.Date()"/>
</int-jpa:outbound-channel-adapter>
<1>  当适配器通过通道接收消息时,我们希望它执行的命名查询。

配置参数参考

以下列表显示了您可以在出站通道适配器上设置的所有属性:

<int-jpa:outbound-channel-adapter
  auto-startup="true"  [id="CO5-1"]1
  channel=""  [id="CO5-2"]2
  entity-class=""  [id="CO5-3"]3
  entity-manager=""  [id="CO5-4"]4
  entity-manager-factory=""  [id="CO5-5"]5
  id=""
  jpa-operations=""  [id="CO5-6"]6
  jpa-query=""  [id="CO5-7"]7
  named-query=""  [id="CO5-8"]8
  native-query=""  [id="CO5-9"]9
  order=""  [id="CO5-10"]10
  parameter-source-factory=""   [id="CO5-11"]11
  persist-mode="MERGE"   [id="CO5-12"]12
  flush="true"   [id="CO5-13"]13
  flush-size="10"   [id="CO5-14"]14
  clear-on-flush="true"   [id="CO5-15"]15
  use-payload-as-parameter-source="true"   [id="CO5-16"]16
	<int:poller/>
	<int-jpa:transactional/>    [id="CO5-17"]17
	<int-jpa:parameter/>    [id="CO5-18"]18
</int-jpa:outbound-channel-adapter>
1 生命周期属性,指示此组件是否应在应用程序上下文启动期间启动。默认为 true。可选。
2 出站适配器接收消息以执行所需操作的通道。
3 JPA 操作的实体类的完全限定名称。entity-classquerynamed-query 属性是互斥的。可选。
4 用于执行 JPA 操作的 jakarta.persistence.EntityManager 实例。可选。
5 用于获取 jakarta.persistence.EntityManager 实例以执行 JPA 操作的 jakarta.persistence.EntityManagerFactory 实例。可选。
6 org.springframework.integration.jpa.core.JpaOperations 的实现,用于执行 JPA 操作。我们建议不要提供自己的实现,而是使用默认的 org.springframework.integration.jpa.core.DefaultJpaOperations 实现。您可以使用 entity-managerentity-manager-factoryjpa-operations 属性中的任何一个。可选。
7 此适配器要执行的 JPA QL。可选。
8 此适配器需要执行的命名查询。可选。
9 此适配器要执行的原生查询。您可以使用 jpa-querynamed-querynative-query 属性中的任何一个。可选。
10 当注册多个消费者时,此消费者的顺序,从而管理负载均衡和故障转移。默认为 Ordered.LOWEST_PRECEDENCE。可选。
11 o.s.i.jpa.support.parametersource.ParameterSourceFactory 的实例,用于获取 o.s.i.jpa.support.parametersource.ParameterSource 实例,该实例用于解析查询中参数的值。如果您使用 JPA 实体执行操作,则忽略。parameter 子元素与 parameter-source-factory 属性互斥,并且必须在提供的 ParameterSourceFactory 上配置。可选。
12 接受以下之一:PERSISTMERGEDELETE。指示适配器需要执行的操作。仅当您使用实体进行 JPA 操作时才相关。如果您提供 JPA QL、命名查询或原生查询,则忽略。默认为 MERGE。可选。从 Spring Integration 3.0 开始,用于持久化或合并的有效载荷也可以是 java.lang.Iterable 类型。在这种情况下,Iterable 返回的每个对象都被视为一个实体,并使用底层 EntityManager 进行持久化或合并。迭代器返回的空值将被忽略。
13 如果您希望在持久化、合并或删除操作后立即刷新持久化上下文,并且不希望依赖 EntityManagerflushMode,请将此值设置为 true。默认为 false。仅当您未指定 flush-size 属性时才适用。如果此属性设置为 true,则 flush-size 会隐式设置为 1,如果没有其他值配置。
14 如果您希望在持久化、合并或删除操作后立即刷新持久化上下文,并且不希望依赖 EntityManagerflushMode,请将此属性设置为大于“0”的值。默认值设置为“0”,表示“不刷新”。此属性适用于带有 Iterable 有效载荷的消息。例如,如果 flush-size 设置为 3,则在每三个实体之后调用 entityManager.flush()。此外,在整个循环之后还会再次调用 entityManager.flush()。如果 flush-size 属性指定的值大于“0”,则无需配置 flush 属性。
15 如果您希望在每次刷新操作后立即清除持久化上下文,请将此值设置为“true”。此属性的值仅在 flush 属性设置为 trueflush-size 属性设置为大于 0 的值时才适用。
16 如果设置为 true,则消息的有效载荷用作参数的源。如果设置为 false,则整个 Message 可用作参数的源。可选。
17 定义事务管理属性和 JPA 适配器要使用的事务管理器的引用。可选。
18 一个或多个 parameter 属性——查询中使用的每个参数一个。值或表达式用于计算参数的值。可选。

使用 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 = "jpaPersistChannel")
       @Transactional
       void persistStudent(StudentDomain payload);

    }

    @Bean
    public JpaExecutor jpaExecutor() {
        JpaExecutor executor = new JpaExecutor(this.entityManagerFactory);
        jpaExecutor.setEntityClass(StudentDomain.class);
        jpaExecutor.setPersistMode(PersistMode.PERSIST);
        return executor;
    }

    @Bean
    @ServiceActivator(channel = "jpaPersistChannel")
    public MessageHandler jpaOutbound() {
        JpaOutboundGateway adapter = new JpaOutboundGateway(jpaExecutor());
        adapter.setProducesReply(false);
        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 outboundAdapterFlow() {
        return f -> f
                .handle(Jpa.outboundAdapter(this.entityManagerFactory)
                                .entityClass(StudentDomain.class)
                                .persistMode(PersistMode.PERSIST),
                        e -> e.transactional());
    }

}