【问题标题】:how to insert record in objectify entity having one-to-one or one-to-many relationship in android如何在android中具有一对一或一对多关系的objectify实体中插入记录
【发布时间】:2016-05-27 05:40:48
【问题描述】:

我有如下 City 类的模型:

@Entity
public class City {
    @Id
    Long id;
    String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

我在下面给出了另一个模型类 Person:

@Entity
public class Person {
    @Id
    Long id;
    String name;
    public String getName() {
        return name;
    }

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


    @ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
    Key<City> city;
}

之后,我使用 android studio 为两个类生成端点并部署它。

这里是生成的端点的代码:

PersonEndpoint

@Api(
        name = "personApi",
        version = "v1",
        resource = "person",
        namespace = @ApiNamespace(
                ownerDomain = "backend.faceattendence.morpho.com",
                ownerName = "backend.faceattendence.morpho.com",
                packagePath = ""
        )
)
public class PersonEndpoint {

    private static final Logger logger = Logger.getLogger(PersonEndpoint.class.getName());

    private static final int DEFAULT_LIST_LIMIT = 20;

    static {
        // Typically you would register this inside an OfyServive wrapper. See: https://code.google.com/p/objectify-appengine/wiki/BestPractices
        ObjectifyService.register(Person.class);
    }

    /**
     * Returns the {@link Person} with the corresponding ID.
     *
     * @param id the ID of the entity to be retrieved
     * @return the entity with the corresponding ID
     * @throws NotFoundException if there is no {@code Person} with the provided ID.
     */
    @ApiMethod(
            name = "get",
            path = "person/{id}",
            httpMethod = ApiMethod.HttpMethod.GET)
    public Person get(@Named("id") Long id) throws NotFoundException {
        logger.info("Getting Person with ID: " + id);
        Person person = ofy().load().type(Person.class).id(id).now();
        if (person == null) {
            throw new NotFoundException("Could not find Person with ID: " + id);
        }
        return person;
    }

    /**
     * Inserts a new {@code Person}.
     */
    @ApiMethod(
            name = "insert",
            path = "person",
            httpMethod = ApiMethod.HttpMethod.POST)
    public Person insert(Person person) {
        // Typically in a RESTful API a POST does not have a known ID (assuming the ID is used in the resource path).
        // You should validate that person.id has not been set. If the ID type is not supported by the
        // Objectify ID generator, e.g. long or String, then you should generate the unique ID yourself prior to saving.
        //
        // If your client provides the ID then you should probably use PUT instead.
        ofy().save().entity(person).now();
        logger.info("Created Person.");

        return ofy().load().entity(person).now();
    }

    /**
     * Updates an existing {@code Person}.
     *
     * @param id     the ID of the entity to be updated
     * @param person the desired state of the entity
     * @return the updated version of the entity
     * @throws NotFoundException if the {@code id} does not correspond to an existing
     *                           {@code Person}
     */
    @ApiMethod(
            name = "update",
            path = "person/{id}",
            httpMethod = ApiMethod.HttpMethod.PUT)
    public Person update(@Named("id") Long id, Person person) throws NotFoundException {
        // TODO: You should validate your ID parameter against your resource's ID here.
        checkExists(id);
        ofy().save().entity(person).now();
        logger.info("Updated Person: " + person);
        return ofy().load().entity(person).now();
    }

    /**
     * Deletes the specified {@code Person}.
     *
     * @param id the ID of the entity to delete
     * @throws NotFoundException if the {@code id} does not correspond to an existing
     *                           {@code Person}
     */
    @ApiMethod(
            name = "remove",
            path = "person/{id}",
            httpMethod = ApiMethod.HttpMethod.DELETE)
    public void remove(@Named("id") Long id) throws NotFoundException {
        checkExists(id);
        ofy().delete().type(Person.class).id(id).now();
        logger.info("Deleted Person with ID: " + id);
    }

