出站通道适配器

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

使用实体类

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

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

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

从 Spring Integration 3.0 开始,PERSISTMERGE 的有效负载也可以是 java.lang.Iterable 类型。在这种情况下,迭代器返回的每个对象都被视为一个实体,并使用底层的 EntityManager 持久化或合并。迭代器返回的空值将被忽略。
从 5.5.4 版本开始,JpaOutboundGateway(配置了 PersistMode.DELETEJpaExecutor)可以接受 Iterable 有效负载,以对提供的实体执行批处理删除持久化操作。

使用 JPA 查询语言 (JPA QL)

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

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

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

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 不支持它们。请参阅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)"  (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"	 (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"  (1)
  channel=""  (2)
  entity-class=""  (3)
  entity-manager=""  (4)
  entity-manager-factory=""  (5)
  id=""
  jpa-operations=""  (6)
  jpa-query=""  (7)
  named-query=""  (8)
  native-query=""  (9)
  order=""  (10)
  parameter-source-factory=""   (11)
  persist-mode="MERGE"   (12)
  flush="true"   (13)
  flush-size="10"   (14)
  clear-on-flush="true"   (15)
  use-payload-as-parameter-source="true"   (16)
	<int:poller/>
	<int-jpa:transactional/>    (17)
	<int-jpa:parameter/>    (18)
</int-jpa:outbound-channel-adapter>
1 生命周期属性,指示此组件是否应在应用程序上下文启动期间启动。默认值为true。可选。
2 出站适配器接收消息以执行所需操作的通道。
3 JPA操作的实体类的完全限定名称。entity-classquerynamed-query属性是互斥的。可选。
4 用于执行JPA操作的jakarta.persistence.EntityManager实例。可选。
5 用于获取jakarta.persistence.EntityManager实例的jakarta.persistence.EntityManagerFactory实例,该实例执行JPA操作。可选。
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()。如果使用大于 '0' 的值指定 'flush-size' 属性,则无需配置 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());
    }

}