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进行事务提交。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏逸鹏说道

C# 温故而知新: 线程篇(二) 上

线程池和异步线程 目录: 1 什么是CLR线程池? 2 简单介绍下线程池各个优点的实现细节 3 线程池ThreadPool的常用方法介绍 4 简单理解下异步线程...

3199
来自专栏用户2442861的专栏

linux select函数详解

http://blog.csdn.net/lingfengtengfei/article/details/12392449

1022
来自专栏杂烩

mongodb拾遗

831
来自专栏Java编程技术

一个有关定时生产与消费的问题

按照上面的逻辑看的话,每个队列里面最多有一个元素。其实不然,因为在多线程模型中每个线程占用cpu执行的时间是按照时间片来划分的,每个线程执行完自己的时间片后会被...

821
来自专栏ImportSource

并发编程-用锁来保护状态

由于锁机制可以让他保护起来的代码片段始终被串行访问。也就是一个访问完了,再由下一个来访问。我们可以利用锁的这种特点,来约定一些协议,来对共享的状态进行独占访问。...

3365
来自专栏Java帮帮-微信公众号-技术文章全总结

hibernate延迟加载详解

hibernate延迟加载详解 Hibernae 的延迟加载是一个非常常用的技术,实体的集合属性默认会被延迟加载,实体所关联的实体默认也会被延迟加载。hiber...

3123
来自专栏用户画像

指令及操作数的寻址方式

  操作数的寻址:把操作数的形式地址,根据间址和变址等组合变换为操作数有效地址的过程。

4062
来自专栏云计算教程系列

如何使用Grep

Grep是一个命令行实用程序,可以使用常见的正则表达式语法搜索和过滤文本。它无处不在,动词“to grep”已经成为“搜索”的同义词。它grep是一个有用的工具...

1153
来自专栏nnngu

02 Java类的加载机制

1、什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Cla...

2957
来自专栏芋道源码1024

【死磕Java并发】—–Java内存模型之happens-before

在上篇博客(【死磕Java并发】—–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性...

2965

扫码关注云+社区

领取腾讯云代金券