在Python中使用sqlalchemy来操作数据库的几个小总结
在探索使用 FastAPI, SQLAlchemy, Pydantic,Redis, JWT 构建的项目的时候,其中数据库访问采用SQLAlchemy,并采用异步方式。数据库操作和控制器操作,采用基类继承的方式减少重复代码,提高代码复用性。在这个过程中设计接口和测试的时候,对一些问题进行跟踪解决,并记录供参考。
1、SQLAlchemy事务处理
在异步环境中,批量更新操作需要使用异步方法来执行查询和提交事务。
async def update_range(self, obj_in_list: List[DtoType], db: AsyncSession) -> bool:
"""批量更新对象"""
try:
async with db.begin(): # 使用事务块确保批量操作的一致性
for obj_in in obj_in_list:
# 查询对象
query = select(self.model).filter(self.model.id == obj_in.id)
result = await db.execute(query)
db_obj = result.scalars().first() if db_obj:
# 获取更新数据
update_data = obj_in.model_dump(skip_defaults=True) # 更新对象字段
for field, value in update_data.items():
setattr(db_obj, field, value) return True
except SQLAlchemyError as e:
print(e)
# 异常处理时,事务会自动回滚
return False
在这个改进后的代码中:
- 事务块:使用
async with db.begin()
创建事务块,以确保批量操作的一致性。事务块会在操作完成后自动提交,并在出现异常时回滚。 - 查询对象:使用
select(self.model).filter(self.model.id == obj_in.id)
进行异步查询,并使用await db.execute(query)
执行查询。 - 更新对象字段:用
setattr
更新对象的字段。 - 异常处理:捕获
SQLAlchemyError
异常,并在异常发生时回滚事务。事务块会自动处理回滚,因此不需要手动回滚。
这种方式确保了在异步环境中批量更新操作的正确性和一致性。
在使用 async with db.begin()
进行事务管理时,事务会自动提交。如果在事务块内执行的所有操作都成功,事务会在退出时自动提交;如果出现异常,事务会自动回滚。
因此,手动调用 await db.commit()
是不必要的,因为事务块会处理这些操作。如果你不使用事务块,并希望手动控制事务的提交,可以如下修改:
async def update_range(self, obj_in_list: List[DtoType], db: AsyncSession) -> bool:
"""批量更新对象"""
try:
for obj_in in obj_in_list:
query = select(self.model).filter(self.model.id == obj_in.id)
result = await db.execute(query)
db_obj = result.scalars().first() if db_obj:
update_data = obj_in.model_dump(skip_defaults=True) for field, value in update_data.items():
setattr(db_obj, field, value) await db.commit() # 手动提交事务
return True
except SQLAlchemyError as e:
print(e)
await db.rollback() # 确保在出错时回滚事务
return False
在这个手动提交事务的例子中:
- 在更新对象的操作完成后,使用
await db.commit()
来提交事务。 - 如果发生异常,使用
await db.rollback()
来回滚事务。
根据需求选择合适的方法进行事务管理。事务块方式通常是更安全和简洁的选择。
在异步环境中,create_update
方法需要对数据库进行异步查询、更新或创建操作。
async def create_update(
self, obj_in: DtoType, id: PrimaryKeyType, db: AsyncSession
) -> bool:
"""创建或更新对象"""
try:
# 查询对象
query = select(self.model).filter(self.model.id == id)
result = await db.execute(query)
db_obj = result.scalars().first() if db_obj:
# 更新对象
return await self.update(obj_in, db)
else:
# 创建对象
return await self.create(obj_in, db)
except SQLAlchemyError as e:
print(e)
# 确保在出错时回滚事务
await db.rollback()
return False
在这个代码中:
- 异步查询:使用
select(self.model).filter(self.model.id == id)
来构建查询,并用await db.execute(query)
执行查询。 - 获取对象:使用
result.scalars().first()
来获取查询结果中的第一个对象。 - 调用更新或创建方法:根据查询结果的有无,分别调用
self.update
或self.create
方法。确保这两个方法都是异步的,并在调用时使用await
。 - 异常处理:捕获
SQLAlchemyError
异常,并在发生异常时使用await db.rollback()
来回滚事务。
在异步环境中,批量插入对象通常需要使用异步方法来执行数据库操作。由于 bulk_insert_mappings
在 SQLAlchemy 的异步版本中可能不直接支持,你可以使用 add_all
方法来批量添加对象。
async def save_import(self, data: List[DtoType], db: AsyncSession) -> bool:
"""批量导入对象"""
try:
# 将 DTO 转换为模型实例
db_objs = [self.model(**obj_in.model_dump()) for obj_in in data] # 批量添加对象
db.add_all(db_objs) # 提交事务
await db.commit() return True
except SQLAlchemyError as e:
print(e)
await db.rollback() # 确保在出错时回滚事务
return False
代码说明:
- 转换 DTO 为模型实例:使用
[self.model(**obj_in.model_dump()) for obj_in in data]
将data
列表中的 DTO 转换为模型实例列表。 - 批量添加对象:使用
db.add_all(db_objs)
批量添加对象到数据库会话。 - 提交事务:使用
await db.commit()
异步提交事务。 - 异常处理:捕获
SQLAlchemyError
异常,使用await db.rollback()
回滚事务以确保在出错时数据库状态的一致性。
这种方式确保了在异步环境中正确地进行批量导入操作,并处理可能出现的异常。
2、在 SQLAlchemy 中select(...).where(...)
和 select(...).filter(...)的差异
在 SQLAlchemy 中,select(...).where(...)
和 select(...).filter(...)
都用于构造查询条件,但它们有一些细微的差别和适用场景。
1. where(...)
- 定义:
where
是 SQLAlchemy 中select
对象的方法,用于添加查询的条件。 - 用法:
query = select(self.model).where(self.model.id == id)
- 描述:
where
方法用于指定 SQLWHERE
子句的条件。在大多数情况下,它的行为和filter
是等效的。
2. filter(...)
- 定义:
filter
是 SQLAlchemy 中Query
对象的方法,用于添加查询的条件。 - 用法:
query = select(self.model).filter(self.model.id == id)
- 描述:
filter
方法也用于指定 SQLWHERE
子句的条件。它通常用于更复杂的查询构建中,尤其是在 ORM 查询中。
主要差异
上下文:
where
是select
对象的一部分,通常用于构建 SQL 查询(SQLAlchemy Core)。而filter
是Query
对象的一部分,通常用于 ORM 查询(SQLAlchemy ORM)。然而,在 SQLAlchemy 2.0+ 中,select
和filter
的使用变得更加一致。语义:在使用 SQLAlchemy Core 时,
where
更加明确地表示你正在添加 SQL 语句中的WHERE
子句。在 ORM 查询中,filter
也做了类似的事情,但它提供了更多 ORM 相关的功能。
使用 where
的示例(SQLAlchemy Core):
from sqlalchemy.future import select
from sqlalchemy.ext.asyncio import AsyncSession async def get(self, id: int, db: AsyncSession) -> Optional[ModelType]:
query = select(self.model).where(self.model.id == id)
result = await db.execute(query)
return result.scalars().first()
使用 filter
的示例(SQLAlchemy ORM):
from sqlalchemy.orm import sessionmaker async def get(self, id: int, db: AsyncSession) -> Optional[ModelType]:
query = select(self.model).filter(self.model.id == id)
result = await db.execute(query)
return result.scalars().first()
总结
- 在 SQLAlchemy Core 中:
where
是构建查询条件的标准方法。 - 在 SQLAlchemy ORM 中:
filter
用于构建查询条件,但在 Core 中,filter
的使用相对较少。
在 SQLAlchemy 2.0 及更高版本中,select
的 where
和 filter
的用法变得越来越一致,你可以根据自己的习惯和需求选择其中一种。在实际开发中,选择哪一种方法通常取决于你的代码上下文和个人偏好。
3、model_dump(exclude_unset=True) 和model_dump(skip_defaults=True)有什么差异
model_dump(exclude_unset=True)
和 model_dump(skip_defaults=True)
是用于处理模型实例的序列化方法,它们的用途和行为略有不同。这两个方法通常用于将模型实例转换为字典,以便进行进一步的处理或传输。
model_dump(exclude_unset=True)
exclude_unset=True
是一个选项,通常用于序列化方法中,表示在转换模型实例为字典时,排除那些未设置的字段。
- 功能:排除所有未显式设置(即使用默认值)的字段。
- 使用场景:适用于需要忽略那些未被用户设置的字段,以避免在输出中包含默认值。
# 假设模型有字段 'name' 和 'age',且 'age' 使用了默认值
model_instance = MyModel(name='Alice', age=25)
# 如果 age 的默认值是 0, exclude_unset=True 将只包含 'name'
serialized_data = model_instance.model_dump(exclude_unset=True)
model_dump(skip_defaults=True)
skip_defaults=True
是另一个选项,表示在转换模型实例为字典时,排除那些使用了默认值的字段。
- 功能:排除所有字段的值等于其默认值的字段。
- 使用场景:适用于需要排除那些显式设置为默认值的字段,以减少输出的冗余信息。
# 假设模型有字段 'name' 和 'age',且 'age' 使用了默认值
model_instance = MyModel(name='Alice', age=25)
# 如果 age 的默认值是 0, skip_defaults=True 将只包含 'name'
serialized_data = model_instance.model_dump(skip_defaults=True)
主要区别
排除条件:
exclude_unset=True
排除那些在模型实例中未显式设置的字段(即字段值为默认值或未赋值)。skip_defaults=True
排除那些字段值等于其默认值的字段。
适用场景:
- 使用
exclude_unset=True
时,目的是排除那些在实例化过程中未被显式赋值的字段,这通常用于避免包含那些尚未配置的字段。 - 使用
skip_defaults=True
时,目的是去掉那些显式设置为默认值的字段,以避免输出不必要的信息。
- 使用
4、使用**kwargs 参数,在接口中实现数据软删除的处理
例如我们在删除接口中,如果传递了 kwargs
参数,则进行软删除(更新记录),否则进行硬删除(删除记录)。
async def delete_byid(self, id: PrimaryKeyType, db: AsyncSession, **kwargs) -> bool:
"""根据主键删除一个对象 :param kwargs: for soft deletion only
"""
if not kwargs:
result = await db.execute(sa_delete(self.model).where(self.model.id == id))
else:
result = await db.execute(
sa_update(self.model).where(self.model.id == id).values(**kwargs)
) await db.commit()
return result.rowcount > 0
实例代码如下所示。
# 示例模型
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Boolean Base = declarative_base() class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String)
is_deleted = Column(Boolean, default=False) # 示例使用
async def main():
async with AsyncSession(engine) as session:
controller = BaseController(Customer) # 硬删除
result = await controller.delete_byid(1, session)
print(f"Hard delete successful: {result}") # 软删除
result = await controller.delete_byid(2, session, is_deleted=True)
print(f"Soft delete successful: {result}") # 确保运行主程序
import asyncio
if __name__ == "__main__":
asyncio.run(main())
注意事项
模型定义:确保你的模型中包含
is_deleted
字段,并且字段名正确。传递参数:在调用
delete_byid
方法时,正确传递kwargs
参数。例如,如果你要进行软删除,可以传递is_deleted=True
。调试输出:你可以添加一些调试输出(如
print(kwargs)
),以确保正确传递了参数。
# 示例硬删除调用
await controller.delete_byid(1, session) # 示例软删除调用
await controller.delete_byid(2, session, is_deleted=True)
如果我们的is_deleted
字段是Int类型的,如下所示,那么处理有所不同
class Customer(Base):
__tablename__ = "t_customer" id = Column(String, primary_key=True, comment="主键")
name = Column(String, comment="姓名")
age = Column(Integer, comment="年龄")
creator = Column(String, comment="创建人")
createtime = Column(DateTime, comment="创建时间")
is_deleted = Column(Integer, comment="是否删除")
操作代码
# 硬删除
result = await controller.delete_byid("1", session)
print(f"Hard delete successful: {result}") # 软删除
result = await controller.delete_byid("2", session, is_deleted=1)
print(f"Soft delete successful: {result}")
注意事项
模型定义:你的
Customer
模型定义看起来是正确的,确保所有字段和注释都符合你的要求。硬删除和软删除:
- 硬删除:直接从数据库中删除记录。
- 软删除:通过更新
is_deleted
字段来标记记录为已删除,而不是实际删除记录。
正确传递参数:
- 硬删除时,不需要传递额外参数。
- 软删除时,传递
is_deleted=1
作为参数。
通过确保正确传递参数并且模型包含正确的字段,你应该能够正确执行软删除和硬删除操作。
5、Python处理接口的时候,Iterable 和List有什么差异
在 Python 中,Iterable
和 List
是两个不同的概念,它们有各自的特点和用途:
Iterable
Iterable
是一个更广泛的概念,指的是任何可以返回一个迭代器的对象。迭代器是一个实现了 __iter__()
方法的对象,能够逐个返回元素。几乎所有的容器类型(如列表、元组、字典、集合等)都是可迭代的。要检查一个对象是否是可迭代的,可以使用 collections.abc.Iterable
来进行检查。
特点
- 通用性:
Iterable
是一个通用的接口,表示对象可以被迭代。 - 惰性:一些
Iterable
可能是惰性计算的(如生成器),即它们不会立即计算所有元素,而是按需生成元素。 - 示例:列表(
List
)、元组(Tuple
)、字典(Dict
)、集合(Set
)、生成器(Generator
)等都是可迭代对象。
from collections.abc import Iterable print(isinstance([1, 2, 3], Iterable)) # True
print(isinstance((1, 2, 3), Iterable)) # True
print(isinstance({1, 2, 3}, Iterable)) # True
print(isinstance({'a': 1}, Iterable)) # True
print(isinstance((x for x in range(3)), Iterable)) # True
List
List
是 Python 中的一种具体的容器类型,表示一个有序的元素集合,可以包含重复的元素。它是最常用的可变序列类型之一,支持索引访问、切片操作以及其他多种方法来操作列表中的元素。
特点
- 具体实现:
List
是一个具体的类型,表示一个动态数组,可以存储多个对象。 - 有序:列表保持元素的插入顺序。
- 可变:可以对列表中的元素进行修改(如添加、删除、更新)。
- 示例:
[1, 2, 3]
是一个列表。
my_list = [1, 2, 3]
print(my_list) # [1, 2, 3]
my_list.append(4) # [1, 2, 3, 4]
my_list[0] = 10 # [10, 2, 3, 4]
总结一下:
- Iterable:一个广泛的概念,表示可以被迭代的对象,不一定是具体的数据结构。例如,生成器是可迭代的但不是列表。
- List:一个具体的容器类型,是一种有序的可变集合。列表是
Iterable
的一种实现,但并不是所有的Iterable
都是列表。
Iterable
是一个抽象概念,而 List
是一个具体的实现。你可以在 List
之上使用许多操作和方法来处理数据,而 Iterable
主要关注的是是否可以进行迭代。
因此接收结合的处理,我们可以使用Iterable接口更加通用一些。
async def create_range(
self, obj_in_list: Iterable[DtoType], db: AsyncSession
) -> bool:
"""批量创建对象"""
try:
# 将 DTO 转换为模型实例
db_objs = [self.model(**obj_in.model_dump()) for obj_in in obj_in_list] # 批量添加到数据库
db.add_all(db_objs)
await db.commit()
return True
except SQLAlchemyError as e:
print(e)
await db.rollback() # 确保在出错时回滚事务
return False
以上就是在Python中使用sqlalchemy来操作数据库的时候,对一些小问题的总结,供大家参考。
在Python中使用sqlalchemy来操作数据库的几个小总结的更多相关文章
- 第二百八十九节,MySQL数据库-ORM之sqlalchemy模块操作数据库
MySQL数据库-ORM之sqlalchemy模块操作数据库 sqlalchemy第三方模块 sqlalchemysqlalchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API ...
- 【转】python 历险记(四)— python 中常用的 json 操作
[转]python 历险记(四)— python 中常用的 json 操作 目录 引言 基础知识 什么是 JSON? JSON 的语法 JSON 对象有哪些特点? JSON 数组有哪些特点? 什么是编 ...
- 在Python中使用lambda高效操作列表的教程
在Python中使用lambda高效操作列表的教程 这篇文章主要介绍了在Python中使用lambda高效操作列表的教程,结合了包括map.filter.reduce.sorted等函数,需要的朋友可 ...
- 【简说Python WEB】视图函数操作数据库
目录 [简说Python WEB]视图函数操作数据库 系统环境:Ubuntu 18.04.1 LTS Python使用的是虚拟环境:virutalenv Python的版本:Python 3.6.9 ...
- 48.Python中ORM模型实现mysql数据库基本的增删改查操作
首先需要配置settings.py文件中的DATABASES与数据库的连接信息, DATABASES = { 'default': { 'ENGINE': 'django.db.backends.my ...
- Python中的SQLAlchemy
在Python中,使用SQLAlchemy可以对数据库进行操作. SQLAlchemy是Python中的一个标准库. 要使用SQLAlchemy,首先要创建连接: url = mysql+pymysq ...
- Python中实现异步并发查询数据库
这周又填了一个以前挖下的坑. 这个博客系统使用Psycopy库实现与PostgreSQL数据库的通信.前期,只是泛泛地了解了一下SQL语言,然后就胡乱拼凑出这么一个简易博客系统. 10月份找到工作以后 ...
- Django框架中的model(操作数据库)
什么是ORM ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复 ...
- python 历险记(四)— python 中常用的 json 操作
目录 引言 基础知识 什么是 JSON? JSON 的语法 JSON 对象有哪些特点? JSON 数组有哪些特点? 什么是编码和解码? 常用的 json 操作有哪些? json 操作需要什么库? 如何 ...
- 在PHP中使用MySQL Mysqli操作数据库 ,以及类操作方法
先来操作函数部分,普遍的MySQL 函数方法,但随着PHP5的发展,有些函数使用的要求加重了,有些则将废弃不用,有些则参数必填... ================================= ...
随机推荐
- 【jetson nano】烧录系统
烧录固件 烧录固件是为了让板子用tf卡作为系统启动(非板载启动),一般来说只需要刷写一遍. 安装vm,找到虚拟机镜像,解压part01就能获取镜像. 打开vm,打开此虚拟机镜像,账号clb,密码为12 ...
- Ubuntu 上使能 SELinux
首发公号:Rand_cs 此文档说明如何在 ubuntu 上启用 SELinux,测试环境为虚拟机,开始前一定一定一定先来个快照,不要问我为什么有三个一定. 卸载 apparmor(可选) ubunt ...
- 雪花算法(SnowFlake)
引言 唯一ID可以标识数据的唯一性,在分布式系统中生成唯一ID的方案有很多,常见的方式大概有以下三种: 依赖数据库,使用如MySQL自增列或Oracle序列等. UUID随机数 snowflake雪花 ...
- json字符串转换对象或列表,多了字段不会报错
json字符串转换对象或列表,多了字段不会报错 //DEMO1 转换对象 应用 riskId public class Item { private String id; private String ...
- 服务器上安装centos7系统遇到的坑
centos7的安装报错"no controller found" 出现no controller found解决方案1.等待命令行出现 2.输入ls /dev/sd* 找到自 ...
- 在Linux驱动中使用LED子系统
在Linux驱动中使用LED子系统 原文:https://blog.csdn.net/hanp_linux/article/details/79037684 前提配置device driver下面的L ...
- Android 7 修改启动动画和开机声音
背景 在修改开机音量的时候,发现找不到对应的声音功能调用. 因此了解了一下安卓的开机声音是如何实现的. 安卓4~安卓7 都可以这么做. 参考: https://blog.csdn.net/chen82 ...
- 背包dp——01背包
01背包是背包dp的基础的重点,重点的基础!!! 题意概要:有 n 个物品和一个容量为 W 的背包,每个物品有重量 w_{i} 和价值 v_{i} 两种属性,要求选若干物品放入背包使背包中物品的总价值 ...
- FFmpeg开发笔记(三十六)Linux环境安装SRS实现视频直播推流
<FFmpeg开发实战:从零基础到短视频上线>一书在第10章介绍了轻量级流媒体服务器MediaMTX,通过该工具可以测试RTSP/RTMP等流媒体协议的推拉流.不过MediaMTX的功能 ...
- Maven的依赖详解和打包方式
设置maven maven下载与安装教程: https://blog.csdn.net/YOL888666/article/details/122008374 1. 在File->setting ...