如若不關(guān)心實(shí)現(xiàn)細(xì)節(jié)可直接查看“ObjectBox 架構(gòu)”怀樟、“總結(jié)”這兩部分內(nèi)容。(簡(jiǎn)書(shū)不支持錨點(diǎn)(盆佣;′⌒`))
一往堡、ObjectBox 是什么?
greenrobot 團(tuán)隊(duì)(現(xiàn)有 EventBus共耍、greenDAO 等開(kāi)源產(chǎn)品)推出的又一數(shù)據(jù)庫(kù)開(kāi)源產(chǎn)品虑灰,主打移動(dòng)設(shè)備、支持跨平臺(tái)痹兜,最大的優(yōu)點(diǎn)是速度快穆咐、操作簡(jiǎn)潔,目前已在實(shí)際項(xiàng)目中踩坑字旭。下面將逐步分析這一堪稱(chēng)超快數(shù)據(jù)庫(kù)的 SDK 源碼(Android 部分)对湃,一起探個(gè)究竟。
市面上已經(jīng)有諸如 greenDAO谐算、Realm熟尉、Room 等眾多開(kāi)源產(chǎn)品,至于為什么還選擇 ObjectBox洲脂,暫不在本文討論范圍內(nèi)斤儿。
二剧包、ObjectBox 怎么用?
在開(kāi)始源碼解析之前往果,先介紹一下用法疆液。
1、項(xiàng)目配置依賴(lài)陕贮,根據(jù)官網(wǎng)介紹一步步操作即可堕油,比較簡(jiǎn)單。
2肮之、創(chuàng)建業(yè)務(wù)實(shí)體類(lèi)掉缺,添加@Entity
,同時(shí)通過(guò)@Id
指定主鍵戈擒,之后Build -> Make Project
眶明。
3筐高、ObjectBox Gradle 插件會(huì)在項(xiàng)目的 build 目錄下生成 MyObjectBox
類(lèi)搜囱,以及輔助類(lèi)(如圖中的User_
、UserCursor
柑土、Order_
蜀肘、OrderCursor
),接下來(lái)直接調(diào)用MyObjectBox
稽屏。
4扮宠、通過(guò)
MyObjectBox
類(lèi)獲取數(shù)據(jù)庫(kù)(BoxStore),通過(guò)數(shù)據(jù)庫(kù)獲取對(duì)應(yīng)的表(Box)狐榔,進(jìn)行 CRUD 操作涵卵。總結(jié):實(shí)際開(kāi)發(fā)過(guò)程中的感受,使用簡(jiǎn)單典鸡,配合 ObjectBrowser 直接在瀏覽器查看數(shù)據(jù)被廓,開(kāi)發(fā)體驗(yàn)好。
但是萝玷,為什么插件要自動(dòng)創(chuàng)建MyObjectBox
和User_
嫁乘、UserCursor
、Order_
球碉、OrderCursor
類(lèi)呢蜓斧?他們又分別起什么作用?SDK 內(nèi)部如何運(yùn)行睁冬?
三挎春、ObjectBox 架構(gòu)
要回答以上問(wèn)題看疙,先介紹一下 ObjectBox 架構(gòu)。
從下往上看直奋,主要分成 Engine能庆、Core、Extentions 三層脚线。
Engine 層屬于 Native搁胆,是整個(gè)數(shù)據(jù)庫(kù)的引擎,可跨平臺(tái)邮绿。
目前已支持 Android(4.0+)渠旁、Linux(64位)、Windows(64位)船逮,而 macOS顾腊、iOS 的支持在開(kāi)發(fā)中。
大部分 Java 層的數(shù)據(jù)庫(kù)操作都調(diào)用了 Native 方法傻唾,但 Native 部分目前沒(méi)有開(kāi)源投慈。Core 和 Extentions 屬于 Java。
Core 層是核心冠骄,負(fù)責(zé)數(shù)據(jù)庫(kù)管理伪煤、CRUD 以及和 Native 通信;
Extentions 提供了諸如 Reactive凛辣、LiveData抱既、Kotlin 等一系列的擴(kuò)展。
下面將重點(diǎn)對(duì) Core 層進(jìn)行解析扁誓。
四防泵、ObjectBox 源碼解析
4.1 Entity
指的是添加了@Entity
注解的業(yè)務(wù)實(shí)體,如上文中提到的 User
類(lèi)蝗敢,一個(gè) Entity 可看做一張數(shù)據(jù)庫(kù)表捷泞。從上文可知 Gradle 插件自動(dòng)生成了對(duì)應(yīng)的 User_
、UserCursor
類(lèi)寿谴,其中 User_
就是 EntityInfo锁右。
4.2 EntityInfo
和 Entity 是成對(duì)出現(xiàn)的,目的是保存 Entity 的相關(guān)信息讶泰,如名稱(chēng)咏瑟、屬性(字段)等,用于后續(xù)的查詢(xún)等一系列操作痪署。
4.3 MyObjectBox
除了User_
码泞,插件還自動(dòng)生成了 MyObjectBox
類(lèi),它只對(duì)外提供了 builder
方法返回 BoxStoreBuilder狼犯,用來(lái)構(gòu)造數(shù)據(jù)庫(kù)余寥。
/**
* 創(chuàng)建 BoxStore 構(gòu)造器
*
* @return 構(gòu)造器
*/
public static BoxStoreBuilder builder() {
BoxStoreBuilder builder = new BoxStoreBuilder(getModel());
builder.entity(User_.__INSTANCE);
builder.entity(Order_.__INSTANCE);
return builder;
}
主要是做了兩件事情领铐,一個(gè)是getModel
返回 Model,注意這里的 Model 是給 Native 層創(chuàng)建數(shù)據(jù)庫(kù)用的劈狐,數(shù)據(jù)格式是 byte[]
罐孝;
另一個(gè)是通過(guò)entity
把所有 EntityInfo 保存起來(lái),后續(xù) Java 層的一系列操作都會(huì)用到肥缔。
可見(jiàn)插件把 @Entity 生成為 EntityInfo 和 Model莲兢,前者是給 Java 層用,后者是給 Native 層用续膳。開(kāi)發(fā)者會(huì)經(jīng)常和 EntityInfo 打交道改艇,但卻不會(huì)感知到 Model 的存在。
4.4 BoxStore
BoxStore 代表著整個(gè)數(shù)據(jù)庫(kù)坟岔,由 BoxStoreBuilder#build
生成(通過(guò) BoxStoreBuilder 可以進(jìn)行一些定制化配置谒兄,如最大讀并發(fā)數(shù)、最大容量社付、數(shù)據(jù)庫(kù)文件名等)承疲,從源碼中可以看出 BoxStoreBuilder#build
方法 new 了一個(gè) BoxStore 對(duì)象并返回:
public BoxStore build() {
if (directory == null) {
name = dbName(name);
directory = getDbDir(baseDirectory, name);
}
return new BoxStore(this);
}
BoxStore 的作用:
- 加載所有 Native 庫(kù)
- 調(diào)用 Native 方法創(chuàng)建數(shù)據(jù)庫(kù)
- 調(diào)用 Native 方法依次創(chuàng)建 Entity
- 創(chuàng)建并管理 Box(和 Entity對(duì)應(yīng),下文介紹)
- 創(chuàng)建并管理 Transaction(所有數(shù)據(jù)庫(kù)操作都會(huì)放到事務(wù)中鸥咖,下文介紹)
- 提供數(shù)據(jù)訂閱(有興趣可自行分析
Reactive
拓展模塊)
其中燕鸽,1、2啼辣、3 都在 BoxStore 構(gòu)造方法中完成啊研,來(lái)看看代碼:
BoxStore(BoxStoreBuilder builder) {
// 1、加載 Native
NativeLibraryLoader.ensureLoaded();
…… // 省略各種校驗(yàn)
// 2鸥拧、調(diào)用 Native 方法創(chuàng)建數(shù)據(jù)庫(kù)党远,并返回句柄(其實(shí)就是id)
// 后續(xù)一系列操作 Native 方法的調(diào)用都要回傳這個(gè)句柄
handle = nativeCreate(canonicalPath, builder.maxSizeInKByte, builder.maxReaders, builder.model);
……
for (EntityInfo entityInfo : builder.entityInfoList) {
……
// 3、調(diào)用 Native 方法依次注冊(cè) Entity富弦,并返回句柄
int entityId = nativeRegisterEntityClass(handle, entityInfo.getDbName(), entityInfo.getEntityClass());
entityTypeIdByClass.put(entityInfo.getEntityClass(), entityId);
}
……
}
構(gòu)造函數(shù)執(zhí)行完沟娱,數(shù)據(jù)庫(kù)就已準(zhǔn)備就緒。
4.5 Box
通過(guò)調(diào)用 public <T> Box<T> boxFor(Class<T> entityClass)
方法腕柜,BoxStore 會(huì)為對(duì)應(yīng)的 EntityClass 生成并管理 Box(和 EntityClass 一一對(duì)應(yīng)):
/**
* 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) {
// 創(chuàng)建 Box花沉,傳入 BoxStore 實(shí)例,以及 EntityClass
box = new Box<>(this, entityClass);
boxes.put(entityClass, box);
}
}
}
return box;
}
Box 的職責(zé)就是進(jìn)行 Entity 的 CRUD 操作媳握,在深入分析其 CRUD 操作之前,必須先了解兩個(gè)概念:Transaction(事務(wù))和Cursor(游標(biāo))磷脯。
4.6 Transaction
Transaction(事務(wù))是數(shù)據(jù)庫(kù)管理系統(tǒng)執(zhí)行過(guò)程中的一個(gè)邏輯單位蛾找,在 BoxStore 的介紹一節(jié)中提到其主要作用之一是“創(chuàng)建并管理 Transaction”。其實(shí)赵誓,在 ObjectBox 中打毛,所有 Transaction 對(duì)象都是通過(guò) BoxStore 的兩個(gè)內(nèi)部方法 beginTx()
和 beginReadTx()
生成柿赊,后者生成一個(gè)只讀 Transaction(不允許寫(xiě)入,可復(fù)用幻枉,性能會(huì)更好)碰声。
@Internal
public Transaction beginTx() {
// 1、調(diào)用 Native 方法生成事務(wù)熬甫,并返回其句柄
long nativeTx = nativeBeginTx(handle);
// 2胰挑、生成 Transaction 對(duì)象,傳入 BoxStore椿肩、Native 事務(wù)句柄瞻颂、已提交事務(wù)數(shù)量(當(dāng)該事務(wù)準(zhǔn)備提交時(shí),用來(lái)判斷有沒(méi)有被其他事務(wù)搶先提交郑象,有點(diǎn)繞哈贡这,可以不管)
Transaction tx = new Transaction(this, nativeTx, initialCommitCount);
synchronized (transactions) {
transactions.add(tx);
}
return tx;
}
@Internal
public Transaction beginReadTx() {
……
// 唯一不同的是,這里調(diào)用了 nativeBeginReadTx 生成只讀事務(wù)
long nativeTx = nativeBeginReadTx(handle);
……
}
從以上兩個(gè)方法中厂榛,可以發(fā)現(xiàn)所有的事務(wù)最終都是調(diào)用 Native 生成盖矫,Transaction 對(duì)象只是持有其句柄(一個(gè)類(lèi)型為 long 的變量),以便后續(xù)各個(gè)操作時(shí)回傳給 Native击奶,如:
/** 調(diào)用 Transaction 對(duì)象的提交方法 */
public void commit() {
checkOpen();
// 交由 Native 進(jìn)行事務(wù)提交
int[] entityTypeIdsAffected = nativeCommit(transaction);
store.txCommitted(this, entityTypeIdsAffected);
}
/** 調(diào)用 Transaction 對(duì)象的中斷方法 */
public void abort() {
checkOpen();
// 交由 Native 進(jìn)行事務(wù)中斷
nativeAbort(transaction);
}
此外辈双,在 ObjectBox 中,事務(wù)分為兩類(lèi)“顯式事務(wù)”和“隱式事務(wù)”正歼。
“顯式事務(wù)”是指開(kāi)發(fā)者直接調(diào)用以下方法運(yùn)行的事務(wù):
BoxStore#runInTx(Runnable)
BoxStore#runInReadTx(Runnable)
BoxStore#runInTxAsync(Runnable,TxCallback)
BoxStore#callInTx(Callable)
BoxStore#callInReadTx(Callable)
BoxStore#callInTxAsync(Callable,TxCallback)
“隱式事務(wù)”是指對(duì)開(kāi)發(fā)者透明的辐马,框架隱式創(chuàng)建和管理的事務(wù),如下面會(huì)分析到的Box#get(long)
方法局义。
有了事務(wù)喜爷,就可以在其中進(jìn)行一系列數(shù)據(jù)庫(kù)的操作,那么怎么創(chuàng)建“操作”萄唇?這些“操作”又是如何執(zhí)行檩帐?。
4.7 Cursor
上文中所說(shuō)的“操作”另萤,實(shí)際上是 Cursor (游標(biāo))湃密。
我們?cè)賮?lái)回顧一下,文章一開(kāi)始我們提到 Gradle 插件會(huì)為 User
這個(gè) Entity 生成一個(gè)叫做UserCursor
的文件四敞,這就是所有針對(duì)User
的 CRUD 操作真正發(fā)生的地方——游標(biāo)泛源,來(lái)看看其內(nèi)容。
UserCursor
繼承了 Cursor<T>
忿危,提供 Factory 供創(chuàng)建時(shí)調(diào)用达箍,同時(shí)實(shí)現(xiàn)了 getId
方法,以及put
方法實(shí)現(xiàn)寫(xiě)入數(shù)據(jù)庫(kù)操作铺厨。
上文中提到 Box 的職責(zé)是 CRUD缎玫,其實(shí)最終都落實(shí)到了游標(biāo)身上硬纤。雖然開(kāi)發(fā)過(guò)程中不會(huì)直接調(diào)用 Cursor 類(lèi),但是有必要弄明白其中原理赃磨。
首先筝家,所有游標(biāo)的創(chuàng)建,必須調(diào)用 Transation 的 createCursor
方法(注意看注釋?zhuān)?/p>
public <T> Cursor<T> createCursor(Class<T> entityClass) {
checkOpen();
EntityInfo entityInfo = store.getEntityInfo(entityClass);
CursorFactory<T> factory = entityInfo.getCursorFactory();
// 1邻辉、調(diào)用 Native 創(chuàng)建游標(biāo)溪王,傳入 transaction (事務(wù)句柄),dbName恩沛,entityClass 三個(gè)參數(shù)在扰,并返回句柄(游標(biāo)ID)
// 通過(guò)這三個(gè)參數(shù),把[游標(biāo)]和[事務(wù)]雷客、[數(shù)據(jù)庫(kù)表名]芒珠、[EntityClass]進(jìn)行綁定
long cursorHandle = nativeCreateCursor(transaction, entityInfo.getDbName(), entityClass);
// 2、調(diào)用 factory 創(chuàng)建 Cursor 對(duì)象搅裙,傳入游標(biāo)句柄(后續(xù)一系列操作會(huì)回傳給 Native)
return factory.createCursor(this, cursorHandle, store);
}
其次皱卓,拿到游標(biāo),就可以調(diào)用相關(guān)方法部逮,進(jìn)行 CRUD 操作:
// Cursor<T> 抽象類(lèi)
public T get(long key) {
// Native 查詢(xún)娜汁,傳入游標(biāo)句柄、ID值
return (T) nativeGetEntity(cursor, key);
}
public T next() {
// Native 查詢(xún)下一條兄朋,傳入游標(biāo)句柄
return (T) nativeNextEntity(cursor);
}
public T first() {
// Native 查詢(xún)第一條掐禁,傳入游標(biāo)句柄
return (T) nativeFirstEntity(cursor);
}
public void deleteEntity(long key) {
// Native 刪除,傳入游標(biāo)句柄颅和、ID值
nativeDeleteEntity(cursor, key);
}
// UserCursor 類(lèi) (extends Cursor<User>)
@Override
public final long put(User entity) {
……
// Native 進(jìn)行插入/更新傅事,傳入游標(biāo)句柄
long __assignedId = collect313311(cursor, entity.getId(),……);
……
return __assignedId;
}
Cursor 類(lèi)提供了一系列 collectXXXXXX 的方法供數(shù)據(jù)插入/更新,比較有意思的思路峡扩,感興趣的可以自行閱讀蹭越。
而游標(biāo)的 CRUD 操作(如寫(xiě)),最終都是要依靠事務(wù)才能完成提交教届。
那么响鹃,又回到 Box 一節(jié)的問(wèn)題,Box 是如何把Transaction
和Cursor
結(jié)合起來(lái)完成 CRUD 操作的呢案训?
4.8 Box 的 CRUD 操作
下圖是開(kāi)發(fā)者直接調(diào)用 Box 進(jìn)行 CRUD 操作的所有接口买置。
我們挑兩個(gè)例子來(lái)分析。
4.8.1 查詢(xún) Box#get(long)
public T get(long id) {
// 1强霎、獲取一個(gè)只讀游標(biāo)
Cursor<T> reader = getReader();
try {
// 2堕义、調(diào)用游標(biāo)的 get 方法
return reader.get(id);
} finally {
// 3、釋放,只讀事務(wù)只會(huì)回收倦卖,以便復(fù)用
releaseReader(reader);
}
}
從“游標(biāo)”一節(jié)中我們知道,游標(biāo)必須由事務(wù)創(chuàng)建椿争,我們來(lái)看看Box#getReader()
方法:
Cursor<T> getReader() {
// 1怕膛、判斷當(dāng)前線(xiàn)程是否有可用事務(wù)和可用游標(biāo)(ThreadLocal<Cursor<T>>變量保存)
Cursor<T> cursor = getActiveTxCursor();
if (cursor != null) {
return cursor;
} else {
…… (省略緩存處理邏輯)
// 2、當(dāng)前線(xiàn)程無(wú)可用游標(biāo)秦踪,調(diào)用 BoxStore 啟動(dòng)只讀事務(wù)褐捻、創(chuàng)建游標(biāo)
cursor = store.beginReadTx().createCursor(entityClass);
// 3、緩存游標(biāo)椅邓,下次使用
threadLocalReader.set(cursor);
}
return cursor;
}
所以 Box 所有查詢(xún)操作柠逞,先去 BoxStore 獲取一個(gè)只讀游標(biāo),隨后調(diào)用其 Cursor#get(long)
方法并返回結(jié)果景馁,最后再回收該游標(biāo)及其對(duì)應(yīng)的事務(wù)板壮。
4.8.2 添加 Box#put(T)
public long put(T entity) {
// 1、獲取游標(biāo)(默認(rèn)可以讀寫(xiě))
Cursor<T> cursor = getWriter();
try {
// 2合住、調(diào)用游標(biāo)的 put 方法
long key = cursor.put(entity);
// 3绰精、事務(wù)提交
commitWriter(cursor);
return key;
} finally {
// 4、釋放透葛,讀寫(xiě)事務(wù)會(huì)被銷(xiāo)毀笨使,無(wú)法復(fù)用
releaseWriter(cursor);
}
}
和 getReader
方法不同,因?yàn)椤皩?xiě)事務(wù)”無(wú)法復(fù)用僚害,所以getWriter
少了緩存事務(wù)的邏輯硫椰,完整代碼:
Cursor<T> getWriter() {
// 1、和 getReader 一樣萨蚕,判斷當(dāng)前線(xiàn)程是否有可用事務(wù)和可用游標(biāo)
Cursor<T> cursor = getActiveTxCursor();
if (cursor != null) {
return cursor;
} else {
// 2靶草、當(dāng)前線(xiàn)程無(wú)可用游標(biāo),調(diào)用 BoxStore 啟動(dòng)事務(wù)门岔、創(chuàng)建游標(biāo)
Transaction tx = store.beginTx();
try {
return tx.createCursor(entityClass);
} catch (RuntimeException e) {
tx.close();
throw e;
}
}
}
所以 Box 所有添加操作爱致,先去 BoxStore 獲取一個(gè)游標(biāo),隨后調(diào)用其 Cursor#put(T)
方法并返回 id寒随,最后再銷(xiāo)毀該游標(biāo)及其對(duì)應(yīng)的事務(wù)糠悯。
當(dāng)我們調(diào)用 Box 相關(guān) CRUD 操作時(shí),事務(wù)妻往、游標(biāo)的處理都在 Box 及 BoxStore 內(nèi)部處理完成互艾,對(duì)開(kāi)發(fā)者是透明的,也就是上面說(shuō)到的“隱式事務(wù)”讯泣。
另外纫普,Box 只能夠滿(mǎn)足根據(jù)“主鍵”的查詢(xún),如果查詢(xún)條件涉及到“過(guò)濾”、“多屬性聯(lián)合”昨稼、“聚合”等比較復(fù)雜的节视,得借助 Query 類(lèi)。
4.9 Query
我們先來(lái)看看 Query 用法:
首先通過(guò) Box#query()
調(diào)用 Native 方法獲取 QueryBuilder 對(duì)象(持有 Native 句柄)假栓。針對(duì) QueryBuilder 可以設(shè)置各種查詢(xún)條件寻行,比如 equal(Property,long)
:
public QueryBuilder<T> equal(Property property, long value) {
……
// 調(diào)用 Native 方法,設(shè)置 equal 查詢(xún)條件匾荆,傳入屬性 id 及目標(biāo)數(shù)值
checkCombineCondition(nativeEqual(handle, property.getId(), value));
return this;
}
再通過(guò) QueryBuilder#build()
調(diào)用 Native 方法生成 Query 對(duì)象(持有 Native 句柄)拌蜘,最后,通過(guò) Query#find()
返回所需數(shù)據(jù)牙丽,且 Query 對(duì)象可以重復(fù)使用简卧。
在理解了事務(wù)、游標(biāo)等概念后烤芦,很容易理解 QueryBuilder 以及 Query举娩,更多代碼就不貼出來(lái)了。
五拍棕、總結(jié)
以上晓铆,我們逐一分析了 ObjectBox 架構(gòu) Core 層各核心類(lèi)的作用及其關(guān)系,總結(jié)起來(lái)就是:
參考資料