最近接触greenDao3.x,总结一些业务中用到的东西和坑。
greenDao 到3.x之后用法比2.x和1.x时候更加简单,通过对bean类增加注解实现持续化。
许多文章介绍了大致情况,如 http://www.jianshu.com/p/f2737d23cb2a
如何上手和了解大致情况也可以阅读greenDao的官方文档 http://greenrobot.org/greendao/
其他文章介绍的东西就不再重复了,本文总结一下实际应用中用到的一些特性和遇到的坑。
除了注解方式以外,实际上,3.X仍然支持像2.x中的generator的方式来生成相关类。并且generator支持更多新特性,如多schema,生成ContentProvider等,这些功能还不支持通过注解来实现。(见http://greenrobot.org/greendao/documentation/updating-to-greendao-3-and-annotations/)
- greenDao3.x 原理简介:
greenDao3.x引入了新的Gradle plugin:greendao-gradle-plugin 。用Gradle插件来生成所需要的类。greenrobot目前还没把gradle插件源码还没放出来。可以见所有注解都是@Retention(RetentionPolicy.SOURCE)。gradle plugin就是根据这些注解,为实体生成对应的各种类。
主要的类包括:
(1) DaoMaster :Dao的顶层对象,包含DevOpenHelper内部类,并可以新建DaoSession。获取
(2) DaoSession :管理各个Dao类,用Map管理了各个Dao类的实例对象(key是Dao的类对象,value是实例对象)。
(3) xxxDao:与Entity一一对应,管理对应的表,完成从对象到表的增删改查。我们实际对实例对象的操作都是通过该Entity对应的Dao对象完成的。
Database/DaoMaster/DaoSession对象的初始化过长如下:
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(application,
DATABASE_NAME, null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
mDaoSession = daoMaster.newSession();
此外如果阅读源码还有xxxDaoConfig类。但此类为GreenDao的内部所用,实际不会应用到, 但对理解源码比较有帮助。
- 相关注解的实际用法
(1) Entity:该注解是给bean类的。注意了,greenDao目前只支持一个Entity对应一张表(见https://github.com/greenrobot/greenDAO/issues/455#issuecomment-249790184)。
一对多或者多对一目前还实现不了。如果有类似需求可以用ToOne 和 ToMany 代替(下文会介绍)。
Entity的属性:
indexes:可以用来创建该表的索引。可以与unique一同使用实现唯一索引,如:
@Entity(indexes = {
@Index(value = "primaryCode, secondCode", unique = true)
})
nameInDb:自定义Entity对应的表名(默认是用类名)
createInDb:是否在数据库中创建该表,源码的注释这么说的:
/**
* Advanced flag to disable table creation in the database (when set to false). This can be used to create partial
* entities, which may use only a sub set of properties. Be aware however that greenDAO does not sync multiple
* entities, e.g. in caches.
*/
大意是在只需持久化某Entity类中的一部分属性时使用,不是特别理解,实操确实没创建该类的表。但貌似是不能实现多个实体对应到一张表里。希望有高人能解答该属性有何用。
(2).Id:被修饰的域会成为表中的主键,该Id对应了xxxDao类中相关操作中的Key。
(3).Convert:修饰域,如果Entity中的一个域的类型不能直接保存(比如枚举类型)或者该域的值与您想保存到数据库中的类型不一样的话,可以用Convert修改该域实际保存的类型。如下将Convert讲时间(String)转成了Long来保存。
public class TimeConverter implements PropertyConverter {
private static final String TAG = "TimeConverter";
static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public String convertToEntityProperty(Long databaseValue) {
Log.d(TAG, "convertToEntityProperty:"+formatter.format(new Date(databaseValue)));
return formatter.format(new Date(databaseValue));
}
@Override
public Long convertToDatabaseValue(String entityProperty) {
try {
Log.d(TAG, "convertToDatabaseValue:"+formatter.parse(entityProperty).getTime());
return formatter.parse(entityProperty).getTime();
} catch (ParseException e) {
Log.e(TAG, e.getMessage());
}
return null;
}
}
(4).Index:与Indexs标签一起添加索引,见indexes标签。
(5).ToOne/ToMany:Entity的某个域是Entity,对应数据库的表连接。ToOne为一对一,ToMany为该域是List,对应多个实体。
要用joinProperty属性指明连接的key,该域对应被连接的Entity的Id。
例:
@ToOne(joinProperty = "amountId")
private PaymentAmount amount;
private long amountId;
注意:这里保存和读取时候都有坑:
当存一个Entity的时候,与其ToOne和ToMany的Entity都不会被保存,需要单独再去存每个对象,并且要在设置amountId :如:
mPaymentAmountDao.insert(paymentAmount);
record.setAmountId(record.getAmount().getId());
mPaymentRecordDao.insert(record);
- 数据的增删改查:与Entity对应的Dao类提供了对应每个Entity的相关方法。这里列举一些常 用的:
public void save(T entity)
public void delete(T entity)
public void update(T entity)
public long insertOrReplace(T entity)
public void deleteAll()
public Long getKey(PaymentRecord entity)
public boolean hasKey(PaymentRecord entity)
public List loadAll()
public PaymentRecord loadDeep(Long key)
public List queryDeep(String where, String... selectionArg)
public QueryBuilder queryBuilder()
注意
(1) 如果该Entity有ToMany或者ToOne字段,该Dao类中会有xxxDeep的方法:该方法会一起获取ToMany和ToOne字段,也就是其ToMany和ToOne字段不为空(查询过程中会join相关的表),如果用其他方法查询该对象的ToMany和ToOne字段是null。
其中,queryDeep中的where和selectionArg直接传给数据库做查询,所以需要为数据库的相关字段名而非实体的,可见源码:
/** A raw-style query where you can pass any WHERE clause and arguments. */
public List queryDeep(String where, String... selectionArg) {
Cursor cursor = db.rawQuery(getSelectDeep() + where, selectionArg);
return loadDeepAllAndCloseCursor(cursor);
}
(2) queryBuilder
这与SQLiteQueryBuilder不同,queryBuilder方法是直接对应的Entity的字段的,而非数据库。如:
List list = mDayRecordDao.queryBuilder()
.where(DayRecordDao.Properties.CurrencyCode.eq(currencyCode))
.where(DayRecordDao.Properties.Day.eq(day)).list();
(3) 这里查询是有缓存的:比如:
Long id = record1.getId();//record1已保存过
Record record2 = mRecordDao.loadDeep(id);
boolean isSameObj = (record1 == record2) // true
如果record1被修改,还想读原来数据库中其值,请在loadDeep前调用:daoSession.clear()。
自动生成的xxxDao类的代码可见 dentityScope为缓存,
public abstract class AbstractDao {
...
protected final IdentityScope identityScope;
...
public AbstractDao(DaoConfig config, AbstractDaoSession daoSession) {
...
identityScope = (IdentityScope) config.getIdentityScope();
...
}
}
daoSession.clear():
public void clear() {
xxxDaoConfig.clearIdentityScope();
}
- 相关的坑:
(1)不支持Entity继承: Entity类在生成表和Dao类时候,是不支持其父类的相关属性的,即父类中的相关域不会在表中有对应字段。这就导致了所有的model类都不能继承,导致有些model类比较类似,也都需要都写两遍,破坏了代码结构。。蛋疼
(2)不支持一个Entity对应多表,或多个Entity对应成一个表。蛋疼如上
(3)不支持一个字段convert成多字段,智能convert成一个其他字段。蛋疼如上
(4)注解方式不支持provider生成:不能自动生成provider。generator方式可以,但provider的功能还没完善。所以如果工程需要跨进程提供数据则还需要手动写provider。
总结:整体来讲,GreenDao 作为对象持久化框架还是比较好用,但不足也比较明显:Entity的灵活性不足,有时Bean类的一些由于业务上的设计并不适合直接转成数据库表存储,而Entity映射到数据库表的方法灵活性不高,导致表结构和Bean类的结构要互相迁就。
接下来后续还会再去总结GreenDao的数据库升级/加密等。