手把手教你在Flutter專案優雅的使用ORM資料庫

williamwen1986發表於2019-01-21

Flutter ORM資料庫介紹

Flutter現在開發上最大的槽點可能就是資料庫使用了,Flutter現在只提供了sqflite外掛,這表明開發者手動寫sql程式碼,建表、建索引、transation、db執行緒控制等等繁瑣的事情必然接踵而至,這種資料庫使用方式是最低效的了。例如IOS平臺有coredata、realm等等的框架提供便捷的資料庫操作,但來到flutter就又倒退回去裸寫sql,這對大部分團隊都是重大的成本。

本文將詳細介紹一種在Flutter專案中優雅的使用ORM資料庫的方法,我們使用的ORM框架是包含在一個Flutter外掛flutter_luakit_plugin(如何使用可參考介紹文章)中的其中一個功能,本文只詳細介紹這套ORM框架的使用和實現原理。我們給出了一個demo

我們demo中實現了一個簡單的功能,從一個天氣網站上查詢北京的天氣資訊,解析返回的json然後存資料庫,下次啟動優先從資料庫查資料馬上顯示,再發請求向天氣網站更新天氣資訊,就這麼簡單的一個功能。雖然功能簡單,但是我們99%日常的業務邏輯也就是由這些簡單的邏輯組成的了。下面是demo執行的效果圖。

image

看完執行效果,我們開始看看ORM資料庫的使用。ORM資料庫的核心程式碼都是lua,其中WeatherManager.lua是業務邏輯程式碼,其他的lua檔案是ORM資料庫的核心程式碼,全部是lua實現的,所有程式碼檔案加起來也就120k左右,非常輕量。

針對上面提到的天氣資訊的功能,我們來設計資料模型,從demo的展示我們看到每天天氣資訊包含幾個資訊,城市名、日出日落時間、最高溫度、最低溫度、風向、風力,然後為了區分是哪一天的資料,我們給每條資訊加上個id的屬性,作為主鍵。想好我們就開始定義第一個ORM資料模型,有幾個必要的資訊,db名,表名,後面的就是我們需要的各個欄位了,我們提供IntegerField、RealField、BlobField、TextField、BooleandField。等常用的資料型別。weather 就是這個模型的名字,之後我們weather為索引使用這個資料模型。定義模型程式碼如下。

weather = {
   __dbname__ = "test.db",
    __tablename__ = "weather",
    id = {"IntegerField",{unique = true, null = false, primary_key = true}},
    wind = {"TextField",{}},
    wind_direction = {"TextField",{}},
    sun_info = {"TextField",{}},
    low = {"IntegerField",{}},
    high = {"IntegerField",{}},
    city =  {"TextField",{}},
 },
複製程式碼

定義好模型後,我們看看如何使用,我們跟著業務邏輯走,首先網路請求回來我們要生成模型物件存到資料庫,分下面幾步

  • 獲取模型物件
local Table = require('orm.class.table')
local _weatherTable = Table("weather”)
複製程式碼
  • 準備資料,建立資料物件
local t = {}
t.wind = flDict[v.fg]
t.wind_direction = fxDict[v.ff]
t.sun_info = v.fi
t.low = tonumber(v.fd)
t.high = tonumber(v.fc)
t.id = i
t.city = city
local weather = _weatherTable(t)
複製程式碼
  • 儲存資料
weather:save()
複製程式碼
  • 讀取資料
_weatherTable.get:all():getPureData()
複製程式碼

是不是很簡單,很優雅,什麼建表、拼sql、transation、執行緒安全等等都不用考慮,傻瓜式地使用,一個業務就幾行程式碼搞定。這裡只演示了簡單的存取,更多的select、update、聯表等高階用法可參考db_test demo

Flutter ORM資料庫原理詳解

好了,上面已經介紹完如何使用了,如果大家僅僅關心使用下面的可以不看了,如果大家想了解這套跨平臺的ORM框架的實現原理,下面就會詳細介紹,其實瞭解了實現原理,對大傢俱體業務使用還是很有好處的,雖然我感覺大家用的時候極少瞭解原理。

我們把orm框架分為三層接入層,cache層,db操作層,三個層分別處於對應的執行緒,具體可以參考下圖。接入層可以在任意執行緒發起,接入層也是每次資料庫操作的發起點,上面的demo所有操作都是在接入層,cache層,db操作層僅僅是ORM內部劃分,對使用者來講不需要關心cache層和db操作層。我們把所有的操作分成兩種,db後續相關的,和db後續無關的。

image

db後續無關的操作是從接入層不同的執行緒進入到cache層的佇列,所有操作在這個佇列裡先同步完成記憶體操作,然後即可馬上返回接入層,非同步再到db操作層進行db操作。db後續無關的操作包括 save、update、delete。

db後續相關的操作依賴db操作層操作的結果,這樣的話就必須等真實的db操作完成了再返回接入層。db後續相關的操作包括select。

要做到這種資料同步,我們必須先把orm操作介面抽象化,只給幾個常用的介面,所有操作都必須通過指定的介面來完成。我們總結了如下基本操作介面。

1、save

2、select where

3、select PrimaryKey

4、update where

5、update PrimaryKey

6、delete where

7、delete PrimaryKey

這七種操作只要在操作前返回前對記憶體中的cache做相應的處理,即可保證記憶體cache始終和db保持一致,這樣以後我們就可以優先使用cache層的資料了。這七種操作的實現邏輯,這裡先說明一下,cache裡面的物件都是以主鍵為key,orm物件為value的形式儲存在記憶體中的,這些控制邏輯是寫在cache.lua裡面的。

下面詳細介紹七種基本操作的邏輯。

  • save操作,同步修改記憶體cache,然後馬上返回接入層,再非同步進行db replace into 的操作

image

  • where條件select,這個必須先同步到db執行緒獲取查詢結果,再同步修改記憶體裡面的cache值,再返回給接入層

image

  • select PrimaryKey,就是選一定PrimaryKey值的orm物件,這個操作首先看cache裡面是否有primarykey 值的orm對,如果有,直接返回,如果沒有,先同步到db執行緒獲取查詢結果,再同步修改記憶體裡面的cache值,再返回給接入層

image

  • update where,先同步到db執行緒通過where 條件select出需要update的主鍵值,根據主鍵值和需要update的內容,同步更新記憶體cache,然後非同步進行db的update操作

image

  • update PrimaryKey,根據PrimaryKey進行update操作,先同步更新記憶體cache,然後非同步進行db的update操作

image

  • delete where,先同步到db執行緒通過where 條件select出需要delete的主鍵值,根據主鍵值刪除記憶體cache,然後非同步進行db的delete操作

image

  • delete PrimaryKey,根據PrimaryKey進行delete操作,先同步刪除記憶體cache,然後非同步進行db的delete操作

image

只要保證上面七種基本操作邏輯,即可保證cache中的內容和db最終的內容是一致的,這種儘量使用cache的特性可以提升資料庫操作的效率,而且保證同一個db的所有操作都在指定的cache執行緒和db執行緒裡面完成,也可以保證執行緒安全。

最後,由於我們所有的db操作都集中起來了,我們可以定時的transation 儲存,這樣可以大幅提升資料庫操作的效能。

結語

目前Flutter領域最大的痛點就是資料庫操作,本文提供了一種優雅使用ORM資料庫的方法,大幅降低了使用資料庫的門檻。希望這篇文章和flutter_luakit_plugin可以幫到大家更方便的開發Flutter 應用。

相關文章