MountainClimb / datanucleus-appengine

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

JPA child collection merge broken #167

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago

Hi,

If I store an entity with children, remove some of those children from the
collection, store the parent entity again, and fetch it, some of the
deleted chilren magically re-appear in the collection. This happens both in
the dev server and in production. Here's the code and the resulting log.

I'm using SDK 1.2.6.

First, the trivial entity classes:

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import com.google.appengine.api.datastore.Key;

@Entity
public class Parent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Key key;

    private String name;

    public Parent(String name) {
this.name = name;
    }

    @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
    private List<Child> children = new ArrayList<Child>();

    public void setChildren(List<Child> children) {
this.children = children;
    }

    public List<Child> getChildren() {
return children;
    }

    public void setKey(Key key) {
this.key = key;
    }

    public Key getKey() {
return key;
    }

    public void setName(String name) {
this.name = name;
    }

    public String getName() {
return name;
    }
}

and the child:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import com.google.appengine.api.datastore.Key;

@Entity
public class Child {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Key key;

    private String data;

    public Child(String data) {
this.data = data;
    }

    public void setData(String data) {
this.data = data;
    }

    public String getData() {
return data;
    }

    public void setKey(Key key) {
this.key = key;
    }

    public Key getKey() {
return key;
    }
}

Now, I do the following steps (in a actual app these happen in different
requests):
- Create a new Parent and add 3 Child instances to it
- Create a EntityManager, persist the Parent and close the EM
- Remove the children from the parent
- Create a EntityManager, merge the Parent with 0 children, and close the EM

As the last step I create a new EntityManager and fetch the Parent. It
should have 0 children but it has 2. Only one of the children was actually
removed.

Testcase code below, this is the console output:

Parent 8ec9014f-9226-42a4-90af-dca7619320c5 stored succesfully
Parent contains child 1
Parent contains child 2
Parent contains child 3
Children removed, now contains 0 children
Parent 8ec9014f-9226-42a4-90af-dca7619320c5 stored succesfully with 0 children
Parent 8ec9014f-9226-42a4-90af-dca7619320c5 fetched succesfully
After fetch: Parent contains child 2 SHOULDN'T HAVE ANY CHILDREN!
After fetch: Parent contains child 3 SHOULDN'T HAVE ANY CHILDREN!

Test case:
Parent p = new Parent(UUID.randomUUID().toString());

EntityManager em = EMF.get().createEntityManager();
try {
    p.getChildren().add(new Child("1"));
    p.getChildren().add(new Child("2"));
    p.getChildren().add(new Child("3"));

    em.getTransaction().begin();
    em.persist(p);
    em.getTransaction().commit();

    System.out.println("Parent " + p.getName() + " stored succesfully");
} finally {
    em.close();
}

for (Child c : p.getChildren()) {
    System.out.println("Parent contains child " + c.getData());
}

p.getChildren().remove(1); // "2"
p.getChildren().remove(1); // "3"
p.getChildren().remove(0); // "1"

System.out.println("Children removed, now contains "
+ p.getChildren().size() + " children");

em = EMF.get().createEntityManager();
try {
    em.getTransaction().begin();
    p = em.merge(p);
    em.getTransaction().commit();

    System.out.println("Parent " + p.getName()
    + " stored succesfully with " + p.getChildren().size()
    + " children");
} catch (Exception e) {
    e.printStackTrace();
    em.getTransaction().rollback();
} finally {
    em.close();
}

em = EMF.get().createEntityManager();

try {
    em.getTransaction().begin();
    p = (Parent) em.createQuery(
    "select p from com.vaadin.gaejdotest.Parent p where p.name='"
    + p.getName() + "'").getResultList().get(0);
    em.getTransaction().commit();

    System.out
    .println("Parent " + p.getName() + " fetched succesfully");
} catch (Exception e) {
    e.printStackTrace();
    em.getTransaction().rollback();
} finally {
    em.close();
}

for (Child c : p.getChildren()) {
    System.out.println("After fetch: Parent contains child "
    + c.getData() + " SHOULDN'T HAVE ANY CHILDREN!");
}

Original issue reported on code.google.com by henri.mu...@gmail.com on 29 Nov 2009 at 2:14

GoogleCodeExporter commented 8 years ago
Does this have something to do with issue #144?

Original comment by henri.mu...@gmail.com on 29 Nov 2009 at 2:16

GoogleCodeExporter commented 8 years ago
I have duplicated this problem with GAE 1.3.1.  It's a show-stopper for my 
planned app; 
unowned 1-to-n relationships won't give the query performance I need, and owned 
1-to-n 
relationships are broken.

Original comment by patrick....@gmail.com on 12 May 2010 at 3:31

GoogleCodeExporter commented 8 years ago
I'm seeing this in JDO as well, when using "primitives" like List<Key> inside 
of a detached object.

Original comment by jamesk...@gmail.com on 1 Jul 2010 at 5:17

GoogleCodeExporter commented 8 years ago
Observing similar behavior using JPA with GAE 1.3.7.

Original comment by maliniakh on 1 Sep 2010 at 9:29

GoogleCodeExporter commented 8 years ago
Uses remove(int) so see issue 179 regarding how GAE datastore provides access 
to such information. 

Original comment by googleco...@yahoo.co.uk on 28 Sep 2011 at 6:12