greenDao:操作數據庫的開源框架


greenDAO: Android ORM for your SQLite database

 

1. greenDao庫獲取

  英文標題借鑒的是greendrobot官網介紹greenDao時給出的Title,鏈接:http://greenrobot.org/greendao/,有興趣的可以點進去(不用外網),里面有使用說明及相關資料的下載接口。greenDao目前版本已經更新到3.x.x,與2.x.x相比支持的功能發生了很大的改變(后面會提到),所以強烈推薦使用最新的版本。如3.1.1版本的獲取方法如下:

  a. 通過鏈接進入官網,點擊右邊的greenDao按鈕進入其GitHub頁面:

  b. greenDao GitHub頁面上的“Add greenDAO to your project”模塊是針對Android Studio開發者的引用庫添加說明,在app和project build.gradle文件中分別編寫以下代碼並進行同步(Studio界面上有一個同步按鈕,不會的自己查)就可以使用greenDao提供的方法來操作SQLite數據庫了:

 1 buildscript {
 2     repositories {
 3         mavenCentral()
 4     }
 5     dependencies {
 6         classpath 'org.greenrobot:greendao-gradle-plugin:3.1.0'
 7     }
 8 }
 9 //上面是在project的build.gradle文件中,下面是在app的build.gradle文件中,具體見后面實例
10 apply plugin: 'org.greenrobot.greendao'
11 
12 dependencies {
13     compile 'org.greenrobot:greendao:3.1.0'
14 }

  當然,人家也說了“Please ensure that you are using the latest versions by checking here and here”,即點進去瞧瞧版本更新到多少了,條件允許的話盡量用最新的。目前這兩個鏈接分別對應版本3.1.1和3.1.0,如果在Studio中想用3.1,1,就將上述代碼中的3.1.0改為3.1.1。

  c. 以版本3.1.1為例,點擊checking here后會進入其資源下載頁面(包括Eclipse開發者喜愛的jar包文件):


  如果使用的集成開發環境是Eclipse,那么會與Studio不同,在導入庫方面還是較原始(需要自行下載jar包並添加到項目中,有些jar包難找的時候體會最深),點擊圖中的jar按鈕即可開始下載。

  d. 想偷懶的可以直接點擊鏈接進行jar包的獲取:http://files.cnblogs.com/files/tgyf/greendao-3.1.1.rar下載后不用解壓,直接將后綴改為“.jar”即可。

  e. 注意,還需要用同樣的方法下載jar包freemarker和greendao-generator,同樣在上面的下載頁面搜索就好:

  http://files.cnblogs.com/files/tgyf/freemarker-1.19.2.rar

  http://files.cnblogs.com/files/tgyf/greendao-generator-3.1.0.rar,下載后處理方式如上面紅色字體描述——改后綴名"rar"->"jar"。

  這三個jar包的作用分別是:

  greendao——SQLite數據庫操作核心,對一些常用的方法進行了封裝;

  generator——根據需求表對應實體類生成相關的greendao類,如上面提到的DaoSession等;

  freemarker——將自動生成的類以文本形式輸出;

  至於在開發中用哪個版本合適,視實際情況而定。能用Google親生的Studio最好不過了,不用下載jar包,還有人家對eclipse已經不進行新特性的支持了。

 

2. greenDao操作SQLite數據庫

  前面提到,新版本與老版本在支持的功能上有了很大的提升,最明顯的地方就是gen目錄下文件的生成時機或者說是方法。有老版本使用經驗的小伙伴應該清楚,要想在android中利用greenDao相關類來處理SQLite數據庫,必須先在另一個Java工程中建立需求表的實體類,生成gen目錄下的文件(如xxxDao、DaoSession以及DaoMaster等),然后將gen目錄拷入android工程中方能使用。當然,也可以將gen目錄的生成路徑直接定位到android工程中,免得每次修改實體類並重新編譯后都需要拷貝這些文件。

  不過,這些問題在新版中就不存在了,因為實體類的定義與gen目錄的生成都可以直接在android工程中完成。下面就來看看Studio中具體是怎么回事吧,至於對舊版本的用法感興趣的自己去研究咯。

  2.1 在Studio中新建一個project greendaoTest,過程中根據需求設置各種屬性,默認布局為顯示一個字串“Hello World!”。其實如果只是對greenDao框架進行學習與測試,可以利用數據庫查詢工具或log打印內容來檢測操作結果,不是必須將內容顯示在布局組件中。

  2.2 在項目中添加庫依賴代碼,完整文件代碼分別如下:

