INT-2562: JPA: Add 'flush' Operation

JIRA: https://jira.springsource.org/browse/INT-2562

INT-2562: Add 'flush-size' & 'clear-on-flush' attrs

INT-2562: Documentation

INT-2562: Polishing

INT-2562: Addressing PR's comments

INT-2562: Fix typos and JavaDocs
This commit is contained in:
Artem Bilan
2013-11-21 08:04:03 +02:00
committed by Gary Russell
parent 34683bd560
commit 14c966c1fa
26 changed files with 350 additions and 50 deletions

View File

@@ -67,6 +67,7 @@ public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChanne
jpaExecutorBuilder.addPropertyValue("maxResultsExpression", definition);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "delete-after-poll");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "flush-after-delete", "flush");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "delete-in-batch");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "expect-single-result");

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.integration.jpa.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -25,7 +27,6 @@ import org.springframework.integration.config.xml.AbstractOutboundChannelAdapter
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jpa.outbound.JpaOutboundGatewayFactoryBean;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* The parser for JPA outbound channel adapter
@@ -55,6 +56,9 @@ public class JpaOutboundChannelAdapterParser extends AbstractOutboundChannelAdap
final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getJpaExecutorBuilder(element, parserContext);
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "persist-mode");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "flush");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "flush-size");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "clear-on-flush");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "parameter-source-factory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "use-payload-as-parameter-source");

View File

@@ -72,6 +72,7 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-after-poll");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "flush-after-delete", "flush");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-in-batch");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "expect-single-result");

View File

@@ -15,13 +15,14 @@
*/
package org.springframework.integration.jpa.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jpa.support.OutboundGatewayType;
import org.w3c.dom.Element;
/**
* The Parser for Updating JPA Outbound Gateway.
@@ -42,6 +43,9 @@ public class UpdatingJpaOutboundGatewayParser extends AbstractJpaOutboundGateway
final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getOutboundGatewayJpaExecutorBuilder(gatewayElement, parserContext);
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "persist-mode");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "flush");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "flush-size");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "clear-on-flush");
final BeanDefinition jpaExecutorBuilderBeanDefinition = jpaExecutorBuilder.getBeanDefinition();
final String gatewayId = this.resolveId(gatewayElement, jpaOutboundGatewayBuilder.getRawBeanDefinition(), parserContext);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ import org.springframework.util.Assert;
abstract class AbstractJpaOperations implements JpaOperations, InitializingBean {
protected EntityManager entityManager;
private EntityManagerFactory entityManagerFactory;
@@ -65,4 +66,9 @@ abstract class AbstractJpaOperations implements JpaOperations, InitializingBean
}
@Override
public void flush() {
this.entityManager.flush();
}
}

View File

@@ -203,17 +203,28 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
@Override
public Object merge(Object entity) {
return this.merge(entity, 0, false);
}
@Override
public Object merge(Object entity, int flushSize, boolean clearOnFlush) {
Assert.notNull(entity, "The object to merge must not be null.");
return persistOrMerge(entity, true);
return this.persistOrMerge(entity, true, flushSize, clearOnFlush);
}
@Override
public void persist(Object entity) {
Assert.notNull(entity, "The object to persist must not be null.");
persistOrMerge(entity, false);
this.persist(entity, 0, false);
}
private Object persistOrMerge(Object entity, boolean isMerge) {
@Override
public void persist(Object entity, int flushSize, boolean clearOnFlush) {
Assert.notNull(entity, "The object to persist must not be null.");
persistOrMerge(entity, false, flushSize, clearOnFlush);
}
private Object persistOrMerge(Object entity, boolean isMerge, int flushSize, boolean clearOnFlush) {
Object result = null;
if (entity instanceof Iterable) {
@@ -237,6 +248,12 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
entityManager.persist(iteratedEntity);
}
savedEntities++;
if (flushSize > 0 && savedEntities % flushSize == 0) {
entityManager.flush();
if (clearOnFlush) {
entityManager.clear();
}
}
}
}
@@ -246,30 +263,34 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
}
if (isMerge) {
return mergedEntities;
}
else {
return null;
result = mergedEntities;
}
}
else {
if (isMerge) {
return entityManager.merge(entity);
result = entityManager.merge(entity);
}
else {
entityManager.persist(entity);
return null;
}
}
if (flushSize > 0) {
entityManager.flush();
if (clearOnFlush) {
entityManager.clear();
}
}
return result;
}
/**
* Given a JPQL query, this method gets all parameters defined in this query and
* use the {@link JPAQLParameterSource} to find their values and set them
* use the {@link ParameterSource} to find their values and set them.
*
*/
private void setParametersIfRequired(String queryString,
ParameterSource source, Query query) {
private void setParametersIfRequired(String queryString, ParameterSource source, Query query) {
Set<Parameter<?>> parameters = query.getParameters();
if(parameters != null && !parameters.isEmpty()) {
@@ -311,4 +332,5 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
}
}
}

