Android數據庫greenDAO框架用法和源碼分析

greenDAO簡介

greenDAO是一種Android數據庫ORM(object/relational mapping)框架,與OrmLite、ActiveOrm、LitePal等數據庫相比,單位時間內可以插入、更新和查詢更多的數據,而且提供了大量的靈活通用接口。

如何開始

greenDAO需要提前生成Entity、DAO等文件,因此需要先建立一個java工程用於生成上述文件。具體可以參照 GitHub源碼

1、 在Android Studio中選擇File -> New -> New Module -> Java Library建立greenDAO Generate工程

2、 在新建的Java工程中新建一個Java類,該Java類用於生成項目所需的Entity、DAO等文件,以下是該類的模板代碼:

public static void main(String[] args) throws Exception { Schema schema = new Schema(1000, "de.greenrobot.daoexample"); addNote(schema); new DaoGenerator().generateAll(schema, "./DaoExample/src/main/java"); } private static void addNote(Schema schema) { Entity note = schema.addEntity("Note"); note.addIdProperty().primaryKey().autoincrement(); note.addStringProperty("text").notNull(); note.addStringProperty("comment"); note.addDateProperty("date"); }

在main方法中,

Schema schema = new Schema(3, "de.greenrobot.daoexample");

該方法第一個參數用來更新數據庫版本號,第二個參數爲要生成的DAO類所在包路徑。

然後進行建表和設置要生成DAO文件的目標工程的項目路徑。

addNote(schema); new DaoGenerator().generateAll(schema, "./DaoExample/src/main/java");

最後生成的文件會在目錄./DaoExample/src/main/java/de/greenrobot/daoexample下看到。

創建一個實體類Entity就是對應一張表,默認表名就是類名,也可以自定義表名

Entity note = schema.addEntity("Note"); // 默認表名爲類名 note.setTableName("CustomNote"); // 自定義表名

greenDAO會自動根據實體類屬性創建表字段,並賦予默認值。例如在數據庫方面的表名和列名都來源於實體類名和屬性名。默認的數據庫名稱是大寫使用下劃線分隔單詞,而不是在Java中使用的駝峯式大小寫風格。例如,一個名爲“CREATIONDATE”屬性將成爲一個數據庫列“CREATION_DATE”。

可以設置一個自增長ID列爲主鍵,也可以設置其他各種類型的屬性:

note.addIdProperty().primaryKey().autoincrement(); // 自增長ID爲主鍵 note.addStringProperty("text").notNull(); // text列不能爲空

3、 最後還需要在Java工程下的build.gradle文件中引入greendao-generator

compile 'de.greenrobot:greendao-generator:2.1.0'

4、 執行Java工程,就可以生成項目所需的各種Entity、DAO等文件

5、 Android工程還需要引入greendao

compile 'de.greenrobot:greendao:2.1.0' 數據庫常用操作

在正式開始進行增刪改查操作前還需要簡單的初始化,代碼如下:

DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db", null); db = helper.getWritableDatabase(); daoMaster = new DaoMaster(db); daoSession = daoMaster.newSession(); noteDao = daoSession.getNoteDao();

其中notes-db爲數據庫名稱,DevOpenHelper文件繼承SQLiteOpenHelper,數據庫的創建和升級就是在其中完成,GreenDAO已經默認實現了,代碼如下:

public static abstract class OpenHelper extends SQLiteOpenHelper { public OpenHelper(Context context, String name, CursorFactory factory) { super(context, name, factory, SCHEMA_VERSION); } @Override public void onCreate(SQLiteDatabase db) { Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION); createAllTables(db, false); } } /** WARNING: Drops all table on Upgrade! Use only during development. */ public static class DevOpenHelper extends OpenHelper { public DevOpenHelper(Context context, String name, CursorFactory factory) { super(context, name, factory); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables"); dropAllTables(db, true); onCreate(db); } }

可以看到在onCreate方法中調用了createAllTable方法,顧名思義就是創建所有的數據表,而在onUpgrade方法中先是刪除所有的數據表,然後再調用onCreate方法,也可以在onUpgrade方法中實現自定義的數據庫升級操作。

根據DevOpenHelper可以得到SQLiteDatabase對象,SQLiteDatabase是Android原生的數據庫操作類,該類提供了大量操作數據庫的方法,如果想在項目中調用Android原生的sql語句就可以用該類實現。

此外greenDAO還提供了兩個類,一個是DaoMaster,一個是DaoSession,關於這兩個類與各DAO文件的關係見下圖:

greenDAO類關係圖

DaoMaster包含了DevOpenHelper,上述createAllTables(db, false)和dropAllTables(db, true)也是DaoMaster提供的,DaoMaster可以新建DaoSession,DaoSession管理所有的DAO文件,並提供相應的getter方法。

XyzDao文件提供了大量的數據庫操作方法,也是最常用的一個類,下面分別介紹數據庫增刪改查操作。

插入數據 Note note = new Note(null, "title", "comment", new Date()); noteDao.insert(note);

greenDAO不僅提供了插入單條數據的方法,還提供了批量插入的方法,代碼如下:

noteDao.insertInTx((Note[])noteList.toArray(new Note[noteList.size()]));

如果數據庫中已有要插入的數據,那麼上面的插入方法就會失敗,可以調用insertOrReplace和insertOrReplaceInTx方法。跟蹤源碼可以發現,insert最終執行的sql語句是"INSERT INTO ...",而insertOrReplace執行的sql語句是"INSERT OR REPLACE INTO ..."。

刪除數據 noteDao.delete(note); noteDao.deleteByKey(note.getId()); noteDao.deleteByKeyInTx(deleteList); noteDao.deleteAll();

delete方法需要傳入Entity對象,deleteByKey方法需要傳入主鍵,此處主鍵就是Note中的id屬性,也即Java Generate工程中的

note.addIdProperty().primaryKey().autoincrement();

deleteByKeyInTx批量刪除,參數爲List,其包含需要刪除的Entity的id,deleteAll表示刪除所有數據。

修改數據

更新單條數據:

note.setText("update_title"); note.setComment("update_comment"); noteDao.update(note);

批量更新數據:

noteDao.updateInTx(noteList); 查詢數據 List<Note> list1 = noteDao.queryRaw("where _id = ?", new String[]{"20"}); List<Note> list2 = noteDao.queryBuilder() .where(NoteDao.Properties.Id.ge(10)) .limit(10000) .offset(0) .orderAsc(NoteDao.Properties.Date) .list(); QueryBuilder<Note> qb = noteDao.queryBuilder(); qb.where(qb.and(NoteDao.Properties.Id.between(10, 15), NoteDao.Properties.Comment.eq("comment"))).list();

queryRaw基本上就是對Android原生的查詢方法的簡單封裝,跟蹤queryRaw查看其具體的實現,代碼如下:

public List<T> queryRaw(String where, String... selectionArg) { Cursor cursor = db.rawQuery(statements.getSelectAll() + where, selectionArg); return loadAllAndCloseCursor(cursor); }

可以看到queryRaw就是將在原生的queryRaw的查詢語句前加了"SELECT * FROM Note ",因此只需要再傳入具體的查詢條件即可。

queryBuilder方法採用build鏈式結構可以靈活地添加各種查詢相關的約束,where包含具體的查詢條件,limit表示查詢數據的條目數量,offset表示查詢數據的起始位置,orderAsc表示根據某一列進行排序,最後list得到查詢結果。

greenDAO還提供了多重條件查詢。db.and表示查詢條件取"與",db.or表示查詢條件取"或"。

關聯查詢

關聯查詢屬於greenDAO比較高級的用法,目前greenDAO支持一對一、一對多,不支持多對多。

一對一

在greenDAO generator中建模時,必須使一個屬性作爲外鍵,使用這個屬性,你可以用Entity.addToOne方法增加to-one關係。 addToOne方法的參數是另一個實體,和本實體的外鍵屬性。

/** * Adds a to-one relationship to the given target entity using the given given foreign key property (which belongs * to this entity). */ public ToOne addToOne(Entity target, Property fkProperty) { if (protobuf) { throw new IllegalStateException("Protobuf entities do not support realtions, currently"); } Property[] fkProperties = {fkProperty}; ToOne toOne = new ToOne(schema, this, target, fkProperties, true); toOneRelations.add(toOne); return toOne; } /** Convenience for {@link #addToOne(Entity, Property)} with a subsequent call to {@link ToOne#setName(String)}. */ public ToOne addToOne(Entity target, Property fkProperty, String name) { ToOne toOne = addToOne(target, fkProperty); toOne.setName(name); return toOne; }

例如:user有一個photo屬性,user和photo都是普通實體

Entity customer = schema.addEntity("Customer"); customer.addIdProperty(); customer.addStringProperty("name").notNull(); Entity photo = schema.addEntity("Photo"); Property photoIdProperty = customer.addLongProperty("photoId").getProperty(); customer.addToOne(photo, photoIdProperty, "photo");

這樣就是customer有一個photo屬性,並且可以直接操作Photo對象,customer類具有Photo屬性的getPhoto/setPhoto方法。to-one關係中的getter方法在第一次加載目標實體的時候是懶漢式加載,之後的訪問將返回先前已解析的對象。

注意外鍵屬性(“photoId”)和實體對象的屬性(“Photo”)綁在一起。如果你改變了photoId,下一次調用getPhoto()的時候就會用更新之後的id重新解析Photo實體。同樣,如果設置了一個新的Photo實體,photoId屬性也會被更新。

一對多

在greenDAO中建立to-many模型的方法和數據庫中的操作類似,首先需要在目標實體中增加一個屬性,用於關聯To-many關係中的資源實體,然後使用這個屬性,添加到資源實體的To-many關係。

例如:客戶/訂單的例子,客戶可以有多個訂單,所以我們用To-Many關係模型,在數據庫中,在訂單表中創建customerID列,來創建1:N關係。這樣的話,就可以使用客戶的id查詢客戶的所有的訂單。

Entity customer = schema.addEntity("Customer"); customer.addIdProperty(); customer.addStringProperty("name").notNull(); Entity order = schema.addEntity("Order"); order.setTableName("ORDERS"); // "ORDER" is a reserved keyword order.addIdProperty(); Property orderDate = order.addDateProperty("date").getProperty(); Property customerId = order.addLongProperty("customerId").notNull().getProperty(); order.addToOne(customer, customerId); ToMany customerToOrders = customer.addToMany(order, customerId); customerToOrders.setName("orders"); customerToOrders.orderAsc(orderDate);

這樣,我們可以在客戶類中簡單的調用生成的getOrders()方法獲取訂單,同樣,也可以在訂單類中調用生成的getCustomer方法獲取客戶信息。

源碼分析

greenDAO有別於其他通過反射機制實現的ORM框架,greenDAO需要一個Java工程事先生成需要的文件,而在每一個DAO文件中都已經自動組裝好創建和刪除數據表的sql語句。代碼如下:

/** Creates the underlying database table. */ public static void createTable(SQLiteDatabase db, boolean ifNotExists) { String constraint = ifNotExists? "IF NOT EXISTS ": ""; db.execSQL("CREATE TABLE " + constraint + ""NOTE" (" + // ""_id" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id ""TEXT" TEXT NOT NULL ," + // 1: text ""COMMENT" TEXT," + // 2: comment ""DATE" INTEGER);"); // 3: date } /** Drops the underlying database table. */ public static void dropTable(SQLiteDatabase db, boolean ifExists) { String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + ""NOTE""; db.execSQL(sql); }

根據之前介紹的用法知道,數據庫需要提前做一些初始化

DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db", null); db = helper.getWritableDatabase(); daoMaster = new DaoMaster(db); daoSession = daoMaster.newSession(); noteDao = daoSession.getNoteDao();

此處的DevOpenHelper類的構造方法就包含了創建所有數據表的操作,代碼在上面數據庫常用用法處已經展示過,此處不再重複。

greenDAO的增刪改查方法有一些是在Android原生的操作方法上進行了封裝,比如說上面的查詢方法queryRaw就是對原生的queryRaw進行簡單的封裝,對於上面鏈式查詢的最終執行也是調用了Android原生的查詢操作。

public List<T> list() { checkThread(); Cursor cursor = dao.getDatabase().rawQuery(sql, parameters); return daoAccess.loadAllAndCloseCursor(cursor); }

同時還有一些方法是基於SQLiteStatement實現的,SQLiteStatement相比原生的execSQL方法還要快一些,並且最終執行時也開啓了事務,性能又提升了很多。下面是插入數據的最終實現方法:

private long executeInsert(T entity, SQLiteStatement stmt) { long rowId; if (db.isDbLockedByCurrentThread()) { synchronized (stmt) { bindValues(stmt, entity); rowId = stmt.executeInsert(); } } else { // Do TX to acquire a connection before locking the stmt to avoid deadlocks db.beginTransaction(); try { synchronized (stmt) { bindValues(stmt, entity); rowId = stmt.executeInsert(); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } } updateKeyAfterInsertAndAttach(entity, rowId, true); return rowId; }

可以看到先執行bindValues方法,該方法是一個抽象方法,需要業務方在DAO文件中實現,跟蹤至NoteDao文件查看該方法代碼如下:

@Override protected void bindValues(SQLiteStatement stmt, Note entity) { stmt.clearBindings(); Long id = entity.getId(); if (id != null) { stmt.bindLong(1, id); // 1爲索引值,id爲入庫的值 } stmt.bindString(2, entity.getText()); String comment = entity.getComment(); if (comment != null) { stmt.bindString(3, comment); } java.util.Date date = entity.getDate(); if (date != null) { stmt.bindLong(4, date.getTime()); } }

這樣就將SQLiteStatement需要的數據都進行了封裝,然後執行stmt.executeInsert()方法即可完成數據庫的插入操作。縱觀整個數據插入流程,greenDAO藉助SQLiteStatement完成了數據的插入,避免了其他框架利用反射拼裝sql語句而造成的執行效率低下的問題。

發佈了19 篇原創文章 · 獲贊 18 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章