project build.gradle:

 1 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 2 
 3 buildscript {
 4     repositories {
 5         jcenter()
 6     }
 7     dependencies {
 8         classpath 'com.android.tools.build:gradle:2.1.0'
 9         classpath 'org.greenrobot:greendao-gradle-plugin:3.1.1'  //greendao
10 
11         // NOTE: Do not place your application dependencies here; they belong
12         // in the individual module build.gradle files
13     }
14 }
15 
16 allprojects {
17     repositories {
18         jcenter()
19     }
20 }
21 
22 task clean(type: Delete) {
23     delete rootProject.buildDir
24 }

app build.gradle文件:

 1 apply plugin: 'com.android.application'
 2 apply plugin: 'org.greenrobot.greendao'  //greendao
 3 
 4 android {
 5     compileSdkVersion 24
 6     buildToolsVersion "24.0.0"
 7 
 8     greendao{  //greendao
 9         schemaVersion 1
10         targetGenDir 'src/main/java'
11     }
12     defaultConfig {
13         applicationId "com.learn.greendaotest"
14         minSdkVersion 22
15         targetSdkVersion 24
16         versionCode 1
17         versionName "1.0"
18     }
19     buildTypes {
20         release {
21             minifyEnabled false
22             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23         }
24     }
25 }
26 
27 dependencies {
28     compile fileTree(dir: 'libs', include: ['*.jar'])
29     testCompile 'junit:junit:4.12'
30     compile 'com.android.support:appcompat-v7:24.0.0'
31     compile 'org.greenrobot:greendao:3.1.1'  //greendao
32 }

  和greenDao相關的語句,末尾都加了注釋“greenDao”,方便區分與維護。可以看出,這里導入的是最新的版本3.1.1。

  2.3 在與主類文件MainActivity.java同一目錄下定義表——實體類Student,代碼如下:

 1 package com.learn.greendaotest;
 2 
 3 import org.greenrobot.greendao.annotation.Entity;
 4 import org.greenrobot.greendao.annotation.Id;
 5 import org.greenrobot.greendao.annotation.Property;
 6 
 7 @Entity
 8 public class Student {
 9     @Id
10     private Long id;
11     @Property(nameInDb = "NAME")
12     private String name;
13     @Property(nameInDb = "AGE")
14     private int age;
15 }

  簡單起見,聲明了三個屬性:id、name、age,分別表示學生的序號、名字、年齡,其中序號是唯一的。當然,如果需要,可以設置序號的順序、屬性的非空等限制條件。定義好實體類之后,只需要運行程序(或者點擊build菜單中的make project/make module 'app'選項)就可以生成相關的greenDao操作類了。

  首先,仍舊看Student這個類,發現多了以下代碼:

 1 @Generated(hash = 352757281)
 2 public Student(Long id, String name, int age) {
 3     this.id = id;
 4     this.name = name;
 5     this.age = age;
 6 }
 7 @Generated(hash = 1556870573)
 8 public Student() {
 9 }
10 public Long getId() {
11     return this.id;
12 }
13 public void setId(Long id) {
14     this.id = id;
15 }
16 public String getName() {
17     return this.name;
18 }
19 public void setName(String name) {
20     this.name = name;
21 }
22 public int getAge() {
23     return this.age;
24 }
25 public void setAge(int age) {
26     this.age = age;
27 }

  代碼並不陌生,帶參數與不帶參數的構造函數,以及三個屬性的set/get方法,但是這些是自動生成的(包括接下來要講的三個類,會貼出部分關鍵代碼,所有的大家可以下載文末提供的源碼)。

  表操作類StudentDao,可以說是greenDao生成的和Student類最親近的一個類了,關鍵的地方有以下幾個:

  a. 內部類——屬性類Properties,關於屬性Property,greenDao還提供了一系列好用的方法,后面會提到。