View File

@@ -61,6 +61,7 @@ import org.springframework.util.Assert;
*
* @author Gunnar Hillert
* @author Amol Nayak
* @author Artem Bilan
* @since 2.2
*
*/
@@ -84,7 +85,14 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
private volatile ParameterSourceFactory parameterSourceFactory = null;
private volatile ParameterSource parameterSource;
private volatile boolean flush = false;
private volatile int flushSize = 0;
private volatile boolean clearOnFlush = false;
private volatile boolean deleteAfterPoll = false;
private volatile boolean deleteInBatch = false;
private volatile boolean expectSingleResult = false;
@@ -194,6 +202,13 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
this.usePayloadAsParameterSource = true;
}
}
if (this.flushSize > 0) {
this.flush = true;
}
else if (this.flush) {
this.flushSize = 1;
}
}
/**
@@ -228,14 +243,17 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
else {
if (PersistMode.PERSIST.equals(this.persistMode)) {
this.jpaOperations.persist(message.getPayload());
this.jpaOperations.persist(message.getPayload(), this.flushSize, this.clearOnFlush);
result = message.getPayload();
}
else if (PersistMode.MERGE.equals(this.persistMode)) {
result = this.jpaOperations.merge(message.getPayload());
result = this.jpaOperations.merge(message.getPayload(), this.flushSize, this.clearOnFlush);
}
else if (PersistMode.DELETE.equals(this.persistMode)) {
this.jpaOperations.delete(message.getPayload());
if (this.flush) {
this.jpaOperations.flush();
}
result = message.getPayload();
}
else {
@@ -310,6 +328,9 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
this.jpaOperations.delete(payload);
}
if (this.flush) {
this.jpaOperations.flush();
}
}
return payload;
}
@@ -453,6 +474,45 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
this.usePayloadAsParameterSource = usePayloadAsParameterSource;
}
/**
* If set to {@code true} the {@link javax.persistence.EntityManager#flush()} will be called
* after persistence operation.
* Has the same effect, if the {@link #flushSize} is specified to {@code 1}.
* For convenience in cases when the provided entity to persist is not an instance of {@link Iterable}.
*
* @param flush defaults to 'false'.
*/
public void setFlush(boolean flush) {
this.flush = flush;
}
/**
* If the provided value is greater than {@code 0}, then {@link javax.persistence.EntityManager#flush()}
* will be called after persistence operations as well as within batch operations.
* This property has precedence over the {@link #flush}, if it is specified to a value greater than {@code 0}.
* If the entity to persist is not an instance of {@link Iterable} and this property is greater than {@code 0},
* then the entity will be flushed as if the {@link #flush} attribute was set to {@code true}.
*
* @param flushSize defaults to '0'.
*/
public void setFlushSize(int flushSize) {
Assert.state(flushSize >= 0, "'flushSize' cannot be less than '0'.");
this.flushSize = flushSize;
}
/**
* If set to {@code true} the {@link javax.persistence.EntityManager#clear()} will be called,
* and only if the {@link javax.persistence.EntityManager#flush()} was called after performing persistence operations.
*
* @see #setFlush(boolean)
* @see #setFlushSize(int)
*
* @param clearOnFlush defaults to 'false'.
*/
public void setClearOnFlush(boolean clearOnFlush) {
this.clearOnFlush = clearOnFlush;
}
/**
* If not set, this property defaults to <code>false</code>, which means that
* deletion occurs on a per object basis if a collection of entities is being

View File

@@ -158,6 +158,19 @@ public interface JpaOperations {
*/
Object merge(Object entity);
/**
* The entity to be merged with the {@link EntityManager}. The provided object can
* also be an {@link Iterable} in which case each object of the {@link Iterable}
* is treated as an entity and merged with the {@link EntityManager}.
* In addition the {@link javax.persistence.EntityManager#flush()} is called after the merge
* and after each batch, as it is specified using {@code flushSize} parameter and if
* provided object is {@link Iterable}.
* {@code clearOnFlush}parameter specifies, if the {@link javax.persistence.EntityManager#clear()}
* should be called after each {@link javax.persistence.EntityManager#flush()}.
*/
Object merge(Object entity, int flushSize, boolean clearOnFlush);
/**
* Persists the entity. The provided object can also be an {@link Iterable}
* in which case each object of the {@link Iterable} is treated as an entity
@@ -169,4 +182,22 @@ public interface JpaOperations {
*/
void persist(Object entity);
/**
* Persists the entity. The provided object can also be an {@link Iterable}
* in which case each object of the {@link Iterable} is treated as an entity
* and persisted with the {@link EntityManager}. {@code Null} values returned
* while iterating over the {@link Iterable} are ignored.
* In addition the {@link javax.persistence.EntityManager#flush()} is called after the persist
* and after each batch, as it is specified using {@code flushSize} parameter and if
* provided object is {@link Iterable}.
* {@code clearOnFlush}parameter specifies, if the {@link javax.persistence.EntityManager#clear()}
* should be called after each {@link javax.persistence.EntityManager#flush()}.
*/
void persist(Object entity, int flushSize, boolean clearOnFlush);
/**
* Executes {@link javax.persistence.EntityManager#flush()}.
*/
void flush();
}

