ORM數據庫框架 greenDAO SQLite MD


Markdown版本筆記 我的GitHub首頁 我的博客 我的微信 我的郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

ORM數據庫框架 greenDAO SQLite MD


目錄

簡介

相關資源

特性與優點

  • greenDAO是一款輕巧快捷的Android版ORM,可將對象映射到SQLite數據庫。greenDAO針對Android進行了高度優化,性能卓越,占用內存極少。
  • GreenDao支持Protocol buffers協議數據的直接存儲,如果通過protobuf協議和服務器交互,不需要任何的映射。

Protocol Buffers協議:以一種高效可擴展的對結構化數據進行編碼的方式。google內部的RPC協議和文件格式大部分都是使用它。
RPC:Remote Procedure Call遠程過程調用,是一個計算機通信協議,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。

特性:

  • 堅如磐石[Rock solid]:greenDAO自2011年發布以來一直被無數著名的應用程序使用着
  • 超級簡單:簡潔直接[concise and straight-forward]的API,在V3版本中支持注解
  • 小:庫大小<150K,它只是簡單的Java jar包
  • 快速:可能是Android上最快的ORM,由智能代碼生成器驅動
  • 安全和富有表現力[expressive]的查詢API:QueryBuilder使用屬性常量來避免拼寫錯誤[typos]
  • 強大的 joins 連接:跨實體查詢[query across entities],甚至是復雜關系的鏈接
  • 靈活的屬性類型:使用自定義類或枚舉來表示實體中的數據
  • 加密:支持使用 SQLCipher 加密數據庫

優點:

  • 是 Android 上最流行、最高效、支持RxJava操作而且還在迭代的關系型數據庫
  • 存取速度快:每秒中可以操作數千個實體,速度是 OrmLite 的數倍
  • 支持數據庫加密:支持SQLite和SQLCipher(在SQLite基礎上加密型數據庫)
  • 輕量級:greenDao的代碼庫僅僅100k大小
  • 激活實體:處於激活狀態下的實體可以有更多操作方法
  • 支持緩存:能夠將使用過的實體存在內存緩存中,下次使用時可以直接從緩存中取,這樣可以使性能提高N個數量級
  • 代碼自動生成:greenDao 會根據modle類自動生成 DAO 類(DaoMaster、DaoSession 和 **DAO)

其他開源項目

greenrobot 其他流行的開源項目:

  • ObjectBox 是一個面向移動設備的新的超快速面向對象數據庫[object-oriented database]。是比SQLite更快的對象持久化方案[object persistence]。
  • EventBus 是為Android設計的中央發布/訂閱總線,具有可選的傳遞線程[delivery],優先級粘性事件[sticky]。這是一個非常好的工具,可以將組件 (e.g. Activities, Fragments, logic components)彼此分離。

官網介紹文檔

文檔地址

greenDA:適用於您 SQLite 數據庫的 Android ORM
注意:對於新的應用程序,我們建議使用 ObjectBox,這是一個新的面向對象的數據庫,它比 SQLite 快得多並且更易於使用。 對於基於 greenDAO 的現有應用程序,我們提供 DaoCompat 以實現輕松切換(另請參閱 announcement)。

greenDAO是一個開源的 Android ORM,使 SQLite 數據庫的開發再次變得有趣。它減輕了開發人員處理低級數據庫需求的同時節省了開發時間。 SQLite是一個很棒的嵌入式關系數據庫。盡管如此,編寫 SQL 和解析查詢結果仍然是一項非常繁瑣且耗時的任務。greenDAO 通過將 Java 對象映射到數據庫表(稱為ORM,“對象/關系映射”)將您從這些中解放出來。 這樣,您可以使用簡單的面向對象的API來存儲,更新,刪除和查詢Java對象。

greenDAO的特點一覽

  • 最高性能(可能是Android上最快的ORM); 我們的基准測試也是開源的
  • 易於使用的強大API,涵蓋關系和連接[relations and joins]
  • 最小的內存消耗
  • 較小的庫大小(<100KB)以保持較低的構建時間並避免65k方法限制
  • 數據庫加密:greenDAO支持SQLCipher,以確保用戶的數據安全
  • 強大的社區:超過5000個GitHub星表明有一個強大而活躍的社區

您想了解有關greenDAO功能的更多信息,例如活動實體[active entities],protocol buffers 支持或者預加載[eager loading]? 可以看看我們的完整功能列表。

如何開始使用greenDAO,文檔
有關greenDAO的第一步,請查看 documentation,尤其是 getting started guideintroduction tutorial

誰在使用greenDAO?
許多頂級Android應用都依賴於greenDAO。 其中一些應用程序的安裝量超過1000萬。 我們認為,這表明在行業中是可靠的。 在 AppBrain 上查看自己的當前統計數據。

greenDAO真的那么快嗎? 它是最快的Android ORM嗎?
我們相信它是。我們不是營銷人員,我們是開發人員。 我們經常做基准測試來優化性能,因為我們認為性能很重要。 我們希望提供最快的Android ORM。 雖然有些事情讓我們感到很自豪,但我們並不熱衷於營銷演講。 我們所有的基准測試都是開源的,可以在達到高標准的同時實現最大透明度。你可以自己檢查最新的基准測試結果並得出自己的結論。

Greendao 注解

此庫中注解的保留策略都是:@Retention(RetentionPolicy.SOURCE)

  • CLASS:在class文件中有效。編譯器將把注解記錄在class文件中,但在運行時 JVM 不需要保留注解。這是默認的行為。也就是說,默認行為是:當運行Java程序時,JVM不可獲取Annotation信息。
  • RUNTIME:在運行時有效。編譯器將把注解記錄在class文件中,在運行時 JVM 將保留注解,因此可以反射性地讀取。
  • SOURCE:只在源文件中有效, 編譯器要丟棄的注解

greenDAO嘗試使用合理的默認值,因此開發人員不必配置每一個屬性值。
例如,數據庫端的表名和列名是從實體名和屬性名派生的,而 不是 Java中使用的駝峰案例樣式,默認數據庫名稱是大寫的,使用下划線來分隔單詞。例如,名為 creationDate 的屬性將成為數據庫列CREATION_DATE。

實體注解 @Entity

@Entity 注解用於將 Java 類轉換為數據庫支持的實體。這也將指示 greenDAO 生成必要的代碼(例如DAO)。

注意:僅支持Java類。 如果你喜歡另一種語言,如Kotlin,你的實體類仍然必須是Java。

@Entity( //為 greendao 指明這是一個需要映射到數據庫的實體類,通常不需要任何額外的參數
        nameInDb = "AWESOME_USERS",// 指定該表在數據庫中的名稱,默認是基於實體類名
        indexes = {@Index(value = "name DESC", unique = true)},// 在此處定義跨越多列的索引
        createInDb = true,// 是否創建該表。默認為true。如果有多個實體映射到一個表,或者該表是在greenDAO外部創建的,則可置為false
        schema = "myschema",// 如果您有多個模式,則可以告訴greenDAO實體所屬的模式(選擇任何字符串作為名稱)
        active = true,// 標記一個實體處於活動狀態,活動實體(設置為true)有更新、刪除和刷新方法。默認為false
        generateConstructors = true,//是否生成所有屬性的構造器。注意:無參構造器總是會生成。默認為true
        generateGettersSetters = true//是否為屬性生成getter和setter方法。因為**Dao會用到這些方法,如果不自動生成,必須手動生成。默認為true
)

