【问题标题】:How do I remove an item from a Set persisted with JDO?如何从使用 JDO 保留的 Set 中删除项目?
【发布时间】:2012-05-07 05:03:36
【问题描述】:

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

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

显示日志级别的控制台日志转储为here(更新:这是无事务版本)。

带有事务的控制台日志转储为here

Container.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>();
    }
}

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();
    }
}

测试 sn-p(作为 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());

...

编辑:

使用事务测试 sn-p

    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();
    }

【问题讨论】:

  • 调用remove时“之后”是什么状态?集合是什么类型的?包装类型?还是 java.util.HashSet?然后当你调用remove时日志中会出现什么? (在删除呼叫之前打印,然后在之后打印)。然后在事务中重复该操作......因为非事务时更新的语义不同
  • @DataNucleus 我已更新问题以回答您的问题——日志文件中列出了状态,收集类型为org.datanucleus.sco.backed.Set。还列出了事务版本和日志——输出看起来是一样的。
  • 我已经能够将其简化为一个更简单的测试用例:集合是不必要的,非原始类型的任何属性都表现出相同的行为。人们对发布新问题与重写原问题有何看法?

标签: java google-app-engine jdo datanucleus


【解决方案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;

Containee.java

// ...
@Persistent
private Container container;

【讨论】:

  • 如果关系是单向的,那么它不需要返回容器的链接
  • 它是否适用于 JDO 2.3?他不需要使用 Set 而不是 Set 吗?看到这个链接unowend
  • JDO 不是问题(因为它允许规范单向和双向关系),你的意思是 GAE 插件 v1.x 是否可以这样工作。当然,使用 GAE 插件的 v2 没有理由有任何反向链接,我会惊讶于 v1.x 也需要一个。 Set 仅用于模拟无主关系,并且用户没有声明他需要它
【解决方案2】:

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

    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 框架使用对象引用而不是相等性检查来处理删除,但我在文档中没有找到任何证实或反驳该假设的内容。

【讨论】:

  • datanucleus-appengine 2.1.2 的情况似乎仍然如此,谢谢你为我节省了 75% 的周末头疼!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-28
  • 2021-10-19
  • 2019-11-22
  • 2011-10-21
  • 1970-01-01
相关资源
最近更新 更多