View File

@@ -389,7 +389,7 @@
<xsd:attribute name="requires-reply" type="xsd:string" use="optional" default="true">
<xsd:annotation>
<xsd:documentation>
Specify whether this outbound gateway must return a non-null value. This value is
Specifies whether this outbound gateway must return a non-null value. This value is
'true' by default, and a ReplyRequiredException will be thrown when
the underlying service returns a null value.
</xsd:documentation>
@@ -411,6 +411,39 @@
<xsd:union memberTypes="persistMode xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="flush" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation>
Specifies whether EntityManager.flush() should be called after performing persistence operations.
If 'flush-size' is configured to a value greater than '0' this attribute will be ignored.
Defaults to 'false'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="flush-size" type="xsd:string" default="0">
<xsd:annotation>
<xsd:documentation>
Specifies the number of entities after which to call EntityManager.flush()
when performing persistence operations.
This attribute is applicable for payload instances of "java.lang.Iterable".
Defaults to '0' - 'flush' won't be called.
This attribute has precedence over the 'flush' attribute,
if it is configured to a value greater than '0'.
If the entity to persist is not an instance of "java.lang.Iterable"
and 'flush-size' is greater than '0',
then the entity will be flushed as if the 'flush' attribute was set to 'true'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="clear-on-flush" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation>
Specifies whether EntityManager.clear() should be called after EntityManager.flush().
Applies only if 'flush-size > 0' or 'flush = true'.
Defaults to 'false'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="commonRetrievingJpaAttributes">
@@ -495,6 +528,15 @@
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="flush-after-delete" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation>
Specifies whether EntityManager.flush() should be called after performing 'deletes' for retrieved entities.
Applies only if 'delete-after-poll = true'.
Defaults to 'false'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="coreJpaComponentAttributes">

View File

@@ -33,6 +33,7 @@ import org.springframework.integration.test.util.TestUtils;
/**
* @author Gunnar Hillert
* @author Amol Nayak
* @author Artem Bilan
*
* @since 2.2
*
@@ -95,6 +96,8 @@ public class JpaInboundChannelAdapterParserTests {
assertEquals("13", TestUtils.getPropertyValue(expression, "literalValue"));
assertTrue(TestUtils.getPropertyValue(jpaExecutor, "deleteAfterPoll", Boolean.class));
assertTrue(TestUtils.getPropertyValue(jpaExecutor, "flush", Boolean.class));
}
@Test

View File

@@ -25,6 +25,8 @@
entity-manager-factory="entityManagerFactory"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
max-results="13"
delete-after-poll="true"
flush-after-delete="true"
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@@ -39,6 +39,7 @@ import org.springframework.integration.test.util.TestUtils;
/**
*
* @author Gunnar Hillert
* @author Artem Bilan
* @since 2.2
*
*/
@@ -81,6 +82,8 @@ public class JpaMessageHandlerParserTests {
assertNotNull(jpaParameters);
assertTrue(jpaParameters.size() == 3);
assertEquals(Integer.valueOf(10), TestUtils.getPropertyValue(jpaExecutor, "flushSize", Integer.class));
assertTrue(TestUtils.getPropertyValue(jpaExecutor, "clearOnFlush", Boolean.class));
}
@Test

View File

@@ -19,6 +19,8 @@
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
jpa-query="from Student"
persist-mode="PERSIST"
flush-size="10"
clear-on-flush="true"
order="1"
channel="target">
<int-jpa:parameter name="firstName" value="kenny" type="java.lang.String"/>

View File

@@ -80,6 +80,9 @@ public class JpaOutboundGatewayParserTests extends AbstractRequestHandlerAdvice
TestUtils.getPropertyValue(jpaExecutor, "maxResultsExpression", LiteralExpression.class);
assertNotNull(maxResultsExpression);
assertEquals("55", TestUtils.getPropertyValue(maxResultsExpression, "literalValue"));
assertTrue(TestUtils.getPropertyValue(jpaExecutor, "deleteAfterPoll", Boolean.class));
assertTrue(TestUtils.getPropertyValue(jpaExecutor, "flush", Boolean.class));
}
@Test
@@ -160,6 +163,8 @@ public class JpaOutboundGatewayParserTests extends AbstractRequestHandlerAdvice
assertEquals(PersistMode.PERSIST, persistMode);
assertEquals(Integer.valueOf(100), TestUtils.getPropertyValue(jpaExecutor, "flushSize", Integer.class));
assertTrue(TestUtils.getPropertyValue(jpaExecutor, "clearOnFlush", Boolean.class));
}
@Test

View File

@@ -20,6 +20,8 @@
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
order="1"
max-number-of-results="55"
delete-after-poll="true"
flush-after-delete="true"
request-channel="in"
reply-channel="out"
reply-timeout="100"
@@ -72,6 +74,8 @@
request-channel="in"
reply-channel="out"
reply-timeout="100"
flush-size="100"
clear-on-flush="true"
requires-reply="false"/>
<int-jpa:updating-outbound-gateway id="advised"

View File

