数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。例如,在新增数据A后更新数据B,当更新数据B失败后,要回滚数据库,使得数据A不能新增到数据库中,这就是数据的原子性。
在flask中,如果要想保证事务的原子性应该如何操作呢?
看下面这个例子:
业务逻辑
新增分组并保存分组与用户的关联关系。在分组表中新增一个分组并获取到分组id,然后在关联表中循环插入分组与用户的关联关系。
数据库表结构
数据库中有一个分组表(case_group)和分组权限表(user_auth),分组权限表中保存组id和用户id来关联用户和分组,用户和分组是多对多的关系。此处省略用户表。
CREATE TABLE `case_group` (
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`delete_time` datetime DEFAULT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分组id',
`name` varchar(20) DEFAULT NULL COMMENT '分组名称 全局唯一不可重复',
`info` varchar(50) DEFAULT NULL COMMENT '分组描述',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `user_auth` (
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`delete_time` datetime DEFAULT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户id',
`auth_id` int(11) NOT NULL COMMENT '权限id',
`type` smallint(6) NOT NULL COMMENT '权限id类型 ; 1 -> group分组 | 2 -> project工程',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4;
模型中操作数据库的方法
@classmethod
def new_group(cls, form):
group = CaseGroup.query.filter_by(name=form.name.data, delete_time=None).first()
if group is not None:
raise ParameterException(msg='分组已存在')
# 新增分组的时候同时新增可查看当前用例组的人员。当出现问题时进行回滚,人员和分组都不插入
try:
group = CaseGroup()
group.name = form.name.data
group.info = form.info.data
db.session.add(group)
db.session.flush()
if form.users.data:
current_app.logger.info(group.id)
for user in form.users.data:
user_auth = UserAuth()
user_auth.user_id = user
user_auth.auth_id = group.id
user_auth.type = UserAuthEnum.GROUP
db.session.add(user_auth)
db.session.commit()
except Exception as e:
db.session.rollback()
raise UnknownException(msg='新增异常 数据已回滚')
return True
用try except 将2次新增操作包裹起来,当出现异常时使用db.session.rollback()
进行数据回滚。
在一次新增后使用db.session.add()
进行数据暂存,此时并未真正提交到数据库中,需要在所有操作执行后执行db.session.commit()
进行数据提交。
在这个例子中,新增关联表的数据需要已新增分组数据的id,此时未commit所以自增id为None,需要在新增分组后使用db.session.flush()
刷新获取分组id。