注意,當使用Gradle插件時,目前不支持多個模式。目前,繼續使用你的 generator 項目。

@Target(ElementType.TYPE)
public @interface Entity {
    String nameInDb() default ""; //指定此實體映射到的DB側的名稱(例如,表名)。 默認情況下,名稱基於實體類名稱。
    Index[] indexes() default {}; //注意:要創建單列索引,請考慮在屬性本身上使用 Index
    boolean createInDb() default true; //高級標志,設置為false時可禁用數據庫中的表創建。這可以用於創建部分實體,其可能僅使用子屬性的子集。但請注意,greenDAO不會同步多個實體,例如在緩存中。
    String schema() default "default"; //指定實體的模式名稱:greenDAO可以為每個模式生成獨立的類集。屬於不同模式的實體應不具有關系。
    boolean active() default false; //是否應生成更新/刪除/刷新方法。如果實體已定義 ToMany 或 ToOne 關系,那么它 active 獨立於此值
    boolean generateConstructors() default true; //是否應生成一個具有所有屬性構造函數。一個 no-args 的構造函數總是需要的。
    boolean generateGettersSetters() default true; //如果缺少,是否應生成屬性的getter和setter。
    Class protobuf() default void.class; //定義此實體的protobuf類,以便為其創建額外的特殊DAO。
}

添加 active = true 時會自動生成以下代碼:

@Generated(hash = 2040040024) private transient DaoSession daoSession; //Used to resolve relations
@Generated(hash = 363862535)  private transient NoteDao myDao; //Used for active entity operations.
​
//被內部機制所調用,自己不要調用
@Generated(hash = 799086675)
public void __setDaoSession(DaoSession daoSession) {
    this.daoSession = daoSession;
    myDao = daoSession != null ? daoSession.getNoteDao() : null;
}
​
//方便調用 org.greenrobot.greendao.AbstractDao 的 delete(Object) 方法。實體必須附加到實體上下文。
@Generated(hash = 128553479)
public void delete() {
    if (myDao == null) throw new DaoException("Entity is detached from DAO context");
    myDao.delete(this);
}
​
//方便調用 org.greenrobot.greendao.AbstractDao 的 refresh(Object) 方法。實體必須附加到實體上下文。
@Generated(hash = 1942392019)
public void refresh() {
    if (myDao == null) throw new DaoException("Entity is detached from DAO context");
    myDao.refresh(this);
}
​
//方便調用 org.greenrobot.greendao.AbstractDao 的 update(Object) 方法。實體必須附加到實體上下文。
@Generated(hash = 713229351)
public void update() {
    if (myDao == null)  throw new DaoException("Entity is detached from DAO context");
    myDao.update(this);
}

設置 generateConstructors = false 時,如果沒有無參的構造方法,則會生成一個普通的無參構造方法(如果已存在此無參構造方法,則不會生成):

public Note() {}

添加 generateConstructors = true 時,如果沒有無參的構造方法,則會生成一個帶 @Generated 注解的無參構造方法(如果已存在此無參構造方法,則不會生成):

@Generated(hash = 1272611929)
public Note() {
}

並且會生成一個帶 @Generated 注解的具有所有屬性的構造方法(如果已存在此具有所有屬性的構造方法,會編譯時會報錯,提示你 Can't replace constructor ):

@Generated(hash = 2139673067)
public Note(Long id, @NotNull String text, Date date, NoteType type) {
    this.id = id;
    this.text = text;
    this.date = date;
    this.type = type;
}

屬性注解

@Id 主鍵

@Id 注解選擇long/Long屬性作為實體ID,在數據庫術語中,它是主鍵[primary key]。可以通過 autoincrement = true 設置自增長(不重用舊值)

@Target(ElementType.FIELD)
public @interface Id {
    boolean autoincrement() default false; //指定id應自增長(僅適用於Long/long字段)。SQLite上的自增長會引入額外的資源使用,通常可以避免
}

官方文檔對主鍵的說明:

目前,實體必須使用long或Long屬性作為其主鍵。這是Android和SQLite的推薦做法。
要解決此問題[To work around this],請將你的鍵屬性定義為其他屬性[additional property],但為其創建唯一索引[create a unique index for it]:

@Id private Long id;
@Index(unique = true) private String key;

@Property 字段名

@Property(nameInDb = "") 為該屬性在數據庫中映射的字段名設置一個非默認的名稱。默認是將單詞大寫,用下划線分割單詞,如屬性名 customName 對應列名 CUSTOM_NAME

//可選:為持久字段配置映射列。 此注釋也適用於@ToOne,無需額外的外鍵屬性
@Target(ElementType.FIELD)
public @interface Property {
    String nameInDb() default ""; //此屬性的數據庫列的名稱。 默認為字段名稱。
}
@Property(nameInDb = "USERNAME") private String name;

@NotNull 非空

@NotNull 表明這個列非空,通常使用 @NotNull 標記基本類型,然而可使用包裝類型(Long, Integer, Short, Byte)使其可空

//您還可以使用任何其他NotNull或NonNull注釋(來自任何庫或您自己的),它們是等價的
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface NotNull { }
@NotNull private int age;
@NotNull private String name; //在保存到數據庫中之前需要保證此值不為null,否則會報 IllegalArgumentException

@Transient 忽略

@Transient 表明此字段不存儲到數據庫中,用於不需要持久化的字段,比如臨時狀態

@Target(ElementType.FIELD)
public @interface Transient { }
@Transient private int tempUsageCount;

索引注解

@Index 索引

@Index 為相應的數據庫列[column]創建數據庫索引[index]。如果不想使用 greenDAO 為該屬性生成的默認索引名稱,可通過name設置;向索引添加UNIQUE約束,強制所有值是唯一的。

//可以用來:1、指定應對屬性編制索引;2、通過 Entity 的 indexes() 定義多列索引
@Target(ElementType.FIELD)
public @interface Index {
    String value() default ""; //以逗號分隔的應該 be indexed 的屬性列表,例如 “propertyA,propertyB,propertyC”。要指定順序,請在列名后添加 ASC 或 DESC,例如:“propertyA DESC,propertyB ASC”。只有在 Entity 的 indexes() 中使用此注解時才應設置此項
    String name() default ""; //索引的可選名稱。如果省略,則由 greenDAO 自動生成基於屬性列名稱
    boolean unique() default false; //是否應該基於此索引創建唯一約束
}
@Index(name = "indexNo", unique = true) private String name;

@Unique 唯一

@Unique 為相應列添加唯一約束。注意,SQLite會隱式地為該列創建索引

//在表創建期間標記屬性應具有UNIQUE約束。此注釋也適用於 @ToOne,無需額外的外鍵屬性。要在創建表后擁有唯一約束,可以使用 Index 的 unique()。注意同時擁有 @Unique 和 Index 是多余的,會導致性能降低
@Target(ElementType.FIELD)
public @interface Unique { }
@Unique private String name;

關系注解(略)

  • @ToOne 一對一
  • @ToMany 一對多
  • @JoinEntity 多對多
  • @JoinEntity 多對多
  • @OrderBy 指定 ToMany 關系的相關集合的順序

@Generated 自動生成

@Generated 用以標記由 greenDAO 自動生成的字段、構造函數或方法。

greenDAO中的實體類由開發人員創建和編輯。然而,在代碼生成過程中,greenDAO可能會增加實體的源代碼。greenDAO將為它創建的方法和字段添加一個@Generated注解,以通知開發人員並防止任何代碼丟失。在大多數情況下,你不必關心使用@Generated注解的代碼。

作為預防措施,greenDAO不會覆蓋現有代碼,並且如果手動更改生成的代碼會引發錯誤:

Constructor (see Note:34) has been changed after generation.
Please either mark it with @Keep annotation instead of @Generated to keep it untouched[保持不變], or use @Generated (without hash) to allow to replace it[允許替換].

正如錯誤消息所提示的,通常有兩種方法來解決此問題:

  • 還原對使用@Generated注解的代碼的更改。或者,你也可以完全刪除更改的構造函數或方法,它們將在下一次build時重新生成。
  • 使用@Keep注解替換@Generated注解。這將告訴greenDAO永遠不要觸摸[touch]帶注解的代碼。請記住,你的更改可能會中斷實體和其他greenDAO之間的約定[break the contract]。 此外,未來的greenDAO版本可能期望生成的方法中有不同的代碼。所以,要謹慎!采用單元測試的方法來避免麻煩是個不錯的選擇。
//All the code elements that are marked with this annotation can be changed/removed during next run of generation in respect of[就...而言] model changes
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface Generated {
    int hash() default -1;
}

@Keep 保留

@Keep 指定在下次運行 greenDAO 生成期間應保留的目標。

在 Entity 類上使用此批注會以靜默方式禁用任何類修改[itself silently disables any class modification]。由用戶負責編寫和支持 greenDAO 所需的任何代碼。如果您不完全確定自己在做什么,請不要在類成員上使用此注釋,因為在模型更改的情況下,greenDAO 將無法對目標代碼進行相應的更改。

@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE})
public @interface Keep { }

