如何在 android 中具有一对一或一对多关系的对象化实体中插入记录

how to insert record in objectify entity having one-to-one or one-to-many relationship in android

我有如下 class 城市的模型:

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

我有另一个模型class下面给出的人:

@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 为 class 生成端点并进行部署。

这里是生成端点的代码:

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

我想在城市和人之间建立关系,使得许多人可以属于一个城市。 问题:

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

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

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

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

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

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

首先阅读下面 link 中关于 objectify 组的讨论: how to insert record in objectify entity having one-to-one or one-to-many relationship in android 因此,基于密钥(即网络安全密钥)而不是基于 ID 设计 API 是一种更好的方法,因为 ID 在数据存储中不是唯一的。 所以我 将我的人物模型 class 更改为 :

@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() 会 return 为拥有 parent 信息的人 class 提供网络安全密钥。我想要这个网络安全密钥,这样我就可以通过查询密钥直接检索存储的实体。

对于城市模型 class,因为它没有 parent getWebsafeKey() 方法,所以看起来像:

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

问题2的答案: 在问题中插入城市将是相同的,意味着 city endpoint 的 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,因为当您从模型 class 自动生成端点时,所有 @ApiMethod 都将基于 "id" 如问题所示。所以你必须用 "web safe key" 替换 "id" 参数。 同样的事情也适用于城市端点方法。 为了更好地了解参数,您应该[单击此处][2]

例如,person 端点的获取方法如下所示:

@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