「Android」GreenDao

譯文html

版本:greenDAO 3.2.2
寫在前面:
這個庫極大的優化了咱們使用SQLite數據庫,但對SQLite數據庫及其語法的掌握依舊是咱們須要作的, 不建議新手在沒使用過Android原生的數據庫API的狀況下就使用這個。
簡介:
greenDAO 是一款開源的面向 Android 的輕便、快捷的 ORM 框架,將 Java 對象映射到 SQLite   數據庫中,咱們操做數據庫的時候,不在須要編寫複雜的 SQL語句, 在性能方面,greenDAO 針對 Android 進行了高度優化,   最小的內存開銷 、依賴體積小 同時仍是支持數據庫加密。
何爲ORM?
對象關聯映射(英語:Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),是一種 程序設計技術,用於實現 面向對象編程語言裏不一樣 類型系統的數據之間的轉換。
它的特徵:
  • 最高性能(多是最快的Android ORM),咱們也是開源的
  • 容易使用
  • 最小的內存消耗
  • 庫很小(<100KB)可讓你構建花費的時間變低而且能夠避免65k方法的限制
  • 數據庫加密:greenDAO支持SQLCipher來保證您的用戶數據安全
  • 強大的社區:超過5000的GitHub stars證實了這是一個強大並活躍的社區
何爲SQLCipher?
    Android SQLite是不支持數據加密的,這樣對於用戶的數據來講是不安全的(不少手機都是Root過的,其能夠直接進入到/data/data/<package_name>/databases目錄下面),因此,咱們須要對其進行加密,一種是對內容進行加密(但數據庫的結構仍是能盡收眼底,同時這樣加密後搜索會是一個問題),一種是直接對SQLite數據庫進行加密,直接對數據庫文件進行加密就會用到SQLCipher,它是加密工具中的一種,它是免費的,其它的多爲收費。
    SQLCipher,徹底開源,託管在GitHub( https://github.com/sqlcipher/sqlcipher)上。
誰在用greenDAO?
    不少頂級的Android應用依賴於greenDAO,這些APP中有一些已經有超過1000萬的安裝量,咱們認爲,這在業界證實了它的可靠性。你能夠在AppBrain中查看當前的統計數據。

讓咱們開始使用它吧

先配置Gradlejava

// In your root build.gradle file:
buildscript {
    repositories {
      jcenter()
      mavenCentral() // add repository
  }
  dependencies {
      classpath 'com.android.tools.build:gradle:2.3.3'
      classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin
  }
}
// In your app projects build.gradle file:
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // apply plugin
dependencies {
    compile 'org.greenrobot:greendao:3.2.2' // add library
}

或許,你也能夠先看下官方的例子:

正文

Note實體和DAO實體

讓咱們跳進代碼:在 src 文件夾你會找到筆記的實體類, Note.java。它被保存在數據庫中幷包含做爲筆記一部分的全部數據,如id,筆記的文本和建立日期。android

@Entity(indexes = {
    @Index(value = "text, date DESC", unique = true)
})
public class Note {

    @Id
    private Long id;

    @NotNull
    private String text;
    private Date date;
    ...
}

總而言之,一個實體做爲一個類保存在數據庫中(例如 每一個對象對應一行)。一個實體包含映射到數據庫列的屬性。git

如今作這個項目,例如在Android Studio中 Build > Make project 。greenDAO觸發器會生成DAO類,如 like NoteDao.java, 這將幫助咱們添加筆記到數據庫。github

插入和刪除筆記

要學習如何添加一些筆記,請查看 NoteActivity 類. 首先,咱們必須爲咱們的 Note 類準備一個DAO對象,咱們在onCreate()中:sql

// get the note DAO
DaoSession daoSession = ((App) getApplication()).getDaoSession();
noteDao = daoSession.getNoteDao();

當用戶點擊添加按鈕的時候,方法 addNote() 被調用。這裏,咱們建立一個新的 Note 對象並經過DAO的 insert() 方法將其插入到數據庫中:數據庫

Note note = new Note();
note.setText(noteText);
note.setComment(comment);
note.setDate(new Date());
note.setType(NoteType.TEXT);
noteDao.insert(note);
Log.d("DaoExample", "Inserted new note, ID: " + note.getId());

注意當咱們建立筆記的時候沒有傳入id。在這種狀況下數據庫決定筆記的id。DAO負責在返回插入結果以前自動的設置新的id(參閱日誌表)。編程

刪除日誌也很簡單,請參閱 NoteClickListener:緩存

noteDao.deleteByKey(id);

更新筆記和更多

在example例子中沒有展現,可是一樣簡單:更新筆記,只須要修改它的屬性並調用DAO的update() 方法:安全

note.setText("This note has changed.");
noteDao.update(note);

還有其它方法來插入,查詢,更新和刪除實體。查看全部DAO繼承的the AbstractDao class (AbstractDao類)的方法以瞭解更多信息。

建立數據庫

你已經看到了DAOs,可是你如何初始化greenDAO和底層的數據庫?一般你須要初始化一個 DaoSession,在 the Application class 裏,對於整個應用一般執行一次。

DevOpenHelper helper = new DevOpenHelper(this, "notes-db");
Database db = helper.getWritableDb();
daoSession = new DaoMaster(db).newSession();

數據庫由助手類 DevOpenHelper 建立,將數據庫傳入生成 DaoMaster 類.在DaoMaster中實現了 OpenHelper 類,它爲您設置全部的數據庫。不須要寫 「CREATE TABLE」 語句。

而後Activities and fragments 能夠調用 getDaoSession() 來訪問全部的實體DAO,入上面插入和刪除筆記時看到的。

擴展和添加實體

爲了擴展咱們的筆記或建立一個新的實體,你只須要以一樣的方式修改和建立它們的Java類和註解。而後重建您的項目。

詳情請參閱Modelling entities

Modelling entities
實體類

目錄
  1. Schema
  2. Entities and Annotations
  3. Defaults
  4. Relations
  5. Triggering generation
  6. Modifying generated code

爲了在一個項目中使用greenDAO,你須要建立一個實體模型表示將保存數據到您的應用裏。而後,基於這個模型greenDAO爲DAO類生成Java代碼。

模型自己是使用具備註解的Java類定義的。

Schema

你能夠開始使用greenDAO Gradle插件而無需任何額外配置。儘管如此,你至少應該考慮設置個schema的版本:

// In the build.gradle file of your app project:
android {
...
}

greendao {
    schemaVersion 2
}

此外,greendao配置元素支持許多配置選項:

  • schemaVersion: The當前的數據庫schema版本。這由OpenHelpers 類在schema版本間遷移。若是您修改了您實體或者數據庫,則必須增長此值。默認爲1。
  • daoPackage: 生成DAO,DaoMaster,和DaoSession的包名。默認爲你的源實體的包名稱。
  • targetGenDir: 生成的源文件應該保存的位置。默認生成的源文件夾放在build目錄裏(build/generated/source/greendao)
  • generateTests: 設置爲true將自動生成單元測試。
  • targetGenDirTests: 生成單元測試的基礎目錄應該存儲的地方。默認是src/androidTest/java.

Entities and Annotations

greenDAO 3 用註解來定義模式和實體。這是一個簡單的例子:

@Entity
public class User {
    @Id
    private Long id;

    private String name;

    @Transient
    private int tempUsageCount; // not persisted

   // getters and setters for id and user ...
}

@Entity 註解將Java類 User 轉換成數據庫支持的實體。這也將指示greenDAO生成必要的代碼(例如DAO)。

注意: 只支持Java類。若是您喜歡其它語言好比Kotlin,您的實體類必須任然是Java。

The @Entity Annotation

正如你在上面例子看到的那樣,@Entity 註解將一個Java類標記爲一個預先存在的實體。

雖然不添加任何附加參數一般很好,可是您仍然可使用它配置一些細節。

@Entity:

@Entity(
        // If you have more than one schema, you can tell greenDAO
        // to which schema an entity belongs (pick any string as a name).

        // 若是你有超過一個schema,你能夠告訴greenDAO實體所屬的schema(選擇任何字符串做爲名字)
        schema = "myschema",

        // Flag to make an entity "active": Active entities have update,
        // delete, and refresh methods.

       // 將實體標記爲"active":Active 實體有更新,刪除,和刷新方法。
        active = true,

        // Specifies the name of the table in the database.
        // By default, the name is based on the entities class name.

        // 給數據庫的表指定一個名字。默認,名字是基於實體的類名。
        nameInDb = "AWESOME_USERS",

        // Define indexes spanning multiple columns here.
        // 定義跨越多個列的索引
        indexes = {
                @Index(value = "name DESC", unique = true)
        },

        // Flag if the DAO should create the database table (default is true).
        // Set this to false, if you have multiple entities mapping to one table,
        // or the table creation is done outside of greenDAO.

        // 標記DAO是否應建立數據庫表(默認爲true)。設置它爲false,若是你有多個實體映射到一張表,或者建立表是在greenDAO以外完成的。
        createInDb = false,

        // Whether an all properties constructor should be generated.
        // A no-args constructor is always required.

        // 是否生成全部屬性的構造函數。老是須要一個無參數的構造器
        generateConstructors = true,

        // Whether getters and setters for properties should be generated if missing.

        // 若是錯失,是否應該生成屬性的getter和setter
        generateGettersSetters = true
)
public class User {
  ...
}

注意,當使用Gradle插件時,目前不支持多個模式(https://github.com/greenrobot/greenDAO/issues/356)。暫時,繼續使用你的生成器項目。

Basic properties

@Entity
public class User {
    @Id(autoincrement = true)
    private Long id;

    @Property(nameInDb = "USERNAME")
    private String name;

    @NotNull
    private int repos;

    @Transient
    private int tempUsageCount;

    ...
}

@Id 註解選擇一個 long/ Long 屬性做爲實體ID。在數據庫的術語中,它是主鍵。參數autoincrement 是使ID值不斷增長的標記(不重複使用舊值)。

@Property 當屬性被映射到的時候,容許你定義一個非默認的列名。若是沒有,greenDAO將以SQL-ish方式使用字段名稱(大寫字母,下劃線來替換駝峯命名,例如 customName 將是 CUSTOM_NAME )。注意:你當前只能使用內置的常量(能夠理解爲關鍵字)來指定列名

@NotNull 使該屬性在數據庫端爲「NOT NULL」列。一般使用 @NotNull 標記基本類型(long,int,short,byte)是有意義的,同時其包裝類能夠爲空值(Long, Integer, Short, Byte)。

@Transient 標記屬性被排除在持久化之中。用於臨時狀態,等等。或者,您也可使用Java的 transient 關鍵字。

Primary key restrictions

目前,屬性必須擁有一個 long or Long 屬性做爲他們的主鍵。 這是 Android 和 SQLite 推薦的作法。

爲了解決這個問題,將您的關鍵屬性定義爲一個額外的屬性,可是請爲它建立一個惟一的索引。

@Id
private Long id;

@Index(unique = true)
private String key;

Property indexes

在屬性中使用@Index 爲相應的數據庫列建立一個數據庫索引。使用如下參數定製:

  • name: 若是你不喜歡greenDAO爲索引生成的指定名稱,你也能夠在這裏指定它。
  • unique: 添加對索引的惟一約束,強調全部值都是惟一的。
@Entity
public class User {
    @Id private Long id;
    @Index(unique = true)
    private String name;
}

@Unique 像數據庫列添加惟一的約束。注意,SQLite也隱式的爲其建立了一個索引。

@Entity
public class User {
    @Id private Long id;
    @Unique private String name;
}

Defaults

greenDAO 嘗試使用合理的默認值,所以開發者沒必要一一配置。

例如數據庫端的表名和列明來自實體和屬性的名字。與在Java中使用駝峯命名不一樣,默認的數據庫名字是大寫的,使用下劃線來分隔單詞。

例如,一個名爲 creationDate 將變成在數據庫列 CREATION_DATE。

Relations

要學習如何增長一對一和一對多的關聯,請參閱 Relations.

Triggering generation

一旦你的實體schema就位,你就能夠在您的IDE中使用「Make project」 來觸發代碼的生成過程。或者直接執行 greendao 的Gradle task.

若是您在更改了您的實體類後遇到了錯誤, 嘗試從新構建您的項目以確保清理舊的生成類。

Modifying generated code

在greenDAO 3中實體類是由開發人員本身建立和編輯。然而,在代碼的生成過程當中可能會增長實體中的源代碼。

greenDAO將爲它生成的方法和字段添加@Generated 註解,通知開發人員並防止代碼的丟失。在大多數狀況下你不比關心 @Generated.生成的代碼。

做爲預防措施,greenDAO不會覆蓋現有代碼,而且若是你手動更改生成的代碼會引起錯誤:

Error:Execution failed for task ':app:greendao'.
> Constructor (see ExampleEntity:21) 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.生成的代碼,你也能夠徹底刪除更改的構造器和方法。它們將在下一次構建中從新生成。
  • 用 @Keep 註解代替@Generated 註解。這將告訴greenDAO永遠不要觸摸帶註解的代碼。您的更改可能會破壞實體和greenDAO其他部分之間的契約。另外,greenDAO的將來版本可能會在生成的方法中有不一樣的代碼。因此,當心!在適當的地方進行單元測試以免麻煩是一個好主意。

Keep sections

部分不在支持舊版本中greenDAO的KEEP部分

然而,若是Gradle插件檢測到 KEEP FIELDS 部分它會自動使用 @Transient.註解字段。而後又,周圍的 KEEP FIELDS註釋可能會被刪除。

Relations
關係

數據庫表可使用 1:1, 1:N, or N:M 的關係關聯起來。若是你是一個數據庫關係的新手,在咱們討論 ORM 細節以前最好先了解一下。 這裏是一些討論關係的隨機連接( some random links )

在greenDAO,實體與一個或多個關係有關。例如,若是你想在greenDAO中創建一個1:n的關係,那麼您將擁有一個一對一或一對多的關係。可是,注意一對一和一對多關係之間沒有聯繫,因此你將更新這兩種關係。

創建一對一的關係

@ToOne 註解定義了與另外一個實體(一個實體對象)的關係。將其應用於持有其它對象的屬性。

在內部,greenDAO須要一個附加屬性指向目標實體的ID,該屬性由 joinProperty 參數指定。若是沒有此參數,則會自動建立一個額外的列來保存鍵。

@Entity
public class Order {
    @Id private Long id;

    private long customerId;

    @ToOne(joinProperty = "customerId")
    private Customer customer;
}

@Entity
public class Customer {
    @Id private Long id;
}

對一個關係的getter方法(在這個示例中,getCustomer())在第一次調用時延遲地解析目標實體。後續調用將當即返回先前解析的對象。

注意,若是你改變了外鍵的屬性(這裏是customerId),接下來調用getter (getCustomer()) 將會爲更新的ID解析實體。

一樣,若是你設置一個新的實體( setCustomer()),那麼外鍵屬性( customerId)也將會被更新。

Customer customerA = user.getCustomer();

// change the customer id
user.setCustomerId(customerIdB);
// or set a customer with a different id
user.setCustomer(customerB);

customerB = user.getCustomer();
assert(customerA.getId() != customerB.getId());

注意: 爲了急切的加載到一對一的關係,請使用實體DAO類的 loadDeep() 和 queryDeep() 。這將經過單個數據庫查詢來解析全部 與一對一關係的實體。若是您老是想訪問相關的實體,這對於性能是很是好的。

創建一對多的關係

@ToMany 定義了與一組其它實體(多個實體對象)的關係。將此應用於表示目標實體 List的屬性。引用的實體必須有一個或多個屬性指向擁有@ToMany的實體。

在內部, greenDAO 須要一個額外的屬性來指向目標實體的ID, 這由 joinProperty 參數指定。若是此參數不存在,則會自動建立一個附加列來保存鍵。

@Entity
public class Order {
    @Id private Long id;

    private long customerId;

    @ToOne(joinProperty = "customerId")
    private Customer customer;
}

@Entity
public class Customer {
    @Id private Long id;
}

一對一關係中的getter方法(在這個例子中 getCustomer())在第一次調用時延遲解析目標實體。後續調用將當即返回先前解析的對象。

N注意,若是你更改了外鍵的屬性(這裏 customerId),下次調用getter(getCustomer())將解析更新ID後的實體。

另外,若是你設置了一個新的實體( setCustomer()),外鍵屬性( customerId) 也將會被更新。

Customer customerA = user.getCustomer();

// change the customer id
user.setCustomerId(customerIdB);
// or set a customer with a different id
user.setCustomer(customerB);

customerB = user.getCustomer();
assert(customerA.getId() != customerB.getId());

注意:要急切的加載到一對一的關係,請使用實體DAO的 loadDeep() 和 queryDeep() 。這將經過單個數據庫來解析全部一對一關係的實體。若是你老是訪問相關的實體,這對於性能是很是好的。

創建多對多關係

@ToMany 定義了與一組其餘實體(多個實體對象)的關係。將這個屬性應用於表示目標實體的 List 。被引用的實體有一個或多個屬性指向擁有 @ToMany 的實體。

有三種可能性來指定關係映射,只使用他們中的一個:

  1. referencedJoinProperty 參數:指定目標實體中指向此實體的ID的「外鍵」屬性的名稱。
    @Entity
    public class Customer {
        @Id private Long id;
    
        @ToMany(referencedJoinProperty = "customerId")
        @OrderBy("date ASC")
        private List orders;
    }
    
    @Entity
    public class Order {
        @Id private Long id;
        private Date date;
        private long customerId;
    }
  2. joinProperties 參數:對於更復雜的關係,你能夠指定一列 @JoinProperty 註解。每一個@JoinProperty 須要一個原始實體中的源屬性和目標實體中的引用屬性。
    @Entity
    public class Customer {
        @Id private Long id;
        @Unique private String tag;
    
        @ToMany(joinProperties = {
                @JoinProperty(name = "tag", referencedName = "customerTag")
        })
        @OrderBy("date ASC")
        private List orders;
    }
    
    @Entity
    public class Order {
        @Id private Long id;
        private Date date;
        @NotNull private String customerTag;
    }
  3. @JoinEntity 註解:若是你正在作一個N:M(多對多)關係包含另外一個鏈接實體/表,那麼在你的屬性上加上這個額外的註釋。
    @Entity
    public class Product {
        @Id private Long id;
    
        @ToMany
        @JoinEntity(
                entity = JoinProductsWithOrders.class,
                sourceProperty = "productId",
                targetProperty = "orderId"
        )
        private List ordersWithThisProduct;
    }
    
    @Entity
    public class JoinProductsWithOrders {
        @Id private Long id;
        private Long productId;
        private Long orderId;
    }
    
    @Entity
    public class Order {
        @Id private Long id;
    }

一旦運行,插件將會生成一個getter來解析被引用的實體的列表。例如,在前兩種狀況下:

// return all orders where customerId == customer.getId()
List orders = customer.getOrders();

解決和更新多對多的關係

多對多關係在第一次請求中解析比較慢,而後緩存在 List 對象內的源實體中。後續對get方法的調用不會查詢數據庫。

更新多對多關係須要一些額外的工做。由於許多列表都被緩存了,當相關實體被添加到數據庫中時,它們不會被更新。如下代碼說明了這種行爲:

// get the current list of orders for a customer // 獲取客戶的當前訂單列表
List orders1 = customer.getOrders();

// insert a new order for this customer // 爲這個客戶插入新的訂單
Order order = new Order();
order.setCustomerId(customer.getId());
daoSession.insert(order);

// get the list of orders again // 再次獲取訂單列表
List orders2 = customer.getOrders();

// the (cached) list of orders was not updated
// orders1 has the same size as orders2 // (緩存)訂單列表沒有被更新 // orders1和orders2具備相同的大小
assert(orders1.size() == orders2.size);
// orders1 is the same object as orders2
// orders1 與 orders2是同一個對象
assert(orders1.equals(orders2));

所以,要添加新的相關實體,將它們手動的添加到源實體的許多列表中:

// get the to-many list before inserting the new entity
// otherwise the new entity might be in the list twice // 在插入新實體以前獲取多個列表 // 不然新的實體可能在列表中兩次
List orders = customer.getOrders();
// create the new entity
// 建立新的實體
Order newOrder = ...
// set the foreign key property
// 設置外鍵屬性
newOrder.setCustomerId(customer.getId());
// persist the new entity
// 保存新的實體
daoSession.insert(newOrder);
// add it to the to-many list // 將其添加到多個列表中
orders.add(newOrder);

一樣,你能夠刪除相關實體:

List orders = customer.getOrders();
// remove one of the orders from the database // 從數據庫中刪除一個訂單
daoSession.delete(someOrder);
// manually remove it from the to-many list // 從對多列表中刪除它
orders.remove(someOrder);

在添加、更新或刪除許多相關的實體時,可使用reset方法清除緩存列表。下一步將從新的查詢相關的實體:

// clear any cached list of related orders
// 清除任何相關訂單的緩存列表
customer.resetOrders();
List orders = customer.getOrders();

雙向1:N 關係

有時,你想在兩個方向上操縱1:N關係。在greenDAO,你必須添加一對一和一對多的關係來實現這一點。

接下來的例子展現了完成建立custormer和order實體,咱們用先前的一個例子。此次,咱們用 customerId 屬性來建立兩個關係:

@Entity
public class Customer {
    @Id private Long id;

    @ToMany(referencedJoinProperty = "customerId")
    @OrderBy("date ASC")
    private List orders;
}

@Entity
public class Order {
    @Id private Long id;
    private Date date;
    private long customerId;

    @ToOne(joinProperty = "customerId")
    private Customer customer;
}

假設咱們有一個訂單實體。使用這兩種關係,咱們能夠獲得客戶和客戶曾今創造的全部訂單。

List allOrdersOfCustomer = order.getCustomer().getOrders();

例子:創建關係樹

你能夠經過創建一個指向自身一對一和一對多關係的實體來建立一個關係樹。

@Entity
public class TreeNode {
    @Id private Long id;

    private Long parentId;

    @ToOne(joinProperty = "parentId")
    private TreeNode parent;

    @ToMany(referencedJoinProperty = "parentId")
    private List children;
}

生成的實體可讓你操縱它的父類和子類:

TreeNode parent = entity.getParent();
List children = entity.getChildren();
相關文章
相關標籤/搜索