【问题标题】:JDO Loading Inconsistencies on Google App EngineGoogle App Engine 上的 JDO 加载不一致
【发布时间】:2011-07-29 22:51:57
【问题描述】:

我一直在尽可能地关注应用引擎 JDO 文档,但在加载我的 Board 对象包含的持久集合时遇到了奇怪且不一致的问题。在我手动将“最终一致性”指定为不存在后,即使在本地开发 Web 服务器中也会出现不一致。

有时当我使用我创建的加载辅助方法加载我的对象/集合时,它加载没有问题。其他时候会返回一个空集合(请注意,我 am 使用 getter 方法“接触”该集合,以确保数据不仅仅是延迟加载到代理对象中)。

最初我认为问题只是与高复制存储引擎的“最终一致性”缺陷有关,但在我自己制定了 LocalServiceTestHelper 中最终一致性为 0% 的策略后,我相当肯定情况并非如此。

我创建了一个 JUnit 测试来说明这个问题。基本上,我尝试在 testInsertUser 函数中创建并保存一个虚拟用户和板对象。我将一个新创建的 PlayedTile 对象的 ArrayList 附加到此板,然后执行 DataMaster.saveUser 辅助方法,该方法使用 Google App Engine 的持久性管理器将用户(以及板和 PlayedTile 集合)保存到数据存储区。在下一个方法中,我们尝试加载该用户(及其 Board 和 PlayedTile 集合)并显示那些保存的结果。混乱随之而来。

这是 JUnit 代码:

package com.astar.wordswall.test.data;

import java.util.ArrayList;

import com.astar.wordswall.data.DataMaster;
import com.astar.wordswall.data.jdo.Board;
import com.astar.wordswall.data.jdo.User;
import com.astar.wordswall.data.jdo.PlayedTile;
import com.astar.wordswall.test.appengine.LocalCustomHighRepPolicy;
import com.google.appengine.api.datastore.Key;

import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
// import com.google.gwt.user.client.Random;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;


public class SaveUsersBoardWithTilesTest {

    Key userKey;

    private final LocalServiceTestHelper helper =
        new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
            .setAlternateHighRepJobPolicyClass(LocalCustomHighRepPolicy.class));

    @Before
    public void setUp() {
        helper.setUp();
    }

    @After
    public void tearDown() {
        helper.tearDown();
    }

    @Test
    public void testInsert1() {
        testInsertUser();
        testReadUser();
    }

    /**
     * Creation and insertion of a user, board, and linked set of tiles into BigTable.
     */
    private void testInsertUser() {
        User u = new User("Simon");
        Board b = new Board();
        ArrayList<PlayedTile> tiles = new ArrayList<PlayedTile>(7);

        u.setBoard(b);
        b.setPlayedTiles(tiles);

        for (int j = 0; j < 7; j++) tiles.add(new PlayedTile('T'));

        DataMaster.saveUser(u);
        // Retrieve the user's key so that we can read him from the database later
        userKey = u.getUserKey();

        // Display all of our saved tiles:
        System.out.println("Saved tiles:");
        // Note that "getTileString()" just iterates through each Played tile printing the letter
        System.out.println("\t" + u.getBoard().getTileString());
    }

    /**
     * A typical read of a user object from the Datastore.
     */
    private void testReadUser() {
        User u = DataMaster.getUserWithBoard(userKey);
        // Display all of our saved tiles:
        System.out.println("Loaded tiles:");
        System.out.println("\t" + u.getBoard().getTileString());
    }
}

这里是实际执行 JDO 加载的相关 DataMaster.getUserWithBoard 静态函数:

/**
 * Loads a uniquely specified User and their associated board from 
 * the Datastore.  It also loads the board's complete list of PlayedTiles.
 * @param userKey the unique key assigned to this user
 */
public static User getUserWithBoard(Key userKey){
    User u = null;
    PersistenceManager pm = PMF.get().getPersistenceManager();
    try{
        u = pm.getObjectById(User.class, userKey);
        // In order for the board and tile collection to load, we must "touch" it while PM is active
        if (u.getBoard().getPlayedTiles().size() != 0) u.getBoard().getPlayedTiles().get(0);
        if (u.getBoard().getPlayedWords().size() != 0) u.getBoard().getPlayedWords().get(0);
    } finally{
        pm.close();
    }
    return u;
}

奇怪的是,这段代码有时会按预期工作:它会打印出与 testReadUser() 中的数据存储区加载后保存的完全相同的图块集。有时它只是加载一个空集合,尽管特别奇怪的是 u.getBoard().getPlayedWords().get(0) 调用不会引发空指针异常。

输出在之间振荡

正确:

Saved tiles:
    T T T T T T T 
Loaded tiles:
    T T T T T T T 

而且不正确:

Saved tiles:
    T T T T T T T 
Loaded tiles:

完全随意。

有人可以对此有所了解吗?这让我彻底疯了。 :)

编辑:另一个奇怪的线索/事实是,如果我通过将 testSaveUser 和 testReadUser 方法调用都包含在 for 循环中来使整个测试迭代,则每个加载操作都正确执行,或者没有他们这样做。这是本地 Google App Engine 测试环境中的错误吗?

【问题讨论】:

    标签: google-app-engine junit jdo google-data-api


    【解决方案1】:

    只是为了检查:您是否将数据类标记为可拆卸?即

    @PersistenceCapable(detachable = "true")
    public class Board { /* fun stuff here */ }
    

    此外,查看数据存储查看器以查看是否真的有效可能会有所帮助:http://localhost:8888/_ah/admin/

    【讨论】:

    • 我试图让数据类可分离,但我试图找到一个解决方案,尽管它们目前不可是可分离的,因为我试图在 PersistenceManager 事务中显式保存所有内容.不过,您有一个好主意,可以通过使用数据存储查看器来验证发生了什么;我将构建一个小型演示 servlet 并在明天对其进行测试。
    • 好吧,事实证明你一直都是对的,杰弗里!使我的所有对象都可拆卸后,问题得到了解决。我只是不明白为什么这是一项要求,因为我几乎更愿意将所有数据读/写操作留在一个委派的地方以获得最大的 ACID 效果。为什么非分离的一对多双向拥有关系不可能存在?
    • 嗯。好吧,我认为(默认情况下)对象与 PersistenceManager 的关联比看上去更紧密,也许是为了延迟加载优化。因此,当 PM 超出范围时,那些附加对象处于不确定状态(除非可拆卸)。到目前为止,在我对 GAE/J 的(诚然短暂的)经验中,我尝试过使它们可拆卸(出于与您类似的原因),或者在函数调用之间传递 PM。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-25
    • 1970-01-01
    • 2010-11-27
    • 1970-01-01
    • 2010-12-11
    • 2011-12-28
    • 2011-06-19
    相关资源
    最近更新 更多