作用:注解代碼段以禁止 greenDAO 修改注解的代碼段,注解實體類以禁止 greenDAO 修改此類。

注意:不再支持舊版本的greenDAO中使用的KEEP。
KEEP sections used in older versions of greenDAO are no longer supported。

對 Generated 和 Keep 的理解

@Generated 注解的代碼一般是 greenDAO 需要的但是之前不存在的代碼,所以在 build 時 greenDAO 自動生成了這些代碼。

比如,示例代碼中會自動生成以下一個 @Generated 注解的構造方法:

@Generated(hash = 1272611929)
public Note() {
}

此時,如果更改此方法為:

@Generated(hash = 1272611929)
public Note() {
    System.out.println("需要自定義此構造方法");
}

則在 build 時會報錯,按照提示,可以改為:

@Generated()
public Note() {
    System.out.println("需要自定義此構造方法");
}

但實際上,這樣的修改並沒有任何意義,因為下次 build 時會刪掉你添加的所有代碼,自動會恢復原樣(和整個刪掉這些代碼的效果相同)。

PS:恢復的注解中的 hash 值和之前也是完全一樣的,因為其實 hash 是根據生成的代碼計算后得來的,改動此塊代碼就會導致 hash 值不符,相同代碼的 hash 值也一樣。

或者使用 @Keep 代替 @Generated,這將告訴 greenDao 不會修改帶有改注解的代碼:

@Keep
public Note() {
    System.out.println("需要自定義此構造方法");
}

這樣,下次 build 時此代碼就會保持你自定義的樣子。但是這可能會破壞 entities 類和 greenDAO 的其他部分的連接(因為你修改了 greenDAO 需要的邏輯),並且文檔中也明確說明了,一般情況下都不建議使用 @Keep

其實,我感覺添加 @Keep 的效果和去掉所有注解的效果可能是一樣的,比如改成這樣:

public Note() {
    System.out.println("需要自定義此構造方法");
}

但是我感覺可能在這種情況下,greenDAO 仍然可以在需要的時候自動修改此方法(也即不會像添加 @Keep 那樣可能會破壞 greenDAO 的邏輯),因為按照文檔的意思,只有添加 @Keep 才會阻止 greenDAO 自動修改代碼。

但如果是這樣的話,為何錯誤提示沒有說明呢?

@Convert 自定義類型

自定義類型

public @interface Convert {
    Class<? extends PropertyConverter> converter();
    Class columnType(); //可以在DB中保留的列的類。這僅限於greenDAO原生支持的所有java類。
}
@Convert(converter = NoteTypeConverter.class, columnType = String.class) //類型轉換類,自定義的類型在數據庫中存儲的類型
private NoteType type; //在保存到數據庫時會將自定義的類型 NoteType 通過 NoteTypeConverter 轉換為數據庫支持的 String 類型。反之亦然

使用教程

greenDAO是Android的對象/關系映射(ORM)工具。 它為關系數據庫SQLite提供了一個面向對象的接口。像greenDAO一類的ORM工具為你做很多重復性的任務,提供簡單的數據接口。

核心類簡介

mark

一旦項目構建完畢,你就可以在Android項目中開始使用greenDAO了。
以下核心類是greenDAO的基本接口:

  • DaoMaster:是 GreenDao 的入口,DaoMaster保存數據庫對象(SQLiteDatabase)並管理特定模式[specific schema]的DAO類。它有靜態方法來創建表或刪除表。它的內部類OpenHelper和DevOpenHelper都是SQLiteOpenHelper的實現,用來在SQLite數據庫中創建 schema。
  • DaoSession:管理特定schema的所有可用DAO對象,你可以使用其中一個的getter方法獲取DAO對象。DaoSession還為實體提供了一些通用的持久性[persistence]方法,如插入,加載,更新,刷新和刪除。 最后,DaoSession對象也跟蹤[keeps track of] identity scope。
  • DAOs:數據訪問對象[Data access objects],用於實體的持久化和查詢。 對於每個實體,greenDAO會生成一個DAO。 它比DaoSession擁有更多的持久化方法,例如:count,loadAll和insertInTx。
  • Entities:可持久化對象[Persistable objects]。 通常,實體是使用標准Java屬性(如POJO或JavaBean)表示數據庫行的對象。

核心初始化

最后,以下代碼示例說明了初始化數據庫和核心greenDAO類的第一步:

//下面代碼僅僅需要執行一次,一般會放在application
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db");
Database db = helper.getWritableDb();
DaoMaster daoMaster = new DaoMaster(db);
DaoSession daoSession = daoMaster.newSession();
NoteDao noteDao = daoSession.getNoteDao();//一般在activity或者fragment中獲取Dao對象

該示例假設存在一個Note實體。 通過使用它的DAO,我們可以調用這個特定實體的持久化操作。

完成以上所有工作以后,我們的數據庫就已經自動生成了,接下來就可以對數據庫進行操作了。

