Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
ORM數據庫框架 greenDAO SQLite MDjava
針對Android進行了高度優化,性能卓越,佔用內存極
少。Protocol buffers
協議數據的直接存儲,若是經過protobuf協議和服務器交互,不須要任何的映射。Protocol Buffers協議:以一種高效
可擴展
的對結構化數據
進行編碼
的方式。google內部的RPC
協議和文件格式大部分都是使用它。
RPC:Remote Procedure Call
,遠程過程調用
,是一個計算機通訊協議,它是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡技術的協議。android
特性:git
Android上最快的ORM
,由智能代碼生成器
驅動QueryBuilder
使用屬性常量來避免拼寫錯誤[typos]SQLCipher
加密數據庫優勢:github
100k
大小內存緩存
中,下次使用時能夠直接從緩存中取,這樣可使性能提升N個數量級代碼自動生成
:greenDao 會根據modle類自動生成 DAO 類(DaoMaster、DaoSession 和 **DAO)greenrobot 其餘流行的開源項目:正則表達式
面向對象數據
庫[object-oriented database]。是比SQLite更快的對象持久化方案[object persistence]。中央發佈/訂閱總線
,具備可選的傳遞線程
[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的特色一覽瀏覽器
您想了解有關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。 雖然有些事情讓咱們感到很自豪,但咱們並不熱衷於營銷演講。 咱們全部的基準測試都是開源的,能夠在達到高標準的同時實現最大透明度。你能夠本身檢查最新的基準測試結果並得出本身的結論。
此庫中註解的保留策略都是:@Retention(RetentionPolicy.SOURCE)
源文件
中有效, 編譯器要丟棄的註解
。greenDAO嘗試使用合理的默認值,所以開發人員沒必要配置每個屬性值。
例如,數據庫端的表名和列名
是從實體名和屬性名
派生的,而 不是 Java中使用的駝峯案例樣式,默認數據庫名稱是大寫的,使用下劃線來分隔單詞
。例如,名爲 creationDate 的屬性將成爲數據庫列CREATION_DATE。
@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 註解選擇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(nameInDb = "")
爲該屬性在數據庫中映射的字段名設置一個非默認的名稱。默認是將單詞大寫,用下劃線分割單詞,如屬性名 customName 對應列名 CUSTOM_NAME
//可選:爲持久字段配置映射列。 此註釋也適用於@ToOne,無需額外的外鍵屬性 @Target(ElementType.FIELD) public @interface Property { String nameInDb() default ""; //此屬性的數據庫列的名稱。 默認爲字段名稱。 }
@Property(nameInDb = "USERNAME") private String name;
@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 代表此字段不存儲到數據庫中,用於不須要持久化的字段,好比臨時狀態
@Target(ElementType.FIELD) public @interface Transient { }
@Transient private int tempUsageCount;
@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 爲相應列添加惟一約束。注意,SQLite會隱式地爲該列建立索引
//在表建立期間標記屬性應具備UNIQUE約束。此註釋也適用於 @ToOne,無需額外的外鍵屬性。要在建立表後擁有惟一約束,可使用 Index 的 unique()。注意同時擁有 @Unique 和 Index 是多餘的,會致使性能下降 @Target(ElementType.FIELD) public @interface Unique { }
@Unique private String name;
@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
指定在下次運行 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
註解的代碼通常是 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 自動修改代碼。
但若是是這樣的話,爲什麼錯誤提示沒有說明呢?
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
的意思,也就是主鍵
的意思。
// 數據刪除相關 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) // 使用事務操做刪除數據庫中給定的實體
// 數據插入相關 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) // 將給定的實體插入數據庫,但不設定主鍵
// 新增數據插入相關API void save(T entity) // 將給定的實體插入數據庫,若此實體類存在,則更新 void saveInTx(java.lang.Iterable<T> entities) // 使用事務操做,將給定的實體插入數據庫,若此實體類存在,則更新 void saveInTx(T... entities) // 使用事務操做,將給定的實體插入數據庫,若此實體類存在,則更新
//更新數據 void update(T entity) // 更新給定的實體 void updateInTx(java.lang.Iterable<T> entities) // 使用事務操做,更新給定的實體 void updateInTx(T... entities) // 使用事務操做,更新給定的實體
// 加載相關 T load(K key) // 加載給定主鍵的實體 List<T> loadAll() // 加載數據庫中全部的實體 T loadByRowId(long rowId) // 加載某一行並返回該行的實體
官方文檔
查詢返回符合特定條件[certain criteria]的實體。在 greenDAO 中,你可使用原始[raw] SQL 來制定查詢[formulate queries],或者使用 QueryBuilder
API,相對而言後者會更容易。
此外,查詢支持懶加載
結果,當在大結果集上操做時,能夠節省內存和性能。
編寫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
支持的操做:
您能夠排序查詢結果。基於姓氏和出生年份的人的例子:
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類的文檔。
有時候,你只須要一個查詢結果的子集,例如在用戶界面中顯示前10個元素。當擁有大量實體時,這是很是有用的(而且也節省資源),而且你也不能使用 where 語句來限制結果。QueryBuilder 有定義限制和偏移的方法:
一般,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 類表示能夠屢次執行的查詢[executed multiple times]。當你使用 QueryBuilder 中的一個方法來獲取結果時,執行過程當中 QueryBuilder 內部會使用 Query 類
。若是要屢次運行同一個查詢,應該在 QueryBuilder 上調用 build()
來建立 query 而並不是執行它。
greenDAO 支持惟一結果(0或1個結果)和結果列表。若是你想在 Query(或QueryBuilder)上有惟一的結果,請調用 unique()
,這將爲你提供單個結果或者在沒有找到匹配的實體時返回 null。若是你的用例禁止 null 做爲結果,調用 uniqueOrThrow()
將保證返回一個非空的實體(不然會拋出一個 DaoException)。
若是但願多個實體做爲查詢結果,請使用如下方法之一:
方法 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.]。
若是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");
批量刪除不會刪除單個實體[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支持如下類型:
若是 greenDao 的默認參數類型知足不了你的需求,那麼你可使用數據庫支持的原生數據類型(上面列出的那些)經過 PropertyConverter
類轉換成你想要的屬性。
@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將爲全部轉換使用單個轉換器實例。確保轉換器除了無參數默認構造函數以外沒有任何其餘構造函數。另外,確保它是線程安全的,由於它可能在多個實體上併發調用。
如何正確轉換枚舉
greenDAO 支持加密的數據庫來保護敏感數據。
雖然較新版本的Android支持文件系統加密[file system encryption],但Android自己並不爲數據庫文件提供加密。所以,若是攻擊者得到對數據庫文件的訪問(例如經過利用安全缺陷或欺騙有 root 權限的設備的用戶),攻擊者能夠訪問該數據庫內的全部數據。使用受密碼保護的加密數據庫增長了額外的安全層。它防止攻擊者簡單地打開數據庫文件。
由於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();
greenDAO 對全部數據庫交互使用一個小型的抽象層[a thin abstraction layer],所以支持標準和非標準的SQLite實現:
android.database.sqlite.SQLiteDatabase
net.sqlcipher.database.SQLiteDatabase
org.greenrobot.greendao.database.Database
這使您可以輕鬆地從標準數據庫切換到加密數據庫,由於在針對DaoSession和單個DAO時,代碼將是相同的。
Demo地址:https://github.com/baiqiantao/AndroidOrmTest.git
在根 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。
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