DATADOC-48 added advice for moving changeset from detached entity to newly merged persistent entity
This commit is contained in:
@@ -102,6 +102,11 @@ public class MongoChangeSetPersister implements ChangeSetPersister<Object> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object persistState(ChangeSetBacked entity, ChangeSet cs) throws DataAccessException {
|
public Object persistState(ChangeSetBacked entity, ChangeSet cs) throws DataAccessException {
|
||||||
|
if (cs == null) {
|
||||||
|
log.debug("Flush: changeset was null, nothing to flush.");
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("Flush: changeset: " + cs.getValues().keySet());
|
log.debug("Flush: changeset: " + cs.getValues().keySet());
|
||||||
|
|
||||||
String collName = getCollectionNameForEntity(entity.getClass());
|
String collName = getCollectionNameForEntity(entity.getClass());
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package org.springframework.data.persistence.document.mongo;
|
package org.springframework.data.persistence.document.mongo;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.Transient;
|
import javax.persistence.Transient;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Id;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.aspectj.lang.JoinPoint;
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.reflect.FieldSignature;
|
import org.aspectj.lang.reflect.FieldSignature;
|
||||||
|
|
||||||
import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
|
||||||
import org.springframework.data.document.mongodb.mapping.Document;
|
import org.springframework.data.document.mongodb.mapping.Document;
|
||||||
|
|
||||||
import org.springframework.data.persistence.document.DocumentBacked;
|
import org.springframework.data.persistence.document.DocumentBacked;
|
||||||
@@ -73,11 +73,37 @@ public aspect MongoDocumentBacking {
|
|||||||
args(newVal) &&
|
args(newVal) &&
|
||||||
!set(* DocumentBacked.*);
|
!set(* DocumentBacked.*);
|
||||||
|
|
||||||
// protected pointcut entityIdSet(DocumentBacked entity, Object newVal) :
|
// intercept EntityManager.merge calls
|
||||||
// set(@Id * DocumentBacked+.*) &&
|
public pointcut entityManagerMerge(EntityManager em, Object entity) :
|
||||||
// this(entity) &&
|
call(* EntityManager.merge(Object)) &&
|
||||||
// args(newVal) &&
|
target(em) &&
|
||||||
// !set(* DocumentBacked.*);
|
args(entity);
|
||||||
|
|
||||||
|
// intercept EntityManager.remove calls
|
||||||
|
public pointcut entityManagerRemove(EntityManager em, Object entity) :
|
||||||
|
call(* EntityManager.remove(Object)) &&
|
||||||
|
target(em) &&
|
||||||
|
args(entity);
|
||||||
|
|
||||||
|
// move changeSet from detached entity to the newly merged persistent object
|
||||||
|
Object around(EntityManager em, Object entity) : entityManagerMerge(em, entity) {
|
||||||
|
Object mergedEntity = proceed(em, entity);
|
||||||
|
if (entity instanceof DocumentBacked && mergedEntity instanceof DocumentBacked) {
|
||||||
|
((DocumentBacked)mergedEntity).changeSet = ((DocumentBacked)entity).getChangeSet();
|
||||||
|
}
|
||||||
|
return mergedEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear changeSet from removed entity
|
||||||
|
Object around(EntityManager em, Object entity) : entityManagerRemove(em, entity) {
|
||||||
|
if (entity instanceof DocumentBacked) {
|
||||||
|
Map<String,Object> cs = ((DocumentBacked)entity).getChangeSet().getValues();
|
||||||
|
for (String key : cs.keySet()) {
|
||||||
|
cs.put(key, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return proceed(em, entity);
|
||||||
|
}
|
||||||
|
|
||||||
before(DocumentBacked entity) : arbitraryUserConstructorOfChangeSetBackedObject(entity) {
|
before(DocumentBacked entity) : arbitraryUserConstructorOfChangeSetBackedObject(entity) {
|
||||||
LOGGER
|
LOGGER
|
||||||
@@ -88,26 +114,26 @@ public aspect MongoDocumentBacking {
|
|||||||
entity.itdChangeSetPersister = changeSetPersister;
|
entity.itdChangeSetPersister = changeSetPersister;
|
||||||
entity.itdTransactionSynchronization =
|
entity.itdTransactionSynchronization =
|
||||||
new DocumentBackedTransactionSynchronization(changeSetPersister, entity);
|
new DocumentBackedTransactionSynchronization(changeSetPersister, entity);
|
||||||
registerTransactionSynchronization(entity);
|
//registerTransactionSynchronization(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void registerTransactionSynchronization(DocumentBacked entity) {
|
private static void registerTransactionSynchronization(DocumentBacked entity) {
|
||||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
if (!TransactionSynchronizationManager.getSynchronizations().contains(entity.itdTransactionSynchronization)) {
|
if (!TransactionSynchronizationManager.getSynchronizations().contains(entity.itdTransactionSynchronization)) {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("Adding transaction synchronization for " + entity.getClass());
|
LOGGER.debug("Adding transaction synchronization for " + entity);
|
||||||
}
|
}
|
||||||
TransactionSynchronizationManager.registerSynchronization(entity.itdTransactionSynchronization);
|
TransactionSynchronizationManager.registerSynchronization(entity.itdTransactionSynchronization);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("Transaction synchronization already active for " + entity.getClass());
|
LOGGER.debug("Transaction synchronization already active for " + entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("Transaction syncronization is not active for " + entity.getClass());
|
LOGGER.debug("Transaction synchronization is not active for " + entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,27 +167,33 @@ public aspect MongoDocumentBacking {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lifecycle methods
|
// lifecycle methods
|
||||||
@javax.persistence.PrePersist public void DocumentBacked.prePersist() {
|
@javax.persistence.PostPersist public void DocumentBacked.itdPostPersist() {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("JPA lifecycle called PrePersist: " + this.getClass().getName() + " :: " + this.get_persistent_id());
|
LOGGER.debug("JPA lifecycle event PrePersist: " + this.getClass().getName());
|
||||||
}
|
}
|
||||||
registerTransactionSynchronization(this);
|
registerTransactionSynchronization(this);
|
||||||
}
|
}
|
||||||
@javax.persistence.PreUpdate public void DocumentBacked.preUpdate() {
|
@javax.persistence.PreUpdate public void DocumentBacked.itdPreUpdate() {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("JPA lifecycle called PreUpdate: " + this.getClass().getName() + " :: " + this.get_persistent_id());
|
LOGGER.debug("JPA lifecycle event PreUpdate: " + this.getClass().getName() + " :: " + this);
|
||||||
}
|
}
|
||||||
registerTransactionSynchronization(this);
|
registerTransactionSynchronization(this);
|
||||||
}
|
}
|
||||||
@javax.persistence.PreRemove public void DocumentBacked.preRemove() {
|
@javax.persistence.PostUpdate public void DocumentBacked.itdPostUpdate() {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("JPA lifecycle called PreRemove: " + this.getClass().getName() + " :: " + this.get_persistent_id());
|
LOGGER.debug("JPA lifecycle event PostUpdate: " + this.getClass().getName() + " :: " + this);
|
||||||
}
|
}
|
||||||
registerTransactionSynchronization(this);
|
registerTransactionSynchronization(this);
|
||||||
}
|
}
|
||||||
@javax.persistence.PostLoad public void DocumentBacked.postLoad() {
|
@javax.persistence.PostRemove public void DocumentBacked.itdPostRemove() {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("JPA lifecycle called PostLoad: " + this.getClass().getName() + " :: " + this.get_persistent_id());
|
LOGGER.debug("JPA lifecycle event PostRemove: " + this.getClass().getName() + " :: " + this);
|
||||||
|
}
|
||||||
|
registerTransactionSynchronization(this);
|
||||||
|
}
|
||||||
|
@javax.persistence.PostLoad public void DocumentBacked.itdPostLoad() {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("JPA lifecycle event PostLoad: " + this.getClass().getName() + " :: " + this);
|
||||||
}
|
}
|
||||||
registerTransactionSynchronization(this);
|
registerTransactionSynchronization(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.document.mongodb.MongoTemplate;
|
import org.springframework.data.document.mongodb.MongoTemplate;
|
||||||
import org.springframework.persistence.document.test.Person;
|
import org.springframework.data.document.persistence.test.Person;
|
||||||
import org.springframework.persistence.document.test.Resume;
|
import org.springframework.data.document.persistence.test.Resume;
|
||||||
import org.springframework.test.annotation.Rollback;
|
import org.springframework.test.annotation.Rollback;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
@@ -99,18 +99,53 @@ public class CrossStoreMongoTests {
|
|||||||
@Test
|
@Test
|
||||||
public void testMergeJpaEntityWithMongoDocument() {
|
public void testMergeJpaEntityWithMongoDocument() {
|
||||||
TransactionTemplate txTemplate = new TransactionTemplate(transactionManager);
|
TransactionTemplate txTemplate = new TransactionTemplate(transactionManager);
|
||||||
final Person found = entityManager.find(Person.class, 1L);
|
final Person detached = entityManager.find(Person.class, 1L);
|
||||||
found.setAge(77);
|
detached.getResume().addJob("TargetRx, Developer, 2000-2005");
|
||||||
found.getResume().addJob("TargetRx, Developer, 2000-2005");
|
Person merged = txTemplate.execute(new TransactionCallback<Person>() {
|
||||||
txTemplate.execute(new TransactionCallback<Object>() {
|
public Person doInTransaction(TransactionStatus status) {
|
||||||
public Object doInTransaction(TransactionStatus status) {
|
return entityManager.merge(detached);
|
||||||
entityManager.merge(found);
|
}
|
||||||
|
});
|
||||||
|
Assert.assertTrue(detached.getResume().getJobs().contains("TargetRx, Developer, 2000-2005"));
|
||||||
|
Assert.assertTrue(merged.getResume().getJobs().contains("TargetRx, Developer, 2000-2005"));
|
||||||
|
final Person updated = entityManager.find(Person.class, 1L);
|
||||||
|
Assert.assertTrue(updated.getResume().getJobs().contains("TargetRx, Developer, 2000-2005"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveJpaEntityWithMongoDocument() {
|
||||||
|
TransactionTemplate txTemplate = new TransactionTemplate(transactionManager);
|
||||||
|
txTemplate.execute(new TransactionCallback<Person>() {
|
||||||
|
public Person doInTransaction(TransactionStatus status) {
|
||||||
|
Person p2 = new Person("Thomas", 20);
|
||||||
|
Resume r2 = new Resume();
|
||||||
|
r2.addEducation("Skanstulls High School, 1975");
|
||||||
|
r2.addJob("DiMark, DBA, 1990-2000");
|
||||||
|
p2.setResume(r2);
|
||||||
|
p2.setId(2L);
|
||||||
|
entityManager.persist(p2);
|
||||||
|
Person p3 = new Person("Thomas", 20);
|
||||||
|
Resume r3 = new Resume();
|
||||||
|
r3.addEducation("Univ. of Stockholm, 1980");
|
||||||
|
r3.addJob("VMware, Developer, 2007-");
|
||||||
|
p3.setResume(r3);
|
||||||
|
p3.setId(3L);
|
||||||
|
entityManager.persist(p3);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final Person updated = entityManager.find(Person.class, 1L);
|
txTemplate.execute(new TransactionCallback<Person>() {
|
||||||
// assert that the new values are in respective DBs
|
public Person doInTransaction(TransactionStatus status) {
|
||||||
// TODO: during merge we lose the changeset since JPA creates a new persistent instance -
|
final Person found2 = entityManager.find(Person.class, 2L);
|
||||||
// we need to move the existing changeset over somehow
|
final Person found3 = entityManager.find(Person.class, 3L);
|
||||||
|
entityManager.remove(found2);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final Person found2 = entityManager.find(Person.class, 2L);
|
||||||
|
final Person found3 = entityManager.find(Person.class, 3L);
|
||||||
|
// TODO: assert that any documents for Person 2 are gone
|
||||||
|
// System.out.println(found2);
|
||||||
|
// System.out.println(found3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.springframework.persistence.document.test;
|
package org.springframework.data.document.persistence.test;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
package org.springframework.persistence.document.test;
|
package org.springframework.data.document.persistence.test;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.data.persistence.document.mongo.MongoDocumentBacking;
|
||||||
|
|
||||||
//@DocumentEntity
|
//@DocumentEntity
|
||||||
public class Resume {
|
public class Resume {
|
||||||
|
|
||||||
|
private static final Log LOGGER = LogFactory.getLog(Resume.class);
|
||||||
|
|
||||||
private String education = "";
|
private String education = "";
|
||||||
|
|
||||||
private String jobs = "";
|
private String jobs = "";
|
||||||
@@ -12,6 +18,7 @@ public class Resume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addEducation(String education) {
|
public void addEducation(String education) {
|
||||||
|
LOGGER.debug("Adding education " + education);
|
||||||
this.education = this.education + (this.education.length() > 0 ? "; " : "") + education;
|
this.education = this.education + (this.education.length() > 0 ? "; " : "") + education;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,6 +27,7 @@ public class Resume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addJob(String job) {
|
public void addJob(String job) {
|
||||||
|
LOGGER.debug("Adding job " + job);
|
||||||
this.jobs = this.jobs + (this.jobs.length() > 0 ? "; " : "") + job;
|
this.jobs = this.jobs + (this.jobs.length() > 0 ? "; " : "") + job;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
||||||
<persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
|
<persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
|
||||||
<provider>org.hibernate.ejb.HibernatePersistence</provider>
|
<provider>org.hibernate.ejb.HibernatePersistence</provider>
|
||||||
<class>org.springframework.persistence.document.test.Person</class>
|
<class>org.springframework.data.document.persistence.test.Person</class>
|
||||||
<properties>
|
<properties>
|
||||||
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
|
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
|
||||||
<!--value='create' to build a new database on each run; value='update' to modify an existing database; value='create-drop' means the same as 'create' but also drops tables when Hibernate closes; value='validate' makes no changes to the database-->
|
<!--value='create' to build a new database on each run; value='update' to modify an existing database; value='create-drop' means the same as 'create' but also drops tables when Hibernate closes; value='validate' makes no changes to the database-->
|
||||||
|
|||||||
Reference in New Issue
Block a user