diff --git a/spring-data-mongodb-cross-store/src/main/java/org/springframework/data/persistence/document/mongo/MongoDocumentBacking.aj b/spring-data-mongodb-cross-store/src/main/java/org/springframework/data/persistence/document/mongo/MongoDocumentBacking.aj index e98e8395c..6d1b29b0a 100644 --- a/spring-data-mongodb-cross-store/src/main/java/org/springframework/data/persistence/document/mongo/MongoDocumentBacking.aj +++ b/spring-data-mongodb-cross-store/src/main/java/org/springframework/data/persistence/document/mongo/MongoDocumentBacking.aj @@ -44,11 +44,8 @@ public aspect MongoDocumentBacking { // ITD to introduce N state to Annotated objects declare parents : (@Entity *) implements DocumentBacked; -// declare @type: DocumentBacked+: @Configurable; - + // The annotated fields that will be persisted in MongoDB rather than with JPA declare @field: @Document * (@Entity+ *).*:@Transient; - declare @field: ChangeSet (DocumentBacked+).*:@Transient; - declare @field: ChangeSetPersister (DocumentBacked+).*:@Transient; // ------------------------------------------------------------------------- // Advise user-defined constructors of ChangeSetBacked objects to create a new @@ -86,27 +83,44 @@ public aspect MongoDocumentBacking { LOGGER .debug("User-defined constructor called on DocumentBacked object of class " + entity.getClass()); + // Populate all ITD fields + entity.setChangeSet(new HashMapChangeSet()); entity.itdChangeSetPersister = changeSetPersister; - // Populate all properties - ChangeSet changeSet = new HashMapChangeSet(); - // changeSetManager.populateChangeSet(changeSet, entity); - entity.setChangeSet(changeSet); - if (!TransactionSynchronizationManager.isSynchronizationActive()) { - throw new InvalidDataAccessResourceUsageException( - "No transaction synchronization is active"); + entity.itdTransactionSynchronization = + new DocumentBackedTransactionSynchronization(changeSetPersister, entity); + registerTransactionSynchronization(entity); + } + + private static void registerTransactionSynchronization(DocumentBacked entity) { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + if (!TransactionSynchronizationManager.getSynchronizations().contains(entity.itdTransactionSynchronization)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Adding transaction synchronization for " + entity.getClass()); + } + TransactionSynchronizationManager.registerSynchronization(entity.itdTransactionSynchronization); + } + else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Transaction synchronization already active for " + entity.getClass()); + } + } + } + else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Transaction syncronization is not active for " + entity.getClass()); + } } - TransactionSynchronizationManager - .registerSynchronization(new DocumentBackedTransactionSynchronization( - changeSetPersister, entity)); } // ------------------------------------------------------------------------- // ChangeSet-related mixins // ------------------------------------------------------------------------- // Introduced field - private ChangeSet DocumentBacked.changeSet; + @Transient private ChangeSet DocumentBacked.changeSet; - private ChangeSetPersister DocumentBacked.itdChangeSetPersister; + @Transient private ChangeSetPersister DocumentBacked.itdChangeSetPersister; + + @Transient private DocumentBackedTransactionSynchronization DocumentBacked.itdTransactionSynchronization; public void DocumentBacked.setChangeSet(ChangeSet cs) { this.changeSet = cs; @@ -126,6 +140,32 @@ public aspect MongoDocumentBacking { return itdChangeSetPersister.getPersistentId(this, this.changeSet); } + // lifecycle methods + @javax.persistence.PrePersist public void DocumentBacked.prePersist() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("JPA lifecycle called PrePersist: " + this.getClass().getName() + " :: " + this.get_persistent_id()); + } + registerTransactionSynchronization(this); + } + @javax.persistence.PreUpdate public void DocumentBacked.preUpdate() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("JPA lifecycle called PreUpdate: " + this.getClass().getName() + " :: " + this.get_persistent_id()); + } + registerTransactionSynchronization(this); + } + @javax.persistence.PreRemove public void DocumentBacked.preRemove() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("JPA lifecycle called PreRemove: " + this.getClass().getName() + " :: " + this.get_persistent_id()); + } + registerTransactionSynchronization(this); + } + @javax.persistence.PostLoad public void DocumentBacked.postLoad() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("JPA lifecycle called PostLoad: " + this.getClass().getName() + " :: " + this.get_persistent_id()); + } + registerTransactionSynchronization(this); + } + /** * delegates field reads to the state accessors instance */ @@ -160,18 +200,6 @@ public aspect MongoDocumentBacking { return proceed(entity, newVal); } -// /** -// * delegates field writes to the state accessors instance -// */ -// Object around(DocumentBacked entity, Object newVal) : entityIdSet(entity, newVal) { -// Field f = field(thisJoinPoint); -// String propName = f.getName(); -// LOGGER.trace("SET @Id -> ChangeSet @Id property [" + propName -// + "] with value=[" + newVal + "]"); -// entity.getChangeSet().set("_id", newVal); -// return proceed(entity, newVal); -// } - Field field(JoinPoint joinPoint) { FieldSignature fieldSignature = (FieldSignature) joinPoint.getSignature(); return fieldSignature.getField(); diff --git a/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/CrossStoreMongoTests.java b/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/CrossStoreMongoTests.java index b7fc0036e..b52a4f17e 100644 --- a/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/CrossStoreMongoTests.java +++ b/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/CrossStoreMongoTests.java @@ -13,7 +13,11 @@ import org.springframework.persistence.document.test.Resume; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; import com.mongodb.DBCollection; import com.mongodb.Mongo; @@ -30,6 +34,9 @@ public class CrossStoreMongoTests { private EntityManager entityManager; + @Autowired + private PlatformTransactionManager transactionManager; + @PersistenceContext public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; @@ -71,6 +78,7 @@ public class CrossStoreMongoTests { Assert.assertEquals("DiMark, DBA, 1990-2000" + "; " + "VMware, Developer, 2007-", found.getResume().getJobs()); found.getResume().addJob("SpringDeveloper.com, Consultant, 2005-2006"); + found.setAge(44); } @Test @@ -87,4 +95,22 @@ public class CrossStoreMongoTests { + "VMware, Developer, 2007-" + "; " + "SpringDeveloper.com, Consultant, 2005-2006", found.getResume().getJobs()); } + + @Test + public void testMergeJpaEntityWithMongoDocument() { + TransactionTemplate txTemplate = new TransactionTemplate(transactionManager); + final Person found = entityManager.find(Person.class, 1L); + found.setAge(77); + found.getResume().addJob("TargetRx, Developer, 2000-2005"); + txTemplate.execute(new TransactionCallback() { + public Object doInTransaction(TransactionStatus status) { + entityManager.merge(found); + return null; + } + }); + final Person updated = entityManager.find(Person.class, 1L); + // assert that the new values are in respective DBs + // TODO: during merge we lose the changeset since JPA creates a new persistent instance - + // we need to move the existing changeset over somehow + } }