sqlite與monad transformers

前言

數(shù)據(jù)庫連接是常見的IO操作塞俱,這一節(jié)我們將一些IO與monad transformer結合起來飞涂,看看能否對我們有啟發(fā)恩沛。

這里我們使用sqlite3數(shù)據(jù)庫枣耀,庫用sqlite-simple霉晕。

連接數(shù)據(jù)庫

我們從教程中可以得到一個非常簡單的示例。

main :: IO ()
main = do
  conn <- open db
  execute_ conn "create table if not exists users (id integer primary key, age integer not null, name text not null)"
  execute conn "insert into users (name, age) values (?, ?)" ("haoren" :: T.Text, 10 :: Int)
  id' <- lastInsertRowId conn
  user' <- query conn "select * from users where id = ?" (Only id') :: IO [User]
  close conn
  print user'
  putStrLn "Finished"
# output
[User {userId = 1, userName = "haoren", userAge = 10}]
Finished

open打開一個連接捞奕,之后就可以用這個連接進行任何數(shù)據(jù)庫操作了牺堰。

繁瑣的代碼

仔細觀察上面的代碼,每次操作都需要打開一個連接颅围,而且每次操作都要帶上conn連接伟葫,我們有沒有什么辦法做到簡單呢?或者我們觀察其它語言會如何做的院促?

如果用過orm筏养,大家一般都知道需要初始化后才能使用,用javascript可能會這樣寫:

let db = null;

orm.init(config)
    .then(instance => {
        instance.query("select 1");
        // 保存db實例
        db = instance;
        ....
    })
;

當然常拓,我們有時會保存instance這個變量渐溶,以供其它地方使用。像上面的db變量一樣弄抬。

但在Haskell中無法這樣做掌猛,這是因為它追求純度決定的。那么我們還有什么辦法解決呢?或者我們考慮一下上面的instance實例荔茬,它并不會憑空產(chǎn)生废膘,它是由orm初始化產(chǎn)生的一個特定實例,在它產(chǎn)生那刻起慕蔚,它的所有一切都已經(jīng)決定好了丐黄。簡而言之,instance的上下文是由config決定孔飒。

或許我們可以特例化這些函數(shù)灌闺。

exe' :: Query -> IO ()
exe' sql = do
  conn <- open db
  execute_ conn sql
  close conn

qq :: (FromRow r, ToRow t) => Query -> t -> IO [r]
qq sql arg = do
  conn <- open db
  result <- query conn sql arg
  close conn
  return result

exe'execute_的特例,跟原始函數(shù)比較坏瞄,它們都少了Connection參數(shù)桂对,我們手動幫它做了。但這種寫法有個問題鸠匀,那就是每執(zhí)行一些這樣的函數(shù)蕉斜,都要年尊連接一次數(shù)據(jù)庫,沒有辦法做到一次連接執(zhí)行多個操作缀棍。

不知道你有沒有這種感覺宅此,db是我們的一個環(huán)境變量,我們的所有實例連接也都是產(chǎn)生于它爬范,再往下說父腕,每次數(shù)據(jù)庫操作都依然于conn這個環(huán)境變量。我們是時候來試試ReaderT了青瀑。

ReaderT封裝

在封裝之前璧亮,我們需要確定要封裝到什么程度,或者說我們期待以什么樣的形式書寫斥难。結合上面我們已遇到的問題枝嘶,我們現(xiàn)在確定需要一種簡便的方法,隱式或自動傳遞conn蘸炸,還要允許一次打開數(shù)據(jù)庫,能多次操作尖奔。我們可以預想到這樣的代碼:

