Android 數據庫 ObjectBox 源碼解析


一、ObjectBox 是什么?

greenrobot 團隊(現有 EventBusgreenDAO 等開源產品)推出的又一數據庫開源產品,主打移動設備、支持跨平台,最大的優點是速度快、操作簡潔,目前已在實際項目中踩坑。下面將逐步分析這一堪稱超快數據庫的 SDK 源碼(Android 部分),一起探個究竟。

ObjectBox Android 介紹

市面上已經有諸如 greenDAO、Realm、Room 等眾多開源產品,至於為什么還選擇 ObjectBox,暫不在本文討論范圍內。

二、ObjectBox 怎么用?

在開始源碼解析之前,先介紹一下用法。
1、項目配置依賴,根據官網介紹一步步操作即可,比較簡單。
2、創建業務實體類,添加@Entity,同時通過@Id指定主鍵,之后Build -> Make Project

創建業務實體,添加 ObjectBox 注解

3、ObjectBox Gradle 插件會在項目的 build 目錄下生成 MyObjectBox 類,以及輔助類(如圖中的User_UserCursorOrder_OrderCursor),接下來直接調用MyObjectBox

插件自動生成數據庫輔助類

4、通過 MyObjectBox 類獲取數據庫(BoxStore),通過數據庫獲取對應的表(Box),進行 CRUD 操作。
創建數據庫,獲取表,增刪改查

總結:實際開發過程中的感受,使用簡單,配合 ObjectBrowser 直接在瀏覽器查看數據,開發體驗好。

但是,為什么插件要自動創建MyObjectBoxUser_UserCursorOrder_OrderCursor類呢?他們又分別起什么作用?SDK 內部如何運行?

三、ObjectBox 架構

要回答以上問題,先介紹一下 ObjectBox 架構。

ObjectBox 架構

從下往上看,主要分成 Engine、Core、Extentions 三層。

  1. Engine 層屬於 Native,是整個數據庫的引擎,可跨平台。
    目前已支持 Android(4.0+)、Linux(64位)、Windows(64位),而 macOS、iOS 的支持在開發中。
    大部分 Java 層的數據庫操作都調用了 Native 方法,但 Native 部分目前沒有開源。

  2. CoreExtentions 屬於 Java。
    Core 層是核心,負責數據庫管理、CRUD 以及和 Native 通信;
    Extentions 提供了諸如 Reactive、LiveData、Kotlin 等一系列的擴展。

下面將重點對 Core 層進行解析

四、ObjectBox 源碼解析

4.1 Entity

指的是添加了@Entity 注解的業務實體,如上文中提到的 User 類,一個 Entity 可看做一張數據庫表。從上文可知 Gradle 插件自動生成了對應的 User_UserCursor 類,其中 User_ 就是 EntityInfo

User

4.2 EntityInfo

和 Entity 是成對出現的,目的是保存 Entity 的相關信息,如名稱、屬性(字段)等,用於后續的查詢等一系列操作。


User_(User 類的 EntityInfo)

4.3 MyObjectBox

除了User_,插件還自動生成MyObjectBox 類,它只對外提供了 builder 方法返回 BoxStoreBuilder,用來構造數據庫。

    /** * 創建 BoxStore 構造器 * * @return 構造器 */ public static BoxStoreBuilder builder() { BoxStoreBuilder builder = new BoxStoreBuilder(getModel()); builder.entity(User_.__INSTANCE); builder.entity(Order_.__INSTANCE); return builder; } 

主要是做了兩件事情,一個是getModel返回 Model,注意這里的 Model 是給 Native 層創建數據庫用的,數據格式是 byte[]

創建 Model

另一個是通過entity把所有 EntityInfo 保存起來,后續 Java 層的一系列操作都會用到。

可見插件把 @Entity 生成為 EntityInfo 和 Model,前者是給 Java 層用,后者是給 Native 層用。開發者會經常和 EntityInfo 打交道,但卻不會感知到 Model 的存在。

4.4 BoxStore

BoxStore 代表着整個數據庫,由 BoxStoreBuilder#build 生成(通過 BoxStoreBuilder 可以進行一些定制化配置,如最大讀並發數、最大容量、數據庫文件名等),從源碼中可以看出 BoxStoreBuilder#build 方法 new 了一個 BoxStore 對象並返回:

    public BoxStore build() { if (directory == null) { name = dbName(name); directory = getDbDir(baseDirectory, name); } return new BoxStore(this); } 

BoxStore 的作用:

  1. 加載所有 Native 庫
  2. 調用 Native 方法創建數據庫
  3. 調用 Native 方法依次創建 Entity
  4. 創建並管理 Box(和 Entity對應,下文介紹)
  5. 創建並管理 Transaction(所有數據庫操作都會放到事務中,下文介紹)
  6. 提供數據訂閱(有興趣可自行分析 Reactive 拓展模塊)

其中,1、2、3 都在 BoxStore 構造方法中完成,來看看代碼:

    BoxStore(BoxStoreBuilder builder) {
        // 1、加載 Native NativeLibraryLoader.ensureLoaded(); …… // 省略各種校驗 // 2、調用 Native 方法創建數據庫,並返回句柄(其實就是id) // 后續一系列操作 Native 方法的調用都要回傳這個句柄 handle = nativeCreate(canonicalPath, builder.maxSizeInKByte, builder.maxReaders, builder.model); …… for (EntityInfo entityInfo : builder.entityInfoList) { …… // 3、調用 Native 方法依次注冊 Entity,並返回句柄 int entityId = nativeRegisterEntityClass(handle, entityInfo.getDbName(), entityInfo.getEntityClass()); entityTypeIdByClass.put(entityInfo.getEntityClass(), entityId); } …… } 

構造函數執行完,數據庫就已准備就緒。

4.5 Box

通過調用 public <T> Box<T> boxFor(Class<T> entityClass) 方法,BoxStore 會為對應的 EntityClass 生成並管理 Box(和 EntityClass 一一對應):

    /** * Returns a Box for the given type. Objects are put into (and get from) their individual Box. */ public <T> Box<T> boxFor(Class<T> entityClass) { Box box = boxes.get(entityClass); if (box == null) { …… // 省略 synchronized (boxes) { box = boxes.get(entityClass); if (box == null) { // 創建 Box,傳入 BoxStore 實例,以及 EntityClass box = new Box<>(this, entityClass); boxes.put(entityClass, box); } } } return box; } 

Box 的職責就是進行 Entity 的 CRUD 操作,在深入分析其 CRUD 操作之前,必須先了解兩個概念:Transaction(事務)Cursor(游標)

4.6 Transaction

Transaction(事務)是數據庫管理系統執行過程中的一個邏輯單位,在 BoxStore 的介紹一節中提到其主要作用之一是“創建並管理 Transaction”。其實,在 ObjectBox 中,所有 Transaction 對象都是通過 BoxStore 的兩個內部方法 beginTx()beginReadTx() 生成,后者生成一個只讀 Transaction(不允許寫入,可復用,性能會更好)。

    @Internal public Transaction beginTx() { // 1、調用 Native 方法生成事務,並返回其句柄 long nativeTx = nativeBeginTx(handle); // 2、生成 Transaction 對象,傳入 BoxStore、Native 事務句柄、已提交事務數量(當該事務准備提交時,用來判斷有沒有被其他事務搶先提交,有點繞哈,可以不管) Transaction tx = new Transaction(this, nativeTx, initialCommitCount); synchronized (transactions) { transactions.add(tx); } return tx; } 
    @Internal public Transaction beginReadTx() { …… // 唯一不同的是,這里調用了 nativeBeginReadTx 生成只讀事務 long nativeTx = nativeBeginReadTx(handle); …… } 

從以上兩個方法中,可以發現所有的事務最終都是調用 Native 生成,Transaction 對象只是持有其句柄(一個類型為 long 的變量),以便后續各個操作時回傳給 Native,如:

    /** 調用 Transaction 對象的提交方法 */ public void commit() { checkOpen(); // 交由 Native 進行事務提交 int[] entityTypeIdsAffected = nativeCommit(transaction); store.txCommitted(this, entityTypeIdsAffected); } 
    /** 調用 Transaction 對象的中斷方法 */ public void abort() { checkOpen(); // 交由 Native 進行事務中斷 nativeAbort(transaction); } 

此外,在 ObjectBox 中,事務分為兩類“顯式事務”和“隱式事務”。

“顯式事務”是指開發者直接調用以下方法運行的事務:
BoxStore#runInTx(Runnable)
BoxStore#runInReadTx(Runnable)
BoxStore#runInTxAsync(Runnable,TxCallback)
BoxStore#callInTx(Callable)
BoxStore#callInReadTx(Callable)
BoxStore#callInTxAsync(Callable,TxCallback)

“隱式事務”是指對開發者透明的,框架隱式創建和管理的事務,如下面會分析到的Box#get(long)方法。

有了事務,就可以在其中進行一系列數據庫的操作,那么怎么創建“操作”?這些“操作”又是如何執行?。

4.7 Cursor

上文中所說的“操作”,實際上是 Cursor (游標)。

我們再來回顧一下,文章一開始我們提到 Gradle 插件會為 User 這個 Entity 生成一個叫做UserCursor的文件,這就是所有針對User 的 CRUD 操作真正發生的地方——游標,來看看其內容。

UserCursor 文件

UserCursor 繼承了 Cursor<T> ,提供 Factory 供創建時調用,同時實現了 getId 方法,以及put 方法實現寫入數據庫操作。

上文中提到 Box 的職責是 CRUD,其實最終都落實到了游標身上。雖然開發過程中不會直接調用 Cursor 類,但是有必要弄明白其中原理。

首先,所有游標的創建,必須調用 Transation 的 createCursor 方法(注意看注釋):

    public <T> Cursor<T> createCursor(Class<T> entityClass) { checkOpen(); EntityInfo entityInfo = store.getEntityInfo(entityClass); CursorFactory<T> factory = entityInfo.getCursorFactory(); // 1、調用 Native 創建游標,傳入 transaction (事務句柄),dbName,entityClass 三個參數,並返回句柄(游標ID) // 通過這三個參數,把[游標]和[事務]、[數據庫表名]、[EntityClass]進行綁定 long cursorHandle = nativeCreateCursor(transaction, entityInfo.getDbName(), entityClass); // 2、調用 factory 創建 Cursor 對象,傳入游標句柄(后續一系列操作會回傳給 Native) return factory.createCursor(this, cursorHandle, store); } 

其次,拿到游標,就可以調用相關方法,進行 CRUD 操作:

// Cursor<T> 抽象類 public T get(long key) { // Native 查詢,傳入游標句柄、ID值 return (T) nativeGetEntity(cursor, key); } public T next() { // Native 查詢下一條,傳入游標句柄 return (T) nativeNextEntity(cursor); } public T first() { // Native 查詢第一條,傳入游標句柄 return (T) nativeFirstEntity(cursor); } public void deleteEntity(long key) { // Native 刪除,傳入游標句柄、ID值 nativeDeleteEntity(cursor, key); } 
// UserCursor 類 (extends Cursor<User>) @Override public final long put(User entity) { …… // Native 進行插入/更新,傳入游標句柄 long __assignedId = collect313311(cursor, entity.getId(),……); …… return __assignedId; } 

Cursor 類提供了一系列 collectXXXXXX 的方法供數據插入/更新,比較有意思的思路,感興趣的可以自行閱讀。

而游標的 CRUD 操作(如寫),最終都是要依靠事務才能完成提交。

那么,又回到 Box 一節的問題,Box 是如何把TransactionCursor結合起來完成 CRUD 操作的呢?

4.8 Box 的 CRUD 操作

下圖是開發者直接調用 Box 進行 CRUD 操作的所有接口。

Box CRUD 接口

我們挑兩個例子來分析。

4.8.1 查詢 Box#get(long)

public T get(long id) { // 1、獲取一個只讀游標 Cursor<T> reader = getReader(); try { // 2、調用游標的 get 方法 return reader.get(id); } finally { // 3、釋放,只讀事務只會回收,以便復用 releaseReader(reader); } } 

從“游標”一節中我們知道,游標必須由事務創建,我們來看看Box#getReader()方法:

Cursor<T> getReader() { // 1、判斷當前線程是否有可用事務和可用游標(ThreadLocal<Cursor<T>>變量保存) Cursor<T> cursor = getActiveTxCursor(); if (cursor != null) { return cursor; } else { …… (省略緩存處理邏輯) // 2、當前線程無可用游標,調用 BoxStore 啟動只讀事務、創建游標 cursor = store.beginReadTx().createCursor(entityClass); // 3、緩存游標,下次使用 threadLocalReader.set(cursor); } return cursor; } 

所以 Box 所有查詢操作,先去 BoxStore 獲取一個只讀游標,隨后調用其 Cursor#get(long) 方法並返回結果,最后再回收該游標及其對應的事務。

4.8.2 添加 Box#put(T)

public long put(T entity) { // 1、獲取游標(默認可以讀寫) Cursor<T> cursor = getWriter(); try { // 2、調用游標的 put 方法 long key = cursor.put(entity); // 3、事務提交 commitWriter(cursor); return key; } finally { // 4、釋放,讀寫事務會被銷毀,無法復用 releaseWriter(cursor); } } 

getReader 方法不同,因為“寫事務”無法復用,所以getWriter 少了緩存事務的邏輯,完整代碼:

Cursor<T> getWriter() { // 1、和 getReader 一樣,判斷當前線程是否有可用事務和可用游標 Cursor<T> cursor = getActiveTxCursor(); if (cursor != null) { return cursor; } else { // 2、當前線程無可用游標,調用 BoxStore 啟動事務、創建游標 Transaction tx = store.beginTx(); try { return tx.createCursor(entityClass); } catch (RuntimeException e) { tx.close(); throw e; } } } 

所以 Box 所有添加操作,先去 BoxStore 獲取一個游標,隨后調用其 Cursor#put(T) 方法並返回 id,最后再銷毀該游標及其對應的事務。

當我們調用 Box 相關 CRUD 操作時,事務、游標的處理都在 Box 及 BoxStore 內部處理完成,對開發者是透明的,也就是上面說到的“隱式事務”。

另外,Box 只能夠滿足根據“主鍵”的查詢,如果查詢條件涉及到“過濾”、“多屬性聯合”、“聚合”等比較復雜的,得借助 Query 類。

4.9 Query

我們先來看看 Query 用法:

Query 用法

首先通過 Box#query() 調用 Native 方法獲取 QueryBuilder 對象(持有 Native 句柄)。針對 QueryBuilder 可以設置各種查詢條件,比如 equal(Property,long)

public QueryBuilder<T> equal(Property property, long value) { …… // 調用 Native 方法,設置 equal 查詢條件,傳入屬性 id 及目標數值 checkCombineCondition(nativeEqual(handle, property.getId(), value)); return this; } 

再通過 QueryBuilder#build() 調用 Native 方法生成 Query 對象(持有 Native 句柄),最后,通過 Query#find() 返回所需數據,且 Query 對象可以重復使用。

在理解了事務、游標等概念后,很容易理解 QueryBuilder 以及 Query,更多代碼就不貼出來了。

五、總結

以上,我們逐一分析了 ObjectBox 架構 Core 層各核心類的作用及其關系,總結起來就是:

Core 層關系圖

參考資料

ObjectBox 官網

ObjectBox 文檔



作者:Cavabiao
鏈接:https://www.jianshu.com/p/b9f459927ddb
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

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



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