【Python百日进阶-Web开发-Peewee】Day249 - 数据库 事务管理

6.12 事务管理

Peewee 提供了几个用于处理事务的接口。最通用的是Database.atomic()方法,它也支持嵌套事务。atomic()块将在事务或保存点中运行,具体取决于嵌套级别。

如果包装块中发生异常,则当前事务/保存点将回滚。否则,语句将在包装块的末尾提交。

笔记

在由上下文管理器包装的块内时,您可以通过调用或atomic()在任何时候显式回滚或提交
。当您在包装的代码块中执行此操作时,将自动启动一个新事务。Transaction.rollback()Transaction.commit()

with db.atomic() as transaction:  # Opens new transaction.
    try:
        save_some_objects()
    except ErrorSavingData:
        # Because this block of code is wrapped with "atomic", a
        # new transaction will begin automatically after the call
        # to rollback().
        transaction.rollback()
        error_saving = True

    create_report(error_saving=error_saving)
    # Note: no need to call commit. Since this marks the end of the
    # wrapped block of code, the `atomic` context manager will
    # automatically call commit for us.

笔记

atomic()可以用作上下文管理器或装饰器。

6.12.1 上下文管理器

atomic用作上下文管理器:

db = SqliteDatabase(':memory:')

with db.atomic() as txn:
    # This is the outer-most level, so this block corresponds to
    # a transaction.
    User.create(username='charlie')

    with db.atomic() as nested_txn:
        # This block corresponds to a savepoint.
        User.create(username='huey')

        # This will roll back the above create() query.
        nested_txn.rollback()

    User.create(username='mickey')

# When the block ends, the transaction is committed (assuming no error
# occurs). At that point there will be two users, "charlie" and "mickey".

您也可以使用该atomic方法执行get 或 create操作:

try:
    with db.atomic():
        user = User.create(username=username)
    return 'Success'
except peewee.IntegrityError:
    return 'Failure: %s is already in use.' % username

6.12.2 装饰者

atomic用作装饰器:

@db.atomic()
def create_user(username):
    # This statement will run in a transaction. If the caller is already
    # running in an `atomic` block, then a savepoint will be used instead.
    return User.create(username=username)

create_user('charlie')

6.12.3 嵌套事务

atomic()提供透明的事务嵌套。使用时atomic(),最外层的调用将被包装在事务中,任何嵌套调用都将使用保存点。

with db.atomic() as txn:
    perform_operation()

    with db.atomic() as nested_txn:
        perform_another_operation()

Peewee 通过使用保存点支持嵌套事务(有关更多信息,请参阅 参考资料savepoint())。

6.12.4 显式事务

如果您希望在事务中显式运行代码,可以使用 transaction(). 像atomic(), transaction()可以用作上下文管理器或装饰器。

如果包装块中发生异常,则事务将回滚。否则,语句将在包装块的末尾提交。

db = SqliteDatabase(':memory:')

with db.transaction() as txn:
    # Delete the user and their associated tweets.
    user.delete_instance(recursive=True)

事务可以在包装块内显式提交或回滚。发生这种情况时,将启动一个新事务。

with db.transaction() as txn:
    User.create(username='mickey')
    txn.commit()  # Changes are saved and a new transaction begins.
    User.create(username='huey')

    # Roll back. "huey" will not be saved, but since "mickey" was already
    # committed, that row will remain in the database.
    txn.rollback()

with db.transaction() as txn:
    User.create(username='whiskers')
    # Roll back changes, which removes "whiskers".
    txn.rollback()

    # Create a new row for "mr. whiskers" which will be implicitly committed
    # at the end of the `with` block.
    User.create(username='mr. whiskers')

笔记

如果您尝试使用 transaction()上下文管理器将事务与 peewee
嵌套,则只会使用最外层的事务。但是,如果嵌套块中发生异常,这可能会导致不可预知的行为,因此强烈建议您使用atomic().

6.12.5 显式保存点

正如您可以显式创建事务一样,您也可以使用该savepoint()方法显式创建保存点。保存点必须出现在事务中,但可以嵌套任意深度。

with db.transaction() as txn:
    with db.savepoint() as sp:
        User.create(username='mickey')

    with db.savepoint() as sp2:
        User.create(username='zaizee')
        sp2.rollback()  # "zaizee" will not be saved, but "mickey" will be.

警告

如果您手动提交或回滚保存点,则不会自动创建新的保存点。这与 的行为不同 transaction,后者会在手动提交/回滚后自动打开一个新事务。

6.12.6 自动提交模式

默认情况下,Peewee 在自动提交模式下运行,这样在事务之外执行的任何语句都在它们自己的事务中运行。为了将多个语句组合成一个事务,Peewee 提供了 atomic()上下文管理器/装饰器。这应该涵盖所有用例,但万一您想完全暂时禁用 Peewee 的事务管理,您可以使用 Database.manual_commit()上下文管理器/装饰器。

以下是您可以如何模拟 transaction()上下文管理器的行为:

with db.manual_commit():
    db.begin()  # Have to begin transaction explicitly.
    try:
        user.delete_instance(recursive=True)
    except:
        db.rollback()  # Rollback! An error occurred.
        raise
    else:
        try:
            db.commit()  # Commit changes.
        except:
            db.rollback()
            raise

再说一次——我预计没有人需要这个,但它在这里以防万一。

6.13 数据库错误

Python DB-API 2.0 规范描述了几种类型的异常。因为大多数数据库驱动程序都有自己的这些异常的实现,Peewee 通过为任何特定于实现的异常类提供自己的包装器来简化事情。这样,您不必担心导入任何特殊的异常类,您可以使用 peewee 中的那些:

  • DatabaseError
  • DataError
  • IntegrityError
  • InterfaceError
  • InternalError
  • NotSupportedError
  • OperationalError
  • ProgrammingError

