Markdown版本筆記 | 我的GitHub首頁 | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
ORM數據庫框架 greenDAO SQLite MD
目錄
簡介
相關資源
特性與優點
其他開源項目
官網介紹文檔
Greendao 注解
實體注解 @Entity
屬性注解
@Id 主鍵
@Property 字段名
@NotNull 非空
@Transient 忽略
索引注解
@Index 索引
@Unique 唯一
關系注解(略)
@Generated 自動生成
@Keep 保留
對 Generated 和 Keep 的理解
@Convert 自定義類型
使用教程
核心類簡介
核心初始化
增刪改
delete
insert
save
update
load
查詢
QueryBuilder 查詢條件
Order 排序
Limit, Offset 偏移和分頁
Custom Types as Parameters
Query and LazyList
在多線程中使用查詢
使用原始 SQL 語句查詢
DeleteQuery
查詢時疑難問題的處理
自定義類型
數據庫加密
使用示例
配置 gradle
定義實體類
演示 DAO 的使用
簡介
相關資源
- Demo地址:https://github.com/baiqiantao/AndroidOrmTest.git
- GitHub
- 官網
- Features
- greenDAO 3
- Documentation
- Changelog
- Technical FAQ
- Non-Technical FAQ
特性與優點
- 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 guide 和 introduction 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工具為你做很多重復性的任務,提供簡單的數據接口。
核心類簡介
一旦項目構建完畢,你就可以在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 中的
PK
是PrimaryKey
的意思,也就是主鍵
的意思。
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