假设有一个类似于 Instagram 的产品,核心的数据类型可能包括:用户(User),图片(Image),评论(Comment),点赞(Like)。他们之间的关系如下:
对于这样的模型,在 MySQL 中我们能很容易地通过主键、外键建立关联,但在 AVOS Cloud 里面如何表现出来呢?下面我会为大家仔细说明一下如何处理这种复杂的数据建模。
由于图片和发布者是紧密联系在一起的,它们是一对一的关系,这在 AVOS Cloud 的数据模型里面可以使用在一个 Image Object 中直接保存另一个 User Object 的 pointer 实现;图片和评论之间则是一对多的关系,由于评论都是随图片一起出现的,所以可以在一个 Image Object 中保存另外一类 Comment Object 的实例数组来实现;至于点赞这一个操作,由于它链接了人和图片两方,并且也需要双向查询,所以是多对多的关系,可以使用 AVOS Cloud 提供的 AVRelation 来实现,这也是 AVOS Cloud 提供的数据模型中最复杂的部分(详见 关联查询)。
下面我们来看一下具体如何实现这种一对一、一对多、多对多的数据映射关系。
User 对象我们可以直接使用 AVUser,不必再额外定义一个新的数据类型。而对于 Image 类,则可以这样声明(注意这里我们使用了子类化):
@AVClassName("Image")
public class Image extends AVObject{
public Image() {
super();
}
public AVUser getPublisher() {
return (AVUser)super.getAVUser("publisher");
}
public void setPublisher(AVUser user) {
super.put("publisher", user);
}
public String getCaption() {
return getString("caption");
}
public void setCaption(String caption) {
put("caption", caption);
}
public String getTakenAt() {
return super.getCreatedAt().toString();
}
public AVFile getRawImage() {
return super.getAVFile("imageFile");
}
public void setRawImage(AVFile file) {
super.put("imageFile", file);
}
@SuppressWarnings("unchecked")
public List getComments() {
return (List)getList("comments");
}
public void addComment(Comment com) {
addUnique("comments", com);
}
} Comment 的声明是这样的:
@AVClassName("Comment")
public class Comment extends AVObject{
public Comment() {
super();
}
public String getContent() {
return getString("content");
}
public void setContent(String value) {
put("content", value);
}
public void setCreator(AVUser user) {
put("creator", user);
}
public AVUser getCreator() {
return getAVUser("creator");
}
}
对于Like,我们使用 AVRelation 来链接 Image 和 AVUser,给 Image 类增加如下属性和方法:
@AVClassName("Image")
public class Image extends AVObject{
...
public AVRelation getLiker() {
AVRelation relation = getRelation("likes");
return relation;
}
public void removeLiker(AVUser user) {
AVRelation users = getLiker();
users.remove(user);
this.saveInBackground();
}
public void addLiker(AVUser user) {
AVRelation users = getLiker();
users.add(user);
this.saveInBackground();
}
...
} 并且,为了展示方便,我们给 Image 类增加一个非持久化的属性:
@AVClassName("Image")
public class Image extends AVObject{
List likedUsers = new ArrayList();
public void setLikedUsers(List usr) {
if (null == usr) return;
this.likedUsers = usr;
}
public List getLikedUsers() {
return this.likedUsers;
}
public int getLikerCount() {
return this.likedUsers.size();
}
} 好了,接下来我们就来看看如何对于这些数据进行增删改查。首先,我们看看如何保存这样的数据。
AVFile remoteFile = AVFile.withFile(timeInSeconds, new File(processedImageUri.getPath()));
remoteFile.saveInBackground();
Image image = new Image();
image.setPublisher(AVUser.getCurrentUser());
image.setRawImage(remoteFile);
image.setCaption(txtCaption.getText().toString().trim());
image.saveInBackground();
final Comment comt = new Comment();
comt.setContent(comment);
comt.setCreator(AVUser.getCurrentUser());
comt.saveInBackground(new SaveCallback(){
public void done(com.avos.avoscloud.AVException arg0) {
if (null != arg0) {
Toast.makeText(ImageListActivity.this,
"Save Comment failed", Toast.LENGTH_SHORT).show();
} else {
image.addComment(comt);
image.saveInBackground(new SaveCallback() {
public void done(com.avos.avoscloud.AVException arg0) {
if (null == arg0) {
Toast.makeText(ImageListActivity.this,
"Comment successful", Toast.LENGTH_SHORT).show();
adapter.notifyDataSetChanged();
} else {
Toast.makeText(ImageListActivity.this,
"Save Comment2Image failed", Toast.LENGTH_SHORT).show();
}
}
});
}
}
});
public void like(Image image, String username) {
image.addLiker(AVUser.getCurrentUser());
adapter.notifyDataSetChanged();
}
public void unlike(Image image, String username) {
image.removeLiker(AVUser.getCurrentUser());
adapter.notifyDataSetChanged();
}
其次,我们如何获取这些数据?要获取到图片的信息,很简单,调用 Query 接口就可以了,类似于:
AVQuery query = new AVQuery("Image");
query.orderByDescending("createAt");
query.findInBackground(new FindCallback() {
public void done(List avObjects, AVException e) {
if (null == avObjects || null != e) {
return;
}
adapter.notifyDataSetChanged();
}
}); 但是这里有一个很麻烦的问题:返回的结果数据中,并没有publisher、comments、点赞者的信息,因为他们在 AVOS Cloud 的后台中都是 pointer 类型,系统并不会自动做级联查询并把结果填充完整,我们在显示图片流的时候,要能够如 Instagram 一般显示出所有信息,该怎么办?
一种办法是对结果中的每一个 item,再做一次查询,获取到第二级的对象实体信息;第二种办法是通过云代码来做数据填充,以返回完整的结果。但是这都比较麻烦。AVOS Cloud 为了支持这种需求,Query 接口是提供级联属性自动填充的选项的。我们在查询的时候,设置 include 属性,如下面所示:
AVQuery query = new AVQuery("Image");
query.orderByDescending("createAt");
query.include("publisher");
query.include("rawFile");
query.include("comments");
query.include("likes");
query.findInBackground(new FindCallback() {
public void done(List avObjects, AVException e) {
if (null == avObjects || null != e) {
return;
}
adapter.notifyDataSetChanged();
}
}); 那么,我们得到的结果中就包含完整的对象了。
这种级联数据获取对单个 object 或者 object 数组都是有效的,但是这里还有一个问题:AVRelation 无法自动填充,并且关联的 Comment 中的 creator 属性也不能自动填充!
要是我们在展示图片的时候,要能够显示若干条评论和部分点赞的用户,那么目前为止,数据依然是不完备的,怎么办?可能你已经想到了,我们可以在 Query 的回调函数里面,去再次 fetch 我们需要的第三级数据:
// 先给Comment加一个fetchCreator方法
public class Comment extends AVObject{
...
public void fetchCreator() {
AVUser usr = getAVUser("creator");
if (null == usr.getCreatedAt()) {
try {
usr.fetchInBackground(null);
} catch (Exception ex) {
Log.e("CMT", "failed to fetch user info. cause:" + ex.getMessage());
}
}
}
...
}
// 在Query的回调函数中完成属性更新
AVQuery query = new AVQuery("Image");
query.orderByDescending("createAt");
query.include("publisher");
query.include("rawFile");
query.include("comments");
query.include("likes");
query.findInBackground(new FindCallback() {
public void done(List avObjects, AVException e) {
if (null == avObjects || null != e) {
return;
}
for (AVObject tmp : avObjects) {
final Image img = (Image)tmp;
AVRelation likers = img.getLiker();
likers.getQuery().findInBackground(new FindCallback() {
public void done(List results, AVException e) {
if (e == null) {
// results have all the Posts the current user liked.
img.setLikedUsers(results);
}
}
});
List comments = img.getComments();
if (null != comments) {
for (Comment cmt: comments) {
cmt.fetchCreator();
}
}
instagramImageList.add(img);
}
adapter.notifyDataSetChanged();
}
}); 好了,到这里,我们终于获得了可以展示的完整结果数据了!