1 public static class Properties {
2     public final static Property Id = new Property(0, Long.class, "id", true, "_id");
3     public final static Property Name = new Property(1, String.class, "name", false, "NAME");
4     public final static Property Age = new Property(2, int.class, "age", false, "AGE");
5 }

  b. 表創建與刪除函數——createTable(Database db, boolean ifNotExists)和dropTable(Database db, boolean ifExists),操作時是否做表存在判斷可以通過傳入參數“ifExists”來決定。由於調用對象就是表對應的類本身,所以指定表所屬數據庫對象就好(不用關心表名是什么)。

 1 /** Creates the underlying database table. */
 2 public static void createTable(Database db, boolean ifNotExists) {
 3     String constraint = ifNotExists? "IF NOT EXISTS ": "";
 4     db.execSQL("CREATE TABLE " + constraint + "\"STUDENT\" (" + //
 5             "\"_id\" INTEGER PRIMARY KEY ," + // 0: id
 6             "\"NAME\" TEXT," + // 1: name
 7             "\"AGE\" INTEGER NOT NULL );"); // 2: age
 8 }
 9 
10 /** Drops the underlying database table. */
11 public static void dropTable(Database db, boolean ifExists) {
12     String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"STUDENT\"";
13     db.execSQL(sql);
14 }

  可以看出,自動生成的表創建代碼將屬性id、name、age類型設置為了數據庫中的類型INTEGER、TEXT、INTEGER,id唯一PRIMARY KEY,age NOT NULL,而大寫的名字是我們在定義Student類時自己聲明的,如name:@Property(nameInDb = "NAME")。

  c. 實體對象獲取方法,提供了兩種方式:返回臨時引用和直接給引用參數賦值,而后者對屬性操作調用的是Student類中的set(Object object)方法。

 1 @Override
 2 public Student readEntity(Cursor cursor, int offset) {
 3     Student entity = new Student( //
 4         cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
 5         cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1), // name
 6         cursor.getInt(offset + 2) // age
 7     );
 8     return entity;
 9 }
10  
11 @Override
12 public void readEntity(Cursor cursor, Student entity, int offset) {
13     entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));
14     entity.setName(cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1));
15     entity.setAge(cursor.getInt(offset + 2));
16  }

  類DaoSession,簡單點說就是用來獲取類StudentDao實例的,留意其構造函數和實例返回方法即可。

 1 public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
 2         daoConfigMap) {
 3     super(db);
 4 
 5     studentDaoConfig = daoConfigMap.get(StudentDao.class).clone();
 6     studentDaoConfig.initIdentityScope(type);
 7 
 8     studentDao = new StudentDao(studentDaoConfig, this);
 9 
10     registerDao(Student.class, studentDao);
11 }
12 
13 public StudentDao getStudentDao() {
14     return studentDao;
15 }

  類DaoMaster,是和org.greenrobot.greendao.database.DatabaseOpenHelper最親近的類,那么到這里自定義的實體類Student和greenDao封裝的數據庫處理類終於聯系上了。該類中又定義了兩個靜態內部類OpenHelper和DevOpenHelper,前者重載了類DatabaseOpenHelper的表建立方法onCreate(Database db),后者重載了表更新方法onUpgrade(Database db, int oldVersion, int newVersion)。

 1 /**
 2  * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
 3  */
 4 public static abstract class OpenHelper extends DatabaseOpenHelper {
 5     public OpenHelper(Context context, String name) {
 6         super(context, name, SCHEMA_VERSION);
 7     }
 8 
 9     public OpenHelper(Context context, String name, CursorFactory factory) {
10         super(context, name, factory, SCHEMA_VERSION);
11     }
12 
13     @Override
14     public void onCreate(Database db) {
15         Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
16         createAllTables(db, false);
17     }
18 }
19 
20 /** WARNING: Drops all table on Upgrade! Use only during development. */
21 public static class DevOpenHelper extends OpenHelper {
22     public DevOpenHelper(Context context, String name) {
23         super(context, name);
24     }
25 
26     public DevOpenHelper(Context context, String name, CursorFactory factory) {
27         super(context, name, factory);
28     }
29 
30     @Override
31     public void onUpgrade(Database db, int oldVersion, int newVersion) {
32         Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
33         dropAllTables(db, true);
34         onCreate(db);
35     }
36 }

  還定義了刪除所有表的方法dropAllTables(Database db, boolean ifExists),由於我們之前只定義了一個實體類(表),所以這里只有StudentDao類刪除表的語句。

/** Drops underlying database table using DAOs. */
public static void dropAllTables(Database db, boolean ifExists) {
    StudentDao.dropTable(db, ifExists);
}

  創建表也是如此:

1 /** Creates underlying database table using DAOs. */
2 public static void createAllTables(Database db, boolean ifNotExists) {
3     StudentDao.createTable(db, ifNotExists);
4 }

  以及和DaoSession類獲取StudentSao實例相似的newDevSession(Context context, String name)方法,用以獲取DaoSession類實例。