@@ -13,7 +13,9 @@
package org.springframework.integration.jpa.core;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.text.ParseException;
@@ -255,8 +257,7 @@ public class AbstractJpaOperationsTests {
Assert.assertNull(student2.getRollNumber());
Assert.assertNull(student3.getRollNumber());
Object savedStudents = jpaOperations.merge(students);
entityManager.flush();
Object savedStudents = jpaOperations.merge(students, 10, true);
Assert.assertTrue(savedStudents instanceof List<?>);
@@ -329,9 +330,10 @@ public class AbstractJpaOperationsTests {
final StudentDomain student = JpaTestUtils.getTestStudent();
Assert.assertNull(student.getRollNumber());
jpaOperations.persist(student);
entityManager.flush();
jpaOperations.persist(student, 1, false);
Assert.assertNotNull(student.getRollNumber());
assertTrue(entityManager.contains(student));
}
public void testPersistCollection() {
@@ -355,11 +357,14 @@ public class AbstractJpaOperationsTests {
Assert.assertNull(student2.getRollNumber());
Assert.assertNull(student3.getRollNumber());
jpaOperations.persist(students);
entityManager.flush();
jpaOperations.persist(students, 1, true);
Assert.assertNotNull(student1.getRollNumber());
Assert.assertNotNull(student2.getRollNumber());
Assert.assertNotNull(student3.getRollNumber());
assertFalse(entityManager.contains(student1));
assertFalse(entityManager.contains(student2));
assertFalse(entityManager.contains(student3));
}
public void testPersistNullCollection() {
@@ -401,8 +406,8 @@ public class AbstractJpaOperationsTests {
Assert.assertNull(student2);
Assert.assertNull(student3.getRollNumber());
jpaOperations.persist(students);
entityManager.flush();
jpaOperations.persist(students, 10, false);
Assert.assertNotNull(student1.getRollNumber());
Assert.assertNotNull(student3.getRollNumber());

View File

@@ -5,6 +5,8 @@
<import resource="classpath:/commonJpa-context.xml" />
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect"/>
<bean id="vendorAdaptor" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter"
parent="abstractVendorAdaptor">
<property name="database" value="H2" />

View File

@@ -5,6 +5,8 @@
<import resource="classpath:/commonJpa-context.xml" />
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.OpenJpaDialect"/>
<!-- EclipseLink vendor adaptor with workaround platform class for HSQL usage -->
<bean id="vendorAdaptor" class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"
parent="abstractVendorAdaptor">

View File

@@ -278,6 +278,7 @@ public class JpaPollingChannelAdapterTests {
jpaExecutor.setJpaQuery("from Student s");
jpaExecutor.setDeleteAfterPoll(true);
jpaExecutor.setDeleteInBatch(true);
jpaExecutor.setFlush(true);
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);
@@ -306,9 +307,7 @@ public class JpaPollingChannelAdapterTests {
assertTrue(students.size() == 3);
Long studentCount = waitForDeletes(students);
assertEquals(Long.valueOf(0), studentCount);
assertEquals(Long.valueOf(0), entityManager.createQuery("select count(*) from Student", Long.class).getSingleResult());
}

View File

@@ -1,26 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-jpa="http://www.springframework.org/schema/integration/jpa"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-jpa="http://www.springframework.org/schema/integration/jpa"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jpa http://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd">
<import resource="classpath:/hibernateJpa-context.xml" />
<import resource="classpath:/hibernateJpa-context.xml"/>
<int:channel id="in"/>
<int:channel id="out"/>
<int:channel id="in"/>
<int:channel id="out"/>
<int-jpa:retrieving-outbound-gateway
id="retrievingGateway"
entity-manager-factory="entityManagerFactory"
<int-jpa:retrieving-outbound-gateway
id="retrievingGateway"
entity-manager="entityManager"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
request-channel="in"
reply-channel="out"
first-result-expression="payload"
max-results-expression="headers['maxResults']"
reply-timeout="100"/>
delete-after-poll="true"
flush-after-delete="true"
reply-timeout="100">
<int-jpa:transactional/>
</int-jpa:retrieving-outbound-gateway>
</beans>

View File

@@ -16,9 +16,13 @@
package org.springframework.integration.jpa.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.FlushModeType;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -29,6 +33,7 @@ import org.springframework.integration.MessagingException;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -52,6 +57,9 @@ public class JpaOutboundGatewayIntegrationTests {
@Qualifier("out")
private SubscribableChannel responseChannel;
@Autowired
private EntityManager entityManager;
/**
* Sends a message with the payload as a integer representing the start number in the result
* set and a header with value maxResults to get the max number of results
@@ -63,12 +71,13 @@ public class JpaOutboundGatewayIntegrationTests {
@SuppressWarnings("rawtypes")
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertEquals(1, ((List)message.getPayload()).size());
assertEquals(2, ((List) message.getPayload()).size());
assertEquals(1, entityManager.createQuery("from Student").getResultList().size());
}
});
Message<Integer> message = MessageBuilder
.withPayload(1)
.setHeader("maxResults", "1")
.setHeader("maxResults", "10")
.build();
requestChannel.send(message);
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.jpa.test;
import javax.persistence.EntityManager;
import javax.persistence.FlushModeType;
import javax.persistence.PersistenceException;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
/**
* @author Artem Bilan
* @since 3.0
*/
@SuppressWarnings("serial")
public class TestHibernateJpaDialect extends HibernateJpaDialect {
@Override
public Object prepareTransaction(EntityManager entityManager, boolean readOnly, String name) throws PersistenceException {
entityManager.setFlushMode(FlushModeType.COMMIT);
return super.prepareTransaction(entityManager, readOnly, name);
}
}

View File

@@ -34,7 +34,7 @@
<property name="dataSource" ref="dataSource" />
<property name="persistenceUnitName" value="persistenceUnit" />
<property name="jpaVendorAdapter" ref="vendorAdaptor" />
<!-- <property name="jpaProperties" ref="jpaProperties" /> -->
<property name="jpaDialect" ref="jpaDialect"/>
</bean>
<bean id="abstractVendorAdaptor" abstract="true">

View File

@@ -7,10 +7,12 @@
<import resource="classpath:/commonJpa-context.xml" />
<bean id="jpaDialect" class="org.springframework.integration.jpa.test.TestHibernateJpaDialect"/>
<bean id="vendorAdaptor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
parent="abstractVendorAdaptor">
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect"/>
<property name="showSql" value="true"/>
</bean>
</beans>
</beans>

View File

@@ -411,7 +411,8 @@
max-number-of-results="" ]]><co id="inboundAdapterMaxResults"/><![CDATA[
max-results="" ]]><co id="inboundAdapterMaxResults2"/><![CDATA[
max-results-expression="" ]]><co id="inboundAdapterMaxResultsExpression"/><![CDATA[
delete-after-poll="true"> ]]><co id="inboundAdapterDeleteAfterPoll"/><![CDATA[
delete-after-poll="true" ]]><co id="inboundAdapterDeleteAfterPoll"/><![CDATA[
flush-after-delete="true"> ]]><co id="inboundAdapterFlushAfterDelete"/><![CDATA[
<int:poller fixed-rate="2000" >
<int:transactional propagation="REQUIRED" transaction-manager="transactionManager"/>
</int:poller>
@@ -484,6 +485,14 @@
<emphasis>java.lang.IllegalArgumentException: Removing
a detached instance ...</emphasis>
</para>
</callout>
<callout arearefs="inboundAdapterFlushAfterDelete">
<para>
Set this value to <code>true</code> if you want
to the persistence context immediately after deleting received entities
and if you don't want rely on the<interfacename>EntityManager</interfacename>'s flushMode.
The default value is set to <code>false</code>.
</para>
</callout>
</calloutlist>
<section id="jpaInboundChannelAdapterParameters">
@@ -852,6 +861,9 @@ public class Student {
order="" ]]><co id="outAdaptOrder"/><![CDATA[
parameter-source-factory="" ]]><co id="outAdaptParamSourceFact"/><![CDATA[
persist-mode="MERGE" ]]><co id="outAdaptPersistMode"/><![CDATA[
flush="true" ]]><co id="outAdaptFlush"/><![CDATA[
flush-size="10" ]]><co id="outAdaptFlushSize"/><![CDATA[
clear-on-flush="true" ]]><co id="outAdaptclearOnFlash"/><![CDATA[
use-payload-as-parameter-source="true" ]]><co id="outAdaptUserPayloadAsParamSrc"/><![CDATA[
<int:poller/>
<int-jpa:transactional/> ]]><co id="outAdaptTransactional"/><![CDATA[
@@ -948,6 +960,38 @@ public class Student {
<emphasis>NULL</emphasis> values returned by the iterator are ignored.
</para>
</callout>
<callout arearefs="outAdaptFlush">
<para>
Set this value to <code>true</code> if you want
to flush the persistence context immediately after persist, merge or delete operations
and don't want to rely on the <interfacename>EntityManager</interfacename>'s flushMode.
The default value is set to <code>false</code>. Applies only if the <code>flush-size</code>
attribute isn't specified. If this attribute is set to <code>true</code>,
then <code>flush-size</code> will be implicitly set to <code>1</code>, if it wasn't configured to any other value.
</para>
</callout>
<callout arearefs="outAdaptFlushSize">
<para>
Set this attribute to a value greater than '0' if you want
to flush the persistence context immediately after persist, merge or delete operations
and don't want to rely on the <interfacename>EntityManager</interfacename>'s flushMode.
The default value is set to <code>0</code> which means 'no flush'.
This attribute is geared towards messages with <interfacename>Iterable</interfacename> payloads.
For instance, if <code>flush-size</code> is set to <code>3</code>,
then <code>entityManager.flush()</code> is called after every third entity.
Furthermore, <code>entityManager.flush()</code> will be called once more after the entire loop.
There is no reason to configure the <code>flush</code>
attribute, if the 'flush-size' attribute is specified with a value greater than '0'.
</para>
</callout>
<callout arearefs="outAdaptclearOnFlash">
<para>
Set this value to 'true' if you want
to clear persistence context immediately after each flush operation.
The attribute's value is applied only if the <code>flush</code> attribute is set to
<code>true</code> or if the <code>flush-size</code> attribute is set to a value greater than <code>0</code>.
</para>
</callout>
<callout arearefs="outAdaptUserPayloadAsParamSrc">
<para>
If set to true, the payload of the Message will be used as a source for providing parameters.

View File

@@ -366,7 +366,7 @@
</para>
</section>
<section id="3.0-jpa-persist-merge-collections">
<title>JPA Support Improvements</title>
<title>JPA Support: Improvements</title>
<para>
Payloads to <emphasis>persist</emphasis> or
<emphasis>merge</emphasis> can now be of type
@@ -380,6 +380,11 @@
<interfacename>EntityManager</interfacename>.
<emphasis>NULL</emphasis> values returned by the iterator are ignored.
</para>
<para>
The JPA adapters now have additional attributes to optionally 'flush' and 'clear'
entities from the associated persistence context after performing persistence operations.
</para>
<para>For more information see <xref linkend="jpa"/>.</para>
</section>
<section id="3.0-json-transformers">
<title>Jackson Support (JSON)</title>