INT-3029 JPA: Support Iterables for Persist/Merge

* Add test cases
* Add documentation

Jira: https://jira.springsource.org/browse/INT-3029

INT-3029 Update copyright year

Doc Polishing
This commit is contained in:
Gunnar Hillert
2013-09-17 10:52:05 -04:00
committed by Gary Russell
parent 772a01be79
commit bbf93e1385
8 changed files with 386 additions and 11 deletions

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.
@@ -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<Object> entities = (Iterable<Object>) entity;
int savedEntities = 0;
int nullEntities = 0;
List<Object> mergedEntities = new ArrayList<Object>();
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;
}
}
}
/**

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.
@@ -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
*
*/

View File

@@ -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<StudentDomain> students = new ArrayList<StudentDomain>(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<StudentDomain> savedStudentCollection = (List<StudentDomain>) 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<StudentDomain> students = new ArrayList<StudentDomain>(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<StudentDomain> savedStudentCollection = (List<StudentDomain>) 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<StudentDomain> students = new ArrayList<StudentDomain>(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<StudentDomain> students = new ArrayList<StudentDomain>(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);

View File

@@ -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() {

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
@@ -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() {

View File

@@ -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() {

View File

@@ -634,6 +634,19 @@
them to <emphasis>PERSIST</emphasis>,<emphasis>MERGE</emphasis> or <emphasis>DELETE</emphasis>
it from the underlying data source.
</para>
<note>
<para>
As of <emphasis>Spring Integration 3.0</emphasis>,
payloads to <emphasis>persist</emphasis> or
<emphasis>merge</emphasis> can also be of type
<interfacename><ulink url="http://docs.oracle.com/javase/7/docs/api/java/lang/Iterable.html">java.lang.Iterable</ulink></interfacename>.
In that case, each object returned by the
<interfacename>Iterable</interfacename> is treated as
an entity and persisted or merged using the underlying
<interfacename>EntityManager</interfacename>.
<emphasis>NULL</emphasis> values returned by the iterator are ignored.
</para>
</note>
</section>
<section>
<title>Using JPA Query Language (JPA QL)</title>
@@ -893,10 +906,23 @@ public class Student {
</callout>
<callout arearefs="outAdaptPersistMode">
<para>
Accepts one of the <emphasis>PERSIST</emphasis>,<emphasis>MERGE</emphasis> or <emphasis>DELETE</emphasis>. Indicates the operation that the adapter needs to perform.
Accepts one of the following: <emphasis>PERSIST</emphasis>,
<emphasis>MERGE</emphasis> or <emphasis>DELETE</emphasis>.
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 <emphasis>MERGE</emphasis>. <emphasis>Optional</emphasis>.
</para>
<para>
As of <emphasis role="bold">Spring Integration 3.0</emphasis>,
payloads to <emphasis>persist</emphasis> or
<emphasis>merge</emphasis> can also be of type
<interfacename><ulink url="http://docs.oracle.com/javase/7/docs/api/java/lang/Iterable.html">java.lang.Iterable</ulink></interfacename>.
In that case, each object returned by the
<interfacename>Iterable</interfacename> is treated as
an entity and persisted or merged using the underlying
<interfacename>EntityManager</interfacename>.
<emphasis>NULL</emphasis> values returned by the iterator are ignored.
</para>
</callout>
<callout arearefs="outAdaptUserPayloadAsParamSrc">
<para>

View File

@@ -194,6 +194,21 @@
please see <xref linkend="jdbc-message-store-generic"/>.
</para>
</section>
<section id="3.0-jpa-persist-merge-collections">
<title>JPA Support Improvements</title>
<para>
Payloads to <emphasis>persist</emphasis> or
<emphasis>merge</emphasis> can now be of type
<interfacename><ulink url="http://docs.oracle.com/javase/7/docs/api/java/lang/Iterable.html">java.lang.Iterable</ulink></interfacename>.
</para>
<para>
In that case, each object returned by the
<interfacename>Iterable</interfacename> is treated as
an entity and persisted or merged using the underlying
<interfacename>EntityManager</interfacename>.
<emphasis>NULL</emphasis> values returned by the iterator are ignored.
</para>
</section>
<section id="3.0-json-transformers">
<title>Jackson Support (JSON)</title>
<para>