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 833e2b9fa8..4d6745e396 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 @@ -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. @@ -15,6 +15,7 @@ */ package org.springframework.integration.jpa.core; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -22,6 +23,8 @@ import java.util.Set; import javax.persistence.Parameter; import javax.persistence.Query; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.integration.jpa.support.JpaUtils; import org.springframework.integration.jpa.support.parametersource.ParameterSource; import org.springframework.integration.jpa.support.parametersource.PositionSupportingParameterSource; @@ -40,6 +43,8 @@ import org.springframework.util.StringUtils; */ public class DefaultJpaOperations extends AbstractJpaOperations { + private static final Log logger = LogFactory.getLog(DefaultJpaOperations.class); + public void delete(Object entity) { Assert.notNull(entity, "The entity must not be null!"); entityManager.remove(entity); @@ -172,11 +177,63 @@ public class DefaultJpaOperations extends AbstractJpaOperations { } public Object merge(Object entity) { - return entityManager.merge(entity); + Assert.notNull(entity, "The object to merge must not be null."); + return persistOrMerge(entity, true); } public void persist(Object entity) { - entityManager.persist(entity); + Assert.notNull(entity, "The object to persist must not be null."); + persistOrMerge(entity, false); + } + + private Object persistOrMerge(Object entity, boolean isMerge) { + + if (entity instanceof Iterable) { + + @SuppressWarnings("unchecked") + Iterable entities = (Iterable) entity; + + int savedEntities = 0; + int nullEntities = 0; + + List mergedEntities = new ArrayList(); + + for (Object iteratedEntity : entities) { + if (iteratedEntity == null) { + nullEntities++; + } + else { + if (isMerge) { + mergedEntities.add(entityManager.merge(iteratedEntity)); + } + else { + entityManager.persist(iteratedEntity); + } + savedEntities++; + } + } + + if (logger.isDebugEnabled()) { + logger.debug(String.format("%s %s entities. %s NULL entities were ignored.", + isMerge ? "Merged" : "Persisted", savedEntities, nullEntities)); + } + + if (isMerge) { + return mergedEntities; + } + else { + return null; + } + } + else { + if (isMerge) { + return entityManager.merge(entity); + } + else { + entityManager.persist(entity); + return null; + } + } } /** 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 007cd2c8f2..7c6e3145cc 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 @@ -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. @@ -17,6 +17,8 @@ package org.springframework.integration.jpa.core; import java.util.List; +import javax.persistence.EntityManager; + import org.springframework.integration.jpa.support.parametersource.ParameterSource; /** @@ -138,7 +140,10 @@ public interface JpaOperations { Object getSingleResultForQuery(String query, ParameterSource source); /** - * The entity to be merged with the entity manager + * 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}. {@code Null} + * values returned while iterating over the {@link Iterable} are ignored. * * @param entity Must not be null. * @return The merged managed instance of the entity. @@ -146,7 +151,11 @@ public interface JpaOperations { Object merge(Object entity); /** - * Persists the 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. + * * @param entity Must not be null * */ diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/AbstractJpaOperationsTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/AbstractJpaOperationsTests.java index 137d9464c0..968a5b5971 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/AbstractJpaOperationsTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/AbstractJpaOperationsTests.java @@ -24,7 +24,6 @@ import java.util.List; import javax.persistence.EntityManager; import org.junit.Assert; - import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory; @@ -234,6 +233,92 @@ public class AbstractJpaOperationsTests { Assert.assertTrue(student != savedStudent); } + public void testMergeCollection() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final StudentDomain student1 = JpaTestUtils.getTestStudent(); + final StudentDomain student2 = JpaTestUtils.getTestStudent(); + final StudentDomain student3 = JpaTestUtils.getTestStudent(); + + student1.setFirstName("Karl"); + student2.setFirstName("Otto"); + student3.setFirstName("Wilhelm"); + + List students = new ArrayList(3); + + students.add(student1); + students.add(student2); + students.add(student3); + + Assert.assertNull(student1.getRollNumber()); + Assert.assertNull(student2.getRollNumber()); + Assert.assertNull(student3.getRollNumber()); + + Object savedStudents = jpaOperations.merge(students); + entityManager.flush(); + + Assert.assertTrue(savedStudents instanceof List); + + @SuppressWarnings("unchecked") + List savedStudentCollection = (List) savedStudents; + + Assert.assertNotNull(savedStudentCollection.get(0).getRollNumber()); + Assert.assertNotNull(savedStudentCollection.get(1).getRollNumber()); + Assert.assertNotNull(savedStudentCollection.get(2).getRollNumber()); + } + + public void testMergeNullCollection() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + try { + jpaOperations.merge(null); + } + catch (IllegalArgumentException e) { + Assert.assertEquals("The object to merge must not be null.", e.getMessage()); + return; + } + + Assert.fail("Expected an IllegalArgumentException to be thrown."); + + } + + public void testMergeCollectionWithNullElement() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final List studentsFromDbBeforeTest = jpaOperations.getResultListForClass(StudentDomain.class, 0); + + Assert.assertEquals(3, studentsFromDbBeforeTest.size()); + + final StudentDomain student1 = JpaTestUtils.getTestStudent(); + final StudentDomain student2 = null; + final StudentDomain student3 = JpaTestUtils.getTestStudent(); + + student1.setFirstName("Karl"); + student3.setFirstName("Wilhelm"); + + List students = new ArrayList(3); + + students.add(student1); + students.add(student2); + students.add(student3); + + Assert.assertNull(student1.getRollNumber()); + Assert.assertNull(student2); + Assert.assertNull(student3.getRollNumber()); + + Object savedStudents = jpaOperations.merge(students); + entityManager.flush(); + + Assert.assertTrue(savedStudents instanceof List); + + @SuppressWarnings("unchecked") + List savedStudentCollection = (List) savedStudents; + + Assert.assertNotNull(savedStudentCollection.get(0).getRollNumber()); + Assert.assertNotNull(savedStudentCollection.get(1).getRollNumber()); + + } + /** * Test method for {@link org.springframework.integration.jpa.core.DefaultJpaOperations#persist(java.lang.Object)}. */ @@ -248,6 +333,83 @@ public class AbstractJpaOperationsTests { Assert.assertNotNull(student.getRollNumber()); } + public void testPersistCollection() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final StudentDomain student1 = JpaTestUtils.getTestStudent(); + final StudentDomain student2 = JpaTestUtils.getTestStudent(); + final StudentDomain student3 = JpaTestUtils.getTestStudent(); + + student1.setFirstName("Karl"); + student2.setFirstName("Otto"); + student3.setFirstName("Wilhelm"); + + List students = new ArrayList(3); + + students.add(student1); + students.add(student2); + students.add(student3); + + Assert.assertNull(student1.getRollNumber()); + Assert.assertNull(student2.getRollNumber()); + Assert.assertNull(student3.getRollNumber()); + + jpaOperations.persist(students); + entityManager.flush(); + Assert.assertNotNull(student1.getRollNumber()); + Assert.assertNotNull(student2.getRollNumber()); + Assert.assertNotNull(student3.getRollNumber()); + } + + public void testPersistNullCollection() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + try { + jpaOperations.persist(null); + } + catch (IllegalArgumentException e) { + Assert.assertEquals("The object to persist must not be null.", e.getMessage()); + return; + } + + Assert.fail("Expected an IllegalArgumentException to be thrown."); + + } + + public void testPersistCollectionWithNullElement() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final List studentsFromDbBeforeTest = jpaOperations.getResultListForClass(StudentDomain.class, 0); + + Assert.assertEquals(3, studentsFromDbBeforeTest.size()); + + final StudentDomain student1 = JpaTestUtils.getTestStudent(); + final StudentDomain student2 = null; + final StudentDomain student3 = JpaTestUtils.getTestStudent(); + + student1.setFirstName("Karl"); + student3.setFirstName("Wilhelm"); + + List students = new ArrayList(3); + + students.add(student1); + students.add(student2); + students.add(student3); + + Assert.assertNull(student1.getRollNumber()); + Assert.assertNull(student2); + Assert.assertNull(student3.getRollNumber()); + + jpaOperations.persist(students); + entityManager.flush(); + Assert.assertNotNull(student1.getRollNumber()); + Assert.assertNotNull(student3.getRollNumber()); + + final List studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0); + + Assert.assertNotNull(studentsFromDb); + Assert.assertEquals(5, studentsFromDb.size()); + } public void testDeleteInBatch() { final JpaOperations jpaOperations = getJpaOperations(entityManager); diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests.java index ef107b815b..70dd8a2f86 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests.java @@ -15,7 +15,6 @@ package org.springframework.integration.jpa.core; import java.text.ParseException; import org.junit.Assert; - import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; @@ -99,12 +98,48 @@ public class EclipseLinkJpaOperationsTests extends AbstractJpaOperationsTests { super.testMerge(); } + @Test + @Override + public void testMergeCollection() { + super.testMergeCollection(); + } + + @Test + @Override + public void testMergeNullCollection() { + super.testMergeNullCollection(); + } + + @Test + @Override + public void testMergeCollectionWithNullElement() { + super.testMergeCollectionWithNullElement(); + } + @Test @Override public void testPersist() { super.testPersist(); } + @Test + @Override + public void testPersistCollection() { + super.testPersistCollection(); + } + + @Test + @Override + public void testPersistNullCollection() { + super.testPersistNullCollection(); + } + + @Test + @Override + public void testPersistCollectionWithNullElement() { + super.testPersistCollectionWithNullElement(); + } + @Test @Override public void testGetAllStudents() { diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests.java index d110687109..6ad0872763 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests.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 @@ -128,12 +128,48 @@ public class HibernateJpaOperationsTests extends AbstractJpaOperationsTests { super.testMerge(); } + @Test + @Override + public void testMergeCollection() { + super.testMergeCollection(); + } + + @Test + @Override + public void testMergeNullCollection() { + super.testMergeNullCollection(); + } + + @Test + @Override + public void testMergeCollectionWithNullElement() { + super.testMergeCollectionWithNullElement(); + } + @Test @Override public void testPersist() { super.testPersist(); } + @Test + @Override + public void testPersistCollection() { + super.testPersistCollection(); + } + + @Test + @Override + public void testPersistNullCollection() { + super.testPersistNullCollection(); + } + + @Test + @Override + public void testPersistCollectionWithNullElement() { + super.testPersistCollectionWithNullElement(); + } + @Test @Override public void testDeleteInBatch() { diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests.java index 9f69ac860c..2af29d0863 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests.java @@ -17,7 +17,6 @@ import java.sql.SQLException; import java.text.ParseException; import org.junit.Assert; - import org.apache.openjpa.jdbc.conf.JDBCConfiguration; import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl; import org.apache.openjpa.jdbc.meta.MappingTool; @@ -144,12 +143,48 @@ public class OpenJpaJpaOperationsTests extends AbstractJpaOperationsTests { super.testMerge(); } + @Test + @Override + public void testMergeCollection() { + super.testMergeCollection(); + } + + @Test + @Override + public void testMergeNullCollection() { + super.testMergeNullCollection(); + } + + @Test + @Override + public void testMergeCollectionWithNullElement() { + super.testMergeCollectionWithNullElement(); + } + @Test @Override public void testPersist() { super.testPersist(); } + @Test + @Override + public void testPersistCollection() { + super.testPersistCollection(); + } + + @Test + @Override + public void testPersistNullCollection() { + super.testPersistNullCollection(); + } + + @Test + @Override + public void testPersistCollectionWithNullElement() { + super.testPersistCollectionWithNullElement(); + } + @Test @Override public void testGetAllStudents() { diff --git a/src/reference/docbook/jpa.xml b/src/reference/docbook/jpa.xml index 918f03e8a2..ce7d21bb3a 100644 --- a/src/reference/docbook/jpa.xml +++ b/src/reference/docbook/jpa.xml @@ -634,6 +634,19 @@ them to PERSIST,MERGE or DELETE it from the underlying data source. + + + As of Spring Integration 3.0, + payloads to persist or + merge can also be of type + 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. + +
Using JPA Query Language (JPA QL) @@ -893,10 +906,23 @@ public class Student { - Accepts one of the PERSIST,MERGE or DELETE. Indicates the operation that the adapter needs to perform. + Accepts one of the following: PERSIST, + MERGE or DELETE. + Indicates the operation that the adapter needs to perform. Relevant only if an entity is being used for JPA operations. Ignored if JPA QL, named query or native query is provided. Defaults to MERGE. Optional. + + As of Spring Integration 3.0, + payloads to persist or + merge can also be of type + 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. + diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 668ef20d5c..ed362d8eef 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -194,6 +194,21 @@ please see .
+
+ JPA Support Improvements + + Payloads to persist or + merge can now be of type + 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. + +
Jackson Support (JSON)