main :: IO ()
main = do
  exec $ do
    run' "create table if not exists users (id integer primary key, age integer not null, name text not null)"

  user' <- exec $ do
     run "insert into users (name, age) values (?, ?)" ("haoren" :: T.Text, 10 :: Int)
     id' <- lastId
     find "select * from users where id = ?" (Only id') :: Env [User]

  print user'
  putStrLn "Finished"

一次exec就是一次數(shù)據(jù)庫連接搭儒,do后面可以跟多次操作。

剛才提到了提茁,一次數(shù)據(jù)庫連接可以當成對配置的依賴淹禾,一次數(shù)據(jù)操作可以當成是對連接的依賴,所以我們可能會有以下兩個類型茴扁。

type App = ReaderT Config IO

type Env = ReaderT Connection IO

一個exec可以簡單理解成程序自動調用App這個環(huán)境铃岔,并產(chǎn)生了一個Env,之后execdo語法都將在這個上下文中進行。

我們給出exec的實現(xiàn):

exec :: Env a -> IO a
exec r = flip runReaderT "user.db" $ do
  db <- ask
  conn <- liftIO $ open db
  result <- liftIO $ runReaderT r conn
  liftIO $ close conn
  return result

user.db就是我們的db啦毁习。第一個runReaderT就是在創(chuàng)建一個App上下文智嚷,第二個runReaderT創(chuàng)建了Env上下文,所以我們把r放在第二個runReaderT里纺且。此時它已經(jīng)得到了一個連接實例盏道。

完成了這一步,我們的任務還未完成载碌,sqlite-simple提供的函數(shù)類型并不符合exec的上下文猜嘱,所以我們需要針對Env創(chuàng)建特有的函數(shù),為了避免重名嫁艇,我們重新創(chuàng)建函數(shù)朗伶。

run :: ToRow t => Query -> t -> Env ()
run sql args = do
  conn <- ask
  liftIO $ execute conn sql args

run' :: Query -> Env ()
run' sql = do
  conn <- ask
  liftIO $ execute_ conn sql

find :: (ToRow t, FromRow r) => Query -> t -> Env [r]
find sql args = do
  conn <- ask
  liftIO $ query conn sql args

find' :: FromRow r => Query -> Env [r]
find' sql = do
  conn <- ask
  liftIO $ query_ conn sql

lastId :: Env Int64
lastId = do
  conn <- ask
  liftIO $ lastInsertRowId conn

到這一步,我們就完成了全部的工作步咪,之后只要像樣例那樣使用即可论皆。

多個實例

如果我們需要連接多個數(shù)據(jù)庫,上面這些代碼還能夠使用嗎歧斟?答案是肯定的纯丸,除了exec,其它函數(shù)并不依賴于Config環(huán)境變量静袖,它們僅僅依賴于conn這個上下文觉鼻,所以除了exec其它函數(shù)都可以照舊。

genExec :: Config -> Env a -> IO a
genExec db r = flip runReaderT db $ do
  db <- ask
  conn <- liftIO $ open db
  result <- liftIO $ runReaderT r conn
  liftIO $ close conn
  return result

execA :: Env a -> IO a
execA = genExec "user.db"

execB :: Env a -> IO a
execB = genExec "myuser.db"

main :: IO ()
main = do
  execB $ do
    run' "create table if not exists users (id integer primary key, age integer not null, name text not null)"

  user' <- execA $ do
     run "insert into users (name, age) values (?, ?)" ("haoren" :: T.Text, 10 :: Int)
     id' <- lastId
     find "select * from users where id = ?" (Only id') :: Env [User]

  print user'
  putStrLn "Finished"

小結

我們從實際一個例子队橙,看到了transformer對問題的解決方法坠陈。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市捐康,隨后出現(xiàn)的幾起案子仇矾,更是在濱河造成了極大的恐慌,老刑警劉巖解总,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贮匕,死亡現(xiàn)場離奇詭異,居然都是意外死亡花枫,警方通過查閱死者的電腦和手機刻盐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劳翰,“玉大人敦锌,你說我怎么就攤上這事〖阳ぃ” “怎么了乙墙?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我听想,道長腥刹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任哗魂,我火速辦了婚禮肛走,結果婚禮上,老公的妹妹穿的比我還像新娘录别。我一直安慰自己朽色,他們只是感情好,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布组题。 她就那樣靜靜地躺著葫男,像睡著了一般。 火紅的嫁衣襯著肌膚如雪崔列。 梳的紋絲不亂的頭發(fā)上梢褐,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音赵讯,去河邊找鬼盈咳。 笑死,一個胖子當著我的面吹牛边翼,可吹牛的內容都是我干的鱼响。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼组底,長吁一口氣:“原來是場噩夢啊……” “哼丈积!你這毒婦竟也來了?” 一聲冷哼從身側響起债鸡,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤江滨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后厌均,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唬滑,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年棺弊,在試婚紗的時候發(fā)現(xiàn)自己被綠了晶密。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡镊屎,死狀恐怖惹挟,靈堂內的尸體忽然破棺而出茄螃,到底是詐尸還是另有隱情缝驳,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站用狱,受9級特大地震影響运怖,放射性物質發(fā)生泄漏。R本人自食惡果不足惜夏伊,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一摇展、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧溺忧,春花似錦咏连、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至歌溉,卻和暖如春垄懂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痛垛。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工草慧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匙头。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像乾胶,于是被迫代替她去往敵國和親抖剿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理识窿,服務發(fā)現(xiàn)斩郎,斷路器,智...
    卡卡羅2017閱讀 134,715評論 18 139
  • Python 面向對象Python從設計之初就已經(jīng)是一門面向對象的語言喻频,正因為如此缩宜,在Python中創(chuàng)建一個類和對...
    順毛閱讀 4,224評論 4 16
  • 忽然間,特別期待踏上去云南列車的那一刻的到來狰挡,恨不得下一秒就可以到達捂龄,那種急切的心情從未如此刻般強烈過释涛。 相對于上...
    貓咪家的小豆豆閱讀 826評論 0 1
  • 生活中,無論是收獲倦沧,還是失去唇撬; 朋友中,無論是新朋展融,還是舊友窖认; 工作中,無論是順心告希, 還是壓力扑浸; 家...
    十里煙花閱讀 356評論 1 0
  • 早上,靜準備去上班燕偶,窗外的2只黑頭公小鳥已在機靈地啄食著棕樹上的果實首装。初春的早上8點鐘時,暖白色的太陽已經(jīng)高...
    x若水閱讀 264評論 4 0