1

我有一个容器对象,其中包含一组使用JDO 2.3持久保存在 Google App Engine 中的对象。我想从设置的内容中删除一个对象。使用以下测试代码,remove() 方法返回 false,但更改不会持久化,如以下代码所示。但是,集合基数减少了(这种行为让我感到惊讶)。如何更正此示例以从集合中删除指定的对象(在本例中为对象“一个”)?

我无法在 JDO 文档中找到任何相关内容。平等检查和散列基于这篇文章

带有日志级别的控制台日志转储在这里(更新:这是无事务版本)。

带有事务的控制台日志转储在这里

容器.java

import java.util.HashSet;
import java.util.Set;

import javax.jdo.annotations.FetchGroup;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(detachable = "true")
@FetchGroup(name = "withContents", members = { @Persistent(name = "contents") })
public class Container
{
    @PrimaryKey
    private String id;

    @Persistent(dependentElement = "true")
    private Set<Containee> contents;

    public Set<Containee> getContents()
    {
        return contents;
    }

    public Container(String id)
    {
        super();
        this.id = id;
        contents = new HashSet<Containee>();
    }
}

容器.java

import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(detachable = "true")
public class Containee
{
    @SuppressWarnings("unused")
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName = "datanucleus", 
       key = "gae.encoded-pk", value = "true")
    private String id;

    @Persistent
    private String friendlyName;

    public String getFriendlyName()
    {
        return friendlyName;
    }

    public Containee(String friendlyName)
    {
        this.friendlyName = friendlyName;
    }

    @Override
    public boolean equals(Object other)
    {
        if (other instanceof Containee)
        {
            Containee that = (Containee) other;
            return this.getFriendlyName().equals(that.getFriendlyName());
        }
        return false;
    }

    @Override
    public int hashCode()
    {
        return friendlyName.hashCode();
    }
}

测试片段(作为 RemoteService 的一部分在服务器端运行)

...

        System.out.println("Fetching...");
        Container after = pm.getObjectById(Container.class, "test");

        // prints 2
        System.out.println("Pre-remove set cardinality "
                + after.getContents().size());

        // prints "true"
        System.out.println("Post-store containment: "
                + after.getContents().contains(one));

        for (Containee e : after.getContents())
        {
            System.out.println(e.getFriendlyName());
        }

        System.out.println("Mark");
        boolean result = after.getContents().remove(one);
        System.out.println("End Mark");

        System.out
                .println("'after' object class: " + after.getContents().getClass());

        // prints "false" (!?!?)
        System.out.println("Post-store removal: " + result);

        // prints 1 (...?)
        System.out.println("Post-remove set cardinality: "
                + after.getContents().size());

...

编辑:

带有事务的测试片段

    Container before = new Container("test");

    Containee one = new Containee("one");
    Containee two = new Containee("two");
    Containee three = new Containee("three");

    before.getContents().add(one);
    before.getContents().add(two);
    before.getContents().add(three);

    // prints "true"
    System.out.println("Pre-store containment: "
            + before.getContents().contains(two));

    // prints "true"
    System.out.println("Pre-store removal: "
            + before.getContents().remove(two));

    PersistenceManager pm = pmf.getPersistenceManager();

    try
    {
        pm.makePersistent(before);
    }
    finally
    {
        pm.close();
    }

    pm = pmf.getPersistenceManager();
    pm.getFetchPlan().addGroup("withContents");

    Transaction tx = pm.currentTransaction();

    try
    {
        System.out.println("Fetching...");
        Container after = pm.getObjectById(Container.class, "test");

        // prints 2
        System.out.println("Pre-remove set cardinality "
                + after.getContents().size());

        // prints "true"
        System.out.println("Post-store containment: "
                + after.getContents().contains(one));

        for (Containee e : after.getContents())
        {
            System.out.println(e.getFriendlyName());
        }

        tx.begin();

        System.out.println("Mark");
        boolean hrm = after.getContents().remove(one);
        System.out.println("End Mark");

        tx.commit();

        System.out
                .println("'after' object class: " + after.getContents().getClass());

        // prints "false" (!?!?)
        System.out.println("Post-store removal: " + hrm);

        // prints 1 (...?)
        System.out.println("Post-remove set cardinality: "
                + after.getContents().size());

    }
    finally
    {
        System.out.println("Finalizing transaction...");
        if (tx.isActive())
        {
            System.out.println("Rolling back...");
            tx.rollback();
        }
    }

    pm.close();

    pm = pmf.getPersistenceManager();
    pm.getFetchPlan().addGroup("withContents");

    try
    {
        System.out.println("Fetching again...");
        Container after = pm.getObjectById(Container.class, "test");

        // prints 2
        System.out.println("Final set cardinality "
                + after.getContents().size());
    }
    finally
    {
        pm.close();
    }
4

2 回答 2

1

你的关系没有正确完成。

https://developers.google.com/appengine/docs/java/datastore/jdo/relationships#Owned_One_to_Many_Relationships

容器.java

// ...
@Persistent(mappedBy = "employee", dependentElement = "true")
private Set<Containee> contents;

容器.java

// ...
@Persistent
private Container container;
于 2012-05-07T08:00:51.770 回答
1

经过一个周末的头疼和沮丧之后,我找到了一种解决方法:在调用 Set.remove() 时利用引用相等而不是值相等。下面是代码(有趣的是从注释“获取对持久集中对象的引用”开始):

    Container before = new Container("test");

    Containee one = new Containee("one");
    Containee two = new Containee("two");
    Containee three = new Containee("three");

    before.getContents().add(one);
    before.getContents().add(two);
    before.getContents().add(three);

    pm = pmf.getPersistenceManager();

    try
    {
        pm.makePersistent(before);
    }
    finally
    {
        pm.close();
    }

    pm = pmf.getPersistenceManager();

    try
    {
        Container after = pm.getObjectById(Container.class, "test");

        // prints 3
        System.out.println("Pre-remove set cardinality "
                + after.getContents().size());

        // prints "true"
        System.out.println("Post-store containment: "
                + after.getContents().contains(one));

        //get a reference to the object in the persisted Set
        //that is value-equivalent to Containee #1
        Containee ref = null;
        for (Containee c : after.getContents())
        {
            if (c.equals(one)) ref = c;
        }

        if (ref != null)
        {
            after.getContents().remove(ref);
        }

        // prints 2
        System.out.println("Post-remove set cardinality: "
                + after.getContents().size());

    }
    finally
    {
        pm.close();
    }

    pm = pmf.getPersistenceManager();

    try
    {
        Container after = pm.getObjectById(Container.class, "test");

        // prints 2 (as expected)
        System.out.println("Final set cardinality "
                + after.getContents().size());
    }
    finally
    {
        pm.close();
    }

这段代码没有显示它,但是用悲观事务包装操作可能是一个好计划,以避免并发问题。

这种技术的成功让我怀疑 DataNucleus 框架使用对象引用而不是相等性检查来处理删除,但我在文档中没有发现任何证实或反驳该假设的内容。

于 2012-05-14T04:36:45.317 回答