# JPA 支持
## JPA 支持
Spring 集成的 JPA(Java 持久性 API)模块提供了用于使用 JPA 执行各种数据库操作的组件。
你需要在项目中包含此依赖项:
Maven
```
org.springframework.integration
spring-integration-jpa
5.5.9
```
Gradle
```
compile "org.springframework.integration:spring-integration-jpa:5.5.9"
```
JPA API 必须通过一些特定于供应商的实现来包含,例如 Hibernate ORM 框架。
所提供的构成部分如下:
* [入站通道适配器](#jpa-inbound-channel-adapter)
* [出站通道适配器](#jpa-outbound-channel-adapter)
* [更新出站网关](#jpa-updating-outbound-gateway)
* [检索出站网关](#jpa-retrieving-outbound-gateway)
通过向目标数据库发送和接收消息,这些组件可以用于在目标数据库上执行`select`、`create`、`update`和`delete`操作。
JPA 入站通道适配器允许你使用 JPA 从数据库中轮询和检索(`select`)数据,而 JPA 出站通道适配器允许你创建、更新和删除实体。
你可以使用 JPA 的出站网关将实体持久化到数据库,从而继续流并在下游执行更多组件。类似地,你可以使用出站网关从数据库中检索实体。
例如,你可以使用出站网关,它在其请求通道上接收一个`Message`并将一个`userId`作为有效负载的`Message`,以查询数据库,检索用户实体,并将其传递到下游以进行进一步的处理。
认识到这些语义上的差异, Spring 集成提供了两个独立的 JPA 出站网关:
* 检索出站网关
* 更新出站网关
### 功能
所有 JPA 组件通过使用以下之一来执行其各自的 JPA 操作:
* 实体类别
* 用于更新、选择和删除的 Java 持久性查询语言(JPQL 不支持 INSERT)
* 本机查询
* 命名查询
下面的小节将更详细地描述这些组件中的每一个。
### 支持的持久性提供程序
Spring 集成 JPA 支持已经针对以下持久性提供者进行了测试:
* Hibernate
* EclipseLink
在使用持久性提供程序时,你应该确保该提供程序与 JPA 2.1 兼容。
### Java 实现
所提供的每个组件都使用`o.s.i.jpa.core.JpaExecutor`类,其反过来使用`o.s.i.jpa.core.JPAOperations`接口的实现。`JpaOperations`操作方式类似于典型的数据访问对象,并提供了诸如查找、持久化、ExecuteUpdate 等方法。对于大多数用例,默认实现(`o.s.i.jpa.core.DefaultJpaOperations`)应该足够了。但是,如果需要自定义行为,则可以指定自己的实现。
要初始化`JpaExecutor`,你必须使用一个接受以下条件之一的构造函数:
* EntityManagerFactory
* EntityManager
* JpaOperations
下面的示例展示了如何用`entityManagerFactory`初始化`JpaExecutor`并在出站网关中使用它:
```
@Bean
public JpaExecutor jpaExecutor() {
JpaExecutor executor = new JpaExecutor(this.entityManagerFactory);
executor.setJpaParameters(Collections.singletonList(new JpaParameter("firstName", null, "#this")));
executor.setUsePayloadAsParameterSource(true);
executor.setExpectSingleResult(true);
return executor;
}
@ServiceActivator(inputChannel = "getEntityChannel")
@Bean
public MessageHandler retrievingJpaGateway() {
JpaOutboundGateway gateway = new JpaOutboundGateway(jpaExecutor());
gateway.setGatewayType(OutboundGatewayType.RETRIEVING);
gateway.setOutputChannelName("resultsChannel");
return gateway;
}
```
### 名称空间支持
在使用 XML 名称空间支持时,底层解析器类将为你实例化相关的 Java 类。因此,你通常不需要处理 JPA 适配器的内部工作。本节记录了 Spring Integration 提供的 XML 命名空间支持,并向你展示了如何使用 XML 命名空间支持来配置 JPA 组件。
#### 常见的 XML 名称空间配置属性
某些配置参数由所有 JPA 组件共享:
`auto-startup`
生命周期属性,该属性指示是否应在应用程序上下文启动期间启动此组件。默认值为`true`。可选的。
`id`
标识底层的 Spring Bean 定义,它是`EventDrivenConsumer`或`PollingConsumer`的实例。可选的。
`entity-manager-factory`
对 JPA 实体管理器工厂的引用,适配器用于创建`EntityManager`。你必须提供这个属性,即`entity-manager`属性,或`jpa-operations`属性。
`entity-manager`
对组件使用的 JPA 实体管理器的引用。你必须提供这个属性,即`entity-manager-factory`属性,或`jpa-operations`属性。
| |通常,你的 Spring 应用程序上下文仅定义了 JPA 实体管理器工厂,而`EntityManager`是通过使用`@PersistenceContext`注释注入的。
这种方法不适用于 Spring 集成 JPA 组件。
通常,注入 JPA 实体管理器工厂是最好的,但是,当要显式地注入`EntityManager`时,必须定义`SharedEntityManagerBean`。
有关更多信息,请参见相关的[Javadoc](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/orm/jpa/support/SharedEntityManagerBean.html)。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
下面的示例展示了如何显式地包括一个实体管理器工厂:
```
```
`jpa-operations`
对实现`JpaOperations`接口的 Bean 的引用。在极少数情况下,最好提供你自己的`JpaOperations`接口的实现,而不是依赖默认的实现(`org.springframework.integration.jpa.core.DefaultJpaOperations`)。如果使用`jpa-operations`属性,则不能提供 JPA 实体管理器或 JPA 实体管理器工厂,因为`JpaOperations`包装了必要的数据源。
`entity-class`
实体类的完全限定名称。这个属性的确切语义是不同的,这取决于我们是执行持久化或更新操作,还是从数据库检索对象。
在检索数据时,你可以指定`entity-class`属性,以表示你希望从数据库中检索此类型的对象。在这种情况下,你不能定义任何查询属性(`jpa-query`,`native-query`,或`named-query`)。
当持久化数据时,`entity-class`属性指示要持久化的对象类型。如果没有指定(对于持久操作),则会自动从消息的有效负载中检索实体类。
`jpa-query`
定义要使用的 JPA 查询(Java 持久性查询语言)。
`native-query`
定义要使用的本机 SQL 查询。
`named-query`
指一个已命名的查询。命名查询可以在原生 SQL 或 JPAQL 中定义,但是底层的 JPA 持久性提供程序在内部处理这种区别。
#### 提供 JPA 查询参数
要提供参数,可以使用`parameter`XML 元素。它有一种机制,允许你为基于 Java 持久性查询语言或本机 SQL 查询的查询提供参数。你还可以为命名查询提供参数。
基于表达式的参数
下面的示例展示了如何设置基于表达式的参数:
```
```
基于价值的参数
下面的示例展示了如何设置基于值的参数:
```
```
位置参数
下面的示例展示了如何设置基于表达式的参数:
```
```
#### 事务处理
所有 JPA 操作(例如`INSERT`、`UPDATE`和`DELETE`)都需要一个事务在执行时处于活动状态。对于入站通道适配器,你不需要做任何特别的事情。它的工作方式与我们配置事务管理器的方式类似,它使用与其他入站通道适配器一起使用的 Poller。下面的 XML 示例配置了一个事务管理器,该事务管理器使用带入站通道适配器的 Poller:
```
```
但是,在使用出站通道适配器或网关时,可能需要专门启动事务。如果`DirectChannel`是出站适配器或网关的输入通道,并且如果事务在当前执行线程中处于活动状态,则在相同的事务上下文中执行操作。还可以将此 JPA 操作配置为以新事务的形式运行,如下例所示:
```
```
在前面的示例中,出站网关或适配器的事务元素指定事务属性。如果将`DirectChannel`作为适配器的输入通道,并且希望适配器在与调用方相同的事务上下文中执行操作,则可以选择定义这个子元素。但是,如果使用`ExecutorChannel`,则必须具有`transactional`元素,因为调用客户机的事务上下文不会传播。
| |与 Spring Integration 的命名空间中定义的 Poller 的`transactional`元素不同,用于出站网关或适配器的`transactional`元素是在 JPA 命名空间中定义的。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### 入站通道适配器
入站通道适配器用于使用 JPA QL 在数据库上执行 SELECT 查询并返回结果。消息有效负载是单个实体或`List`的实体。以下 XML 配置`inbound-channel-adapter`:
```
(9)
```
|**1**|在`inbound-channel-adapter`属性中执行 JPA QL 后,`inbound-channel-adapter`在其上放置消息(带有有效负载)的通道。|
|-----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|`EntityManager`实例用于执行所需的 JPA 操作。|
|**3**|属性,表示应用程序上下文启动时组件是否应该自动启动。
值默认为`true`。|
|**4**|JPA QL,其结果作为消息的有效负载发送出去|
|**5**|这个属性告诉 JPQL 查询是在结果中给出单个实体,还是在实体中给出`List`。
如果将值设置为`true`,则将单个实体作为消息的有效载荷发送。
但是,如果,将其设置为`true`后,将返回多个结果,抛出一个`MessagingException`。
该值默认为`false`。|
|**6**|这个非零、非负的整数值告诉适配器,在执行 SELECT 操作时,选择的行数不能超过给定的行数。
默认情况下,如果未设置此属性,则通过查询选择所有可能的记录。
此属性与`max-results-expression`是互斥的。
可选的。|
|**7**|求出结果集中结果的最大数目的表达式。
与`max-results`互斥。
可选。|
|**8**|如果要删除执行查询后收到的行,请将该值设置为`true`。
你必须确保组件作为事务的一部分进行操作。
否则,你可能会遇到异常,例如:`java.lang.IllegalArgumentException: Removing a detached instance …`|
|**9**|如果你想在删除接收到的实体后立即刷新持久性上下文,并且不想依赖`EntityManager`的`flushMode`,则将该值设置为`true`。
值默认为`false`。|
#### 配置参数引用
下面的清单显示了可以为`inbound-channel-adapter`设置的所有值:
```
(14)
```
|**1** |此生命周期属性表示该组件是否应该在应用程序上下文启动时自动启动。
此属性默认为`true`。
可选。|
|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2** |JPA 适配器从执行所需的操作向其发送带有有效负载的消息的通道。|
|**3** |一个布尔标志,指示在适配器轮询后是否删除选定的记录。
默认情况下,该值为`false`(即未删除记录)。
你必须确保组件作为事务的一部分运行。
否则,你可能会遇到异常,例如:`java.lang.IllegalArgumentException: Removing a detached instance …`。
可选的。|
|**4** |一个布尔标志,指示是否可以批量删除记录或必须一次删除一条记录。
默认情况下,该值为`false`(即可以批量删除记录)。
可选。|
|**5** |要从数据库中查询的实体类的完全限定名。
适配器根据实体类名自动构建 JPA 查询。
可选。|
|**6** |用于执行 JPA 操作的`javax.persistence.EntityManager`实例。
可选的。|
|**7** |一个`javax.persistence.EntityManagerFactory`的实例,用于获得执行 JPA 操作的`javax.persistence.EntityManager`的实例。
可选的。|
|**8** |一个布尔标志,指示 SELECT 操作是期望返回单个结果还是`List`的结果。
如果将此标志设置为`true`,则选中的单个实体将作为消息的有效载荷发送。
如果返回多个实体,抛出异常。
如果`false`,则发送实体的`List`作为消息的有效负载。
值默认为`false`。
可选。|
|**9** |用于执行 JPA 操作的`org.springframework.integration.jpa.core.JpaOperations`的实现。
我们建议不提供自己的实现,而是使用默认的`org.springframework.integration.jpa.core.DefaultJpaOperations`实现。
可以使用`entity-manager`、`entity-manager-factory`或`jpa-operations`属性中的任意一个。
可选的。|
|**10**|将由此适配器执行的 JPA QL。
可选的。|
|**11**|需要由此适配器执行的已命名查询。
可选的。|
|**12**|由此适配器执行的本机查询。
可以使用`jpa-query`、`named-query`、`entity-class`或`native-query`属性中的任意一个。
可选的。|
|**13**|一个`o.s.i.jpa.support.parametersource.ParameterSource`的实现,用于解析查询中的参数的值。
如果`entity-class`属性有一个值,则忽略。
可选。|
|**14**|发送消息到通道时等待的最长时间(以毫秒为单位)。
可选。|
#### 使用 Java 配置进行配置
Spring 以下引导应用程序展示了如何使用 Java 配置入站适配器的示例:
```
@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");
return executor;
}
@Bean
@InboundChannelAdapter(channel = "jpaInputChannel",
poller = @Poller(fixedDelay = "${poller.interval}"))
public MessageSource> jpaInbound() {
return new JpaPollingChannelAdapter(jpaExecutor());
}
@Bean
@ServiceActivator(inputChannel = "jpaInputChannel")
public MessageHandler handler() {
return message -> System.out.println(message.getPayload());
}
}
```
#### 使用 Java DSL 进行配置
Spring 以下引导应用程序展示了如何使用 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 pollingAdapterFlow() {
return IntegrationFlows
.from(Jpa.inboundAdapter(this.entityManagerFactory)
.entityClass(StudentDomain.class)
.maxResults(1)
.expectSingleResult(true),
e -> e.poller(p -> p.trigger(new OnlyOnceTrigger())))
.channel(c -> c.queue("pollingResults"))
.get();
}
}
```
### 出站通道适配器
JPA 出站通道适配器允许你通过请求通道接受消息。有效负载可以用作要持久化的实体,也可以与 JPQL 查询的参数表达式中的头一起使用。下面的部分介绍了执行这些操作的可能方法。
#### 使用实体类
以下 XML 将出站通道适配器配置为将一个实体持久化到数据库:
```
(4)
```
|**1**|将有效的 JPA 实体发送到 JPA 出站通道适配器的通道。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|适配器接受的实体类的完全限定名称,以便在数据库中持久化。
实际上,在大多数情况下,你可以不使用此属性,因为适配器可以从 Spring 集成消息有效负载中自动确定实体类。|
|**3**|要由适配器完成的操作。
有效值为`PERSIST`、`MERGE`和`DELETE`。
默认值为`MERGE`。|
|**4**|JPA 要使用的实体管理器。|
将`outbound-channel-adapter`的这四个属性配置为在输入通道上接受实体,并将它们处理为`PERSIST`、`MERGE`或`DELETE`来自底层数据源的实体。
| |在 Spring Integration3.0 中,`PERSIST`或`MERGE`的有效载荷也可以是类型`[java.lang.Iterable](https://docs.oracle.com/javase/7/docs/api/java/lang/Iterable.html)`的。
,在这种情况下,由`Iterable`返回的每个对象都被视为一个实体,并使用底层`EntityManager`进行持久化或合并。迭代器返回的
空值被忽略。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| |从版本 5.5.4 开始,`JpaOutboundGateway`的`JpaExecutor`配置为`PersistMode.DELETE`的`JpaExecutor`可以接受`Iterable`有效负载,为提供的实体执行批删除持久操作。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 使用 JPA 查询语言( JPA ql)
[上一节](#jpa-outbound-channel-adapter-entity-class)演示了如何通过使用实体执行`PERSIST`操作。本节展示了如何使用带有 JPA QL 的出站通道适配器。
以下 XML 将出站通道适配器配置为将一个实体持久化到数据库:
```
(3)
(4)
```
|**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 参数:
```
```
正如前面的示例所示,你可以在出站通道适配器元素中使用多个`parameter`元素,并通过使用表达式和其他具有静态值的元素来定义一些参数。但是,请注意不要多次指定相同的参数名称。你应该为 JPA 查询中指定的每个命名参数提供一个`parameter`元素。例如,我们指定两个参数:`level`和`name`。`level`属性是类型`java.lang.Integer`的静态值,而`name`属性来自消息的有效负载。
| |尽管指定`select`对 JPA QL 有效,但这样做没有意义。
出站通道适配器不返回任何结果。
如果你想选择一些值,请考虑使用出站网关。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 使用本机查询
本节描述如何使用本机查询来使用 JPA 出站通道适配器执行操作。使用本机查询与使用 JPA QL 类似,只是查询是本机数据库查询。通过使用本机查询,我们失去了数据库供应商的独立性,这是我们使用 JPA QL 获得的。
我们可以通过使用本机查询实现的事情之一是执行数据库插入,这在 JPA QL 中是不可能的。(为了执行插入,我们将 JPA 个实体发送到通道适配器,如[前面描述的](#jpa-outbound-channel-adapter-entity-class))。下面是一个小的 XML 片段,演示了如何使用本机查询在表中插入值。
| |JPA 提供程序可能不支持与本机 SQL 查询一起使用的命名参数,
虽然它们在 Hibernate 上运行良好,OpenJPA 和 EclipseLink 不支持它们。
参见[https://issues.apache.org/jira/browse/OPENJPA-111](https://issues.apache.org/jira/browse/OPENJPA-111)。
JPA 2.0 规范的第 3.8.12 节规定:“只有位置参数绑定和对结果项的位置访问可以用于本地查询。”|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
下面的示例使用本机查询配置出站通道适配器:
```
```
|**1**|由此出站通道适配器执行的本机查询。|
|-----|-----------------------------------------------------------|
请注意,其他属性(例如`channel`和`entity-manager`)和`parameter`元素具有与 JPA QL 相同的语义。
#### 使用命名查询
使用命名查询类似于使用[JPA QL](#jpa-using-jpaql)或[本机查询](#jpa-using-native-queries),只是我们指定了命名查询而不是查询。首先,我们介绍如何定义 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 来定义命名查询,如下例所示:
```
...
select s from Student s where s.lastName = 'Last One'
```
现在,我们已经展示了如何通过使用注释或使用`orm.xml`来定义命名查询,现在,我们展示了一个小的 XML 片段,它通过使用命名查询来定义`outbound-channel-adapter`,如下例所示:
```
```
|**1**|当适配器通过通道接收消息时,我们希望它执行的已命名查询。|
|-----|------------------------------------------------------------------------------------------------|
#### 配置参数引用
下面的清单显示了可以在出站通道适配器上设置的所有属性:
```
(17)
(18)
```
|**1** |生命周期属性表示此组件是否应在应用程序上下文启动期间启动。
它默认为`true`。
可选。|
|------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2** |出站适配器接收用于执行所需操作的消息的通道。|
|**3** |JPA 操作的实体类的完全限定名称。
`entity-class`、`query`和`named-query`属性是互斥的。
可选的。|
|**4** |用于执行 JPA 操作的`javax.persistence.EntityManager`实例。
可选的。|
|**5** |一个`javax.persistence.EntityManagerFactory`的实例用于获得`javax.persistence.EntityManager`的实例,它执行 JPA 操作。
可选的。|
|**6** |`org.springframework.integration.jpa.core.JpaOperations`用于执行 JPA 操作的一种实现。
我们建议不提供自己的实现,而是使用默认的`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 集成 3.0 开始,要持久或合并的有效负载也可以是类型`[java.lang.Iterable](https://docs.oracle.com/javase/7/docs/api/java/lang/Iterable.html)`。
在这种情况下,由`Iterable`返回的每个对象都被视为一个实体,并通过使用底层`EntityManager`进行持久化或合并。迭代器返回的
空值被忽略。|
|**13**|如果你想在持久化、合并之后立即刷新持久化上下文,请将该值设置为`true`,或者删除操作,并且不希望依赖`EntityManager`的`flushMode`。
它默认设置为`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 下面的引导应用程序展示了如何使用 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 以下引导应用程序展示了如何使用 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());
}
}
```
### 出站网关
JPA 入站通道适配器允许对数据库进行轮询以检索一个或多个 JPA 实体。因此,检索到的数据被用来启动 Spring 集成流,该集成流将检索到的数据用作消息负载。
此外,你可以在流的末尾使用 JPA 出站通道适配器来持久化数据,基本上是在持久化操作的末尾停止流。
然而,如何在流的中间执行 JPA 持久性操作?例如,你可能拥有你正在 Spring 集成消息流中处理的业务数据,并且你希望将这些数据持久化,但是你仍然需要在下游使用其他组件。或者,你需要执行 JPQL 查询并主动检索数据,然后在流中的后续组件中对数据进行处理,而不是使用 Poller 对数据库进行轮询。
这就是 JPA 出站网关发挥作用的地方。它们使你能够持久存储数据以及检索数据。为了促进这些用途, Spring 集成提供了两种类型的 JPA 出站网关:
* 更新出站网关
* 检索出站网关
每当使用出站网关执行保存、更新或仅删除数据库中的某些记录的操作时,都需要使用更新出站网关。例如,如果你使用`entity`来持久化它,那么结果将返回合并并持久化的实体。在其他情况下,将返回受影响的记录的数量(更新或删除)。
当从数据库检索(选择)数据时,我们使用一个检索出站网关。对于检索出站网关,我们可以使用 JPQL、命名查询(基于原生或 JPQL)或原生查询来选择数据并检索结果。
更新出站网关在功能上类似于出站通道适配器,只是更新出站网关在执行 JPA 操作后将结果发送到网关的应答通道。
检索出站网关类似于入站通道适配器。
| |我们建议你首先阅读本章前面的[出站通道适配器](#jpa-outbound-channel-adapter)部分和[入站通道适配器](#jpa-inbound-channel-adapter)部分,因为其中解释了大多数常见的概念。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
这种相似性是使用中心`JpaExecutor`类来尽可能地统一公共功能的主要因素。
对于所有 JPA 出站网关都是通用的,并且类似于`outbound-channel-adapter`,我们可以用于执行各种 JPA 操作:
* 实体类别
* JPA 查询语言
* 本机查询
* 命名查询
有关配置示例,请参见[JPA Outbound Gateway Samples](#outboundGatewaySamples)。
#### 常见配置参数
JPA 出站网关始终具有对 Spring 集成`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`的访问是非常有益的,包括头。可选的。
#### 更新出站网关
下面的清单显示了可以在更新-出站-网关上设置的所有属性,并描述了关键属性:
```
```
|**1**|出站网关接收用于执行所需操作的消息的通道。
此属性类似于`outbound-channel-adapter`属性的`channel`。
可选的。|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|网关在执行所需的 JPA 操作后向其发送响应的通道。
如果未定义此属性,则请求消息必须具有`replyChannel`头。
可选的。|
|**3**|指定网关等待将结果发送到应答通道的时间。
仅在应答通道本身可能阻止发送操作(例如,当前已满的有界`QueueChannel`)时才应用。
默认情况下,网关无限期地等待。
该值以毫秒为单位指定。
可选。|
其余的属性将在本章前面描述。见[配置参数参考](#jpaInboundChannelAdapterParameters)和[配置参数参考](#jpaOutboundChannelAdapterParameters)。
#### 使用 Java 配置进行配置
Spring 以下引导应用程序展示了如何使用 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 以下引导应用程序展示了如何使用 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"));
}
}
```
#### 检索出站网关
下面的示例展示了你可以在检索出站网关上设置的所有属性,并描述了关键属性:
```
```
|**1**|(自 Spring Integration4.0 以来)用于确定`EntityManager.find(Class entityClass, Object primaryKey)`方法的`primaryKey`值的 SPEL 表达式,该表达式针对`requestMessage`作为求值上下文的根对象。
`entityClass`参数是从`entity-class`属性确定的,如果存在的话,
,否则,它是由`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 版本引入了此属性。
可选。|
其余的属性将在本章前面描述。见[配置参数参考](#jpaInboundChannelAdapterParameters)和[配置参数参考](#jpaOutboundChannelAdapterParameters)。
#### 使用 Java 配置进行配置
Spring 下面的引导应用程序展示了如何使用 Java 配置出站适配器的示例:
```
@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;
}
}
```
#### 使用 Java DSL 进行配置
Spring 以下引导应用程序展示了如何使用 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 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"));
}
}
```
| |当你选择在检索时删除实体,并且你已检索到一组实体时,默认情况下,实体是在每个实体的基础上删除的。
这可能会导致性能问题。
或者,你可以将属性`deleteInBatch`设置为`true`,从而执行批删除。但是,
,这样做的限制是不支持级联删除。,
JSR317:Java 持久性 2.0 在第 4.10 章中指出,“批量更新和删除操作”,即:
“删除操作仅适用于指定类及其子类的实体。
它不会级联到相关实体。”
有关更多信息,请参见[JSR317:Java 持久性 2.0](https://jcp.org/en/jsr/detail?id=317)|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### JPA 出站网关样本
本节包含使用更新出站网关和检索出站网关的各种示例:
##### 使用实体类进行更新
在下面的示例中,通过使用`org.springframework.integration.jpa.test.entity.Student`实体类作为 JPA 定义参数来持久化更新出站网关:
```
```
|**1**|这是出站网关的请求通道。
它类似于`channel`属性的`outbound-channel-adapter`。|
|-----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|这就是网关与出站适配器不同的地方。
这是接收 JPA 操作的回复的通道,
但是,如果你对接收到的回复不感兴趣,并且只想执行操作,使用 JPA `outbound-channel-adapter`是适当的选择。
在本例中,我们使用一个实体类,其答复是作为 JPA 操作的结果而创建或合并的实体对象。|
##### 使用 jpql 进行更新
下面的示例使用 Java 持久性查询语言更新实体,该语言要求使用更新出站网关:
```
```
|**1**|由于我们使用了更新出站网关,只有`update`和`delete`jpql 查询是明智的选择。|
|-----|---------------------------------------------------------------------------------------------------------------------------------------------------------|
当发送带有`String`有效负载的消息时,该消息还包含一个名为`rollNumber`的头,该头具有`long`值,具有指定滚动号的学生的姓氏将更新为消息有效负载中的值。当使用更新网关时,返回值始终是整数值,该整数值表示受 JPA QL 的执行影响的记录的数量。
##### 使用 jpql 检索实体
下面的示例使用一个检索出站网关和 JPQL 从数据库中检索(选择)一个或多个实体:
```
```
##### 使用`id-expression`检索实体
下面的示例使用带有`id-expression`的检索出站网关从数据库中检索(查找)一个且只有一个实体:`primaryKey`是`id-expression`求值的结果。`entityClass`是一类消息`payload`。
```
```
##### 使用命名查询进行更新
使用命名查询基本上与直接使用 JPQL 查询相同。不同之处在于使用了`named-query`属性,如下例所示:
```
```
| |你可以找到一个完整的示例应用程序,它使用 Spring Integration 的 JPA 适配器[here](https://github.com/spring-projects/spring-integration-samples/tree/main/basic/jpa)。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|