增刪改

PS:API 中的 PKPrimaryKey 的意思,也就是主鍵的意思。

delete

// 數據刪除相關 
void    delete(T entity)  // 從數據庫中刪除給定的實體 
void    deleteAll()   // 刪除數據庫中全部數據 
void    deleteByKey(K key)  // 從數據庫中刪除給定Key所對應的實體 
void    deleteByKeyInTx(java.lang.Iterable<K> keys)  // 使用事務操作刪除數據庫中給定的所有key所對應的實體 
void    deleteByKeyInTx(K... keys)  // 使用事務操作刪除數據庫中給定的所有key所對應的實體 
void    deleteInTx(java.lang.Iterable<T> entities)  // 使用事務操作刪除數據庫中給定實體集合中的實體 
void    deleteInTx(T... entities)  // 使用事務操作刪除數據庫中給定的實體 

insert

// 數據插入相關 
long    insert(T entity)  // 將給定的實體插入數據庫 
void    insertInTx(java.lang.Iterable<T> entities)  // 使用事務操作,將給定的實體集合插入數據庫 
void    insertInTx(java.lang.Iterable<T> entities, boolean setPrimaryKey)  // 使用事務操作將給定的實體集合插入數據庫,並設置是否設定主鍵 
void    insertInTx(T... entities)  // 將給定的實體插入數據庫 
long    insertOrReplace(T entity)  // 將給定的實體插入數據庫,若此實體類存在,則覆蓋 
void    insertOrReplaceInTx(java.lang.Iterable<T> entities)  // 使用事務操作,將給定的實體插入數據庫,若此實體類存在,則覆蓋 
void    insertOrReplaceInTx(java.lang.Iterable<T> entities, boolean setPrimaryKey)  // 插入數據庫,若此存在則覆蓋,並設置是否設定主鍵 
void    insertOrReplaceInTx(T... entities)  // 使用事務操作,將給定的實體插入數據庫,若此實體類存在,則覆蓋 
long    insertWithoutSettingPk(T entity)  // 將給定的實體插入數據庫,但不設定主鍵

save

// 新增數據插入相關API 
void    save(T entity)  // 將給定的實體插入數據庫,若此實體類存在,則更新 
void    saveInTx(java.lang.Iterable<T> entities) // 使用事務操作,將給定的實體插入數據庫,若此實體類存在,則更新 
void    saveInTx(T... entities)  // 使用事務操作,將給定的實體插入數據庫,若此實體類存在,則更新 

update

//更新數據 
void    update(T entity)   // 更新給定的實體 
void    updateInTx(java.lang.Iterable<T> entities)   // 使用事務操作,更新給定的實體 
void    updateInTx(T... entities)  // 使用事務操作,更新給定的實體

load

// 加載相關
T   load(K key)  // 加載給定主鍵的實體 
List<T>     loadAll()  // 加載數據庫中所有的實體 
T   loadByRowId(long rowId)   // 加載某一行並返回該行的實體 

查詢

官方文檔
查詢返回符合特定條件[certain criteria]的實體。在 greenDAO 中,你可以使用原始[raw] SQL 來制定查詢[formulate queries],或者使用 QueryBuilder API,相對而言后者會更容易。

此外,查詢支持懶加載結果,當在大結果集上操作時,可以節省內存和性能。

QueryBuilder 查詢條件

編寫SQL可能很困難,並且容易出現錯誤,這些錯誤僅在運行時被察覺。QueryBuilder類允許你為再不寫SQL語句的情況下為實體構建自定義查詢,並幫助在編譯時檢測錯誤。

簡單條件[condition]示例:查詢所有名為“Joe”的用戶,按姓氏排序:

List<User> joes = userDao.queryBuilder()
  .where(Properties.FirstName.eq("Joe"))
  .orderAsc(Properties.LastName)
  .list();

嵌套條件[Nested conditions]示例:獲取1970年10月或之后出生的名為“Joe”的用戶:

QueryBuilder<User> qb = userDao.queryBuilder();
qb.where(Properties.FirstName.eq("Joe"),
    qb.or(Properties.YearOfBirth.gt(1970),
        qb.and(Properties.YearOfBirth.eq(1970), Properties.MonthOfBirth.ge(10))));
List<User> youngJoes = qb.list();

greenDao 的 Properties 支持的操作:

  • eq() ==
  • noteq() !=
  • gt() >
  • lt() <
  • ge >=
  • le <=
  • like() 包含
  • between 倆者之間
  • in 在某個值內
  • notIn 不在某個值內
  • isNull 為空
  • isNotNull 不為空

Order 排序

您可以排序查詢結果。基於姓氏和出生年份的人的例子:

queryBuilder.orderAsc(Properties.LastName); // order by last name
queryBuilder.orderDesc(Properties.LastName); // in reverse
queryBuilder.orderAsc(Properties.LastName).orderDesc(Properties.YearOfBirth); // order by last name and year of birth

greenDAO使用的默認排序規則是COLLATE NOCASE,但可以使用 stringOrderCollation() 進行自定義。有關影響結果順序的其他方法,請參閱QueryBuilder類的文檔。

Limit, Offset 偏移和分頁

有時候,你只需要一個查詢結果的子集,例如在用戶界面中顯示前10個元素。當擁有大量實體時,這是非常有用的(並且也節省資源),並且你也不能使用 where 語句來限制結果。QueryBuilder 有定義限制和偏移的方法:

  • limit(int) //限制查詢返回的結果數。
  • offset(int) //結合 limit 設置查詢結果的偏移量。將跳過第一個偏移結果,並且結果的總數將受 limit 限制。你不能脫離 limit 使用offset。

Custom Types as Parameters

通常,greenDAO以透明方式映射查詢中使用的類型。 例如,boolean被映射到具有0或1值的INTEGER,而Date被映射到(long)INTEGER值。

自定義類型是一個例外:在構建查詢時,您始終必須使用數據庫值類型。 例如,如果使用轉換器將枚舉類型映射到 int 值,則應在查詢中使用 int 值。

public enum NoteType { TEXT, PICTURE, UNKNOWN }
​
public static final String TYPE_TEXT = "文本";
public String convertToDatabaseValue(NoteType entityProperty) {
    if (entityProperty == TEXT) return TYPE_TEXT;
    ...
}
NoteDao.Properties.Type.eq(NoteTypeConverter.TYPE_TEXT); //這里必須使用數據庫中的值類型 String 而不能使用枚舉類型 NoteType.TEXT

Query and LazyList

Query 類表示可以多次執行的查詢[executed multiple times]。當你使用 QueryBuilder 中的一個方法來獲取結果時,執行過程中 QueryBuilder 內部會使用 Query 類。如果要多次運行同一個查詢,應該在 QueryBuilder 上調用 build() 來創建 query 而並非執行它。

greenDAO 支持唯一結果(0或1個結果)和結果列表。如果你想在 Query(或QueryBuilder)上有唯一的結果,請調用 unique(),這將為你提供單個結果或者在沒有找到匹配的實體時返回 null。如果你的用例禁止 null 作為結果,調用 uniqueOrThrow() 將保證返回一個非空的實體(否則會拋出一個 DaoException)。