    /**
     * List all entities.
     *
     * @param cursor used for pagination to determine which page to return
     * @param limit  the maximum number of entries to return
     * @return a response that encapsulates the result list and the next page token/cursor
     */
    @ApiMethod(
            name = "list",
            path = "person",
            httpMethod = ApiMethod.HttpMethod.GET)
    public CollectionResponse<Person> list(@Nullable @Named("cursor") String cursor, @Nullable @Named("limit") Integer limit) {
        limit = limit == null ? DEFAULT_LIST_LIMIT : limit;
        Query<Person> query = ofy().load().type(Person.class).limit(limit);
        if (cursor != null) {
            query = query.startAt(Cursor.fromWebSafeString(cursor));
        }
        QueryResultIterator<Person> queryIterator = query.iterator();
        List<Person> personList = new ArrayList<Person>(limit);
        while (queryIterator.hasNext()) {
            personList.add(queryIterator.next());
        }
        return CollectionResponse.<Person>builder().setItems(personList).setNextPageToken(queryIterator.getCursor().toWebSafeString()).build();
    }

    private void checkExists(Long id) throws NotFoundException {
        try {
            ofy().load().type(Person.class).id(id).safe();
        } catch (com.googlecode.objectify.NotFoundException e) {
            throw new NotFoundException("Could not find Person with ID: " + id);
        }
    }
}

城市端点

@Api(
        name = "cityApi",
        version = "v1",
        resource = "city",
        namespace = @ApiNamespace(
                ownerDomain = "backend.faceattendence.morpho.com",
                ownerName = "backend.faceattendence.morpho.com",
                packagePath = ""
        )
)
public class CityEndpoint {

    private static final Logger logger = Logger.getLogger(CityEndpoint.class.getName());

    private static final int DEFAULT_LIST_LIMIT = 20;

    static {
        // Typically you would register this inside an OfyServive wrapper. See: https://code.google.com/p/objectify-appengine/wiki/BestPractices
        ObjectifyService.register(City.class);
    }

    /**
     * Returns the {@link City} with the corresponding ID.
     *
     * @param id the ID of the entity to be retrieved
     * @return the entity with the corresponding ID
     * @throws NotFoundException if there is no {@code City} with the provided ID.
     */
    @ApiMethod(
            name = "get",
            path = "city/{id}",
            httpMethod = ApiMethod.HttpMethod.GET)
    public City get(@Named("id") Long id) throws NotFoundException {
        logger.info("Getting City with ID: " + id);
        City city = ofy().load().type(City.class).id(id).now();
        if (city == null) {
            throw new NotFoundException("Could not find City with ID: " + id);
        }
        return city;
    }

    /**
     * Inserts a new {@code City}.
     */
    @ApiMethod(
            name = "insert",
            path = "city",
            httpMethod = ApiMethod.HttpMethod.POST)
    public City insert(City city) {
        // Typically in a RESTful API a POST does not have a known ID (assuming the ID is used in the resource path).
        // You should validate that city.id has not been set. If the ID type is not supported by the
        // Objectify ID generator, e.g. long or String, then you should generate the unique ID yourself prior to saving.
        //
        // If your client provides the ID then you should probably use PUT instead.
        ofy().save().entity(city).now();
        logger.info("Created City.");

        return ofy().load().entity(city).now();
    }

    /**
     * Updates an existing {@code City}.
     *
     * @param id   the ID of the entity to be updated
     * @param city the desired state of the entity
     * @return the updated version of the entity
     * @throws NotFoundException if the {@code id} does not correspond to an existing
     *                           {@code City}
     */
    @ApiMethod(
            name = "update",
            path = "city/{id}",
            httpMethod = ApiMethod.HttpMethod.PUT)
    public City update(@Named("id") Long id, City city) throws NotFoundException {
        // TODO: You should validate your ID parameter against your resource's ID here.
        checkExists(id);
        ofy().save().entity(city).now();
        logger.info("Updated City: " + city);
        return ofy().load().entity(city).now();
    }