1 public static DaoSession newDevSession(Context context, String name) {
2     Database db = new DevOpenHelper(context, name).getWritableDb();
3     DaoMaster daoMaster = new DaoMaster(db);
4     return daoMaster.newSession();
5 }

   而方法newSession()繼而會調用DaoSession類的三參構造函數:

1 public DaoSession newSession() {
2     return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
3 }

  所以,不用擔心獲取DaoSession實例時該怎樣傳入參數,因為DaoMaster及其父類都已經完成了。如第三個參數daoConfigMap,在DaoMaster類中找不到,其父類AbstractDaoMaster完成了聲明與初始化工作。

1 protected final Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap;
2 
3 public AbstractDaoMaster(Database db, int schemaVersion) {
4     this.db = db;
5     this.schemaVersion = schemaVersion;
6 
7     daoConfigMap = new HashMap<Class<? extends AbstractDao<?, ?>>, DaoConfig>();
8 }

   2.4 准備工作差不多了,可以開始操作數據庫了。由於只是進行簡單的測試,所以不涉及界面,直接通過log打印的形式來描述結果了。

  2.4.1 建立數據庫及實體類對應的表,第二個參數指定了數據庫的名稱,第三個參數(類型CursorFactory)一般為null即可。

1 DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(getApplicationContext(), "student.db", null);

  結合之前給出的DaoMaster類代碼,這句代碼其實做了很多事情。執行DevOpenHelper類構造函數DevOpenHelper(Context context, String name, CursorFactory factory)時會調用OpenHelper類構造函數OpenHelper(Context context, String name, CursorFactory factory),進而又會調用基類DatabaseOpenHelper構造函數DatabaseOpenHelper(Context context, String name, CursorFactory factory, int version)與被重載方法onCreate(Database db),最后調用DaoMaster類的createAllTables(Database db, boolean ifNotExists)建立所有表。

  數據庫與表格文件都建立好了,下面一步到位獲取StudentDao類實例:

1 DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDb());
2 DaoSession daoSession = daoMaster.newSession();
3 StudentDao studentDao = daoSession.getStudentDao();

  2.4.2 插入兩條數據,一般情況下第一個參數id是不需要特殊指定的(null即可),會自動從1開始增序填入表中。

1 Student studentLl = new Student(null, "Lilei", 21);
2 Student studentHmm = new Student(null, "Hanmeimei", 21);
3 studentDao.insert(studentLl);
4 studentDao.insert(studentHmm);

  關於insert(T entity)方法,在AbstractDao類中的定義如下:

1 /**
2  * Insert an entity into the table associated with a concrete DAO.
3  *
4  * @return row ID of newly inserted entity
5  */
6 public long insert(T entity) {
7     return executeInsert(entity, statements.getInsertStatement(), true);
8 }

  再往下追蹤能看得到不同層次的源碼,這里不打算全部列一遍。關注一下該方法的最后一句注釋:返回新插入實體在數據表中的行號,所以在對表中數據進行查詢之前,可以通過打印返回值來判斷插入是否成功。修改代碼,添加返回值獲取與打印:

long resultLl = studentDao.insert(studentLl);
long resultHmm = studentDao.insert(studentHmm);
showLog("row id of insert Lilei: "+resultLl);
showLog("row id of insert Hanmeimei: "+resultHmm);

  打印出的結果符合預期,表明插入成功了,因為之前已經執行過一次(row id為1和2),所以這里往后遞增為3和4。還可以看出id是從1開始的,而不像熟悉的數組下標是從0開始。

08-29 03:12:29.624 25326-25326/com.learn.greendaotest I/STUDENT: row id of insert Lilei: 3
08-29 03:12:29.624 25326-25326/com.learn.greendaotest I/STUDENT: row id of insert Hanmeimei: 4

  showLog是什么鬼?自定義的一個用來打印log的方法,免得每次都需要傳入TAG標記以及必須傳入String類型參數值,適當的時候利用封裝偷偷懶。

1 private final String TAG = "STUDENT";
2 private void showLog(Object message) {
3     Log.i(TAG, ""+message);
4 }

  同樣地,Toast提示語可以封裝成:

private void showToast(Object message) {
    Toast.makeText(MainActivity.this, ""+message, Toast.LENGTH_SHORT).show();
}

  2.4.3 數據查詢,並將結果打印出來,看看是否真的插入了四個實體值。