如果希望多個實體作為查詢結果,請使用以下方法之一:

  • list():所有實體都加載到內存中。結果通常是一個簡單的ArrayList。最方便使用。
  • listLazy():實體按需[on-demand]加載到內存中。一旦列表中的元素第一次被訪問[Once an element in the list is accessed for the first time],它將被加載並緩存以備將來使用(否則不加載)。使用完后必須關閉。
  • listLazyUncached():一個"虛擬"實體列表,對列表元素的任何訪問都導致從數據庫加載其數據[results in loading its data from the database]。使用完后必須關閉。
  • listIterator():讓我們通過按需加載數據(lazily)來遍歷結果。數據未緩存。使用完后必須關閉。

方法 listLazy()、listLazyUncached() 和 listIterator() 使用 greenDAO 的 LazyList 類。為了按需加載數據,它保存對數據庫游標的引用[holds a reference to a database cursor],這就是為什么你必須確保關閉懶性列表和迭代器(通常在try/finally塊)。

來自 listLazy() 的緩存 lazy 列表和 listIterator() 中的 lazy 迭代器在訪問或遍歷所有元素后自動關閉游標。如果列表處理過早停止,開發者需要自己調用close()進行處理[it’s your job to call close() if the list processing stops prematurely]。

在多線程中使用查詢

如果在多個線程中使用查詢,則必須調用 forCurrentThread() 獲取當前線程的Query實例。 Query實例綁定到構建查詢的那個線程[Object instances of Query are bound to their owning thread that built the query]。

你可以安全地設置Query對象的參數,因為其他線程不會干涉到你[while other threads cannot interfere]。 如果其他線程嘗試修改查詢參數,或執行 query boun 到另一個線程,系統將拋出異常。 用這種方式,你就不再需要 synchronized 語句了。 實際上,你應該避免鎖定,因為如果並發事務[concurrent transactions]使用相同的 Query 對象,這可能導致死鎖。

每次在使用構建器構建查詢時,參數設置為初始參數,forCurrentThread() 將被調用[Every time, forCurrentThread() is called, the parameters are set to the initial parameters at the time the query was built using its builder.]。

使用原始 SQL 語句查詢

如果QueryBuilder不能滿足你的需求,有兩種方法來執行原始 SQL 語句並同樣可以返回實體對象。

第一種,首選的方法是使用 QueryBuilder 和 WhereCondition.StringCondition。這樣,你可以將任何 SQL 語句作為 WHERE 子句傳遞給查詢構建器。

WhereCondition cond = new WhereCondition.StringCondition(NoteDao.Properties.Id.columnName + ">=? ", 2L);
List<Note> list = dao.queryBuilder().where(cond).build().list();

以下代碼是一個理論[theoretical]示例,說明如何運行子選擇(使用 join 連接將是更好的解決方案):

Query<User> query = userDao.queryBuilder()
    .where(new StringCondition("_ID IN " +"(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)"))
    .build();

第二種方法不使用QueryBuilder, 而是使用 queryRaw 或 queryRawCreate 方法。它們允許您傳遞一個原始 SQL 字符串,它附加在 SELECT 和實體列[entities columns]之后。 這樣,你可以有任何 WHERE 和 ORDER BY 子句來選擇實體。

可以使用生成的常量引用表和列名稱。這是建議的方式,因為可以避免打錯字,因為編譯器將檢查名稱。在實體的DAO中,你將發現 TABLENAME 包含數據庫表的名稱,以及一個內部類 Properties,其中包含所有的屬性的常量(字段columnName)。

String where = "where " + NoteDao.Properties.Type.columnName + " like ? AND " + NoteDao.Properties.Id.columnName + ">=?";
ist<Note> list = dao.queryRaw(where, "%" + NoteTypeConverter.TYPE_TEXT + "%", Long.toString(2L));

可以使用別名 T 來引用實體表[entity table]。

以下示例顯示如何創建一個查詢,該查詢使用連接檢索名為“admin”的組的用戶(同樣,greenDAO本身支持 joins 連接,這只是為了演示):

Query<User> query = userDao.queryRawCreate(", GROUP G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin");

DeleteQuery

批量刪除不會刪除單個實體[Bulk deletes do not delete individual entities],但是所有實體都符合一些條件。 要執行批量刪除,請創建一個 QueryBuilder,調用其 buildDelete() 方法,並執行返回的 DeleteQuery。

這一部分的API可能在將來會改變,例如可能會添加使用起來更方便的方法。
請注意,批量刪除目前不影響 identity scope 中的實體,例如,如果已刪除的實體先前已緩存並通過其ID(load 方法)進行訪問,則可以“復活”[resurrect]。 如果這可能會導致你的用例的問題[cause issues for your use case],請考慮立即清除身份范圍[identity scope]。

查詢時疑難問題的處理

QueryBuilder 有兩個靜態標志可以啟用 SQL 和參數 logging,啟用后可以幫助你分析為何您的查詢沒有返回預期的結果:

QueryBuilder.LOG_SQL = true;
QueryBuilder.LOG_VALUES = true;

當調用其中一個構建方法時,它們將 log 生成的 SQL 命令和傳遞的值,並將它們與實際所需的值進行比較。此外,它可能有助於將生成的 SQL 復制到一些 SQLite 數據庫瀏覽器中,並看看它如何執行。

自定義類型

自定義類型允許實體具有任何類型的屬性。默認情況下,greenDAO支持以下類型:

  • boolean, Boolean
  • int, Integer
  • short, Short
  • long, Long
  • float, Float
  • double, Double
  • byte, Byte
  • byte[]
  • String
  • Date

如果 greenDao 的默認參數類型滿足不了你的需求,那么你可以使用數據庫支持的原生數據類型(上面列出的那些)通過 PropertyConverter 類轉換成你想要的屬性。

  • 1、給自定義類型參數添加 @Convert 注釋並添加對應參數
@Convert(converter = NoteTypeConverter.class, columnType = String.class) //類型轉換類,自定義的類型在數據庫中存儲的類型
private NoteType type; //在保存到數據庫時會將自定義的類型 NoteType 通過 NoteTypeConverter 轉換為數據庫支持的 String 類型。反之亦然
public enum NoteType {
    TEXT, PICTURE, UNKNOWN
}
  • 2、自定義 PropertyConverter 接口的實現類,用於自定義類型和在數據庫中存儲的類型之間的相互轉換:
public class NoteTypeConverter implements PropertyConverter<NoteType, String> {
    public static final String TYPE_TEXT = "文本";
    public static final String TYPE_PICTURE = "圖片";
    public static final String TYPE_UNKNOWN = "未知格式";

    @Override
    public NoteType convertToEntityProperty(String databaseValue) { //將 數據庫中存儲的String類型 轉換為 自定義的NoteType類型
        switch (databaseValue) {
            case TYPE_TEXT:
                return NoteType.TEXT;
            case TYPE_PICTURE:
                return NoteType.PICTURE;
            default: //不要忘記正確處理空值
                return NoteType.UNKNOWN;
        }
    }

