diff --git a/pom.xml b/pom.xml
index e936a553f9..39c809795b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-jpa
- 1.7.0.BUILD-SNAPSHOT
+ 1.7.0.DATAJPA-562-SNAPSHOT
Spring Data JPA
Spring Data module for JPA repositories.
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/DetachingJpaResultPostProcessor.java b/src/main/java/org/springframework/data/jpa/repository/support/DetachingJpaResultPostProcessor.java
new file mode 100644
index 0000000000..c6ce78ce1a
--- /dev/null
+++ b/src/main/java/org/springframework/data/jpa/repository/support/DetachingJpaResultPostProcessor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2008-2014 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.data.jpa.repository.support;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.persistence.EntityManager;
+
+import org.springframework.util.Assert;
+
+/**
+ * A {@link JpaResultPostProcessor} that potentially detaches resulting Domain-Objects from the configured
+ * {@link EntityManager}.
+ *
+ * @author Thomas Darimont
+ */
+public class DetachingJpaResultPostProcessor implements JpaResultPostProcessor {
+
+ private final EntityManager em;
+
+ /**
+ * Creates a new {@link DetachingJpaResultPostProcessor}.
+ *
+ * @param em must not be {@literal null}.
+ */
+ public DetachingJpaResultPostProcessor(EntityManager em) {
+
+ Assert.notNull(em, "EntityManager must not be null!");
+
+ this.em = em;
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.jpa.repository.support.JpaResultPostProcessor#postProcessResult(java.lang.Class, java.lang.Object)
+ */
+ @Override
+ public R postProcessResult(Class domainClass, R entity) {
+
+ if (domainClass.isInstance(entity)) {
+ em.detach(entity);
+ }
+
+ return entity;
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.jpa.repository.support.JpaResultPostProcessor#postProcessResults(java.lang.Class, java.util.List)
+ */
+ @Override
+ public List postProcessResults(Class domainClass, List results) {
+
+ List postProcessedResults = new ArrayList();
+
+ for (T entity : results) {
+ if (domainClass.isInstance(entity)) {
+ postProcessedResults.add(postProcessResult(domainClass, entity));
+ }
+ }
+
+ return postProcessedResults;
+ }
+}
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
index eb417336a5..062cbecd4b 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
@@ -35,12 +35,14 @@
* JPA specific generic repository factory.
*
* @author Oliver Gierke
+ * @author Thomas Darimont
*/
public class JpaRepositoryFactory extends RepositoryFactorySupport {
private final EntityManager entityManager;
private final QueryExtractor extractor;
private final CrudMethodMetadataPostProcessor lockModePostProcessor;
+ private final JpaResultPostProcessor jpaResultPostProcessor;
/**
* Creates a new {@link JpaRepositoryFactory}.
@@ -48,12 +50,23 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
* @param entityManager must not be {@literal null}
*/
public JpaRepositoryFactory(EntityManager entityManager) {
+ this(entityManager, NoopJpaResultPostProcessor.INSTANCE);
+ }
+
+ /**
+ * Creates a new {@link JpaRepositoryFactory}.
+ *
+ * @param entityManager must not be {@literal null}
+ * @param evaluationContextProvider must not be {@literal null}
+ */
+ public JpaRepositoryFactory(EntityManager entityManager, JpaResultPostProcessor jpaResultPostProcessor) {
Assert.notNull(entityManager);
this.entityManager = entityManager;
this.extractor = PersistenceProvider.fromEntityManager(entityManager);
this.lockModePostProcessor = CrudMethodMetadataPostProcessor.INSTANCE;
+ this.jpaResultPostProcessor = jpaResultPostProcessor;
addRepositoryProxyPostProcessor(lockModePostProcessor);
}
@@ -68,6 +81,10 @@ protected Object getTargetRepository(RepositoryMetadata metadata) {
SimpleJpaRepository, ?> repository = getTargetRepository(metadata, entityManager);
repository.setRepositoryMethodMetadata(lockModePostProcessor.getLockMetadataProvider());
+ if (jpaResultPostProcessor != null) {
+ repository.setJpaResultPostProcessor(jpaResultPostProcessor);
+ }
+
return repository;
}
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java
index 68ec233f99..be53f6d931 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java
@@ -37,7 +37,9 @@
public class JpaRepositoryFactoryBean, S, ID extends Serializable> extends
TransactionalRepositoryFactoryBeanSupport {
- private EntityManager entityManager;
+ protected EntityManager entityManager;
+
+ private JpaResultPostProcessor jpaResultPostProcessor;
/**
* The {@link EntityManager} to be used.
@@ -58,6 +60,16 @@ public void setMappingContext(MappingContext, ?> mappingContext) {
super.setMappingContext(mappingContext);
}
+ /**
+ * The {@link JpaResultPostProcessor} to be used.
+ *
+ * @param jpaResultPostProcessor the jpaResultPostProcessor to set.
+ * @since 1.7
+ */
+ public void setJpaResultPostProcessor(JpaResultPostProcessor jpaResultPostProcessor) {
+ this.jpaResultPostProcessor = jpaResultPostProcessor;
+ }
+
/*
* (non-Javadoc)
*
@@ -76,7 +88,7 @@ protected RepositoryFactorySupport doCreateRepositoryFactory() {
* @return
*/
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
- return new JpaRepositoryFactory(entityManager);
+ return new JpaRepositoryFactory(entityManager, jpaResultPostProcessor);
}
/*
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaResultPostProcessor.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaResultPostProcessor.java
new file mode 100644
index 0000000000..b3a483ee46
--- /dev/null
+++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaResultPostProcessor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2008-2014 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.data.jpa.repository.support;
+
+import java.util.List;
+
+/**
+ * A {@link JpaResultPostProcessor} allows to post-process a single or multiple entity result.
+ *
+ * @author Thomas Darimont
+ */
+public interface JpaResultPostProcessor {
+
+ /**
+ * @param domainClass
+ * @param entity
+ * @return
+ */
+ R postProcessResult(Class domainClass, R entity);
+
+ /**
+ * @param domainClass
+ * @param results
+ * @return
+ */
+ List postProcessResults(Class domainClass, List results);
+}
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/NoopJpaResultPostProcessor.java b/src/main/java/org/springframework/data/jpa/repository/support/NoopJpaResultPostProcessor.java
new file mode 100644
index 0000000000..7bd53a79aa
--- /dev/null
+++ b/src/main/java/org/springframework/data/jpa/repository/support/NoopJpaResultPostProcessor.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2008-2014 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.data.jpa.repository.support;
+
+import java.util.List;
+
+/**
+ * A {@link JpaResultPostProcessor} that simply returns the results as is.
+ *
+ * @author Thomas Darimont
+ */
+enum NoopJpaResultPostProcessor implements JpaResultPostProcessor {
+
+ INSTANCE;
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.jpa.repository.support.JpaResultPostProcessor#postProcessResult(java.lang.Class, java.lang.Object)
+ */
+ @Override
+ public R postProcessResult(Class domainClass, R entity) {
+ return entity;
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.jpa.repository.support.JpaResultPostProcessor#postProcessResults(java.lang.Class, java.util.List)
+ */
+ @Override
+ public List postProcessResults(Class domainClass, List results) {
+ return results;
+ }
+}
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java
index 67c9b2a98d..85039f585e 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java
@@ -70,6 +70,8 @@ public class SimpleJpaRepository implements JpaRepos
private CrudMethodMetadata crudMethodMetadata;
+ private JpaResultPostProcessor jpaResultPostProcessor = NoopJpaResultPostProcessor.INSTANCE;
+
/**
* Creates a new {@link SimpleJpaRepository} to manage objects of the given {@link JpaEntityInformation}.
*
@@ -106,6 +108,19 @@ public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
this.crudMethodMetadata = crudMethodMetadata;
}
+ /**
+ * Configures a custom {@link JpaResultPostProcessor} to be used to post-process query results.
+ *
+ * @param jpaResultPostProcessor must not be {@literal null}.
+ * @since 1.7
+ */
+ public void setJpaResultPostProcessor(JpaResultPostProcessor jpaResultPostProcessor) {
+
+ Assert.notNull(jpaResultPostProcessor, "JpaResultPostProcessor must not be null!");
+
+ this.jpaResultPostProcessor = jpaResultPostProcessor;
+ }
+
protected Class getDomainClass() {
return entityInformation.getJavaType();
}
@@ -213,13 +228,14 @@ public T findOne(ID id) {
Class domainType = getDomainClass();
if (crudMethodMetadata == null) {
- return em.find(domainType, id);
+ return potentiallyPostProcessResult(em.find(domainType, id));
}
LockModeType type = crudMethodMetadata.getLockModeType();
Map hints = crudMethodMetadata.getQueryHints();
- return type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints);
+ return potentiallyPostProcessResult(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type,
+ hints));
}
/*
@@ -230,7 +246,7 @@ public T findOne(ID id) {
public T getOne(ID id) {
Assert.notNull(id, "The given id must not be null!");
- return em.getReference(getDomainClass(), id);
+ return potentiallyPostProcessResult(em.getReference(getDomainClass(), id));
}
/*
@@ -276,12 +292,20 @@ public boolean exists(ID id) {
return query.getSingleResult() == 1L;
}
+ private List potentiallyPostProcessResults(List resultList) {
+ return jpaResultPostProcessor.postProcessResults(getDomainClass(), resultList);
+ }
+
+ private R potentiallyPostProcessResult(R result) {
+ return jpaResultPostProcessor.postProcessResult(getDomainClass(), result);
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#findAll()
*/
public List findAll() {
- return getQuery(null, (Sort) null).getResultList();
+ return potentiallyPostProcessResults(getQuery(null, (Sort) null).getResultList());
}
/*
@@ -297,7 +321,7 @@ public List findAll(Iterable ids) {
ByIdsSpecification specification = new ByIdsSpecification();
TypedQuery query = getQuery(specification, (Sort) null);
- return query.setParameter(specification.parameter, ids).getResultList();
+ return potentiallyPostProcessResults(query.setParameter(specification.parameter, ids).getResultList());
}
/*
@@ -305,7 +329,7 @@ public List findAll(Iterable ids) {
* @see org.springframework.data.jpa.repository.JpaRepository#findAll(org.springframework.data.domain.Sort)
*/
public List findAll(Sort sort) {
- return getQuery(null, sort).getResultList();
+ return potentiallyPostProcessResults(getQuery(null, sort).getResultList());
}
/*
@@ -328,7 +352,7 @@ public Page findAll(Pageable pageable) {
public T findOne(Specification spec) {
try {
- return getQuery(spec, (Sort) null).getSingleResult();
+ return potentiallyPostProcessResult(getQuery(spec, (Sort) null).getSingleResult());
} catch (NoResultException e) {
return null;
}
@@ -339,7 +363,7 @@ public T findOne(Specification spec) {
* @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework.data.jpa.domain.Specification)
*/
public List findAll(Specification spec) {
- return getQuery(spec, (Sort) null).getResultList();
+ return potentiallyPostProcessResults(getQuery(spec, (Sort) null).getResultList());
}
/*
@@ -349,7 +373,8 @@ public List findAll(Specification spec) {
public Page findAll(Specification spec, Pageable pageable) {
TypedQuery query = getQuery(spec, pageable);
- return pageable == null ? new PageImpl(query.getResultList()) : readPage(query, pageable, spec);
+ return pageable == null ? new PageImpl(potentiallyPostProcessResults(query.getResultList())) : readPage(query,
+ pageable, spec);
}
/*
@@ -358,7 +383,7 @@ public Page findAll(Specification spec, Pageable pageable) {
*/
public List findAll(Specification spec, Sort sort) {
- return getQuery(spec, sort).getResultList();
+ return potentiallyPostProcessResults(getQuery(spec, sort).getResultList());
}
/*
@@ -366,7 +391,7 @@ public List findAll(Specification spec, Sort sort) {
* @see org.springframework.data.repository.CrudRepository#count()
*/
public long count() {
- return em.createQuery(getCountQueryString(), Long.class).getSingleResult();
+ return this.potentiallyPostProcessResult(em.createQuery(getCountQueryString(), Long.class).getSingleResult());
}
/*
@@ -375,7 +400,7 @@ public long count() {
*/
public long count(Specification spec) {
- return getCountQuery(spec).getSingleResult();
+ return this.potentiallyPostProcessResult(getCountQuery(spec).getSingleResult());
}
/*
@@ -451,7 +476,8 @@ protected Page readPage(TypedQuery query, Pageable pageable, Specification
query.setMaxResults(pageable.getPageSize());
Long total = QueryUtils.executeCountQuery(getCountQuery(spec));
- List content = total > pageable.getOffset() ? query.getResultList() : Collections. emptyList();
+ List content = total > pageable.getOffset() ? potentiallyPostProcessResults(query.getResultList()) : Collections
+ . emptyList();
return new PageImpl(content, pageable, total);
}
diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntity.java b/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntity.java
index 51efd9675e..69d33a5a3c 100644
--- a/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntity.java
+++ b/src/test/java/org/springframework/data/jpa/domain/sample/SampleEntity.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2008-2011 the original author or authors.
+ * Copyright 2008-2014 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.
@@ -18,14 +18,18 @@
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
+import org.springframework.util.ObjectUtils;
+
/**
* @author Oliver Gierke
+ * @author Thomas Darimont
*/
@Entity
public class SampleEntity {
- @EmbeddedId
- protected SampleEntityPK id;
+ @EmbeddedId protected SampleEntityPK id;
+
+ protected String attribute1;
protected SampleEntity() {
@@ -49,12 +53,19 @@ public boolean equals(Object obj) {
SampleEntity that = (SampleEntity) obj;
- return this.id.equals(that.id);
+ return this.id.equals(that.id) && ObjectUtils.nullSafeEquals(this.attribute1, that.attribute1);
+ }
+
+ public String getAttribute1() {
+ return attribute1;
+ }
+
+ public void setAttribute1(String attribute1) {
+ this.attribute1 = attribute1;
}
@Override
public int hashCode() {
-
- return id.hashCode();
+ return 17 * ObjectUtils.nullSafeHashCode(id) + ObjectUtils.nullSafeHashCode(attribute1);
}
}
diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java
index b827d9ea94..8238bf68f1 100644
--- a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java
+++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java
@@ -51,12 +51,15 @@ public class JpaRepositoryTests {
JpaRepository repository;
CrudRepository idClassRepository;
+ SampleEntityRepository detachingRepository;
@Before
public void setUp() {
repository = new JpaRepositoryFactory(em).getRepository(SampleEntityRepository.class);
idClassRepository = new JpaRepositoryFactory(em).getRepository(SampleWithIdClassRepository.class);
+ detachingRepository = new JpaRepositoryFactory(em, new DetachingJpaResultPostProcessor(em))
+ .getRepository(SampleEntityRepository.class);
}
@Test
@@ -121,6 +124,59 @@ public void executesExistsForEntityWithIdClass() {
assertThat(idClassRepository.exists(id), is(true));
}
+ /**
+ * @see DATAJPA-562
+ */
+ @Test
+ public void shouldNotPropagateChangesViaImplicitFlushing() {
+
+ SampleEntityPK id = new SampleEntityPK("foo", "bar");
+ SampleEntity entity = new SampleEntity("foo", "bar");
+ entity = detachingRepository.saveAndFlush(entity);
+
+ SampleEntity detachedEntity = detachingRepository.findOne(id);
+ detachedEntity.setAttribute1("bubu");
+
+ SampleEntity detachedEntity2 = detachingRepository.findOne(id);
+ assertThat(detachedEntity2.getAttribute1(), is(nullValue()));
+ }
+
+ /**
+ * @see DATAJPA-562
+ */
+ @Test
+ public void shouldPropagateChangesViaExplcitSaveAndFlush() {
+
+ SampleEntityPK id = new SampleEntityPK("foo", "bar");
+ SampleEntity entity = new SampleEntity("foo", "bar");
+ entity = detachingRepository.saveAndFlush(entity);
+
+ SampleEntity detachedEntity = detachingRepository.findOne(id);
+ detachedEntity.setAttribute1("bubu");
+ detachedEntity = detachingRepository.saveAndFlush(detachedEntity);
+
+ SampleEntity detachedEntity2 = detachingRepository.findOne(id);
+ assertThat(detachedEntity2.getAttribute1(), is(detachedEntity.getAttribute1()));
+ }
+
+ /**
+ * @see DATAJPA-562
+ */
+ @Test
+ public void shouldPropagateChangesViaExplcitSave() {
+
+ SampleEntityPK id = new SampleEntityPK("foo", "bar");
+ SampleEntity entity = new SampleEntity("foo", "bar");
+ entity = detachingRepository.saveAndFlush(entity);
+
+ SampleEntity detachedEntity = detachingRepository.findOne(id);
+ detachedEntity.setAttribute1("bubu");
+ detachedEntity = detachingRepository.save(detachedEntity);
+
+ SampleEntity detachedEntity2 = detachingRepository.findOne(id);
+ assertThat(detachedEntity2.getAttribute1(), is(detachedEntity.getAttribute1()));
+ }
+
private static interface SampleEntityRepository extends JpaRepository {
}