關於greenDao的使用
第一篇How to get started ?
原文地址:http://greendao-orm.com/documentation/how-to-get-started/
該教程會帶你瀏覽一個簡單的greenDao示例工程。地址:https://github.com/greenrobot/greenDAO,該工程包含兩個子工程:
DaoExample和DaoExampleGenerator。你可以clone到本地,運行或者直接在github上直接瀏覽。
如果你從git倉儲中檢出了DaoExample,可以直接像Android應用一樣運行它。正如你所看到的,它就是一個簡單的筆記本。可以添加新的note,
或者點擊已存在的note進行刪除。
預生成代碼和創建表
在src-gen目錄下,你可以找到一些已經生成的文件
1)Note.java 一個包含一個Note所有數據的java類。
2)NoteDao.java 一個DAO類,是操作Note 對象的接口。
你可以通過DaoExampleGenerator工程再次生成Note和NoteDao。
使用DaoMaster類可以獲得一個方便的SQLiteOpenHelper:
new DaoMaster.DevOpenHelper(this, "notes-db", null)
你不必編寫“CREATE TABLE” SQL語句,greenDao會為你完成。
插入和刪除Note對象
創建了Note表后,就可以存儲一些note到數據庫里了。這是在NoteActivity類里完成的。在onCreate方法里,我們准備了一個DAO對象:
1
2
3
|
daoMaster =
new
DaoMaster(db);
daoSession = daoMaster.newSession();
noteDao = daoSession.getNoteDao();
|
添加一個新的note到數據庫中:
1
2
3
|
Note note =
new
Note(
null
, noteText, comment,
new
Date());
noteDao.insert(note);
Log.d(
"DaoExample"
,
"Inserted new note, ID: "
+ note.getId());
|
該示例只是創建並插入了一個java對象。但insert方法返回的時候,數據庫的ID已經分發到了剛插入的Note對象上了。在log中可以看到。
刪除一條note:非常簡單明,在onListItemClick方法中可以看到
1
|
noteDao.deleteByKey(id);
|
你也可以看一下其它的DAO方法:loadAll、update。
數據模型化和代碼的生成
為了擴展note或者創建新的實體,你可以看一下DaoExampleGenerator工程。它包含了一個單例的類,該類中包含了數據模型的定義代碼。
1
2
3
4
5
6
7
|
Schema schema =
new
Schema(1,
"de.greenrobot.daoexample"
);
Entity note= schema.addEntity(
"Note"
);
note.addIdProperty();
note.addStringProperty(
"text"
).notNull();
note.addStringProperty(
"comment"
);
note.addDateProperty(
"date"
);
new
DaoGenerator().generateAll(
"../DaoExample/src-gen"
, schema);
|
正如你所看到的,你可以創建一個Schema對象,通過它你可以添加實體,一個實體連接了一張數據庫表。
一個實體包含一些屬性,它們可以被映射到數據庫的columns。
一旦schema定義完成,你可以觸發代碼生成器,Note.java和NoteDao.java文件就是這樣被創建的。
下一步:
對greenDao有了初步的了解,你可以自己動手試試了。當然,請查看下文檔http://greendao-orm.com/documentation/,
如果沒有找到你想要的,可以使用support options
第二篇 介紹
GreenDao是一個用於Android開發的對象/關系映射(ORM)工具。它向SQLite數據庫提供了一個對象導向的接口。像GreenDao這樣的ORM工具不僅為你省去了很多的重復工作,而且提供了更簡便的操作接口。
代碼生成的工程結構圖
為了在你的Android項目中使用GreenDao,你需要創建一個二級工程:“generator project”,它的任務就是為你的domain生成具體的代碼。這個生成器工程就是一個普通的java工程。確保greenDao 的greenDao-generator.jar和 freemarker.jar 在classpath中。創建一個可執行的java類,構建你的實體模型並觸發代碼生成器,更多細節,可以參看 modelling文檔。
核心類
一旦生成了指定的代碼,就可以在你的android工程中使用greenDao了。別忘記在你的android工程中引入greenDao的核心jar包:greenDao.jar。以下是GreenDao的一些必要接口。
DaoMaster:
daomaster以一定的模式持有數據庫對象(SQLiteDatabase)並管理一些DAO類(而不是對象)。
有一個靜態的方法創建和drop數據庫表。它的內部類OpenHelper和DevOpenHelper是SQLiteOpenHelper的實現類,用於創建SQLite數據庫的模式。
DaoSession:
管理指定模式下所有可用的DAO對象,你可以通過某個get方法獲取到。DaoSession提供一些通用的持久化方法,比如對實體進行插入,加載,更新,刷新和刪除。最后DaoSession對象會跟蹤identity scope,更多細節,可以參看 session文檔。
DAOs(Data access objects):
數據訪問對象,用於實體的持久化和查詢。對於每一個實體,greenDao會生成一個DAO,相對於DaoSession它擁有更多持久化的方法,比如:加載全部,插入(insertInTx,語境不明了,暫且簡單的翻譯成插入)。
實體
可持久化的對象。通常,實體可以被生成,不用手動去寫。在數據庫的行中,使用的都是標准的java對象的屬性(比如POJO或者JavaBean)。
1
2
3
4
|
user.addIdProperty();
user.addStringProperty(
"name"
);
user.addStringProperty(
"password"
);
user.addIntProperty(
"yearOfBirth"
);
|
在示例中有一個Note實體,通過它的DAO,我們可以對指定的實體進行持久化的操作。
第三篇 實體的模型化
使用greenDao的第一步:創建一個代表持久化數據的實體模型。greenDao會依賴該模型為Dao生成java代碼。
該模型本身是用java代碼定義的,很簡單:在DaoExampleGenerator工程的基礎上創建一個java對象。具體你可以參看:
http://greendao-orm.com/documentation/how-to-get-started/
下面的插圖描繪了元模型,展示了一些用於描述domain具體模型的類。
Schema
實體數據schema是你定義的第一個對象,通過schema的版本和缺省的java包調用構造器。
1
|
Schema schema =
new
Schema(1,
"de.greenrobot.daoexample"
);
|
這個缺省的java包會在greenDao生成實體、DAOs、和JUnit測試的時候使用。如果那些缺省值是正確的,那么就完成了第一步。
如果你希望將DAO和測試類創建到不同的包中,可以重新定義schema的定義代碼:
1
2
|
schema.setDefaultJavaPackageTest(
"de.greenrobot.daoexample.test"
);
schema.setDefaultJavaPackageDao(
"de.greenrobot.daoexample.dao"
);
|
對於實體,該schema也有兩個缺省的標記,它們是可以被復寫的。這些標記可以區分實體是否是激活狀態,是否應該使用sections。這些特性在文檔里並沒有,你可以看一下發布源碼中的測試工程。
1
2
|
schema2.enableKeepSectionsByDefault();
schema2.enableActiveEntitiesByDefault();
|
實體
一旦你擁有了一個schema對象,你就可以使用它去添加實體了。
1
|
Entity user = schema.addEntity(
"User"
);
|
一個實體有不同的可變更設置,更重要的是,你可以添加一些屬性到實體。
1
2
3
4
|
user.addIdProperty();
user.addStringProperty(
"name"
);
user.addStringProperty(
"password"
);
user.addIntProperty(
"yearOfBirth"
);
|
除了實體,還可以添加,一對一和一對多的關系。
屬性和主鍵
以上的實體部分展示了如何給一個實體添加屬性,實體的addXXXProperty方法返回一個PropertyBuilder對象,可以用於配制屬性,
例如,使用columnName去復寫缺省的或者你提供的column name。在ProperyBuilder對象上調用getProperty方法去訪問屬性對象,
對於指數(indices )和關系的創建是有必要的。
創建主鍵的約束
現在實體必須擁有一個long或者Long類型的屬性作為它們的主鍵,這是Android和SQLite推薦的實踐方式。因為,在將來,greenDao要准備處理很多主鍵的腳本,但並不是每件事都能完全實現。為了解決這個問題,你可以使用一個long類型的鍵並且使用一個唯一的下標去處理這個預期的key屬性。
缺省
greenDao會嘗試以合理的缺省值進行工作,所以開發者不用單個的配置它們。比如,表和其列名是從實體和屬性名中獲取到的,而不是java中的駝峰。缺省的數據庫名是大寫的,單詞間用下划線分隔開。比如:屬性“creationDate”在數據庫列中的映射為“CREATION_DATE”,
關系
一對多和多對多的關系在http://greendao-orm.com/documentation/relations/中有注釋。
繼承、接口、序列化
實體可以從其他非實體類繼承,其父類可以通過setSuperclass(String)方法指定,注意:它可能會有其它的實體作為父類(但這里沒有多態查詢)。
比如:
1
|
myEntity.setSuperclass(
"MyCommonBehavior"
);
|
通常,使用接口作為實體屬性和行為的通用基類是比較好的。比如:一個實體A和B共享了一套屬性,這些屬性可以定義在C中。下面是一個序列化B的列子:
1
2
3
|
entityA.implementsInterface(
"C"
);
entityB.implementsInterface(
"C"
);
entityB.implementsSerializable();
|
觸發生成器
一旦你的實體schema放置好了,你可以觸發代碼生成器進行處理。在generator工程中,你可以實例化DaGenerator並調用generateAll中的一個方法:
1
2
|
DaoGenerator daoGenerator =
new
DaoGenerator();
daoGenerator.generateAll(schema,
"../MyProject/src-gen"
);
|
你所需要的就是schema對象和目標文件夾,通常該文件夾就是你android工程的資源文件夾。如果你想把這些測試類放到其他目錄下,可以把目的文件夾作為第三個參數傳入。
保持獨立性(Keep sections 保持自定義的代碼不會被覆蓋)
實體類在每一次生成器運行的時候都會被覆蓋。greenDao允許添加自定義的代碼到實體,通過“keep” ,可以滿足它們。在schema中使用enableKeepSectinsByDefault(),或者setHasKeepSections(true)在選中的實體中。一旦使用,3個獨立的部分會在實體中生成:
1
2
3
4
5
6
7
8
|
// KEEP INCLUDES - put your custom includes here
// KEEP INCLUDES END
...
// KEEP FIELDS - put your custom fields here
// KEEP FIELDS END
...
// KEEP METHODS - put your custom methods here
// KEEP METHODS END
|
現在,你可以在 KEEP [...] and KEEP [...] END.中寫入你的代碼。注意,不要修改KEEP注釋。在該范圍的代碼會在代碼重新生成的時候不被覆蓋。對於備份或者提交代碼時出現的意外錯誤,這是一個不錯的選擇解決方案。
第四篇 非技術類的常見問題
通常的疑問
為什么greenDao使用的是code generation,而不是注解?
對於greenDao,代碼生成是非常合理的。在Android平台上,基於注解的解決方式是有缺陷的:它們不得不依賴於元數據的解析和反射。特別是反射,會顯著降低ORM工具的性能。另一方面,greenDao會為Android生成優化過的代碼。這些生成的代碼完全避免了反射。這也是greenDao如此快的主要原因。另一個優勢是大小。
greenDao的核心lib是非常小的(在100K以下,包括單元測試)。這是因為對於一些ORM的內部邏輯都在generator中,而不是在核心庫中。
greenDao包含了:DaoCore,DaoGenerator和DaoTest。DaoCore是需要你加入到android項目中的,在Apache License 2版本以下是許可的。
DaoGenerator是java程序,負責實體的生成,DAO和其它的文件。DaoTest是單元測試用例額,確保了greenDao本身和其穩定性。
DaoGenerator 和DaoTest 在GPL V3以下是可用的。這些許可條款可以滿足大部分的開發者使用。
第五篇 查詢
查詢會返回符合某些特定標准的實體。你可以使用原始的SQL定制查詢語句,或者更好的方式:使用GreenDao的QueryBuilder API。該查詢也支持lazy-loading的結果集。這樣在操作大量結果集的時候可以節省內存和性能。
QueryBuilder
QueryBuilder可以幫助你構建自定義的查詢語句,而不使用SQL的情況。並不是每個人都喜歡書寫SQL語句,當然很容易就會出一些錯,這些錯誤只有在運行的時候才會被發現。而QueryBuilder很容易使用,節省了你書寫SQL語句的時間。當然,由於語法的檢驗是在編譯時才執行,所以在查詢語句中發現bug是很困難的。
QueryBuilder的編譯時間會檢驗屬性的引用,這樣能夠在greenDao后面,通過代碼生成的方法發現bug。
比如:查找所有以“Joe”為first name 的用戶,並以last name排序:
1
2
3
4
|
List joes = userDao.queryBuilder()
.where(Properties.FirstName.eq(
"Joe"
))
.orderAsc(Properties.LastName)
.list();
|
嵌套情況:
獲取用戶名字為“Joe”並且在1970年9月之后出生的用戶
這里要說明下:user 的birthday對於year,month,和day是一個分離的屬性。我們可以以一種更正常的方式表達這種條件:
First name is “Joe” AND (year of birth is greater than 1970 OR (year of birth is 1970 AND month of birth is equal to or greater than 10 (October).
1
2
3
4
5
|
QueryBuilder 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 youngJoes = qb.list();
|
Query 和 LazyList
Query類代表一個可以多次執行的查詢。當你使用QueryBuilder之一的方法去獲取結果的時候,QueryBuilder內部使用了Query 類。
如果你想運行更多相同的查詢,你應該調用build()在QueryBuilder上,去創建Query,而不是執行它。
greenDao支持唯一的結果和結果列表。如果你想得到一個唯一的結果,可以調用Query或者QueryBuilder的unique()方法,這樣在沒有匹配條件的時候會返回一個唯一的結果,而不是null。如果你希望禁止用例中返回null,可以調用uniqueOrThrow(),該方法會保證返回一個非null的實體。否則就會拋出一個DaoException。
如果你期望一次性返回多個實體,可以使用以下方法:
list():所有的實體被加載到內存中。該結果通常是一個沒有magic involved的ArrayList。使用起來最簡單。
listLazy():實體按照需求加載進入內存。一旦列表中的一個元素被第一次訪問,它將被加載同時緩存以便以后使用。必須close。
ListLasyUncached(): 一個“虛擬”的實體列表:任何對列表元素的訪問都會導致從數據庫中加載,必須close。
listIterator(): 遍歷通過需要的時候加載(lazily)獲得的結果,數據沒有緩存,必須close。
listLazy, listLazyUncached 和 listIterator類使用了greenDao的LazyList類。為了根據需求加載數據,它持有了一個數據庫cursor的引用。
這是做是為了確保關閉 lazy list和iterators(通常在try/finally 代碼塊中)。
一旦有的元素被訪問或者遍歷過,來自lsitLazy()的cache lazy list和來自listIterator()方法的lazy iterator將會自動關閉cursor。
然而,如果list的處理過早的完成了,你應該調用 close()手動關閉。
多次執行查詢
一旦你使用QueryBuilder構建了一個query,該query對象以后可以被重復使用。這種方式比總是重復創建query對象要高效。
如果query的參數沒有變更,你只需要再次調用list/unique方法即可。如果有參數變更,你就需要調用setParameter方法處理每一個變更的參數。
現在,個別參數由基於零的參數索引尋址。該下標基於你傳遞到querybuilder的參數。
使用query對象獲取出生在1970年 並且 first name 為 joe 的用戶:
1
2
3
4
|
Query query = userDao.queryBuilder().where(
Properties.FirstName.eq(
"Joe"
), Properties.YearOfBirth.eq(1970))
.build();
List joesOf1970 = query.list();
|
使用query對象,可以查詢
1
2
3
|
query.setParameter(0,
"Maria"
);
query.setParameter(1, 1977);
List mariasOf1977 = query.list();
|
在多個線程中執行查詢
如果你在多線程中使用了查詢,你必須調用query的 forCurrentThread()為當前的線程獲得一個query實例。從greenDAO 1.3開始,
query的實例化被綁定到了那些創建query的線程身上。這樣做保證了query對象設置參數時的安全性,避免其他線程的干擾。如果其他線程
試着在query對象上設置參數或者執行查詢綁定到了其它線程,將會拋出異常。這樣一來,你就不需要一個同步語句了。
事實上你應該避免使用lock,因為如果在並發的事務中使用了同一個query對象,可能會導致死鎖。
為了完全避免那些潛在的死鎖問題,greenDAO 1.3 引入了forCurrentThread方法,它會返回一個query對象的thread—local實例,該實例
在當前的線程中使用是安全的。當每一次調用 forCueerntThread()的時候,該參數會在builder構建query的時候,設置到初始化參數上。
原始的查詢
這里有兩種方式執行原始的SQL去獲取實體。較好的一種方式是使用QueryBuilder 和 WhereCondition.StringCondition。
使用這個方法,你可以為 query builder 的 WHERE 子句傳遞任何SQL片段。
下面是一個笨拙的例子展示如果使用這種方式進行一個替代聯合查詢的子查詢。
1
2
3
|
Query query = userDao.queryBuilder().where(
new
StringCondition(
"_ID IN "
+
"(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)"
).build();
|
該示例中query Builder沒有提供你需要的特性,你可以回到原始的queryRaw或者queryRawCreate方法。它們允許你傳遞原始的SQL字符串,這些字符串會被添加到SELECT 和實體列后面。這種方式,你可以擁有一個 WHERE 和 ORDER BY 語句查詢實體。這種實體表可以通過別名“T”引用。
下面的例子展示了如何創建一個query:使用聯合獲取名為“admin”的group的users
1
|
Query query = userDao.queryRawCreate(
", GROUP G WHERE G.NAME=? AND T.GROUP_ID=G._ID"
,
"admin"
);
|
提示:
你可以通過生成的常量引用表或者列名。這樣建議是為了避免錯別字,因為編譯器會檢驗這些名字。在任何實體的DAO,你可以發現 TABLENAME 持有着數據庫的名字和一個內部類“Properties”.它的所有屬性都是常量。
刪除查詢
批量刪除不刪除單獨的實體,但所有的實體要匹配一些准則。為了執行批量刪除,創建一個QueryBuilder,調用它的buildDelete方法,它會返回一個DeleteQuery。
這部分的API可能會在以后有所變化,比如添加一些更加便利的方法。記住,批量刪除現在不會影響到identity scope中的實體。在它們被通過ID訪問之前(load 方法)
如果它們被緩存了,你可以激活那些將要被刪除的實體。如果導致了一些使用的問題。你可以考慮清除identity scope。
查詢故障處理
如果你的query沒有返回期望的結果,這里有兩個靜態的flag,可以開啟QueryBuilder身上的SQL和參數的log。
1
2
|
QueryBuilder.LOG_SQL =
true
;
QueryBuilder.LOG_VALUES =
true
;
|
它們會在任何build方法調用的時候打印出SQL命令和傳入的值。這樣你可以對你的期望值進行對比,或許也能夠幫助你復制SQL語句到某些
SQLite 數據庫的查看器中,執行並獲取結果,以便進行比較。