MountainClimb / datanucleus-appengine

Automatically exported from code.google.com/p/datanucleus-appengine
0 stars 0 forks source link

Unable to update subclass attributes #261

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
What steps will reproduce the problem?
1. I have a User and Staffer classes, Staffer extends User.
Staffer has a field, "nickname" of type String. It gets persisted only once - 
when new Staffer() is persisted for the first time only. Any subsequent updates 
to the field and exact same persistence (via pm.makePersistent( staffer )) and 
subsequent query (select from abc.Staffer) never return updated but the 
initially set value.
2. If I move the "nickname" field into User (superclass of Staffer), it is 
updatable always, as expected.
3. The problem is reproducable 100% of the time. When I use the test case 
(attached), it works fine even if "nickname" is in Staffer (subclass), however 
if I run in Eclipse / GWT 2.4.0, Appengine SDK 1.6.1, via Eclipse plugin, it 
always behaves as described in 1. I tried every type of @Inheritance strategy 
allowed (read the doc), tried all kinds of things including with or without 
write TXs, tried tuning jdo-config.xml, all to no avail.

What is the expected output? What do you see instead?
Expect to be able to update (via makePersistent()) the nickname field of the 
subclass and retrieve updated value upon refresh, but always get initially set 
nickname value.

What version of the product are you using? On what operating system?
Eclipse / GWT 2.4.0, Appengine SDK 1.6.1, latest (v2) Datanucleus jars (pulled 
them recently from this site, final 2.0.0).

Please provide any additional information below.

This is my test method, it actually works correctly (when using 
LocalServiceTestHelper), however exact same calls when testing at runtime 
always fail as described. jdo-config.xml is attached.
    @Test
    public void testCreateStaffer() {

        Staffer s = new Staffer();

        Address addr = new Address();

        addr.setCity( "Canton" );
        addr.setExtra( "Somewhere" );
        addr.setState( "MA" );
        addr.setStreet( "12 Main St" );
        addr.setZip( "01234" );

        s.setAddress( addr );

        Category c = new Category( Type.Staffer, "TestCat", 0 );

        s.setCategory( c );

        Contact cont = new Contact();
        cont.setPhone( "444-444-4444" );

        s.setContact( cont );

        for ( int i = 0; i < 7; i++ ) {
            s.getDeclaredTimeSlots( i ).clear();
            s.getDeclaredTimeSlots( i ).add( new TimeSlot( Style.Military, 1000 + i, 1700 + i ) );
        }

        s.setDescription( "My desc" );
        s.setEmail( "abc@bcd.com" );
        s.setFirstName( "Vadim" );
        s.setLastName( "Myself" );
        s.setNickname( "MyNick" );

        s.setServices( (List<Service>) this.ucs.getServices().entrySet().iterator().next().getValue() );

        s.setStatus( Status.Verified );

        this.ucs.persist( s );

        DB.get().getPM().evictAll();

        Staffer s2 = (Staffer)this.ucs.getStaffers().entrySet().iterator().next().getValue().get( 0 );

        assertTrue( s2.getAddress().equals( s.getAddress() ) );
        assertTrue( s2.getCategory().equals( s.getCategory() ) );
        assertTrue( s2.getContact().equals( s.getContact() ) );

        for ( int i = 0; i < 7; i++ ) {
            assertTrue( s2.getDeclaredTimeSlots( i ).equals( s.getDeclaredTimeSlots( i ) ) );
        }

        assertTrue( s2.getDescription().equals( s.getDescription() ) );
        assertTrue( s2.getEmail().equals( s.getEmail() ) );
        assertTrue( s2.getFirstName().equals( s.getFirstName() ) );
        assertTrue( s2.getLastName().equals( s.getLastName() ) );
        assertTrue( s2.getNickname().equals( s.getNickname() ) );
        assertTrue( s2.getServices().equals( s.getServices() ) );
        assertTrue( s2.getStatus().equals( s.getStatus() ) );
        assertTrue( s2.getType().equals( s.getType() ) );

        // modify nickname
        s2.setNickname( "newNick" );

        this.ucs.persist( s2 );

        DB.get().getPM().evictAll();

        s = (Staffer)this.ucs.getStaffers().entrySet().iterator().next().getValue().get( 0 );

        assertEquals( "Newnick", s.getNickname() );

        // modify nickname
        s2.setNickname( "newNick2" );

        this.ucs.persist( s2 );

        DB.get().getPM().evictAll();

        s = (Staffer)this.ucs.getStaffers().entrySet().iterator().next().getValue().get( 0 );

        assertEquals( "Newnick2", s.getNickname() );
    }