    /**
     * Deletes the specified {@code City}.
     *
     * @param id the ID of the entity to delete
     * @throws NotFoundException if the {@code id} does not correspond to an existing
     *                           {@code City}
     */
    @ApiMethod(
            name = "remove",
            path = "city/{id}",
            httpMethod = ApiMethod.HttpMethod.DELETE)
    public void remove(@Named("id") Long id) throws NotFoundException {
        checkExists(id);
        ofy().delete().type(City.class).id(id).now();
        logger.info("Deleted City with ID: " + id);
    }

    /**
     * List all entities.
     *
     * @param cursor used for pagination to determine which page to return
     * @param limit  the maximum number of entries to return
     * @return a response that encapsulates the result list and the next page token/cursor
     */
    @ApiMethod(
            name = "list",
            path = "city",
            httpMethod = ApiMethod.HttpMethod.GET)
    public CollectionResponse<City> list(@Nullable @Named("cursor") String cursor, @Nullable @Named("limit") Integer limit) {
        limit = limit == null ? DEFAULT_LIST_LIMIT : limit;
        Query<City> query = ofy().load().type(City.class).limit(limit);
        if (cursor != null) {
            query = query.startAt(Cursor.fromWebSafeString(cursor));
        }
        QueryResultIterator<City> queryIterator = query.iterator();
        List<City> cityList = new ArrayList<City>(limit);
        while (queryIterator.hasNext()) {
            cityList.add(queryIterator.next());
        }
        return CollectionResponse.<City>builder().setItems(cityList).setNextPageToken(queryIterator.getCursor().toWebSafeString()).build();
    }

    private void checkExists(Long id) throws NotFoundException {
        try {
            ofy().load().type(City.class).id(id).safe();
        } catch (com.googlecode.objectify.NotFoundException e) {
            throw new NotFoundException("Could not find City with ID: " + id);
        }
    }
}

我想在 City 和 Person 之间建立关系,这样很多人就可以属于一个城市。 问题:

  1. 对于这种关系,这是正确的类建模吗?如果不是,请告诉我一对一和一对多关系的正确模型

  2. 如何通过 java 代码(端点)和 API explorer 为这种关系在数据存储中插入记录?

  3. 是否需要使用@Parent注解或@Index注解?

  4. 建立此关系后,如果我删除一个城市,则必须自动删除属于该城市的所有人员。这种建模能够实现吗?请告诉我执行此操作的代码。如果没有,那么我该如何使用关系来实现呢?

【问题讨论】:

    标签: android google-app-engine google-cloud-endpoints objectify google-cloud-datastore


    【解决方案1】:

    我无法回答有关 Google Endpoints 的任何问题,但使用指向 City 的 Key 字段对 Person 进行建模的基本思想可能是正确的 - 假设一个城市有很多人。您需要 @Index 关键字段,以便您可以查询城市中的人。请注意,此查询最终会保持一致,因此如果您要在一个城市中添加/删除大量人员,您需要在停止添加人员和执行删除之间设置一个延迟。

    您可以对此进行建模,使 City 成为 Person 的 @Parent。这将消除最终的一致性,但这意味着您永远无法将 Person 移动到新城市。这也意味着由于单个实体组的事务吞吐量限制,该城市中的任何城市或个人每秒更改一次以上。假设你实际上是在谈论人物和城市,你可能不想要这个。但这取决于您的数据集。

    【讨论】:

    • 使用父注释给出错误:不支持参数化类型 com.googlecode.objectify.Key
    • 感谢您的评论@stickfigure,它确实增加了我对关系的理解。但我想知道使用端点的上述问题的答案
    【解决方案2】:

    首先阅读以下链接上与对象化组发生的讨论: how to insert record in objectify entity having one-to-one or one-to-many relationship in android 因此,它是基于密钥(即网络安全密钥)而不是基于 ID 设计 API 的更好方法,因为 ID 在数据存储中不是唯一的。 所以我将我的 Person 模型类更改为

