1. 程式人生 > 其它 >python使用上下文管理器實現sqlite3事務機制

python使用上下文管理器實現sqlite3事務機制

如題,本文記錄如何使用python上下文管理器的方式管理sqlite3的控制代碼建立和釋放以及事務機制。

1、python上下文管理(with)

python上下文管理(context),解決的是這樣一類問題,在進入邏輯之前需要進行一些準備工作,在退出邏輯之前需要進行一些善後工作,上下文管理可以使得這種場景變得清晰和可控。

with語句是python上下文管理的基本用法,例如讀寫檔案

with open('filea', r) as f:
    f.readlines()

file使用的就是上下文管理機制,這樣對於開啟檔案控制代碼和釋放檔案控制代碼無須我們額外的投入精力。

2、sqlite3

sqlite3是一個嵌入式的檔案資料庫,無須開啟額外的程序和埠,就可以通過檔案讀取的方式實現資料庫的操作。優點是輕量級並且支援事務和觸發器等高階特性。

sqlite3在python控制代碼建立和管理上跟mysql表現的很相似。

3、程式碼

我們先貼上本文簡述的這段程式碼,然後後面我們在做詳細解釋。

# -*- coding:utf-8 -*-
import sqlite3
import traceback


class SqliteDB(object):

    def __init__(self, database='sqlitedb', isolation_level='', ignore_exc=False):
        self.database = database
        self.isolation_level = isolation_level
        self.ignore_exc = ignore_exc
        self.connection = None
        self.cursor = None

    def __enter__(self):
        try:
            self.connection = sqlite3.connect(database=self.database, isolation_level=self.isolation_level)
            self.cursor = self.connection.cursor()
            return self.cursor
        except Exception, ex:
            traceback.print_exc()
            raise ex

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            if not exc_type is None:
                self.connection.rollback()
                return self.ignore_exc
            else:
                self.connection.commit()
        except Exception, ex:
            traceback.print_exc()
            raise ex
        finally:
            self.cursor.close()
            self.connection.close()

我們給出一個使用的case

if __name__ == '__main__':
    # 建表
    with SqliteDB('test') as db:
        db.execute('create table if not exists user (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(100), age INTEGER)')

    # 建立一條記錄, 如果丟擲異常, 可以測試事務回滾
    with SqliteDB('test') as db:
        db.execute('insert into user (name, age) values (?, ?)', ('Tom', 10))
        #raise Exception()

    # 查詢記錄
    with SqliteDB('test') as db:
        query_set = db.execute('select * from user where name=? limit ?', ('Tom', 100,)).fetchall()
        print len(query_set)
        for item in query_set:
            print item

    # 刪除記錄
    with SqliteDB('test') as db:
        query_set = db.execute('delete from user where name=?', ('Tom',))

可以看到通過with語句打開了資料庫的控制代碼,執行資料庫操作後,我們並沒有管理控制代碼的釋放和事務回滾。

程式碼的輸出是:

1
(6, u'Tom', 10)

當開啟raise Exception()的註釋,表示在插入的過程中遇到了異常。這時候所有connection中未被提交的資料將被回滾。

那麼,這些如何做到的呢?

上下文管理是通過類SqliteDB中的__enter__和__exit__兩個魔法函式實現的。

1、enter函式,用來實現處理進入with_body之前的準備工作,這裡是建立connect和cursor,enter方法返回了cursor。

enter函式如果有返回值,那麼可以賦值給as後面的變數,如果沒有返回,可以簡單的去掉as子句即可。我們給出一個沒有as子句的例子

lock = threading.Lock()
with lock:
    pass

如果enter函式丟擲異常,那麼在執行with語句的時候會丟擲這個異常,並且中斷程式。

2、邏輯上,enter函式之後,便開始執行with_body內的程式碼,with_body裡的程式碼包含sql語句和一些業務邏輯,這裡說明一下,只要是丟擲異常就會觸發事務的回滾機制,而不會區分到底是sql語句執行異常還是業務邏輯出現的異常。

3、exit函式,在with_body執行成功或者丟擲異常後會執行exit函式。

exit函式傳入三個變數,分別是exc_type異常型別,exc_val異常值,exc_tb錯誤堆疊資訊。如果程式正常,那麼三個值都是None,相反如果不是None,那麼可以就此判斷with_body產生了異常。

這裡,我們判斷了exc_type是否為None,來區分是否丟擲了異常,如果丟擲了異常我們使用connection.rollback進行了事務的回滾,否則我們使用connection.commit進行事務提交。

要注意的是,在出現異常的時候,返回了一個ignore_exc,這個返回如果是True,表示忽略這個異常,這個異常將不會向上級呼叫丟擲,如果返回的是None或者False,異常將會向上丟擲。實際中我們還是希望異常能夠跑出來,方便處理,所以這裡我們預設為False。

注意:

isolation_level這個欄位是隔離級別,這裡我們不做深入的說明。需要知道的是這個欄位

1)傳入空字串‘’,表示手動提交commit,這時需要程式中顯示的執行connection.commit進行事務提交,sql中的dml語句才會生效。

2)傳入None,表示開啟自動提交,這時候自動提交commit,無需在程式中connection.commit進行事務提交。