在我们使用Python来和数据库打交道中,SQLAlchemy是一个非常不错的ORM工具,通过它我们可以很好的实现多种数据库的统一模型接入,而且它提供了非常多的特性,通过结合不同的数据库驱动,我们可以实现同步或者异步的处理封装。

1、SQLAlchemy介绍

SQLAlchemy 是一个功能强大且灵活的 Python SQL 工具包和对象关系映射(ORM)库。它被广泛用于在 Python 项目中处理关系型数据库的场景,既提供了高级的 ORM 功能,又保留了对底层 SQL 语句的强大控制力。SQLAlchemy 允许开发者通过 Python 代码与数据库进行交互,而无需直接编写 SQL 语句,同时也支持直接使用原生 SQL 进行复杂查询。下面是SQLAlchemy和我们常规数据库对象的对应关系说明。
Engine    连接对象         驱动引擎
Session   连接池           事务  由此开始查询
Model     表                   类定义
Column     列  
Query     若干行         可以链式添加多个条件
 
在使用SQLAlchemy时,通常会将其与数据库对象对应起来。以下是SQLAlchemy和常规数据库对象的对应关系说明:

1)数据库表 (Database Table)

  • SQLAlchemy: 使用 Table 对象或 Declarative Base 中的类来表示。
  • 对应关系: 数据库中的每一个表对应于SQLAlchemy中的一个类,该类继承自 declarative_base()
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base):
__tablename__ = 'users' # 数据库表名
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)

2)数据库列 (Database Column)

  • SQLAlchemy: 使用 Column 对象来表示。
  • 对应关系: 每个数据库表中的列在SQLAlchemy中表示为 Column 对象,并作为类的属性定义。
id = Column(Integer, primary_key=True)
name = Column(String(50))

3)数据库行 (Database Row)

  • SQLAlchemy: 每个数据库表的一个实例(对象)代表数据库表中的一行。
  • 对应关系: 在SQLAlchemy中,通过实例化模型类来表示数据库表中的一行。
new_user = User(id=1, name='John Doe', email='john@example.com')

4)主键 (Primary Key)

  • SQLAlchemy: 使用 primary_key=True 参数定义主键。
  • 对应关系: 在数据库表中定义主键列,这列在SQLAlchemy中也需要明确标注。
id = Column(Integer, primary_key=True)

5)外键 (Foreign Key)

  • SQLAlchemy: 使用 ForeignKey 对象来表示。
  • 对应关系: 在SQLAlchemy中使用 ForeignKey 指定关系,指向另一个表的主键列。
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship('User')

6)关系 (Relationships)

  • SQLAlchemy: 使用 relationship 对象来表示。
  • 对应关系: 数据库中表与表之间的关系在SQLAlchemy中通过 relationship 来定义。
addresses = relationship("Address", back_populates="user")

7)会话 (Session)

  • SQLAlchemy: 使用 Session 对象进行事务性操作(如查询、插入、更新、删除)。
  • 对应关系: Session 对象类似于数据库连接对象,用于与数据库进行交互。
from sqlalchemy.orm import sessionmaker

Session = sessionmaker(bind=engine)
session = Session() session.add(new_user)
session.commit()

通过以上对应关系,SQLAlchemy允许开发者以面向对象的方式与数据库交互,提供了一个Pythonic的接口来操作数据库。

2、SQLAlchemy 的同步操作

SQLAlchemy 提供了同步和异步两种操作方式,分别适用于不同的应用场景。以下是如何封装 SQLAlchemy 的同步和异步操作的方法说明:

在同步操作中,SQLAlchemy 使用传统的阻塞方式进行数据库操作。首先,定义一个基础的 SessionEngine 对象:

from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from typing import Generator
from core.config import settings # 常规同步处理
engine = create_engine(settings.DB_URI)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) def get_db() -> Generator:
"""创建一个 SQLAlchemy 数据库会话-同步处理."""
try:
db = SessionLocal()
yield db
finally:
db.close()

前面说了,使用SQLAlchemy可以实现不同数据库的统一模型的处理,我们可以对应创建不同数据库的连接(engine),如下是常规几种关系型数据库的连接处理。

