ORM數據庫框架 greenDAO SQLite MD

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

ORM數據庫框架 greenDAO SQLite MDjava


目錄

簡介

相關資源

特性與優勢

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

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

特性:git

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

優勢:github

  • 是 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)彼此分離。

官網介紹文檔

文檔地址sql

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

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

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約束,強制全部值是惟一的。

//能夠用來:一、指定應對屬性編制索引;二、經過 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 類轉換成你想要的屬性。

  • 一、給自定義類型參數添加 @Convert 註釋並添加對應參數
@Convert(converter = NoteTypeConverter.class, columnType = String.class) //類型轉換類,自定義的類型在數據庫中存儲的類型
private NoteType type; //在保存到數據庫時會將自定義的類型 NoteType 經過 NoteTypeConverter 轉換爲數據庫支持的 String 類型。反之亦然
public enum NoteType {
    TEXT, PICTURE, UNKNOWN
}
  • 二、自定義 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 權限的設備的用戶),攻擊者能夠訪問該數據庫內的全部數據。使用受密碼保護的加密數據庫增長了額外的安全層。它防止攻擊者簡單地打開數據庫文件。

  • 一、使用自定義SQLite構建

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

  • 二、設置數據庫加密

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();
  • 三、數據庫抽象[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、插入一條數據",
                "一、演示 insert 數據時一些特殊狀況",
                "二、插入數據:insertInTx、insertOrReplace、save、saveInTx、insertWithoutSettingPk",
                "三、刪除數據:delete、deleteAll、deleteByKey、deleteByKeyInTx、deleteInTx",
                "四、更新數據:update、updateInTx、updateKeyAfterInsert",
                "五、查詢數據:where、whereOr、limit、offset、*order*、distinct、or、and、queryRaw*",
                "六、數據加載和緩存:load、loadByRowId、loadAll、detach、detachAll、unique、uniqueOrThrow",
                "七、其餘API:getKey、getPkProperty、getProperties、getPkColumns、getNonPkColumns",
                "八、刪除全部數據: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

相關文章
相關標籤/搜索