    @Override
    public String convertToDatabaseValue(NoteType entityProperty) { //將 自定義的NoteType類型 轉換為在數據庫中存儲的String類型
        switch (entityProperty) {
            case TEXT:
                return TYPE_TEXT;
            case PICTURE:
                return TYPE_PICTURE;
            default:
                return TYPE_UNKNOWN;
        }
    }
}

注意:為了獲得最佳性能,greenDAO將為所有轉換使用單個轉換器實例。確保轉換器除了無參數默認構造函數之外沒有任何其他構造函數。另外,確保它是線程安全的,因為它可能在多個實體上並發調用。

如何正確轉換枚舉

  • 不要持久化枚舉的序號或名稱[ordinal or name]:兩者都不穩定[unstable],並且很有可能下次編輯枚舉定義[edit your enum definitions]的時候就變化了。
  • 使用穩定的ids:在你的枚舉中定義一個保證穩定的自定義屬性(整數或字符串)。使用它來進行持久性映射[Use this for your persistence mapping]。
  • 未知值:定義一個 UNKNOWN 枚舉值。它可以用於處理空值或未知值。這將允許你處理像 舊的枚舉值被刪除 而不會導致您的應用程序崩潰的情況。

數據庫加密

數據庫加密

greenDAO 支持加密的數據庫來保護敏感數據。

雖然較新版本的Android支持文件系統加密[file system encryption],但Android本身並不為數據庫文件提供加密。因此,如果攻擊者獲得對數據庫文件的訪問(例如通過利用安全缺陷或欺騙有 root 權限的設備的用戶),攻擊者可以訪問該數據庫內的所有數據。使用受密碼保護的加密數據庫增加了額外的安全層。它防止攻擊者簡單地打開數據庫文件。

  • 1、使用自定義SQLite構建

因為Android不支持加密數據庫,所以你需要在APK中捆綁自定義構建的SQLite[bundle a custom build of SQLite]。 這些定制構建包括CPU相關[dependent]和本地代碼。 所以你的APK大小將增加幾MByte。 因此,你應該只在你真的需要它時使用加密數據庫。

  • 2、設置數據庫加密

greenDAO直接支持帶有綁定的 SQLCipher。 SQLCipher是使用256位AES加密的自定義構建的SQLite。
請參閱 SQLCipher for Android,了解如何向項目添加SQLCipher。

在創建數據庫實例時,只需調用 .getEncryptedWritableDb() 而不是 .getWritableDb() 即可像使用SQLite一樣使用SQLCipher:

DevOpenHelper helper = new DevOpenHelper(this, "notes-db-encrypted.db");
Database db = helper.getEncryptedWritableDb("<your-secret-password>");
daoSession = new DaoMaster(db).newSession();
  • 3、數據庫抽象[Abstraction]

greenDAO 對所有數據庫交互使用一個小型的抽象層[a thin abstraction layer],因此支持標准和非標准的SQLite實現:

  • Android的標准 android.database.sqlite.SQLiteDatabase
  • SQLCipher的 net.sqlcipher.database.SQLiteDatabase
  • 任何SQLite兼容的數據庫,它要實現 org.greenrobot.greendao.database.Database

這使您能夠輕松地從標准數據庫切換到加密數據庫,因為在針對DaoSession和單個DAO時,代碼將是相同的。

使用示例

Demo地址:https://github.com/baiqiantao/AndroidOrmTest.git

配置 gradle

在根 build.gradle 文件中:

buildscript {
    repositories {
        mavenCentral() //greendao
    }
    dependencies {
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' //greendao
    }
}

在模塊的 build.gradle 文件中:

greendao {
    schemaVersion 1 //數據庫模式的當前版本
}

可配置的屬性:

  • schemaVersion:數據庫模式[database schema]的當前版本。 這用於 *OpenHelpers 類在模式版本之間遷移。如果更改 實體/數據庫 模式,則必須增加此值。 默認值為1。
  • daoPackage:生成的DAO,DaoMaster和DaoSession的包名稱。默認為源實體的包名稱。
  • targetGenDir:生成的源碼應存儲的位置。默認目錄為 build/generated/source/greendao
  • generateTests:設置為 true 以自動生成單元測試。
  • targetGenDirTests:生成的單元測試應存儲在的基本目錄。 默認為 src/androidTest/java

在模塊的 build.gradle 文件中:

apply plugin: 'org.greenrobot.greendao' //greendao
implementation 'org.greenrobot:greendao:3.2.2' //greendao

在構建項目時,它會生成 DaoMaster,DaoSession 和 **DAO 等類。如果在更改實體類之后遇到錯誤,請嘗試重新生成項目,以確保清除舊生成的類。

定義實體類

@Entity(
        nameInDb = "BQT_USERS",// 指定該表在數據庫中的名稱,默認是基於實體類名
        indexes = {@Index(value = "text, date DESC", unique = true)},// 定義跨多個列的索引
        createInDb = true,// 高級標志,是否創建該表。默認為true。如果有多個實體映射一個表,或者該表已在外部創建,則可置為false
        //schema = "bqt_schema",// 告知GreenDao當前實體屬於哪個schema。屬於不同模式的實體應不具有關系。
        active = true,// 標記一個實體處於活動狀態,活動實體(設置為true)有更新、刪除和刷新方法。默認為false
        generateConstructors = true,//是否生成所有屬性的構造器。注意:無參構造器總是會生成。默認為true
        generateGettersSetters = true//是否應該生成屬性的getter和setter。默認為true
)
public class Note {
    @Id(autoincrement = false) //必須 Long 或 long 類型的,SQLite 上的自增長會引入額外的資源使用,通常可以避免使用
    private Long id;

    @NotNull//在保存到數據庫中之前需要保證此值不為null,否則會報 IllegalArgumentException ,並直接崩潰
    private String text;

    @Transient //表明此字段不存儲到數據庫中,用於不需要持久化的字段,比如臨時狀態
    private String comment;

    @Property(nameInDb = "TIME") //為該屬性在數據庫中映射的字段名設置一個非默認的名稱
    private Date date;

    @Convert(converter = NoteTypeConverter.class, columnType = String.class) //類型轉換類,自定義的類型在數據庫中存儲的類型
    private NoteType type; //在保存到數據庫時會將自定義的類型 NoteType 通過 NoteTypeConverter 轉換為 String 類型。反之亦然
}

編寫完實體類以后按下 Ctrl+F9(Make project),程序會自動編譯生成 dao 文件,生成的文件一共有三個:DaoMaster,DaoSession 和 **DAO。

演示 DAO 的使用

public class GreenDaoActivity extends ListActivity {
    private Note mNote = Note.newBuilder().text("【text】").date(new Date()).comment("包青天").type(NoteType.PICTURE).build();
    private NoteDao dao;
    private EditText editText;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"0、插入一條數據",
                "1、演示 insert 數據時一些特殊情況",
                "2、插入數據:insertInTx、insertOrReplace、save、saveInTx、insertWithoutSettingPk",
                "3、刪除數據:delete、deleteAll、deleteByKey、deleteByKeyInTx、deleteInTx",
                "4、更新數據:update、updateInTx、updateKeyAfterInsert",
                "5、查詢數據:where、whereOr、limit、offset、*order*、distinct、or、and、queryRaw*",
                "6、數據加載和緩存:load、loadByRowId、loadAll、detach、detachAll、unique、uniqueOrThrow",
                "7、其他API:getKey、getPkProperty、getProperties、getPkColumns、getNonPkColumns",
                "8、刪除所有數據:deleteAll",
                "tag 值 +1",};
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
        editText = new EditText(this);
        editText.setInputType(EditorInfo.TYPE_CLASS_NUMBER);
        getListView().addFooterView(editText);
        initDB();
    }

    private void initDB() {
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db");
        Database db = helper.getWritableDb();
        DaoMaster daoMaster = new DaoMaster(db);
        DaoSession daoSession = daoMaster.newSession();
        dao = daoSession.getNoteDao();
    }

    int tag = -1;

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        if (TextUtils.isEmpty(editText.getText())) {
            tag++;
            Toast.makeText(this, "最新值為" + tag, Toast.LENGTH_SHORT).show();
        } else {
            tag = Integer.parseInt(editText.getText().toString());
            editText.setText("");
        }
        switch (position) {
            case 0://插入一條數據
                simpleInsert();
                break;
            case 1://演示 insert 數據時一些特殊情況
                insert();
                break;
            case 2://插入數據
                inserts();
                break;
            case 3://刪除數據
                deletes();
                break;
            case 4://更新數據
                updates();
                break;
            case 5://查詢數據
                query();
                break;
            case 6:
                loadAndDetach();
                break;
            case 7:
                testOtherApi();
                break;
            case 8:
                dao.deleteAll();
                break;
        }
    }

    private void simpleInsert() {
        Note insertNote = Note.newBuilder().type(tag % 3 == 2 ? NoteType.UNKNOWN :
                (tag % 3 == 1 ? NoteType.PICTURE : NoteType.TEXT))
                .comment("comment" + tag).date(new Date()).text("text" + tag).build();
        long insertId = dao.insert(insertNote);// 將給定的實體插入數據庫,默認情況下,插入的數據的Id將從1開始遞增
        Log.i("bqt", "插入數據的ID= " + insertId + getAllDataString());
    }

    private void insert() {
        long id = -10086;//以下注釋不管是否給 id 設置【@Id(autoincrement = true)】注解時的得出的結論都是一樣的
        if (tag % 9 == 6) id = dao.insert(new Note());//同樣會崩潰,插入數據的 text 不能為空(IllegalArgumentException)
        else if (tag % 9 == 5) id = dao.insert(Note.newBuilder().id(6L).text("text5").build());//會崩潰,因為此 id 已經存在
        else if (tag % 9 == 4) id = dao.insert(Note.newBuilder().text("text4").build());//id =12
        else if (tag % 9 == 3) id = dao.insert(Note.newBuilder().id(6L).text("text3").build());//自定義id,id =6
        else if (tag % 9 == 2) id = dao.insert(Note.newBuilder().text("text2").type(NoteType.UNKNOWN).build());//id = 11
        else if (tag % 9 == 1) id = dao.insert(Note.newBuilder().id(10L).text("text1").build());//插入的數據的Id將從最大值開始
        else if (tag % 9 == 0) id = dao.insert(Note.newBuilder().id(-2L).text("text0").build());//id =-2,並不限制id的值
        Log.i("bqt", "插入數據的ID= " + id + getAllDataString());
    }

    private void inserts() {
        Note note1 = Note.newBuilder().text("text1-" + tag).date(new Date()).comment("包青天").type(NoteType.TEXT).build();
        Note note2 = Note.newBuilder().text("text2-" + tag).date(new Date()).comment("包青天").type(NoteType.TEXT).build();
        if (tag % 9 >= 6) {
            dao.save(note1);// 將給定的實體插入數據庫,若此實體類存在,則【更新】
            Note quryNote = dao.queryBuilder().build().list().get(0);
            quryNote.setText("【新的Text2】");
            mNote.setText("【新的Text2】");
            dao.saveInTx(quryNote, mNote);//同樣參數可以時集合,基本上所有的 **InTx 方法都是這樣的
        } else if (tag % 9 == 5) {
            Note quryNote = dao.queryBuilder().build().list().get(0);
            quryNote.setText("【新的Text】");
            mNote.setText("【新的Text】");
            dao.insertOrReplaceInTx(quryNote, mNote);//同樣參數可以時集合,並可選擇是否設定主鍵
        } else if (tag % 9 == 4) {
            long rowId = dao.insertOrReplace(mNote);// 將給定的實體插入數據庫,若此實體類存在則【覆蓋】,返回新插入實體的行ID
            Log.i("bqt", "對象的ID=" + mNote.getId() + "  返回的ID=" + rowId);
        } else if (tag % 9 == 3) dao.insertWithoutSettingPk(note1);//插入數據庫但不設定主鍵(不明白含義),返回新插入實體的行ID
        else if (tag % 9 == 2) dao.insertInTx(Arrays.asList(note1, note2), false);//並設置是否設定主鍵(不明白含義)
        else if (tag % 9 == 1) dao.insertInTx(Arrays.asList(note1, note2));// 使用事務操作,將給定的實體集合插入數據庫
        else if (tag % 9 == 0) dao.insertInTx(note1, note2);// 使用事務操作,將給定的實體集合插入數據庫
        Log.i("bqt", getAllDataString());
    }

    private void deletes() {
        if (tag % 9 >= 7) dao.deleteAll(); //刪除數據庫中全部數據。再插入的數據的 Id 同樣將從最大值開始
        else if (tag % 9 == 6) dao.deleteByKeyInTx(Arrays.asList(1L, 3L));
        else if (tag % 9 == 5) dao.deleteByKeyInTx(1L, 2L);// 使用事務操作刪除數據庫中給定的所有key所對應的實體
        else if (tag % 9 == 4) dao.deleteByKey(1L);//從數據庫中刪除給定Key所對應的實體
        else if (tag % 9 == 3) dao.delete(new Note());//從數據庫中刪除給定的實體
        else if (tag % 9 == 2) dao.deleteInTx(new Note(), new Note());// 使用事務操作刪除數據庫中給定實體集合中的實體
        else if (tag % 9 == 1) dao.deleteInTx(Arrays.asList(new Note(), new Note()));
        else if (tag % 9 == 0) dao.deleteInTx(dao.queryBuilder().limit(1).list());
        Log.i("bqt", getAllDataString());
    }

    private void query() {
        WhereCondition cond1 = NoteDao.Properties.Id.eq(1);//==
        WhereCondition cond2 = NoteDao.Properties.Type.notEq(NoteTypeConverter.TYPE_UNKNOWN);//!=
        //在構建查詢時,必須使用數據庫值類型。 因為我們使用轉換器將枚舉類型映射到 String 值,則應在查詢中使用 String 值
        WhereCondition cond3 = NoteDao.Properties.Id.gt(10);//大於
        WhereCondition cond4 = NoteDao.Properties.Id.le(5);// less and eq 小於等於
        WhereCondition cond5 = NoteDao.Properties.Id.in(1, 4, 10);//可以是集合。在某些值內,notIn   不在某些值內
        WhereCondition cond6 = NoteDao.Properties.Text.like("%【%");//包含
        // 最常用Like通配符:下划線_代替一個任意字符(相當於正則表達式中的 ?)   百分號%代替任意數目的任意字符(相當於正則表達式中的 *)
        WhereCondition cond7 = NoteDao.Properties.Date.between(System.currentTimeMillis() - 1000 * 60 * 20, new Date());//20分鍾
        WhereCondition cond8 = NoteDao.Properties.Date.isNotNull();//isNull
        WhereCondition cond9 = new WhereCondition.StringCondition(NoteDao.Properties.Id.columnName + ">=? ", 2L);

        //其他排序API:orderRaw(使用原生的SQL語句)、orderCustom、stringOrderCollation、preferLocalizedStringOrder
        Property[] orders = new Property[]{NoteDao.Properties.Type, NoteDao.Properties.Date, NoteDao.Properties.Text};//同樣支持可變參數
        List<Note> list = null;

        if (tag % 9 == 8) {
            String where = "where " + NoteDao.Properties.Type.columnName + " like ? AND " + NoteDao.Properties.Id.columnName + ">=?";
            list = dao.queryRaw(where, "%" + NoteTypeConverter.TYPE_TEXT + "%", Long.toString(2L));
            Log.i("bqt", "queryRaw查詢到的數據" + new Gson().toJson(list));
            list = dao.queryRawCreate(where, "%" + NoteTypeConverter.TYPE_TEXT + "%", Long.toString(2L)).list();
        } else if (tag % 9 == 7) list = dao.queryBuilder().where(cond9).build().list(); //執行原生的SQL查詢語句
        else if (tag % 9 == 6) {
            QueryBuilder<Note> qb = dao.queryBuilder();
            WhereCondition condOr = qb.or(cond2, cond3, cond4);
            WhereCondition condAnd = qb.and(cond6, cond7, cond8);
            list = qb.where(cond1, condOr, condAnd).build().list();
        } else if (tag % 9 == 5) list = dao.queryBuilder().whereOr(cond5, cond6, cond7).build().list();//多個語句間是 OR 的關系
        else if (tag % 9 == 4) list = dao.queryBuilder().where(cond5, cond6, cond7).build().list();//多個語句間是 AND 的關系
        else if (tag % 9 == 3) list = dao.queryBuilder().where(cond4).offset(1).limit(2).build().list();//(必須)與limit一起設置查詢結果的偏移量
        else if (tag % 9 == 2) list = dao.queryBuilder().where(cond5).limit(2).distinct().build().list();//限制查詢結果個數,避免重復的實體(不明白)
        else if (tag % 9 == 1) list = dao.queryBuilder().where(cond2).orderAsc(orders).build().list(); //常用升序orderAsc、降序orderDesc
        else if (tag % 9 == 0) list = dao.queryBuilder().build().list();
        Log.i("bqt", getAllDataString() + "\n查詢到的數據" + new Gson().toJson(list));
    }

    private void updates() {
        dao.save(mNote);//確保要更新的實體已存在

        if (tag % 9 >= 4) dao.update(Note.newBuilder().text("text-update0").build()); //如果要更新的實體不存在,則會報DaoException異常
        else if (tag % 9 == 3) {
            mNote.setText("【Text-update3】");
            long rowId = dao.updateKeyAfterInsert(mNote, 666L);
            Log.i("bqt", "rowId=" + rowId);//更新實體內容,並更新key
        } else if (tag % 9 == 2) {
            mNote.setText("【Text-update2】");
            dao.updateInTx(Arrays.asList(dao.queryBuilder().build().list().get(0), mNote));// 使用事務操作,更新給定的實體
        } else if (tag % 9 == 1) {
            Note updateNote = dao.queryBuilder().build().list().get(0);
            updateNote.setText("【Text-update1】");
            dao.updateInTx(updateNote, mNote);// 使用事務操作,更新給定的實體
        } else if (tag % 9 == 0) dao.update(mNote);//更新給定的實體,不管有沒有變化,只需用新的實體內容替換舊的內容即可(必須是同一實體)
        Log.i("bqt", getAllDataString());
    }

    private void loadAndDetach() {
        Note note1 = dao.load(1L);// 加載給定主鍵的實體
        Note note2 = dao.load(10086L);//如果給定主鍵不存在則返回null
        Note note3 = dao.loadByRowId(1L);// 加載某一行並返回該行的實體
        Note note4 = dao.queryBuilder().limit(1).unique();//返回一個元素(可能為null)。如果查詢結果數量不是0或1,則拋出DaoException
        Note note5 = dao.queryBuilder().limit(1).build().uniqueOrThrow(); //保證返回一個非空的實體(否則會拋出一個 DaoException)

        if (tag % 9 >= 2) {
            note1.setText("-------Text" + System.currentTimeMillis());
            Log.i("bqt", dao.load(1L).getText());//因為這里獲取到的實體對象仍然是 note1 ,所以這里的值立即就改變了!
            dao.detachAll();//清除指定實體緩存,將會重新從數據庫中查詢,然后封裝成新的對象
            Log.i("bqt", dao.load(1L).getText());//因為只更改了實體但沒有調用更新數據庫的方法,所以數據庫中的數據並沒有改變
        } else if (tag % 9 == 1) {
            dao.detach(note4);//清除指定實體緩存;dao.detachAll 清除當前表的所有實體緩存;daoSession.clear() 清除所有的實體緩存
            Note note7 = dao.load(1L);// 清除指定Dao類的緩存后,再次得到的實體就是構造的新的對象
            Log.i("bqt", (note1 == note3 && note1 == note4 && note1 == note5) + "  " + (note1 == note7));//true  false
        } else if (tag % 9 == 0) {
            Log.i("bqt", new Gson().toJson(Arrays.asList(note1, note2, note3, note4, note5)));
        }
    }

    private void testOtherApi() {
        dao.save(mNote);//確保要更新的實體已存在
        //查找指定實體的Key,當指定實體在數據庫表中不存在時返回 null(而不是返回-1或其他值)
        Log.i("bqt", "實體的 Key = " + dao.getKey(mNote) + "  " + dao.getKey(Note.newBuilder().text("text").build()));

        Property pk = dao.getPkProperty();//id  _id 0   true    class java.lang.Long
        for (Property p : dao.getProperties()) {
            Log.i("bqt", p.name + "\t" + p.columnName + "\t" + p.ordinal + "\t" + p.primaryKey + "\t" + p.type + "\t" + (p == pk));
        }

        Log.i("bqt", "所有的PK列:" + Arrays.toString(dao.getPkColumns()));//[_id]
        Log.i("bqt", "所有的非PK列:" + Arrays.toString(dao.getNonPkColumns()));//[TEXT, TIME, TYPE]
    }

    private String getAllDataString() {
        List<Note> list1 = dao.queryBuilder().build().list();//緩存查詢結果
        List<Note> list2 = dao.queryBuilder().list();
        List<Note> list3 = dao.loadAll();
        return " 個數=" + dao.count() + ",等價=" + (list1.equals(list2) && list1.equals(list3)) + ",數據=" + new Gson().toJson(list1);//true
    }
}

2018-5-28


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM