【问题标题】:How properly store and query an Objectify Entity with ancestor()? [closed]如何使用祖先()正确存储和查询 Objectify 实体? [关闭]
【发布时间】:2015-04-28 07:56:07
【问题描述】:

存储和查询 Objectify 实体的正确方法是什么,该实体涉及标有 Objectify 的 @Parent 注释的字段?请提供使用ancestor() 查询的示例。

【问题讨论】:

    标签: java google-app-engine objectify


    【解决方案1】:

    有几个要求:

    1. 索引 - 确保查询的字段已编入索引

    2. 祖先 - 确保使用ancestor(parent)

    3. 提交 - 确保已提交保存

    4. 附件 - 确保将子项附加到父项并保存

    5. 验证 id 是否匹配,而不是 Java 对象实例 - 当您读回它时,它将是一个不同的 Java 对象。但是标有Objectify的@Id的字段应该是一样的

    6. 使用 .now() 保存并使用 .now() 回读 - 请参阅 Why .now()? (Objectify)Objectify error "You cannot create a Key for an object with a null @Id" in JUnit

    7. 不要尝试将标记为@Parent 的字段用作您要过滤的字段;改为复制该字段

    8. datastore-indexes.xml - 如果您要查询实体并过滤多个字段,那么 Objectify 的 @Index 注释是不够的。您还必须在[datastore-indexes.xml](https://cloud.google.com/appengine/docs/java/config/indexconfig) 中输入一个条目。感谢 Patrice 在 cmets 中提到这一点。

    这是我正在编写的一些代码的草图,我让它在单元测试中工作。

    RepositoryTest

    import static org.hamcrest.MatcherAssert.assertThat;
    import static org.hamcrest.Matchers.equalTo;
    import com.googlecode.objectify.ObjectifyService;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    public class RepositoryTest {
        // Tests under worst case of replication https://stackoverflow.com/questions/27727338/which-is-better-setdefaulthighrepjobpolicyunappliedjobpercentage100-vs-custo
        private final LocalServiceTestHelper helper =
                new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
                    .setDefaultHighRepJobPolicyUnappliedJobPercentage(100)); 
    
        private Closeable closeable;
        private Repository repository;
    
        @Before
        public void setup() {
            helper.setUp();
            repository = new Repository();
            ObjectifyService.register(MyCategory.class);
            ObjectifyService.register(MyItem.class);
            closeable = ObjectifyService.begin(); // https://stackoverflow.com/questions/27726961/how-to-resolve-you-have-not-started-an-objectify-context-in-junit
        }
    
        @After
        public void tearDown() {
            closeable.close();
            helper.tearDown();
        }
    
        @Test
        public void testLookupMyItemShouldSucceed() {
            MyCategory myCategory = repository.createMyCategory();
    
            int zero = 0;
            int one = 1;
            int two = 2;
    
            addMyItem(myCategory, zero, "a");
            MyItem expectedMyItem = addMyItem(myCategory, one, "b");
            addMyItem(myCategory, two, "c");
    
            MyItem actualMyItem = repository.lookupMyItem(myCategory, one);
    
            assertThat(actualMyItem, Matchers.notNullValue());
            assertThat(actualMyItem.id, equalTo(expectedMyItem.id));
        }
    
        private MyItem addMyItem(MyCategory myCategory, long index, String label) {
            MyItem myItem = repository.createMyItem();
            myItem.setParent(myCategory);
            myItem.setGroup(myCategory);
            myItem.index = index;
            myItem.label = label;
            repository.updateMyItem(myItem);
        }
    }
    

    存储库

    import static com.googlecode.objectify.ObjectifyService.begin;
    import static com.googlecode.objectify.ObjectifyService.ofy;
    import com.googlecode.objectify.util.Closeable;
    
    public class Repository {
        public Topic createMyCategory() {
            Topic entity = topicProvider.get();
            updateTopic(entity);
            return entity;
        }   
    
        public MyItem lookupMyItem(MyCategory myCategory, long i) {
            return ofy().load().type(MyItem.class).ancestor(myCategory).filter(MyItem.MyCategoryField, myCategory).filter(MyItem.IndexField, i).first().now();
        }
    }
    

    我的物品

    import com.googlecode.objectify.Ref;
    import com.googlecode.objectify.annotation.Entity;
    import com.googlecode.objectify.annotation.Id;
    import com.googlecode.objectify.annotation.Index;
    import com.googlecode.objectify.annotation.Parent;
    
    @Entity
    public class MyItem {
        @Id public Long id;
        @Parent private Ref<MyCategory> parent;
    
        @Index private Ref<MyCategory> myCategory; public static final String MyCategoryField = "myCategory";   
        @Index public Long index; public static final String IndexField = "index"; 
    
        public String label;
        public long weight;
    
        public MyCategory getGroup() {
            return group.get();
        }
    
        public void setGroup(MyCategory group) {
            this.group = Ref.create(group);
        }
    
        public MyCategory getParent() {
            return parent.get();
        }
    
        public void setParent(MyCategory group) {
            this.parent = Ref.create(group);
        }
    }
    

    我的类别

    import com.googlecode.objectify.annotation.Entity;
    import com.googlecode.objectify.annotation.Id;
    
    @Entity
    public class MyCategory {
        @Id public Long id;
    }
    

    datastore-indexes.xml

    <?xml version="1.0" encoding="utf-8"?>
    <datastore-indexes autoGenerate="true">
        <datastore-index kind="MyItem" ancestor="true">
            <property name="myCategory" direction="asc" />
            <property name="index" direction="asc" />
        </datastore-index>
    </datastore-indexes>
    

    上面的代码中可能存在语法错误或拼写错误,因为为了清楚起见,我对原始代码进行了修改。单元测试确实通过并且始终通过(即它有时不会因为 eventual consistency 而失败)。

    【讨论】:

    • 您正在使用祖先查询,因此不会出现最终一致性问题。此外,您需要为复合索引创建一个 datastore-indexes.xml。如果您只需要单个字段,那么没有 xml 就可以了。
    • @Patrice 你是说如果一个查询有多个过滤器,只要其中一个是ancestor(),整个查询是强一致的?
    • 这不完整,因为它离开了本节: public Topic createMyCategory() { Topic entity = topicProvider.get();更新主题(实体);返回实体; } 模棱两可。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-23
    • 1970-01-01
    • 2012-09-13
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多