本文目录:
GreenDao 是一个很成熟的 ORM 框架 ,帮助我们不用去写繁琐的 SQL 去实现 CURD,还有数据库的升级等繁琐的操作都被模板化,让我们更加关注于业务实现上。
在平时的使用中,多表关联会经常遇到。
多表关联主要有这几种:
@ToOne
@ToMany
如果有多表关联,在被关联的实体的获取的地方,GreenDao 模板会主动帮我们加入数据库的联表查询,增强了 Getter 操作。
以一对一关联举例。假设我们现在有一个实体 MediaBean,里面的 UserBean 属于另外一个表的。使用 GreenDao 的注解,可以帮助我们去实现这个关联,并且在获取 UserBean 的地方加入数据操作。
我们这样创建 MediaBean,用注解 @ToOne
去设置一对一关联:
@Entity
public class MediaBean extends BaseBean {
@Id
private Long id;
...
@ToOne(joinProperty = "uid")
private UserBean user;
...
}
在 Gradle 编译结束后,可以看到获取 UserBean 的方法 getUser,被 GreenDao 增强,加入数据库多表查询操作。
增强的关联查询操作如下:
public UserBean getUser() {
Long __key = this.uid;
if (user__resolvedKey == null || !user__resolvedKey.equals(__key)) {
final DaoSession daoSession = this.daoSession;
if (daoSession == null) {
return user;
}
UserBeanDao targetDao = daoSession.getUserBeanDao();
UserBean userNew = targetDao.load(__key);
synchronized (this) {
user = userNew;
user__resolvedKey = __key;
}
}
return user;
}
这个地方我们可以体会到,经过了 GreenDao 增强操作后,MediaBean 的 Getter 方法已经带上业务了。好处是强一致性,每次读取 UserBean 一定会从数据库中获取最新的。但是和我们的设计有冲突。因为一开始 MediaBean 的设计,只是个数据的载体,一个纯粹的 Bean。经过 GreenDao 增强后,不再纯粹了。
这里隐藏着两个比较严重的问题:
这里提出一个解决方案,是比较粗暴的方案, 那就是不使用 GreenDao 的多表关联的特性。
因为我们需要更纯粹的 JavaBean,更干净的 Setter 和 Getter,不再里面带上数据库操作的业务。
那什么地方取做关联查询呢?
我们自己创建一个 MediaDBHelper,进行 MediaBean 的增删改查的具体实现,把对其他表的关联查询封装起来。本质上,是把 GreenDao 原本在 Getter 做的关联查询转移一个工具类中处理。这样子,多表关联的业务,就由我们自己去接管,自己决定什么时候去做,做到对数据库操作的最大的灵活度。
整个流程如下
@Transient
注解,标记该字段不入库。public class MediaDBHelper {
...
public void addMediaBean(MediaBean mediaBean) {
MediaBeanDao mediaBeanDao = getMediaBeanDao();
UserBean userBean = mediaBean.getUser();
if (userBean != null) {
getUserBeanDao.insertOrReplace(userBean);
}
mediaBeanDao.insertOrReplace(mediaBean);
}
public MediaBean getMediaBean(long mid) {
MediaBean mediaBean = getMediaBeanDao().load(mid);
// 补充用户信息
long userId = mediaBean.getUserId();
UserBean userBean = getUserBeanDao().load(userId);
if (userBean != null) {
mediaBean.setUser(userBean);
}
return mediaBean;
}
...
}
有个关键的地方,就是外键 userId 什么时候注入 MediaBean。
疑问 userId 是客户端自己创建的字段,服务端接口并没有返回。我们可以在解析完服务端数据后,主动去设置该 userId。如果使用了 Gson 库,可以使用 JsonDeserializer 来做。
UserBean user = mediaBean.getUser();
if (user != null) {
mediaBean.setUserId(user.getId);
}
总之,从服务端取完数据后,需要主动去补充一些信息,比如它的 user 字段的外键。
ORM 框架帮我们减少了一些体力活,但是因为高度封装的框架,在灵活性会有一定的损失。
我理解 GreenDao 把关联查询放到 Getter 中,是为了数据的强一致性,确保每次读到的都是从数据库中拿出来的最新数据。但实际开发中,这样子反而增加了数据库 IO,移动平台资源本来就紧张,这样会增加程序卡顿的风险。
我的方案有一个缺点,那就是数据一致性问题。如果获取了数据,期间应用其他地方修改了数据,就会出现不一致。按 GreenDao 的设计,数据修改和读取都会经过数据库,所以不会有一致性问题。但我舍弃了它的多表关联,在一次读取之后,基本都是内存操作,所以一致性的问题得另外解决。
这里可以配合其他方案解决,比如消息总线的通知。
具体的做法在数据修改的地方,都会通过消息总线(比如 EventBus)发出通知数据,把已经载入内存中,其他地方还在用的脏数据全部更新。
通过这样的方式,既减少数据库 IO,又保证了数据一致性。
总结,不推荐使用多表关联,然后替代方案可以归纳为三: