出站通道适配器
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 | 适配器要执行的操作。有效值为 PERSIST、MERGE 和 DELETE。默认值为 MERGE。 |
| 4 | 要使用的 JPA 实体管理器。 |
outbound-channel-adapter 的这四个属性配置它以通过输入通道接受实体,并处理它们以从底层数据源中PERSIST、MERGE 或 DELETE 实体。
从 Spring Integration 3.0 开始,用于 PERSIST 或 MERGE 的负载也可以是 java.lang.Iterable 类型。在这种情况下,Iterable 返回的每个对象都被视为一个实体,并使用底层 EntityManager 进行持久化或合并。迭代器返回的空值将被忽略。 |
从 5.5.4 版本开始,JpaOutboundGateway,配置了 PersistMode.DELETE 的 JpaExecutor,可以接受 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 | 用于定义 query 属性中指定的 JPA QL 的参数名称的值的元素(每个参数一个)。 |
parameter 元素接受一个属性,其 name 对应于提供的 JPA QL 中指定的命名参数(上例中的第 2 点)。参数的值可以是静态的,也可以通过表达式派生。静态值和派生值的表达式分别通过 value 和 expression 属性指定。这些属性是互斥的。
如果指定了 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 元素。例如,我们指定两个参数:level 和 name。level 属性是 java.lang.Integer 类型的静态值,而 name 属性是从消息的有效负载中派生的。
尽管为 JPA QL 指定 select 是有效的,但这样做没有意义。出站通道适配器不返回任何结果。如果您想选择一些值,请考虑使用出站网关。 |
使用原生查询
本节介绍如何使用原生查询来执行 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 | 此出站通道适配器执行的原生查询。 |
请注意,其他属性(如 channel 和 entity-manager)以及 parameter 元素与 JPA QL 具有相同的语义。
使用命名查询
使用命名查询类似于使用 JPA QL 或 原生查询,不同之处在于我们指定的是命名查询而不是查询。首先,我们介绍如何定义 JPA 命名查询。然后,我们介绍如何声明一个出站通道适配器以使用命名查询。如果有一个名为 Student 的实体,我们可以在 Student 类上使用注解来定义两个命名查询:selectStudent 和 updateStudent。以下示例展示了如何实现
@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-class、query 和 named-query 属性是互斥的。可选。 |
| 4 | 用于执行 JPA 操作的 jakarta.persistence.EntityManager 实例。可选。 |
| 5 | 用于获取 jakarta.persistence.EntityManager 实例(该实例执行 JPA 操作)的 jakarta.persistence.EntityManagerFactory 实例。可选。 |
| 6 | 用于执行 JPA 操作的 org.springframework.integration.jpa.core.JpaOperations 实现。我们建议不要提供您自己的实现,而是使用默认的 org.springframework.integration.jpa.core.DefaultJpaOperations 实现。您可以使用 entity-manager、entity-manager-factory 或 jpa-operations 属性中的任何一个。可选。 |
| 7 | 此适配器要执行的 JPA QL。可选。 |
| 8 | 此适配器需要执行的命名查询。可选。 |
| 9 | 此适配器要执行的原生查询。您可以使用 jpa-query、named-query 或 native-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 | 接受以下值之一:PERSIST、MERGE 或 DELETE。指示适配器需要执行的操作。仅当您将实体用于 JPA 操作时才相关。如果您提供 JPA QL、命名查询或原生查询,则忽略。默认为 MERGE。可选。从 Spring Integration 3.0 开始,用于持久化或合并的负载也可以是 java.lang.Iterable 类型。在这种情况下,Iterable 返回的每个对象都被视为一个实体,并使用底层 EntityManager 进行持久化或合并。迭代器返回的空值将被忽略。 |
| 13 | 如果您希望在持久化、合并或删除操作后立即刷新持久化上下文,并且不希望依赖 EntityManager 的 flushMode,则将此值设置为 true。默认为 false。仅当您未指定 flush-size 属性时适用。如果此属性设置为 true,则 flush-size 将隐式设置为 1,如果没有其他值配置它。 |
| 14 | 如果您希望在持久化、合并或删除操作后立即刷新持久化上下文,并且不希望依赖 EntityManager 的 flushMode,则将此属性设置为大于 '0' 的值。默认值设置为 0,表示“不刷新”。此属性适用于带有 Iterable 负载的消息。例如,如果 flush-size 设置为 3,则在每第三个实体之后调用 entityManager.flush()。此外,在整个循环结束后还会再次调用 entityManager.flush()。如果 'flush-size' 属性指定的值大于 '0',则无需配置 flush 属性。 |
| 15 | 如果设置为“true”,则在每次刷新操作后立即清除持久化上下文。此属性的值仅在 flush 属性设置为 true 或 flush-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());
}
}