diff --git a/src/reference/asciidoc/jpa.adoc b/src/reference/asciidoc/jpa.adoc index 5ea7b70b5a..76daac60bd 100644 --- a/src/reference/asciidoc/jpa.adoc +++ b/src/reference/asciidoc/jpa.adoc @@ -11,14 +11,14 @@ The following components are provided: -These components can be used to perform _select_, _create_, _update_ and _delete_ operations on the targeted databases by sending/receiving messages to them. +These components can be used to perform _select_, _create_, _update_ and _delete_ operations on the target databases by sending/receiving messages to them. -The JPA _Inbound Channel Adapter_ lets you poll and retrieve (select) data from the database using JPA whereas the JPA_Outbound Channel Adapter_ lets you create, update and delete entities. +The JPA _Inbound Channel Adapter_ lets you poll and retrieve (select) data from the database using JPA whereas the JPA _Outbound Channel Adapter_ lets you create, update and delete entities. Outbound Gateways for JPA can be used to persist entities to the database, yet allowing you to continue with the flow and execute further components downstream. Similarly, you can use an Outbound Gateway to retrieve entities from the database. -For example, you may use the Outbound Gateway, which receives a Message with a user Id as payload on its request channel, to query the database and retrieve the User entity and pass it downstream for further processing. +For example, you may use the Outbound Gateway, which receives a Message with a `userId` as payload on its request channel, to query the database and retrieve the User entity and pass it downstream for further processing. Recognizing these semantic differences, Spring Integration provides 2 separate JPA Outbound Gateways: @@ -46,20 +46,19 @@ In the following sections we will describe each of these components in more deta The Spring Integration JPA support has been tested using the following persistence providers: * _Hibernate_ -* _OpenJPA_ * _EclipseLink_ -When using a persistence provider, please ensure that the provider is compatible with JPA 2.0. +When using a persistence provider, please ensure that the provider is compatible with JPA 2.1. [[jpa-java-implementation]] === Java Implementation -Each of the provided components will use the `o.s.i.jpa.core.JpaExecutor` class which in turn will use an implementation of the `o.s.i.jpa.core.JpaOperations` interface. +Each of the provided components uses the `o.s.i.jpa.core.JpaExecutor` class which, in turn, uses an implementation of the `o.s.i.jpa.core.JpaOperations` interface. `JpaOperations` operates like a typical Data Access Object (DAO) and provides methods such as _find_, _persist_, _executeUpdate_ etc. For most use cases the provided default implementation `o.s.i.jpa.core.DefaultJpaOperations` should be sufficient. -Nevertheless, you have the option to optionally specify your own implementation in case you require custom behavior. +Nevertheless, you have the option to specify your own implementation in case you require custom behavior. For initializing a `JpaExecutor` you have to use one of 3 available constructors that accept one of: @@ -68,51 +67,27 @@ For initializing a `JpaExecutor` you have to use one of 3 available constructors * _JpaOperations_ - -NOTE: The XML Namespace Support described further below is also very flexible and provides configuration attributes for each JPA component to pass in an _EntityManagerFactory_, _EntityManager_ or _JpaOperations_ reference. - -_Java Configuration Example_ - -The following example of a JPA _Retrieving Outbound Gateway_ is configured purely through Java. -In typical usage scenarios you will most likely prefer the XML Namespace Support described further below. -However, the example illustrates how the classes are wired up. -Understanding the inner workings can also be very helpful for debugging or customizing the individual JPA components. - -First, we instantiate a `JpaExecutor` using an `EntityManager` as constructor argument. -The `JpaExecutor` is then in return used as constructor argument for the `o.s.i.jpa.outbound.JpaOutboundGateway` and the `JpaOutboundGateway` will be passed as constructor argument into the `EventDrivenConsumer`. - -[source,xml] +[source,java] ---- - - - - - - - - - - - - - - +@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; +} ---- -NOTE: For more examples of constructing JPA components purely through Java, see the JUnit test-cases for the JPA Adapters. - [[jpa-namespace-support]] === Namespace Support @@ -146,10 +121,10 @@ Either this attribute or the _entity-manager_ attribute or the _jpa-operations_ The reference to the JPA Entity Manager that will be used by the component. Either this attribute or the _entity-manager-factory_ attribute or the _jpa-operations_ attribute must be provided. -NOTE: Usually your Spring Application Context only defines a JPA Entity Manager Factory and the EntityManager is injected using the @PersistenceContext annotation. +NOTE: Usually your Spring Application Context only defines a JPA Entity Manager Factory and the `EntityManager` is injected using the `@PersistenceContext` annotation. This, however, is not applicable for the Spring Integration JPA components. -Usually, injecting the JPA Entity Manager Factory will be best but in case you want to inject an EntityManager explicitly, you have to define a `SharedEntityManagerBean`. -For more information, please see the relevanthttp://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/orm/jpa/support/SharedEntityManagerBean.html[JavaDoc]. +Usually, injecting the JPA Entity Manager Factory will be best but in case you want to inject an `EntityManager` explicitly, you have to define a `SharedEntityManagerBean`. +For more information, please see the relevant http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/orm/jpa/support/SharedEntityManagerBean.html[JavaDoc]. [source,xml] ---- @@ -221,8 +196,9 @@ _Positional Parameters_ [[jpa-transactions]] ==== Transaction Handling -All JPA operations like Insert, Update and Delete require a transaction to be active whenever they are performed. -For Inbound Channel Adapters there is nothing special to be done, it is similar to the way we configure transaction managers with pollers used with other inbound channel adapters.The xml snippet below shows a sample where a transaction manager is configured with the poller used with an _Inbound Channel Adapter_. +All JPA operations like `INSERT`, `UPDATE` and `DELETE` require a transaction to be active whenever they are performed. +For Inbound Channel Adapters there is nothing special to be done, it is similar to the way we configure transaction managers with pollers used with other inbound channel adapters. +The xml snippet below shows a sample where a transaction manager is configured with the poller used with an _Inbound Channel Adapter_. [source,xml] ---- @@ -412,6 +388,81 @@ Ignored if _entity-class_ attribute is provided._Optional_. <14> Maximum amount of time in milliseconds to wait when sending a message to the channel._Optional_. +==== Configuring with Java Configuration + +The following Spring Boot application provides an example of configuring the inbound adapter using Java configuration: +[source, 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()); + } + +} +---- + +==== Configuring with the Java DSL + +The following Spring Boot application provides an example of configuring the Inbound Adapter using the Java DSL: + +[source, 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 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-outbound-channel-adapter]] === Outbound Channel Adapter @@ -448,7 +499,7 @@ The default value is _MERGE_. As we can see above these 4 attributes of the _outbound-channel-adapter_ are all we need to configure it to accept entities over the input channel and process them to _PERSIST_,_MERGE_ or _DELETE_ it from the underlying data source. -NOTE: As of _Spring Integration 3.0_, payloads to _persist_ or _merge_ can also be of type `http://docs.oracle.com/javase/7/docs/api/java/lang/Iterable.html[java.lang.Iterable]`. +NOTE: As of _Spring Integration 3.0_, payloads to _PERSIST_ or _MERGE_ can also be of type `http://docs.oracle.com/javase/7/docs/api/java/lang/Iterable.html[java.lang.Iterable]`. In that case, each object returned by the `Iterable` is treated as an entity and persisted or merged using the underlying `EntityManager`. _NULL_ values returned by the iterator are ignored. @@ -704,6 +755,84 @@ If false, however, the entire Message will be available as a source for paramete <18> One or more _parameter_ attributes, one for each parameter used in the query. The value or expression provided will be evaluated to compute the value of the parameter._Optional_. +==== Configuring with Java Configuration + +The following Spring Boot application provides an example of configuring the inbound adapter using Java configuration: +[source, 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; + } + +} +---- + +==== Configuring with the Java DSL + +The following Spring Boot application provides an example of configuring the Inbound Adapter using the Java DSL: + +[source, 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 IntegrationFlow outboundAdapterFlow() { + return f -> f + .handle(Jpa.outboundAdapter(this.entityManagerFactory) + .entityClass(StudentDomain.class) + .persistMode(PersistMode.PERSIST), + e -> e.transactional()); + } + +} +---- + [[jpa-outbound-gateways]] === Outbound Gateways @@ -724,7 +853,7 @@ To facilitate these uses, Spring Integration provides two types of JPA Outbound -Whenever the Outbound Gateway is used to perform an action that saves, updates or soley deletes some records in the database, you need to use an _Updating Outbound Gateway_ gateway. +Whenever the Outbound Gateway is used to perform an action that saves, updates or solely deletes some records in the database, you need to use an _Updating Outbound Gateway_ gateway. If for example an _entity_ is used to persist it, then a merged/persisted entity is returned as a result. In other cases the number of records affected (updated or deleted) is returned instead. @@ -820,6 +949,77 @@ The value is specified in milliseconds. _Optional_. +==== Configuring with Java Configuration + +The following Spring Boot application provides an example of configuring the inbound adapter using Java configuration: +[source, 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; + } + +} +---- + +==== Configuring with the Java DSL + +The following Spring Boot application provides an example of configuring the Inbound Adapter using the Java DSL: + +[source, 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 IntegrationFlow updatingGatewayFlow() { + return f -> f + .handle(Jpa.updatingGateway(this.entityManagerFactory), + e -> e.transactional(true)) + .channel(c -> c.queue("updateResults")); + } + +} +---- + + [[jpa-retrieving-outbound-gateway]] ==== Retrieving Outbound Gateway @@ -889,6 +1089,78 @@ _Optional_. This attribute is introduced since version 3.0. _Optional_. +==== Configuring with Java Configuration + +The following Spring Boot application provides an example of configuring the inbound adapter using Java configuration: +[source, 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; + } + +} +---- + +==== Configuring with the Java DSL + +The following Spring Boot application provides an example of configuring the Inbound Adapter using the Java DSL: + +[source, 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 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")); + } + +} +---- + [IMPORTANT] =====