Original issue reported on code.google.com by spa.yu...@gmail.com on 4 Feb 2012 at 6:57

Attachments:

GoogleCodeExporter commented 8 years ago
This is my User file's JDO-related info:
...
@PersistenceCapable(detachable = "true")
@Inheritance(strategy = InheritanceStrategy.UNSPECIFIED)
@Discriminator(strategy=DiscriminatorStrategy.UNSPECIFIED)
abstract public class User {
...
}

This is Staffer class:
...
@PersistenceCapable(detachable = "true")
@DatastoreIdentity
public class Staffer extends User {

    @Persistent
    private String nickname;

    /**
     * @return the nickname
     */
    public String getNickname() {

        return nickname;
    }

    /**
     * @param nickname the nickname to set
     */
    public void setNickname( String nickname ) {

        this.nickname = nickname;
    }
}

Original comment by spa.yu...@gmail.com on 4 Feb 2012 at 7:00

GoogleCodeExporter commented 8 years ago
This is how I update the staffer into DB (same as ucs.persist(), which has this 
call below)

...
obj = pm.makePersistent( obj );
...

Original comment by spa.yu...@gmail.com on 4 Feb 2012 at 7:01

GoogleCodeExporter commented 8 years ago
This is PK specification for User. Staffer has no additional PK spec, as it 
inherits the key from the User.

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
    private String key;

Original comment by spa.yu...@gmail.com on 4 Feb 2012 at 7:13

GoogleCodeExporter commented 8 years ago
Staffer extends User, where User is using application-identity, and Staffer 
wants to user datastore-identity ? Really ? Please read the JDO spec; you have 
one id type or another in an inheritance hierarchy.

You present some persistence code referring to various objects yet the majority 
of these objects are obviously nothing related to your problem; if you think 
there is some problem with inherited objects and updating fields in the derived 
class then you present those classes ... completely, and only those, and the 
persistence code ought to only refer to those. You say you have some problem 
yet don't identify the line in the persistence code where that problem comes in.

You make no reference to the log, yet the log would obviously tell you what is 
happening at every step. I'd look at that if I was you

What is "runtime" ? you mean when deployed on GAE? in which case it isn't a 
problem with the GAE DN plugin, but a problem for GAE as a whole.

GAE tests update fields in base and subclasses and have no problem. Either 
provide a complete testcase that demonstrates something. Nontransactional 
updates are not atomic, so you either have to wait for PM.close, or commit a 
transaction for the updates to go to the DB.

Original comment by googleco...@yahoo.co.uk on 4 Feb 2012 at 11:41

GoogleCodeExporter commented 8 years ago
Andy, it is a bug, even though I cannot replicate it when running local tests. 
Here is a link to exact same issue others are having, and it includes a 
solution. The problem is with subclass fields not being marked dirty, or so it 
appears because when I do JDOHelper.makeDirty(obj, "nickname"), it works, when 
I don't - it doesn't. Please see this link, it has a lot more details.
http://stackoverflow.com/questions/7486710/updating-objects-in-gae

Original comment by spa.yu...@gmail.com on 6 Feb 2012 at 5:07

GoogleCodeExporter commented 8 years ago
//my code
staffer.setNickname( "newValue" );

JDOHelper.makeDirty( staffer, "nickname" );//this makes or breaks the code - if 
it is enabled, all is well, if commented out - nickname doesn't get saved. This 
issue exists only for subclasses, and I DID fix the app vs datastore identity, 
it doesn't change the result.

pm.makePersistent( staffer );
pm.close();

pm = pmFactory.getPersistenceManager();
Query q = new Query( "select from my.Staffer" );
List<Staffer> list = (List<Staffer>)q.execute();
list.get( 0 ).getNickname();//it is still "oldValue"

Original comment by spa.yu...@gmail.com on 6 Feb 2012 at 5:15

GoogleCodeExporter commented 8 years ago
That stackoverflow case is someone wanting to update something generically, so 
was using reflection - and as they say in that case ... they can read an object 
in and update fields successfully ... just need it done in a generic way. I see 
no reflection in your case, hence different. That person was using v1 of the 
plugin, you aren't. 

Whatever, a case being non-reproducable (in local store) means low priority 
(unless Google want to look at it of course). You have the log as a clear way 
of debugging your problem (because it tells you objects states and lots more 
after each operation) ... if you choose to do it.

Original comment by googleco...@yahoo.co.uk on 6 Feb 2012 at 6:56

GoogleCodeExporter commented 8 years ago
Can't reproduce no matter how hard I try.

Original comment by googleco...@yahoo.co.uk on 4 Jun 2012 at 10:29