Android ORM 框架 greenDao 3.x 第一篇: 介紹,使用與踩坑

最近接觸greenDao3.x,總結(jié)一些業(yè)務(wù)中用到的東西和坑。
greenDao 到3.x之后用法比2.x和1.x時(shí)候更加簡(jiǎn)單,通過(guò)對(duì)bean類增加注解實(shí)現(xiàn)持續(xù)化。
許多文章介紹了大致情況,如 http://www.itdecent.cn/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)。(見(jiàn)http://greenrobot.org/greendao/documentation/updating-to-greendao-3-and-annotations/

  1. greenDao3.x 原理簡(jiǎn)介:
    greenDao3.x引入了新的Gradle plugin:greendao-gradle-plugin 。用Gradle插件來(lái)生成所需要的類。greenrobot目前還沒(méi)把gradle插件源碼還沒(méi)放出來(lái)??梢砸?jiàn)所有注解都是@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ì)理解源碼比較有幫助。

  1. 相關(guān)注解的實(shí)際用法
    (1) Entity:該注解是給bean類的。注意了,greenDao目前只支持一個(gè)Entity對(duì)應(yīng)一張表(見(jiàn)https://github.com/greenrobot/greenDAO/issues/455#issuecomment-249790184)。
    一對(duì)多或者多對(duì)一目前還實(shí)現(xiàn)不了。如果有類似需求可以用ToOne 和 ToMany 代替(下文會(huì)介紹)。
    Entity的屬性:
    indexes:可以用來(lái)創(chuàng)建該表的索引??梢耘cunique一同使用實(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í)沒(méi)創(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)簽一起添加索引,見(jiàn)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);
  1. 數(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í)體的,可見(jiàn)源碼:

    /** 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類的代碼可見(jiàn) 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();
    }
  1. 相關(guān)的坑:
    (1)不支持Entity繼承: Entity類在生成表和Dao類時(shí)候,是不支持其父類的相關(guān)屬性的,即父類中的相關(guān)域不會(huì)在表中有對(duì)應(yīng)字段。這就導(dǎo)致了所有的model類都不能繼承,導(dǎo)致有些model類比較類似,也都需要都寫(xiě)兩遍,破壞了代碼結(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的功能還沒(méi)完善。所以如果工程需要跨進(jìn)程提供數(shù)據(jù)則還需要手動(dòng)寫(xiě)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í)/加密等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • GreenDao 介紹:greenDAO是一個(gè)對(duì)象關(guān)系映射(ORM)的框架,能夠提供一個(gè)接口通過(guò)操作對(duì)象的方式去操...
    小董666閱讀 844評(píng)論 0 1
  • 一、關(guān)于greenDAO greenDAO應(yīng)該算是當(dāng)前最火的數(shù)據(jù)庫(kù)開(kāi)源框架了,它是一個(gè)將對(duì)象映射到SQLite數(shù)據(jù)...
    當(dāng)幸福來(lái)敲門58閱讀 14,034評(píng)論 3 19
  • CSDN同步更新:http://blog.csdn.net/bskfnvjtlyzmv867/article/de...
    Mr丶sorrow閱讀 1,872評(píng)論 1 0
  • (一)GreenDao簡(jiǎn)介 GreenDao是一個(gè)對(duì)象關(guān)系映射(ORM)的開(kāi)源框架,目前最主流的安卓數(shù)據(jù)庫(kù)操作框架...
    miss2008閱讀 5,547評(píng)論 4 18
  • 前言 我相信,在平時(shí)的開(kāi)發(fā)過(guò)程中,大家一定會(huì)或多或少地接觸到SQLite。然而在使用它時(shí),我們往往需要做許多額外的...
    勤奮的pangdunhu閱讀 2,148評(píng)論 1 11

友情鏈接更多精彩內(nèi)容