笔记

所有这些错误类都扩展了PeeweeException.

6.14 日志查询

所有查询都使用标准库 模块记录到peewee命名空间。logging使用DEBUG级别记录查询。如果您有兴趣对查询做一些事情,您可以简单地注册一个处理程序。

# Print all queries to stderr.
import logging
logger = logging.getLogger('peewee')
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)

6.15 添加新的数据库驱动程序

Peewee 内置了对 Postgres、MySQL 和 SQLite 的支持。这些数据库非常流行,从快速、可嵌入的数据库到适合大规模部署的重量级服务器,应有尽有。话虽这么说,有很多很酷的数据库,如果驱动程序支持 DB-API 2.0 规范,添加对您选择的数据库的支持应该非常容易。

如果您使用过标准库 sqlite3 驱动程序、psycopg2 等,那么您应该熟悉 DB-API 2.0 规范。Peewee 目前依赖于几个部分:

  • Connection.commit
  • Connection.execute
  • Connection.rollback
  • Cursor.description
  • Cursor.fetchone
    这些方法通常包含在更高级别的抽象Database中并由 . 一个例子是“playhouse”模块中的apsw sqlite 驱动程序。

第一件事是提供一个Database将打开连接的子类。

from peewee import Database
import foodb  # Our fictional DB-API 2.0 driver.


class FooDatabase(Database):
    def _connect(self, database, **kwargs):
        return foodb.connect(database, **kwargs)

提供更高级别的DatabaseAPI 并负责执行查询、创建表和索引以及自省数据库以获取表列表。上述实现是绝对最低要求,尽管某些功能将不起作用——为了获得最佳结果,您将需要另外添加一个方法,用于从数据库中提取表列表和表的索引。我们假设它FooDB很像 MySQL 并且有特殊的“SHOW”语句:

class FooDatabase(Database):
    def _connect(self):
        return foodb.connect(self.database, **self.connect_params)

    def get_tables(self):
        res = self.execute('SHOW TABLES;')
        return [r[0] for r in res.fetchall()]

此处未涉及的数据库处理的其他内容包括:

  • last_insert_id()和rows_affected()
  • param和quote,它告诉生成 SQL 的代码如何添加参数占位符和引用实体名称。
  • field_types用于将 INT 或 TEXT 等数据类型映射到其供应商特定的类型名称。
  • operations用于将“LIKE/ILIKE”等操作映射到其数据库等效项
    请参阅DatabaseAPI 参考或源代码。详情。

笔记

如果您的驱动程序符合 DB-API 2.0 规范,那么启动和运行应该不需要太多工作。

我们的新数据库可以像任何其他数据库子类一样使用:

from peewee import *
from foodb_ext import FooDatabase

db = FooDatabase('my_database', user='foo', password='secret')

class BaseModel(Model):
    class Meta:
        database = db

class Blog(BaseModel):
    title = CharField()
    contents = TextField()
    pub_date = DateTimeField()
以下是使用 FastAPI 中 Tortoise-ORM、SQLAlchemy 和 peewee 进行 ORM 查询的示例。 ## Tortoise-ORM ```python from fastapi import FastAPI from tortoise import fields from tortoise.contrib.fastapi import register_tortoise, HTTPNotFoundError from tortoise.models import Model from tortoise import Tortoise class User(Model): id = fields.IntField(pk=True) name = fields.CharField(50) email = fields.CharField(50) class Meta: table = "users" app = FastAPI() @app.on_event("startup") async def startup(): await Tortoise.init( db_url="sqlite://db.sqlite3", modules={"models": ["main"]} ) await Tortoise.generate_schemas() @app.on_event("shutdown") async def shutdown(): await Tortoise.close_connections() @app.get("/users") async def get_users(): users = await User.all() return users @app.get("/users/{user_id}") async def get_user(user_id: int): user = await User.get_or_none(id=user_id) if user is None: raise HTTPNotFoundError return user register_tortoise( app, db_url="sqlite://db.sqlite3", modules={"models": ["main"]}, generate_schemas=True, add_exception_handlers=True ) ``` ## SQLAlchemy ```python from fastapi import FastAPI from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) name = Column(String(50)) email = Column(String(50)) engine = create_engine("sqlite:///db.sqlite3") SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) app = FastAPI() @app.get("/users") async def get_users(): db = SessionLocal() users = db.query(User).all() db.close() return users @app.get("/users/{user_id}") async def get_user(user_id: int): db = SessionLocal() user = db.query(User).filter(User.id == user_id).first() db.close() if user is None: raise HTTPNotFoundError return user ``` ## peewee ```python from fastapi import FastAPI from peewee import SqliteDatabase, Model, CharField, IntegerField from playhouse.shortcuts import model_to_dict db = SqliteDatabase("db.sqlite3") class User(Model): id = IntegerField(primary_key=True) name = CharField() email = CharField() class Meta: database = db table_name = "users" app = FastAPI() @app.on_event("startup") def startup(): db.connect() db.create_tables([User]) @app.on_event("shutdown") def shutdown(): db.close() @app.get("/users") async def get_users(): users = [model_to_dict(user) for user in User.select()] return users @app.get("/users/{user_id}") async def get_user(user_id: int): user = User.get_or_none(User.id == user_id) if user is None: raise HTTPNotFoundError return model_to_dict(user) ``` 注意:以上示例中的代码仅用于演示 ORM 查询的基本用法,并且没有进行错误处理。在实际应用中,你应该根据需要添加适当的错误处理和安全性检查。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岳涛@心馨电脑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值