1 List<Student> listQuery = studentDao.queryBuilder().where(StudentDao.Properties.Id.between(1, 8)).limit(5).build().list();
2 for (Student student : listQuery) {
3     showLog("id: "+student.getId());
4     showLog("name: "+student.getName());
5     showLog("age: "+student.getAge());
6 }

08-29 03:36:28.418 13742-13742/com.learn.greendaotest I/STUDENT: id: 1
08-29 03:36:28.418 13742-13742/com.learn.greendaotest I/STUDENT: name: Lilei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: id: 2
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: name: Hanmeimei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: id: 3
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: name: Lilei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: id: 4
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: name: Hanmeimei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21

  解釋一下where(WhereCondition condition)和limit(int limit)這兩個方法,其實看代碼也能明白大概了。

  where():添加數據查詢的篩選條件,除了這里的ID范圍,還可以是字串包含或不包含某個字符/字串等;

  limit():只將查詢結果中的前五條賦給List對象,其余的忽略;

  前面提到過,屬性類Property有很多好用的方法。比如這里的between(Object value1, Object value2)限定值的區間,查看該類的定義還可以發現eq(Object value)方法是用來比較值是否相等,等等返回值為WhereCondition實例的方法,說它是為了where()方法而生也不為過。

  2.4.4 刪除數據,過程和查詢類似,只不過將符合條件的實體值刪除而已。

1 List<Student> listDelete = (List<Student>) studentDao.queryBuilder().where(StudentDao.Properties.Id.le(3)).build().list();
2 for (Student student : listDelete) {
3     studentDao.delete(student);
4 }

  以上代碼目的為刪除id小於等於3的行,那么再次輸出結果只剩下一條數據了:

08-29 03:59:10.631 2257-2257/com.learn.greendaotest I/STUDENT: id: 4
08-29 03:59:10.631 2257-2257/com.learn.greendaotest I/STUDENT: name: Hanmeimei
08-29 03:59:10.631 2257-2257/com.learn.greendaotest I/STUDENT: age: 21

  2.4.5 更新數據,目前Student表中還剩下一條數據,那就將它的name字段給為Lilei吧。

1 Student stu = studentDao.queryBuilder()
2         .where(StudentDao.Properties.Id.ge(4), StudentDao.Properties.Name.like("%mei%")).build().unique();
3 if (stu == null) {
4     showToast("實體不存在!");
5 }else{
6     stu.setName("Lilei");
7     studentDao.update(stu);
8 }

  注意,這里調用where()方法時傳入了兩個條件參數,其實還可以傳更多。參數的意義分別為id大於等於4,name字段中間部分的值為“mei”。最后的unique()方法使結果唯一,即查詢到一條馬上停止查詢過程並返回結果Student類實例,而不是List<Student>。如果沒有找到則彈出Toast字串提醒用戶,否則進行名字的修改。現在的查詢結果就變成下面這樣了:

08-29 04:08:48.788 10993-10993/? I/STUDENT: id: 4
08-29 04:08:48.788 10993-10993/? I/STUDENT: name: Lilei
08-29 04:08:48.788 10993-10993/? I/STUDENT: age: 21

  2.4.6 升級數據庫,涉及到的重載方法onUpgrade(Database db, int oldVersion, int newVersion)已經在類DaoMaster中實現了,接下來需要做的是改變數據庫版本與實體類屬性。

  在app build.gradle文件將數據庫版本值由1改為2:

1 greendao{  //greendao
2     schemaVersion 2
3     targetGenDir 'src/main/java'
4 }

  在實體類Student中添加性別屬性sex(String型),

1 @Id
2 private Long id;
3 @Property(nameInDb = "NAME")
4 private String name;
5 @Property(nameInDb = "AGE")
6 private int age;
7 @Property
8 private String sex;

  接着執行程序即可。可以發現:性別屬性沒有指定其在StudentDao->Properties類中的別名(在數據表中的列名),而自動生成的屬性別名為“SEX”。可知若沒有顯式聲明,那么就按全大寫來處理,否則按照指定的賦值,但是id除外(別名為_id)。

 

3. 總結

  本文中的測試案例都是很簡單的情況,實際開發時需要處理的數據肯定要多得多。熟悉了greenDao框架的思想及用法之后,處理一般性的數據集時會簡單、高效很多,通過面向對象的思想為實體類生成對應的操作類,結構清晰,易維護。

  最后給出項目下載鏈接:http://files.cnblogs.com/files/tgyf/greendaoTest.rar

 


免責聲明!

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



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