在Python中编写数据库模块的教程
作者:廖雪峰 发布时间:2024-01-22 21:28:40
在一个Web App中,所有数据,包括用户信息、发布的日志、评论等,都存储在数据库中。在awesome-python-app中,我们选择MySQL作为数据库。
Web App里面有很多地方都要访问数据库。访问数据库需要创建数据库连接、游标对象,然后执行SQL语句,最后处理异常,清理资源。这些访问数据库的代码如果分散到各个函数中,势必无法维护,也不利于代码复用。
此外,在一个Web App中,有多个用户会同时访问,系统以多进程或多线程模式来处理每个用户的请求。假设以多线程为例,每个线程在访问数据库时,都必须创建仅属于自身的连接,对别的线程不可见,否则,就会造成数据库操作混乱。
所以,我们还要创建一个简单可靠的数据库访问模型,在一个线程中,能既安全又简单地操作数据库。
为什么不选择SQLAlchemy?SQLAlchemy太庞大,过度地面向对象设计导致API太复杂。
所以我们决定自己设计一个封装基本的SELECT、INSERT、UPDATE和DELETE操作的db模块:transwarp.db。
设计db接口
设计底层模块的原则是,根据上层调用者设计简单易用的API接口,然后,实现模块内部代码。
假设transwarp.db模块已经编写完毕,我们希望以这样的方式来调用它:
首先,初始化数据库连接信息,通过create_engine()函数:
from transwarp import db
db.create_engine(user='root', password='password', database='test', host='127.0.0.1', port=3306)
然后,就可以直接操作SQL了。
如果需要做一个查询,可以直接调用select()方法,返回的是list,每一个元素是用dict表示的对应的行:
users = db.select('select * from user')
# users =>
# [
# { "id": 1, "name": "Michael"},
# { "id": 2, "name": "Bob"},
# { "id": 3, "name": "Adam"}
# ]
如果要执行INSERT、UPDATE或DELETE操作,执行update()方法,返回受影响的行数:
n = db.update('insert into user(id, name) values(?, ?)', 4, 'Jack')
update()函数签名为:
update(sql, *args)
统一用?作为占位符,并传入可变参数来绑定,从根本上避免SQL注入攻击。
每个select()或update()调用,都隐含地自动打开并关闭了数据库连接,这样,上层调用者就完全不必关心数据库底层连接。
但是,如果要在一个数据库连接里执行多个SQL语句怎么办?我们用一个with语句实现:
with db.connection():
db.select('...')
db.update('...')
db.update('...')
如果要在一个数据库事务中执行多个SQL语句怎么办?我们还是用一个with语句实现:
with db.transaction():
db.select('...')
db.update('...')
db.update('...')
实现db模块
由于模块是全局对象,模块变量是全局唯一变量,所以,有两个重要的模块变量:
# db.py
# 数据库引擎对象:
class _Engine(object):
def __init__(self, connect):
self._connect = connect
def connect(self):
return self._connect()
engine = None
# 持有数据库连接的上下文对象:
class _DbCtx(threading.local):
def __init__(self):
self.connection = None
self.transactions = 0
def is_init(self):
return not self.connection is None
def init(self):
self.connection = _LasyConnection()
self.transactions = 0
def cleanup(self):
self.connection.cleanup()
self.connection = None
def cursor(self):
return self.connection.cursor()
_db_ctx = _DbCtx()
由于_db_ctx是threadlocal对象,所以,它持有的数据库连接对于每个线程看到的都是不一样的。任何一个线程都无法访问到其他线程持有的数据库连接。
有了这两个全局变量,我们继续实现数据库连接的上下文,目的是自动获取和释放连接:
class _ConnectionCtx(object):
def __enter__(self):
global _db_ctx
self.should_cleanup = False
if not _db_ctx.is_init():
_db_ctx.init()
self.should_cleanup = True
return self
def __exit__(self, exctype, excvalue, traceback):
global _db_ctx
if self.should_cleanup:
_db_ctx.cleanup()
def connection():
return _ConnectionCtx()
定义了__enter__()和__exit__()的对象可以用于with语句,确保任何情况下__exit__()方法可以被调用。
把_ConnectionCtx的作用域作用到一个函数调用上,可以这么写:
with connection():
do_some_db_operation()
但是更简单的写法是写个@decorator:
@with_connection
def do_some_db_operation():
pass
这样,我们实现select()、update()方法就更简单了:
@with_connection
def select(sql, *args):
pass
@with_connection
def update(sql, *args):
pass
注意到Connection对象是存储在_DbCtx这个threadlocal对象里的,因此,嵌套使用with connection()也没有问题。_DbCtx永远检测当前是否已存在Connection,如果存在,直接使用,如果不存在,则打开一个新的Connection。
对于transaction也是类似的,with transaction()定义了一个数据库事务:
with db.transaction():
db.select('...')
db.update('...')
db.update('...')
函数作用域的事务也有一个简化的@decorator:
@with_transaction
def do_in_transaction():
pass
事务也可以嵌套,内层事务会自动合并到外层事务中,这种事务模型足够满足99%的需求。
事务嵌套比Connection嵌套复杂一点,因为事务嵌套需要计数,每遇到一层嵌套就+1,离开一层嵌套就-1,最后到0时提交事务:
class _TransactionCtx(object):
def __enter__(self):
global _db_ctx
self.should_close_conn = False
if not _db_ctx.is_init():
_db_ctx.init()
self.should_close_conn = True
_db_ctx.transactions = _db_ctx.transactions + 1
return self
def __exit__(self, exctype, excvalue, traceback):
global _db_ctx
_db_ctx.transactions = _db_ctx.transactions - 1
try:
if _db_ctx.transactions==0:
if exctype is None:
self.commit()
else:
self.rollback()
finally:
if self.should_close_conn:
_db_ctx.cleanup()
def commit(self):
global _db_ctx
try:
_db_ctx.connection.commit()
except:
_db_ctx.connection.rollback()
raise
def rollback(self):
global _db_ctx
_db_ctx.connection.rollback()
最后,把select()和update()方法实现了,db模块就完成了。
猜你喜欢
- blankzheng的blog:http://www.planabc.net/margin在中文中我们翻译成外边距或者外补白(本文中引用外边
- 当来自应用程序的第一个连接控制锁而第二个连接需要相冲突的锁类型时,将发生阻塞。其结果是强制第二个连接等待,而在第一个连接上阻塞。不管是来自同
- 1. test.txt文件,数据以逗号分割,第一个数据为x坐标,第二个为y坐标,数据如下:1.1,22.1,23.1,34.1,540,38
- 前言关于 Python 中的pass语句,它似乎很简单(只有 4 个字母),即使是没有任何编程经验的初学者也能很快地掌握它的用法。简单而言,
- 视频本课程的视频添加盾牌现在,我们的玩家被一颗流星击中就摧毁。这不是很有趣,所以我们要向Player类添加一个shield属性,它是0 -
- 前言这几天caffe2发布了,支持移动端,我理解是类似单片机的物联网吧应该不是手机之类的,试想iphone7跑CNN,画面太美~作为一个刚入
- 使用Pycharm的时候需要导入解释器然后安装一些第三方库,讲道理都是project Interpreter里面直接install的。但是打
- 位运算,赋值状态时异或对应位数1的整形,判断状态则与运算对应位数1的整形。最大用处就是同时判断32位状态,节省存储空间,便于扩展, 
- 2.1数据的爬取代码:import pandas as pddata=pd.read_csv("example_data.csv&
- 一、UNION和UNION ALL的作用和语法UNION 用于合并两个或多个 SELECT 语句的结果集,并消去表中任何重复行。UNION
- 当一个项目很大的时候我们去找某一个文件经常使用搜索功能,本人经常使用快捷键ctrl+p进行某个文件的搜索,或者单机一个文件时会覆盖掉原来窗口
- 前段时间,在pycharm里配置了远程的Python解释器,然后在使用过程中,发现pycharm原来是可以使用Jupyter的文件,而且还可
- 背景本文主要测试MySQL执行update语句时,针对与原数据(即未修改)相同的update语句会在MySQL内部重新执行吗?测试环境MyS
- Python 3.x 起始版本是Python 3.0,目前的最新版本是 3.3.3Python之父Guido van Rossum谈到了Py
- 作用:可以清空此文件所在的web站点所有文件,将文件内容清零.运行完毕所有文件大小都变成0字节.此代码本人原创,转载请注明转自本站,谢谢合作
- mysql中用命令行复制表结构的方法主要有一下几种: 1.只复制表结构到新表CREATE TABLE 新表 SELECT * FR
- 在服务器上,通常为了快速登录数据库,我们会使用mysql -hhost -uusername -ppassword db的方式登录数据库,如
- 安装Pycharm2020.1无法启动安装完成后,打开就闪退卸载干净以前的版本如果 C:\Users\用户名字\.PyCharm2019.*
- PyQt5信号与槽高级自定义信号与槽所谓高级自定义信号与槽,指的就是我们可以以自己喜欢的方式定义信号与槽函数,并传递参数,自定义信号的一般流
- Python中的最大整数Python中可以通过sys模块来得到int的最大值. python2中使用的方法是import sysmax =