我正在使用objectify将对象持久保存到Google Cloud数据存储区。 “主键”用@Id注释。
@Entity
class Car {
@Id Long id;
...
}
我生成客户端端点(使用Android Studio)。
我需要所有字段的getter和setter,包括id,以便REST能够序列化和反序列化对象。如果我不添加getter和setter,那么这些是由Google端点构建器创建的。
通常,客户端从数据存储区获取实例,更改属性并将其更新回来。后端处理新属性并将它们存储在数据存储区中。
只要id相同,这就像假设一样。但是当客户端更改ID时会发生什么?由于服务器是无状态的,因此它认为另一个对象已更新,并且插入或更新了错误的对象。换句话说,如果数据存储区中已存在新的ID,则更新该记录;如果它不存在则创建具有该id的新记录。
这种行为是数据存储所固有的,但如果客户端更改了id,这会弄乱数据库,因此必须有办法防止这种情况发生。
我可以看到两种解决方案:
正如@ zgc7009 {@ 3}}所建议的那样,我可以将setter留空,让它什么都不做。我已经尝试了这个,只要没有相关的对象,它就可以工作。
我可以在后端使用一个版本字段,每次调用UPDATE时都会增加。这允许我验证客户端版本是否==服务器版本 - 1并且如果没有则抛出异常。此措施将减少更新错误记录的机会,但在每次写入之前需要从数据存储区读取,因此效率不高。
由于两种解决方案都不好,我相信Google的某个人一直在考虑这个问题,并且必须有一种更好的方法来确保整个REST的数据完整性。由于我的理解 - 而且我不知道是否可以更改 - ,Google端点API的生成方式是ID不包含在URL中,或者在封面下发生。换句话说:通常,URL(元数据)包含存储对象的id。这里,它包含在对象(有效负载)中,因此可以由客户端进行更改。
请有人光明吗?
PS:我知道上面例子中数据存储的最佳实践可能会使用VIN作为id,但这并不总是可行的。有些对象不能创建唯一标识符以用作id。
答案 0 :(得分:1)
看起来像许多人面临的常见问题。你可以做一件事:
上面的解决方案2是一个很好的解决方案,但您担心在每次写入之前从Datastore
读取。这可以通过使用@Cache
注释您的实体类来缓解,以便客户端第一次检索将被缓存的实体。这样,当客户端调用您的后端进行更新或执行任何在比较版本时不会遇到Datastore
的内容。
答案 1 :(得分:0)
如果您担心在端点上暴露敏感属性,您有两种选择:
1)用@ApiResourceProperty(ignored=AnnotationBoolean.TRUE)
注释它们(getters),以便端点序列化器跳过它们。
2)不要通过API发送您的实体,只使用您想要/需要传输的字段创建基本POJO。