最近接觸greenDao3.x公般,總結(jié)一些業(yè)務(wù)中用到的東西和坑万搔。
greenDao 到3.x之后用法比2.x和1.x時(shí)候更加簡(jiǎn)單,通過(guò)對(duì)bean類增加注解實(shí)現(xiàn)持續(xù)化官帘。
許多文章介紹了大致情況瞬雹,如 http://www.reibang.com/p/f2737d23cb2a
如何上手和了解大致情況也可以閱讀greenDao的官方文檔 http://greenrobot.org/greendao/
其他文章介紹的東西就不再重復(fù)了,本文總結(jié)一下實(shí)際應(yīng)用中用到的一些特性和遇到的坑刽虹。
除了注解方式以外酗捌,實(shí)際上,3.X仍然支持像2.x中的generator的方式來(lái)生成相關(guān)類涌哲。并且generator支持更多新特性胖缤,如多schema,生成ContentProvider等阀圾,這些功能還不支持通過(guò)注解來(lái)實(shí)現(xiàn)哪廓。(見http://greenrobot.org/greendao/documentation/updating-to-greendao-3-and-annotations/)
- greenDao3.x 原理簡(jiǎn)介:
greenDao3.x引入了新的Gradle plugin:greendao-gradle-plugin 。用Gradle插件來(lái)生成所需要的類稍刀。greenrobot目前還沒把gradle插件源碼還沒放出來(lái)撩独〕ú埽可以見所有注解都是@Retention(RetentionPolicy.SOURCE)。gradle plugin就是根據(jù)這些注解综膀,為實(shí)體生成對(duì)應(yīng)的各種類澳迫。
主要的類包括:
(1) DaoMaster :Dao的頂層對(duì)象,包含DevOpenHelper內(nèi)部類剧劝,并可以新建DaoSession橄登。獲取
(2) DaoSession :管理各個(gè)Dao類,用Map管理了各個(gè)Dao類的實(shí)例對(duì)象(key是Dao的類對(duì)象讥此,value是實(shí)例對(duì)象)拢锹。
(3) xxxDao:與Entity一一對(duì)應(yīng),管理對(duì)應(yīng)的表萄喳,完成從對(duì)象到表的增刪改查卒稳。我們實(shí)際對(duì)實(shí)例對(duì)象的操作都是通過(guò)該Entity對(duì)應(yīng)的Dao對(duì)象完成的。
Database/DaoMaster/DaoSession對(duì)象的初始化過(guò)長(zhǎng)如下:
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(application,
DATABASE_NAME, null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
mDaoSession = daoMaster.newSession();
此外如果閱讀源碼還有xxxDaoConfig類他巨。但此類為GreenDao的內(nèi)部所用充坑,實(shí)際不會(huì)應(yīng)用到, 但對(duì)理解源碼比較有幫助染突。
- 相關(guān)注解的實(shí)際用法
(1) Entity:該注解是給bean類的捻爷。注意了,greenDao目前只支持一個(gè)Entity對(duì)應(yīng)一張表(見https://github.com/greenrobot/greenDAO/issues/455#issuecomment-249790184)份企。
一對(duì)多或者多對(duì)一目前還實(shí)現(xiàn)不了也榄。如果有類似需求可以用ToOne 和 ToMany 代替(下文會(huì)介紹)。
Entity的屬性:
indexes:可以用來(lái)創(chuàng)建該表的索引司志√鹱希可以與unique一同使用實(shí)現(xiàn)唯一索引,如:
@Entity(indexes = {
@Index(value = "primaryCode, secondCode", unique = true)
})
nameInDb:自定義Entity對(duì)應(yīng)的表名(默認(rèn)是用類名)
createInDb:是否在數(shù)據(jù)庫(kù)中創(chuàng)建該表俐芯,源碼的注釋這么說(shuō)的:
/**
* 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類中的一部分屬性時(shí)使用棵介,不是特別理解钉鸯,實(shí)操確實(shí)沒創(chuàng)建該類的表吧史。但貌似是不能實(shí)現(xiàn)多個(gè)實(shí)體對(duì)應(yīng)到一張表里。希望有高人能解答該屬性有何用唠雕。
(2).Id:被修飾的域會(huì)成為表中的主鍵贸营,該Id對(duì)應(yīng)了xxxDao類中相關(guān)操作中的Key。
(3).Convert:修飾域岩睁,如果Entity中的一個(gè)域的類型不能直接保存(比如枚舉類型)或者該域的值與您想保存到數(shù)據(jù)庫(kù)中的類型不一樣的話钞脂,可以用Convert修改該域?qū)嶋H保存的類型。如下將Convert講時(shí)間(String)轉(zhuǎn)成了Long來(lái)保存捕儒。
public class TimeConverter implements PropertyConverter<String, Long> {
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標(biāo)簽一起添加索引冰啃,見indexes標(biāo)簽邓夕。
(5).ToOne/ToMany:Entity的某個(gè)域是Entity,對(duì)應(yīng)數(shù)據(jù)庫(kù)的表連接阎毅。ToOne為一對(duì)一焚刚,ToMany為該域是List,對(duì)應(yīng)多個(gè)實(shí)體扇调。
要用joinProperty屬性指明連接的key矿咕,該域?qū)?yīng)被連接的Entity的Id。
例:
@ToOne(joinProperty = "amountId")
private PaymentAmount amount;
private long amountId;
注意:這里保存和讀取時(shí)候都有坑:
當(dāng)存一個(gè)Entity的時(shí)候狼钮,與其ToOne和ToMany的Entity都不會(huì)被保存碳柱,需要單獨(dú)再去存每個(gè)對(duì)象,并且要在設(shè)置amountId :如:
mPaymentAmountDao.insert(paymentAmount);
record.setAmountId(record.getAmount().getId());
mPaymentRecordDao.insert(record);
- 數(shù)據(jù)的增刪改查:與Entity對(duì)應(yīng)的Dao類提供了對(duì)應(yīng)每個(gè)Entity的相關(guān)方法熬芜。這里列舉一些常 用的:
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<T> loadAll()
public PaymentRecord loadDeep(Long key)
public List<PaymentRecord> queryDeep(String where, String... selectionArg)
public QueryBuilder<T> queryBuilder()
注意
(1) 如果該Entity有ToMany或者ToOne字段莲镣,該Dao類中會(huì)有xxxDeep的方法:該方法會(huì)一起獲取ToMany和ToOne字段,也就是其ToMany和ToOne字段不為空(查詢過(guò)程中會(huì)join相關(guān)的表)涎拉,如果用其他方法查詢?cè)搶?duì)象的ToMany和ToOne字段是null剥悟。
其中,queryDeep中的where和selectionArg直接傳給數(shù)據(jù)庫(kù)做查詢曼库,所以需要為數(shù)據(jù)庫(kù)的相關(guān)字段名而非實(shí)體的区岗,可見源碼:
/** A raw-style query where you can pass any WHERE clause and arguments. */
public List<PaymentRecord> queryDeep(String where, String... selectionArg) {
Cursor cursor = db.rawQuery(getSelectDeep() + where, selectionArg);
return loadDeepAllAndCloseCursor(cursor);
}
(2) queryBuilder
這與SQLiteQueryBuilder不同,queryBuilder方法是直接對(duì)應(yīng)的Entity的字段的毁枯,而非數(shù)據(jù)庫(kù)慈缔。如:
List<DayRecord> list = mDayRecordDao.queryBuilder()
.where(DayRecordDao.Properties.CurrencyCode.eq(currencyCode))
.where(DayRecordDao.Properties.Day.eq(day)).list();
(3) 這里查詢是有緩存的:比如:
Long id = record1.getId();//record1已保存過(guò)
Record record2 = mRecordDao.loadDeep(id);
boolean isSameObj = (record1 == record2) // true
如果record1被修改,還想讀原來(lái)數(shù)據(jù)庫(kù)中其值种玛,請(qǐng)?jiān)趌oadDeep前調(diào)用:daoSession.clear()藐鹤。
自動(dòng)生成的xxxDao類的代碼可見 dentityScope為緩存,
public abstract class AbstractDao<T, K> {
...
protected final IdentityScope<K, T> identityScope;
...
public AbstractDao(DaoConfig config, AbstractDaoSession daoSession) {
...
identityScope = (IdentityScope<K, T>) config.getIdentityScope();
...
}
}
daoSession.clear():
public void clear() {
xxxDaoConfig.clearIdentityScope();
}
- 相關(guān)的坑:
(1)不支持Entity繼承: Entity類在生成表和Dao類時(shí)候赂韵,是不支持其父類的相關(guān)屬性的娱节,即父類中的相關(guān)域不會(huì)在表中有對(duì)應(yīng)字段。這就導(dǎo)致了所有的model類都不能繼承祭示,導(dǎo)致有些model類比較類似肄满,也都需要都寫兩遍,破壞了代碼結(jié)構(gòu)质涛。稠歉。蛋疼
(2)不支持一個(gè)Entity對(duì)應(yīng)多表,或多個(gè)Entity對(duì)應(yīng)成一個(gè)表。蛋疼如上
(3)不支持一個(gè)字段convert成多字段汇陆,智能convert成一個(gè)其他字段怒炸。蛋疼如上
(4)注解方式不支持provider生成:不能自動(dòng)生成provider。generator方式可以毡代,但provider的功能還沒完善阅羹。所以如果工程需要跨進(jìn)程提供數(shù)據(jù)則還需要手動(dòng)寫provider勺疼。
總結(jié):整體來(lái)講,GreenDao 作為對(duì)象持久化框架還是比較好用捏鱼,但不足也比較明顯:Entity的靈活性不足恢口,有時(shí)Bean類的一些由于業(yè)務(wù)上的設(shè)計(jì)并不適合直接轉(zhuǎn)成數(shù)據(jù)庫(kù)表存儲(chǔ),而Entity映射到數(shù)據(jù)庫(kù)表的方法靈活性不高穷躁,導(dǎo)致表結(jié)構(gòu)和Bean類的結(jié)構(gòu)要互相遷就耕肩。
接下來(lái)后續(xù)還會(huì)再去總結(jié)GreenDao的數(shù)據(jù)庫(kù)升級(jí)/加密等。