From 14c966c1fa676f17de45b8c8622f6d9287975af1 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 21 Nov 2013 08:04:03 +0200 Subject: [PATCH] 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 --- .../xml/JpaInboundChannelAdapterParser.java | 1 + .../xml/JpaOutboundChannelAdapterParser.java | 6 +- .../RetrievingJpaOutboundGatewayParser.java | 1 + .../xml/UpdatingJpaOutboundGatewayParser.java | 6 +- .../jpa/core/AbstractJpaOperations.java | 8 ++- .../jpa/core/DefaultJpaOperations.java | 48 ++++++++++---- .../integration/jpa/core/JpaExecutor.java | 64 ++++++++++++++++++- .../integration/jpa/core/JpaOperations.java | 31 +++++++++ .../config/xml/spring-integration-jpa-3.0.xsd | 44 ++++++++++++- .../JpaInboundChannelAdapterParserTests.java | 3 + .../JpaInboundChannelAdapterParserTests.xml | 2 + .../xml/JpaMessageHandlerParserTests.java | 5 +- .../xml/JpaMessageHandlerParserTests.xml | 2 + .../xml/JpaOutboundGatewayParserTests.java | 5 ++ .../xml/JpaOutboundGatewayParserTests.xml | 4 ++ .../jpa/core/AbstractJpaOperationsTests.java | 21 +++--- .../EclipseLinkJpaOperationsTests-context.xml | 2 + .../OpenJpaJpaOperationsTests-context.xml | 2 + .../JpaPollingChannelAdapterTests.java | 5 +- ...utboundGatewayIntegrationTests-context.xml | 28 ++++---- .../JpaOutboundGatewayIntegrationTests.java | 13 +++- .../jpa/test/TestHibernateJpaDialect.java | 38 +++++++++++ .../src/test/resources/commonJpa-context.xml | 2 +- .../test/resources/hibernateJpa-context.xml | 6 +- src/reference/docbook/jpa.xml | 46 ++++++++++++- src/reference/docbook/whats-new.xml | 7 +- 26 files changed, 350 insertions(+), 50 deletions(-) create mode 100644 spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/TestHibernateJpaDialect.java diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParser.java index be752b47ad..2896af1412 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParser.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParser.java @@ -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"); diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java index 22e7b698ae..19fcd101d8 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java @@ -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"); diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java index faf683e2a6..7d1e158ae9 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java @@ -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"); diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/UpdatingJpaOutboundGatewayParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/UpdatingJpaOutboundGatewayParser.java index bfe05c5e5c..40bbd80ef7 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/UpdatingJpaOutboundGatewayParser.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/UpdatingJpaOutboundGatewayParser.java @@ -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); diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/AbstractJpaOperations.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/AbstractJpaOperations.java index 3e98bb671d..b17d59c120 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/AbstractJpaOperations.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/AbstractJpaOperations.java @@ -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(); + } + } diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/DefaultJpaOperations.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/DefaultJpaOperations.java index ec1acc7bbe..e9dd8b4980 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/DefaultJpaOperations.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/DefaultJpaOperations.java @@ -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> parameters = query.getParameters(); if(parameters != null && !parameters.isEmpty()) { @@ -311,4 +332,5 @@ public class DefaultJpaOperations extends AbstractJpaOperations { } } + } diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java index 204e348046..f0ea84cf4c 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java @@ -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 false, which means that * deletion occurs on a per object basis if a collection of entities is being diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperations.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperations.java index b6aaec97ac..3d62f933a5 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperations.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperations.java @@ -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(); + } diff --git a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-3.0.xsd b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-3.0.xsd index e95dac43cf..a56c43f103 100644 --- a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-3.0.xsd +++ b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-3.0.xsd @@ -389,7 +389,7 @@ - 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. @@ -411,6 +411,39 @@ + + + + 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'. + + + + + + + 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'. + + + + + + + Specifies whether EntityManager.clear() should be called after EntityManager.flush(). + Applies only if 'flush-size > 0' or 'flush = true'. + Defaults to 'false'. + + + @@ -495,6 +528,15 @@ + + + + Specifies whether EntityManager.flush() should be called after performing 'deletes' for retrieved entities. + Applies only if 'delete-after-poll = true'. + Defaults to 'false'. + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.java index 9120d100c1..45eee582b7 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.java @@ -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 diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.xml index 93528c86f5..dd68a9506f 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.xml @@ -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"> diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java index 78e7df421f..8ce8923677 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java @@ -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 diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml index 588dbad2b1..8e05bb03d3 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml @@ -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"> diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java index 776ec14df2..90cb0125d8 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java @@ -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 diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml index 2a00e6e7cb..c350f3eb5c 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml @@ -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"/> ); @@ -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()); diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests-context.xml index 2d9ac822a9..2d16167444 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests-context.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests-context.xml @@ -5,6 +5,8 @@ + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests-context.xml index b4d11e190f..90dc17d2bb 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests-context.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests-context.xml @@ -5,6 +5,8 @@ + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests.java index b427336156..f5adab7374 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests.java @@ -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()); } diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml index 7c650e0bae..5537c66ab3 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml @@ -1,26 +1,30 @@ - + - - + + - + delete-after-poll="true" + flush-after-delete="true" + reply-timeout="100"> + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java index d1e9f6e891..a62d471a27 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java @@ -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 message = MessageBuilder .withPayload(1) - .setHeader("maxResults", "1") + .setHeader("maxResults", "10") .build(); requestChannel.send(message); } diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/TestHibernateJpaDialect.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/TestHibernateJpaDialect.java new file mode 100644 index 0000000000..413bf302df --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/TestHibernateJpaDialect.java @@ -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); + } + +} diff --git a/spring-integration-jpa/src/test/resources/commonJpa-context.xml b/spring-integration-jpa/src/test/resources/commonJpa-context.xml index 63b350f9df..c833c6c5f5 100644 --- a/spring-integration-jpa/src/test/resources/commonJpa-context.xml +++ b/spring-integration-jpa/src/test/resources/commonJpa-context.xml @@ -34,7 +34,7 @@ - + diff --git a/spring-integration-jpa/src/test/resources/hibernateJpa-context.xml b/spring-integration-jpa/src/test/resources/hibernateJpa-context.xml index 69aa1f9ab1..c6504a6a83 100644 --- a/spring-integration-jpa/src/test/resources/hibernateJpa-context.xml +++ b/spring-integration-jpa/src/test/resources/hibernateJpa-context.xml @@ -7,10 +7,12 @@ + + - - \ No newline at end of file + + diff --git a/src/reference/docbook/jpa.xml b/src/reference/docbook/jpa.xml index f2c2eac3f2..cab0c8a85c 100644 --- a/src/reference/docbook/jpa.xml +++ b/src/reference/docbook/jpa.xml @@ -411,7 +411,8 @@ max-number-of-results="" ]]> ]]> ]]> @@ -484,6 +485,14 @@ java.lang.IllegalArgumentException: Removing a detached instance ... + + + + Set this value to true if you want + to the persistence context immediately after deleting received entities + and if you don't want rely on theEntityManager's flushMode. + The default value is set to false. +
@@ -852,6 +861,9 @@ public class Student { order="" ]]> ]]>NULL values returned by the iterator are ignored. + + + Set this value to true if you want + to flush the persistence context immediately after persist, merge or delete operations + and don't want to rely on the EntityManager's flushMode. + The default value is set to false. Applies only if the flush-size + attribute isn't specified. If this attribute is set to true, + then flush-size will be implicitly set to 1, if it wasn't configured to any other value. + + + + + 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 EntityManager's flushMode. + The default value is set to 0 which means 'no flush'. + This attribute is geared towards messages with Iterable payloads. + For instance, if flush-size is set to 3, + then entityManager.flush() is called after every third entity. + Furthermore, entityManager.flush() will be called once more after the entire loop. + There is no reason to configure the flush + attribute, if the 'flush-size' attribute is specified with a value greater than '0'. + + + + + 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 flush attribute is set to + true or if the flush-size attribute is set to a value greater than 0. + + If set to true, the payload of the Message will be used as a source for providing parameters. diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 174005f429..8a6ffa7642 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -366,7 +366,7 @@
- JPA Support Improvements + JPA Support: Improvements Payloads to persist or merge can now be of type @@ -380,6 +380,11 @@ EntityManager. NULL values returned by the iterator are ignored. + + The JPA adapters now have additional attributes to optionally 'flush' and 'clear' + entities from the associated persistence context after performing persistence operations. + + For more information see .
Jackson Support (JSON)