That was what was driving me mad. I was able to persistently navigate to it in the collection of the parent object (in the initial new state). Also DN logged it making the object persistent. Nov 29, 2009 9:45:58 PM org.datanucleus.ObjectManagerImp persistObjectInternal FINE: Making object persistent : "com.ninuku.diary2.server.diary.texten...@13c7edf"
Thanks again for unblocking me on this issue. This JDO ORM layer is a really tough nut to crack. I used to evangelize pure ODBMS for ObjectDesign and thought that was hard. Your JDO snipets that work series is a real boon. Please consider formalizing them. On Tue, Dec 1, 2009 at 9:12 PM, Max Ross (Google) <maxr+appeng...@google.com<maxr%2bappeng...@google.com> > wrote: > :-) Happy to help Andy. > > I'm not sure what is triggering the commit of the new object since I don't > see you calling pm.makePersistent() anywhere. How are you concluding that > the TextEntry is actually persisted? > > Max > > On Tue, Dec 1, 2009 at 12:25 PM, a stevko <andy.ste...@gmail.com> wrote: > >> WOW Max! >> You deserve a gold star for your insight, TY! TY! TY! >> >> I added a PM parameter to my lookup utility layer and the code suddenly >> works as desired. >> While I was using a static PMFactory, I didn't realize that calling get() >> would begin a new transaction context every time, >> It does makes sense given the platforms 1 txn per entity group constraint. >> >> What I don't understand is what triggered the commit of the new object. I >> would assume the commit would be triggered by the 2nd pm going out of scope >> but it was already out of scope when the parent object was passed back from >> the lookup method. >> --Andy >> >> >> >> On Tue, Dec 1, 2009 at 10:23 AM, Max Ross (Google) < >> maxr+appeng...@google.com <maxr%2bappeng...@google.com>> wrote: >> >>> My suspicion is that in the update case you are starting your txn with a >>> PersistenceManager, then using a different PersistenceManager to load the >>> TextEntry (diaryManager.getTextEntryById(). I can't be sure because you >>> haven't posted the code for diaryManager, but if it does the same thing as >>> lookupTextChapterById() and pmp.get() creates a new PersistenceManager, then >>> this is probably what is going on. You're not seeing the updates when you >>> commit the txn because the txn is not associated with the PersistenceManager >>> that loaded the TextEntry. I'd recommend using the same PersistenceManager >>> for all these operations. >>> >>> Hope this helps, >>> Max >>> >>> >>> On Sun, Nov 29, 2009 at 4:00 PM, Stevko <andy.ste...@gmail.com> wrote: >>> >>>> I'm struggling with a JDO data loss situation. >>>> The code below will NEW a TextEntry object and hook it into the >>>> object tree just fine. >>>> The copy DTO updates are performed without error/exception thrown but >>>> all updates are lost. >>>> The fields that are being lost are privateComment, byLine, >>>> shortContent, longContent, longContentSize. >>>> >>>> I've attached a DN transaction log at the bottom. >>>> The object state is marked as 'dirty' but the ObjectManager does not >>>> 'put' the new string values. >>>> According to the logs, for some unknown reason, the DB >>>> DatastorePersistenceHandler commits the "new" object prior to the >>>> copy of the associated data from the action DTO into the persistent >>>> object. >>>> >>>> I've broken this up into two sequential transactions complete with >>>> isolated lookups but the data loss continues. >>>> >>>> //------------------------------------- >>>> // abridged code - error checking removed... >>>> // >>>> private final Provider<PersistenceManager> pmp; >>>> private PersistenceManager pm; >>>> >>>> @Override >>>> public SaveTextEntryResult execute(SaveTextEntry action, >>>> ExecutionContext context) throws ActionException { >>>> >>>> String entryId = action.getEntryId(); >>>> SaveTextEntryResult result; >>>> try { >>>> pm = pmp.get(); >>>> pm.currentTransaction().begin(); >>>> >>>> TextEntry entry; >>>> >>>> // ---------- >>>> // is this new or update? >>>> if (action.isNewEntry()) { >>>> >>>> // lookup the chapterid and verify accountid >>>> TextChapter chapter = >>>> diaryManager.lookupTextChapterById(action >>>> .getChapterId()); >>>> entry = new TextEntry(action.getTimestamp(), >>>> chapter, >>>> action.getSourceType(), >>>> action.getSourceId()); >>>> >>>> } else { >>>> // look up the textentry id >>>> entry = >>>> diaryManager.lookupTextEntryById(action.getEntryId()); >>>> } >>>> //---->>> new object is flushed here <<<------ >>>> // copy fields from action to object >>>> entry.setPrivateComment(action.isPrivateComment()); >>>> entry.setByLine(action.getByLine()); >>>> logger.info("SaveTextEntry() short content =" + >>>> action.getShortContent() ); >>>> entry.setShortContent(action.getShortContent()); >>>> logger.info("SaveTextEntry() long content =" + >>>> action.getLongContent >>>> () ); >>>> entry.setLongContent(action.getLongContent()); >>>> >>>> //---->>> persist object updates are not flushed here <<<------ >>>> pm.currentTransaction().commit(); >>>> >>>> result = new SaveTextEntryResult(true, entry.getId() ); >>>> >>>> } finally { >>>> >>>> if (pm.currentTransaction().isActive()) { >>>> logger.severe("SaveTextEntry caught exception"); >>>> result = new SaveTextEntryResult(false, ""); >>>> pm.currentTransaction().rollback(); >>>> } >>>> } >>>> >>>> return result; >>>> } >>>> //------------ >>>> // related method below >>>> public TextChapter lookupTextChapterById(String id) { >>>> pm = pmp.get(); >>>> >>>> TextChapter chapter = pm.getObjectById(TextChapter.class, >>>> id); >>>> >>>> return (chapter); >>>> } >>>> >>>> //---------------------------------------------------------------- >>>> // Persistent object definition (bean methods removed) >>>> >>>> @PersistenceCapable(identityType = IdentityType.APPLICATION ) >>>> @FetchGroup( name="children", members={ >>>> @Persistent(name="commentEntrys"), >>>> @Persistent(name="chapter") >>>> }) >>>> public class TextEntry implements Entry, Serializable, >>>> Comparable<TextEntry> { >>>> >>>> @PrimaryKey >>>> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) >>>> @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = >>>> "true") >>>> private String id; >>>> >>>> @Persistent >>>> private Long accountId = new Long(0); >>>> >>>> // Parent collection with ArrayList<TextEntry> >>>> @Persistent >>>> private TextChapter chapter; >>>> >>>> @Persistent >>>> private Long timestamp = new Long(0); >>>> >>>> @Persistent >>>> private boolean privateComment = false; >>>> >>>> @Persistent >>>> private String byLine = ""; >>>> >>>> @Persistent(defaultFetchGroup="true") >>>> private SourceType source = SourceType.UNKOWN; >>>> >>>> @Persistent >>>> private String sourceId = ""; >>>> >>>> @Persistent(defaultFetchGroup="true") >>>> private String shortContent = null; >>>> >>>> @Persistent(defaultFetchGroup="true") >>>> private String longContentString = null; >>>> >>>> @Persistent(defaultFetchGroup="true") >>>> private Long longContentSize = new Long(-1); >>>> >>>> @Persistent(mappedBy = "textEntry") >>>> @Order(column="TEXTENTRY_COMMENTENTRY_IDX") >>>> private TreeSet< CommentEntry> commentEntrys = new TreeSet< >>>> CommentEntry>(); >>>> >>>> public TextEntry(Long timestamp, TextChapter chapter, SourceType >>>> source, >>>> String sourceId) { >>>> this.timestamp = timestamp; >>>> this.source = source; >>>> this.sourceId = sourceId; >>>> >>>> // setup external references >>>> this.chapter = chapter; >>>> this.accountId = chapter.getAccountId(); >>>> chapter.add(this); >>>> } >>>> >>>> >>>> //------------------------------------------------------------------------------------------------------- >>>> // this is a log showing the point where the >>>> DatastorePersistenceHandler commits the fields. >>>> >>>> Nov 29, 2009 9:45:58 PM org.datanucleus.ObjectManagerImpl >>>> persistObjectInternal >>>> FINE: Making object persistent : >>>> "com.ninuku.diary2.server.diary.texten...@13c7edf" >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: Putting entity of kind TextEntry with key Diary(4)/TextChapter >>>> (7)/TextEntry(no-id-yet) >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: timestamp : 1259531142320 >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: TEXTCHAPTER_TEXTENTRY_IDX : 7 >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: accountId : 2 >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: shortContent : null >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: source : USER_MANUAL >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: longContentSize : -1 >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: privateComment : false >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: byLine : >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: longContentString : null >>>> Nov 29, 2009 9:45:58 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler put >>>> FINE: sourceId : 1259531142320 >>>> Nov 29, 2009 9:45:58 PM org.datanucleus.sco.backed.TreeSet <init> >>>> FINE: Object "com.ninuku.diary2.server.diary.texten...@13c7edf" field >>>> "commentEntrys" is replaced by a SCO wrapper of type >>>> "org.datanucleus.sco.backed.TreeSet" [cache-values=true, lazy- >>>> loading=true, queued-operations=false, allow-nulls=false] >>>> Nov 29, 2009 9:45:59 PM >>>> org.datanucleus.store.appengine.DatastorePersistenceHandler get >>>> FINE: Getting entity of kind Diary with key Diary(4) >>>> Nov 29, 2009 9:46:22 PM >>>> com.google.appengine.api.datastore.dev.LocalDatastoreService >>>> $PersistDatastore persist >>>> INFO: Time to persist datastore: 10 ms >>>> Nov 29, 2009 9:46:32 PM >>>> com.ninuku.diary2.server.handler.SaveTextEntryHandler execute >>>> INFO: SaveTextEntry() short content =this is a short content update >>>> Nov 29, 2009 9:46:37 PM >>>> com.ninuku.diary2.server.handler.SaveTextEntryHandler execute >>>> INFO: SaveTextEntry() long content =this is a long content update >>>> Nov 29, 2009 9:46:40 PM org.datanucleus.TransactionImpl >>>> internalPreCommit >>>> FINE: Transaction committing for ObjectManager >>>> org.datanucleus.objectmanageri...@beef49 >>>> Nov 29, 2009 9:46:40 PM org.datanucleus.ObjectManagerImpl >>>> flushInternal >>>> FINE: ObjectManager internalFlush() process started - 0 dirty objects >>>> Nov 29, 2009 9:46:40 PM org.datanucleus.ObjectManagerImpl >>>> flushInternal >>>> FINE: ObjectManager internalFlush() process finished >>>> Nov 29, 2009 9:46:40 PM org.datanucleus.transaction.Transaction commit >>>> FINE: Committing [DataNucleus Transaction, ID=Xid= >>>> //----------------------------------------------------------------- >>>> // this is the entry object debugger showing the dirty fields... >>>> this SaveTextEntryHandler (id=151) >>>> action SaveTextEntry (id=169) >>>> context AbstractDispatch$DefaultExecutionContext (id=171) >>>> entryId >>>> "agxuaW51a3UtYWxwaGFyKwsSBURpYXJ5GAQMCxILVGV4dENoYXB0ZXIYBwwLEglUZXh0RW50cnkYFQw" >>>> (id=172) >>>> entry TextEntry (id=160) >>>> accountId Long (id=174) >>>> byLine "" (id=177) >>>> chapter TextChapter (id=178) >>>> commentEntrys TreeSet (id=181) >>>> >>>> id >>>> >>>> "agxuaW51a3UtYWxwaGFyKwsSBURpYXJ5GAQMCxILVGV4dENoYXB0ZXIYBwwLEglUZXh0RW50cnkYFQw" >>>> (id=183) >>>> jdoFlags 1 >>>> jdoStateManager JDOStateManagerImpl (id=188) >>>> objectValGenerators null >>>> activity ActivityState (id=205) >>>> associatedValuesMap HashMap<K,V> (id=207) >>>> changingState false >>>> cmd ClassMetaData (id=211) >>>> currFM null >>>> currFMmonitor Object (id=213) >>>> dirty true >>>> dirtyFields (id=216) >>>> [0] false >>>> [1] false >>>> [2] false >>>> [3] false >>>> [4] false >>>> [5] true >>>> [6] true >>>> [7] true >>>> [8] true >>>> [9] false >>>> [10] false >>>> [11] false >>>> embeddedOwners null >>>> fieldsToBeUpdatedAfterObjectInsertion null >>>> flushedNew false >>>> insertionNotifyList null >>>> jdoDfgFlags 1 >>>> loadedFields (id=218) >>>> loadingFieldsInFetchPlan false >>>> myFP FetchPlan$FetchPlanForClass (id=219) >>>> myID StringIdentity (id=221) >>>> myInternalID null >>>> myLC PersistentNontransactionalDirty (id=224) >>>> myOM ObjectManagerImpl (id=227) >>>> myPC TextEntry (id=160) >>>> myVersion null >>>> operationalFlags 0 >>>> pcObjectType 0 >>>> postLoadPending false >>>> preDeleteLoadedFields null >>>> referencedPC null >>>> relationManager null >>>> restoreValues false >>>> savedFlags 1 >>>> savedImage TextEntry (id=231) >>>> savedLoadedFields (id=232) >>>> storingPC false >>>> transactionalVersion null >>>> updatingForPostInsert false >>>> longContentSize Long (id=192) >>>> longContentString "this is a long content update" (id=173) >>>> privateComment true >>>> shortContent "this is a short content update" (id=168) >>>> source SourceType (id=200) >>>> sourceId "1259536113144" (id=203) >>>> timestamp Long (id=204) >>>> >>>> -- >>>> >>>> You received this message because you are subscribed to the Google >>>> Groups "Google App Engine for Java" group. >>>> To post to this group, send email to >>>> google-appengine-j...@googlegroups.com. >>>> To unsubscribe from this group, send email to >>>> google-appengine-java+unsubscr...@googlegroups.com<google-appengine-java%2bunsubscr...@googlegroups.com> >>>> . >>>> For more options, visit this group at >>>> http://groups.google.com/group/google-appengine-java?hl=en. >>>> >>>> >>>> >>> -- >>> You received this message because you are subscribed to the Google Groups >>> "Google App Engine for Java" group. >>> To post to this group, send email to >>> google-appengine-j...@googlegroups.com. >>> To unsubscribe from this group, send email to >>> google-appengine-java+unsubscr...@googlegroups.com<google-appengine-java%2bunsubscr...@googlegroups.com> >>> . >>> For more options, visit this group at >>> http://groups.google.com/group/google-appengine-java?hl=en. >>> >> >> -- >> You received this message because you are subscribed to the Google Groups >> "Google App Engine for Java" group. >> To post to this group, send email to >> google-appengine-j...@googlegroups.com. >> To unsubscribe from this group, send email to >> google-appengine-java+unsubscr...@googlegroups.com<google-appengine-java%2bunsubscr...@googlegroups.com> >> . >> For more options, visit this group at >> http://groups.google.com/group/google-appengine-java?hl=en. >> > > -- > You received this message because you are subscribed to the Google Groups > "Google App Engine for Java" group. > To post to this group, send email to > google-appengine-j...@googlegroups.com. > To unsubscribe from this group, send email to > google-appengine-java+unsubscr...@googlegroups.com<google-appengine-java%2bunsubscr...@googlegroups.com> > . > For more options, visit this group at > http://groups.google.com/group/google-appengine-java?hl=en. > -- You received this message because you are subscribed to the Google Groups "Google App Engine for Java" group. To post to this group, send email to google-appengine-j...@googlegroups.com. To unsubscribe from this group, send email to google-appengine-java+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/google-appengine-java?hl=en.