# mysql 数据库引擎
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/WinFramework",
pool_recycle=3600,
# echo=True,
) # Sqlite 数据库引擎
engine = create_engine("sqlite:///testdir//test.db") # PostgreSQL 数据库引擎
engine = create_engine(
"postgresql+psycopg2://postgres:123456@localhost:5432/winframework",
# echo=True,
) # SQLServer 数据库引擎
engine = create_engine(
"mssql+pymssql://sa:123456@localhost/WinFramework?tds_version=7.0",
# echo=True,
)

我们可以根据数据库的CRUD操作方式,封装一些操作,如下所示。

class CRUDOperations:
def __init__(self, model):
self.model = model def create(self, db, obj_in):
db_obj = self.model(**obj_in.dict())
db.add(db_obj)
db.commit()
db.refresh(db_obj)
return db_obj def get(self, db, id):
return db.query(self.model).filter(self.model.id == id).first() def update(self, db, db_obj, obj_in):
obj_data = obj_in.dict(exclude_unset=True)
for field in obj_data:
setattr(db_obj, field, obj_data[field])
db.commit()
db.refresh(db_obj)
return db_obj def remove(self, db, id):
obj = db.query(self.model).get(id)
db.delete(obj)
db.commit()
return obj

使用时,构建数据访问类进行操作,如下测试代码所示。

crud_user = CRUDOperations(User)

# Create
with get_db() as db:
user = crud_user.create(db, user_data) # Read
with get_db() as db:
user = crud_user.get(db, user_id) # Update
with get_db() as db:
user = crud_user.update(db, user, user_data) # Delete
with get_db() as db:
crud_user.remove(db, user_id)

3、SQLAlchemy 的异步操作封装

对于异步操作,SQLAlchemy 使用 AsyncSession 来管理异步事务。

首先,定义一个异步的 SessionEngine 对象:

from sqlalchemy import create_engine, URL
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from typing import AsyncGenerator def create_engine_and_session(url: str | URL):
try:
# 数据库引擎
engine = create_async_engine(url, pool_pre_ping=True)
except Exception as e:
print(" 数据库链接失败 {}", e)
sys.exit()
else:
db_session = async_sessionmaker(
bind=engine, autoflush=False, expire_on_commit=False
)
return engine, db_session # 异步处理
async_engine, async_session = create_engine_and_session(settings.DB_URI_ASYNC) async def get_db() -> AsyncGenerator[AsyncSession, None]:
"""创建一个 SQLAlchemy 数据库会话-异步处理."""
async with async_session() as session:
yield session

和同步的处理类似,不过是换了一个对象来实现,并且函数使用了async await的组合来实现异步操作。

为了实现我的SQLSugar开发框架类似的封装模式,我们参考SQLSugar开发框架中基类CRUD的定义方式来实现多种接口的封装处理。

参照上面的实现方式,我们来看看Python中使用泛型的处理封装类的代码。

ModelType = TypeVar("ModelType", bound=Base)
PrimaryKeyType = TypeVar("PrimaryKeyType", int, str, float) # 限定主键的类型
PageDtoType = TypeVar("PageDtoType", bound=BaseModel)
DtoType = TypeVar("DtoType", bound=BaseModel) class BaseCrud(Generic[ModelType, PrimaryKeyType, PageDtoType, DtoType]):
"""
基础CRUD操作类
""" def __init__(self, model: Type[ModelType]):
"""
数据库访问操作的基类对象(CRUD).
**Parameters**
* `model`: A SQLAlchemy model class
"""
self.model = model

这样,我们就可以通过泛型定义不同的类型,以及相关的处理类的信息。

该基类函数中,异步定义get_all的返回所有的数据接口如下所示。

    async def get_all(
self, sorting: Optional[str], db: AsyncSession
) -> List[ModelType] | None:
"""根据ID字符串列表获取对象列表 :param sorting: 格式:name asc 或 name asc,age desc
"""
query = select(self.model)
if sorting:
query = self.apply_sorting(query, sorting) result = await db.execute(query)
items = result.scalars().all()
return items

而对应获得单个对象的操作函数,如下所示。

    async def get(self, id: PrimaryKeyType, db: AsyncSession) -> Optional[ModelType]:
"""根据主键获取一个对象"""
query = select(self.model).filter(self.model.id == id) result = await db.execute(query)
item = result.scalars().first() return item

而创建对象的操作函数,如下所示。

    async def create(self, obj_in: DtoType, db: AsyncSession, **kwargs) -> bool:
"""创建对象,使用 kwargs 时可以扩展创建对象时的字段。 :param obj_in: 对象输入数据
:param kwargs: 扩展字段,如格式: is_deleted=0, is_active=1
"""
try:
if kwargs:
instance = self.model(**obj_in.model_dump(), **kwargs)
else:
instance = self.model(**obj_in.model_dump()) # type: ignore db.add(instance)
await db.commit()
return True
except SQLAlchemyError as e:
print(e)
await db.rollback()
return False

这个异步函数 create 旨在通过 SQLAlchemy 在数据库中创建一个对象,同时允许通过 kwargs 参数动态扩展创建对象时的字段。

  • async def: 表明这是一个异步函数,可以与 await 一起使用。
  • self: 这是一个类的方法,因此 self 引用类的实例。
  • obj_in: DtoType: obj_in 是一个数据传输对象(DTO),它包含了需要插入到数据库中的数据。DtoType 是一个泛型类型,用于表示 DTO 对象。
  • db: AsyncSession: db 是一个 SQLAlchemy 的异步会话(AsyncSession),用于与数据库进行交互。
  • **kwargs: 接受任意数量的关键字参数,允许在对象创建时动态传入额外的字段。
  • obj_in.model_dump(): 假设 obj_in 是一个 Pydantic 模型或类似结构,它可以通过 model_dump() 方法转换为字典格式,用于创建 SQLAlchemy 模型实例。
  • self.model(**obj_in.model_dump(), **kwargs): 使用 obj_in 中的字段以及通过 kwargs 传入的扩展字段来实例化 SQLAlchemy 模型对象。如果 kwargs 非空,它们会被解包并作为额外的字段传入模型构造函数。
  • db.add(instance): 将新创建的对象添加到当前的数据库会话中。
  • await db.commit(): 提交事务,将新对象保存到数据库。
  • SQLAlchemyError: 捕获所有 SQLAlchemy 相关的错误。
  • await db.rollback(): 在发生异常时,回滚事务,以防止不完整或错误的数据被提交。

通过上面的封装,我们可以测试调用的处理例子

from crud.customer import customer as customer_crud
from models.customer import Customer
from pydantic import BaseModel
from schemas.customer import CustomerDto, CustomerPageDto async def test_list_customer():
async with get_db() as db: print("get_list")
totalCount, items = await customer_crud.get_list(
CustomerPageDto(skipCount=0, maxResultCount=10, name="test"),
db,
)
print(totalCount, items)
for customer in customers:
print(customer.name, customer.age) print("get_by_name")
name = "test"
customer = await customer_crud.get_by_name(
name,
db,
)
if customer:
print(customer.name, customer.age)
else:
print(f"{name} not found") print("soft delete")
result = await customer_crud.delete_byid(customer.id, db, is_deleted=1)
print("操作结果:", result) print("soft delete_byids")
result = await customer_crud.delete_byids(
["11122", "2C5F8672-2AA7-4B14-85AD-DF56F5BF7F1F"], db, is_deleted=1
)
print(f"Soft delete successful: {result}") print("update_by_column")
result = await customer_crud.update_by_column(
"id", customer.id, {"age": 30}, db
)
print("操作结果:", result) await db.close()

同步和异步处理的差异:

  • 同步操作 适用于传统的阻塞式应用场景,比如命令行工具或简单的脚本。
  • 异步操作 更适合异步框架如 FastAPI,可以提高高并发场景下的性能。

通过封装数据库操作,可以让代码更具复用性和可维护性,支持不同类型的操作场景。