    @Entity
    public class Person {
        @Id
        Long id;
        String name;
        @Parent
        @Index
        @ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
        Key<City> city;
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getWebsafeKey() {
            return Key.create(city.getKey(), Person.class, id).getString();
        }
    
    }
    

    getWebsafeKey() 将返回具有其父信息的 person 类的 websafe 密钥。我想要这个网络安全密钥,以便我可以通过查询密钥直接检索存储的实体。

    对于 City 模型类,因为它没有父 getWebsafeKey() 方法,所以看起来像:

    public String getWebsafeKey() {
            return Key.create( City.class, id).getString();
        }
    

    问题 2 的答案: 在问题中插入城市将是相同的,这意味着城市端点的 insert() 方法没有变化。在城市中插入人员将如下所示:

    @ApiMethod(
           name = "insert",
           path = "city/{city_web_safe_key}/person",
           httpMethod = ApiMethod.HttpMethod.POST)
           public Person insert(@Named("city_web_safe_key") String cityWebSafeKey,Person person) {
            Key<City> cityKey = Key.create(cityWebSafeKey);
                    person.city = Ref.create(cityKey);
                        ofy().save().entity(person).now();
                        logger.info("Created Person.");
                        return ofy().load().entity(person).now();
                    }
    

    同样,您还必须更改所有其他具有“id”参数的@ApiMethod,因为当您从模型类自动生成端点时,所有@ApiMethod 都将基于“id”,如问题所示。所以你必须用 "web safe key" 替换 "id" 参数。 同样的事情也适用于 City 端点方法。 为了更好地了解参数,您应该[点击这里][2]

    例如,个人端点的 get 方法如下所示:

    @ApiMethod(
                name = "get",
                path = "person/{person_websafe_key}",
                httpMethod = ApiMethod.HttpMethod.GET)
        public Site get(@Named("person_websafe_key") String personWSKey) throws NotFoundException {
            logger.info("Getting person with WSkey: " + personWSKey);
            Person person = (Person) ofy().load().key(Key.create(personWSKey)).now();
            if (person == null) {
                throw new NotFoundException("Could not find person with WSKey: " + personWSKey);
            }
            return person;
        }
    

    问题 4 的答案: Objectify 不会强迫开发人员进行这种删除。这一切都与您有什么样的要求有关。这是 City 的 remove 方法,它也会删除该城市中的人。

    @ApiMethod(
                name = "remove",
                path = "city/{city_web_safe_key}",
                httpMethod = ApiMethod.HttpMethod.DELETE)
        public void remove(@Named("city_web_safe_key") String cityWSKey) throws NotFoundException {
            Key<City> cityKey = Key.create(cityWSKey);
            checkExists(cityKey);
            //ofy().delete().key(cityKey).now();
            ofy().delete().keys(ofy().load().ancestor(cityKey).keys().list());
    
        }
    

    问题 1 和 3 的答案 上面的答案已经涵盖了详细知识see documentation

    【讨论】:

    • 为了更好地了解您应该访问的参数:cloud.google.com/appengine/docs/java/endpoints/…
    • 您能否展示一个完整的简单工作示例代码?尝试重复您的方法时出现 503 HTTP 错误。
    • 如果您遵循以下步骤,代码将在 android studio 中正常工作:创建模型,为这些模型创建端点,在端点中进行上述更改,创建数据存储索引。 xml 用于复合索引,最后将项目部署到应用引擎服务器上,现在您可以从 API 资源管理器或 java 代码发出请求。
    • 看来我错过了这一步“...创建数据存储索引.xml”。这是什么以及如何在 Android Studio 中创建它?顺便说一句,我不需要使用 @Index 注释对实体字段进行索引。
    • 点击可能有帮助的链接:stackoverflow.com/questions/37852404/…
    猜你喜欢
    • 1970-01-01
    • 2020-05-27
    • 2011-07-21
    • 1970-01-01
    • 2013-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多