Python开发中,SQLAlchemy 的同步操作和异步操作封装,以及常规CRUD的处理。的更多相关文章

  1. Python面向对象中的继承、多态和封装

    Python面向对象中的继承.多态和封装 一.面向对象的三大特性 封装:把很多数据封装到⼀个对象中,把固定功能的代码封装到⼀个代码块, 函数,对象, 打包成模块. 这都属于封装思想. 继承:⼦类可以⾃ ...

  2. angular开发中对请求数据层的封装

    代码地址如下:http://www.demodashi.com/demo/11481.html 一.本章节仅仅是对angular4项目开发中数据请求封装到model中 仅仅是在项目angular4项目 ...

  3. Hybrid混合开发中schema协议的使用与封装

    混合开发中JS与APP通信的实现原理: JS通过schema协议,传递参数和全局回调函数给APP端 APP执行完(如微信扫一扫)后,调用协议中传入的回调函数,在前端执行回调处理. 默认写法: < ...

  4. python开发中常用的框架

    以下是15个最受欢迎的Python开源框架.这些框架包括事件I/O,OLAP,Web开发,高性能网络通信,测试,爬虫等. Django: Python Web应用开发框架 Django 应该是最出名的 ...

  5. 解决Python开发中,Pycharm中无法使用中文输入法问题

    Pycharm是开发Python程序的利器,但有时会遇到无法输入中文的情况.表现为:在Ubuntu系统可以正常输入中文,却在Pycharm内写注释的时候,切换不出中文.下面演示如何解决此问题. 1.在 ...

  6. python开发中常见的小坑

    (1)可变参数类型作为函数参数默认值,函数参数默认值的设置在Python中只会被执行一次,也就是定义该函数的时候. 解决办法,设置为None,然后判断 (2)Python中的变量名解析遵循所谓的LEG ...

  7. python开发中容易犯的错误整合

    写在前面 长期更新的博文.多数是一些比较隐蔽的问题.欢迎留言补充. pip并不是那么安逸 pip安装对于开发者来说确实是一种解放.可以自动安装依赖包,但执行最简单的pip安装命令时,并不是所有的依赖都 ...

  8. Python 开发中easy_install的安装及使用

    easy_install是一个python的扩展包,主要是用来简化python安装第三方安装包,在安装了easy_install之后,安装python第三方安装包就只需要在命令行中输入:easy_in ...

  9. 饮冰三年-人工智能-Python-30 python开发中常见的错误

    1:触发条件:创建的实体类生成到数据库表时报错 报错信息:TypeError: __init__() missing 1 required positional argument: 'on_delet ...

  10. python语言中的编码问题(续)

    上文提到了python开发中非常重要的两处设置. 一个是编解码器的默认设置defaultencoding >>> import sys >>> sys.getdef ...

随机推荐

  1. Linux 内核:设备驱动模型(5)平台设备驱动

    Linux 内核:设备驱动模型(5)平台设备驱动 背景 我们已经大概熟悉了Linux Device Driver Model:知道了流程大概是怎么样的,为了加深对LDDM框架的理解,我们继续来看pla ...

  2. Linux驱动中的等待队列与休眠

    Linux驱动中的等待队列与休眠 原文:https://blog.csdn.net/mengluoxixiang/article/details/46239523?spm=1001.2014.3001 ...

  3. QT学习:06 常用的全局变量与宏定义

    --- title: framework-cpp-qt-06-常用的全局变量与宏定义 EntryName: framework-cpp-qt-06-global date: 2020-04-10 11 ...

  4. 《HelloGitHub》第 99 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. github.com/521xueweihan/HelloG ...

  5. 新品再袭,比RK3568J降本40%+,瑞芯微最新性价比工业平台-RK3562J

  6. 使用Jest和React Test library 进行React单元测试

    React单元测试,就是把React 组件渲染出来,看看渲染出来的内容符不符合我们的预期.比如组件加载的时候有loading, 那就渲染组件,看看渲染出的内容中有没有loading. 再比如,ajax ...

  7. 单qubit量子门

    量子编程的基本单元就是量子门.量子编程有点像传统的电路设计,一个量子程序可以被写成量子门序列. 图中有一些符合,比如H门.X门.Z门.测量等,我们都会接触到. 传统计算机程序的输入和输出可以不一样,但 ...

  8. javascript的内存(垃圾)回收机制?

    垃圾回收机制 1.js中的内存回收 在js中,垃圾回收器每隔一段时间就会找出那些不再使用的数据,并释放其所占用的内存空间. 以全局变量和局部变量来说,函数中的局部变量在函数执行结束后这些变量已经不再被 ...

  9. 记一次使用python的selenium库爬取动态页面内容的经历

    安装与配置selenium 安装selenium库 pip install selenium 配置浏览器驱动(本次使用Google Chrome) 打开Chrome,在浏览器的地址栏,输入chrome ...

  10. 解决方案 | Windows 验证账号出现 0x80190001错误解决

    一.问题描述 点击windows开始→账户→更改账户设置→验证,出现下面的错误. 二.解决方法 网上流行的是这个方法,https://blog.csdn.net/qq_36393978/article ...