sqlalchemy 3 异常和事务

本文探讨如何在内存数据库中重新创建用户表,介绍如何避免AttributeError和IntegrityError,使用try/except处理异常,并运用事务确保数据一致性。通过实例演示了建表、数据插入和事务管理,以提升数据库操作的健壮性。

重新造一个数据库和表。c3_manage.py

from sqlalchemy import create_engine, MetaData, Table, Column, Integer, Numeric, String, ForeignKey, Boolean
from sqlalchemy import CheckConstraint
from datetime import datetime
from sqlalchemy import DateTime

engine = create_engine('sqlite:///:memory:')
connection = engine.connect()

metadata = MetaData()

cookies = Table('cookies', metadata,
    Column('cookie_id', Integer(), primary_key=True),
    Column('cookie_name', String(50), index=True),
    Column('cookie_recipe_url', String(225)),
    Column('cookie_sku', String(55)),
    Column('quantity', Integer()),
    Column('unit_cost', Numeric(12,2)),  #长度12、精度2位小数
    CheckConstraint('quantity > 0', name='quantity_positive')
)

users = Table('users', metadata,
    Column('user_id', Integer(), primary_key=True),
    Column('username', String(15), nullable=False, unique=True),
    Column('email_address', String(255), nullable=False),
    Column('phone', String(20), nullable=False),
    Column('password', String(25), nullable=False),
    Column('created_on', DateTime(), default=datetime.now),
    Column('updated_on', DateTime(), default=datetime.now, onupdate=datetime.now),
    )

orders = Table('orders', metadata,
    Column('order_id', Integer(), primary_key=True),
    Column('user_id', ForeignKey('users.user_id')),
    Column('shipped', Boolean(), default=False),
    )

line_items = Table('line_items', metadata,
    Column('line_items_id', Integer(), primary_key=True),
    Column('order_id', ForeignKey('orders.order_id')),
    Column('cookie_id', ForeignKey('cookies.cookie_id')),
    Column('quantity', Integer()),
    Column('extended_cost', Numeric(12,2)),
    )

metadata.create_all(engine)

常见异常 AttributeErrorIntegrityError

AttributeError:当试图访问一个不存在的属性时,就会出现AttributeError 。比如 要访问的列在ResultProxy中不存在。

from sqlalchemy import select, insert
from c3_manage import users, connection
ins = insert(users).values(
    username='cookiemon',
    email_address='mon@cookie.com',
    phone='111-111-1111',
    password='password'
    )
result = connection.execute(ins)

s = select([users.c.username])
result = connection.execute(s)
for r in result:
    print(r.username)    #这一行的还是会执行的
    print(r.password)    #select返回resultproxy里没有password列
#报错
cookiemonTraceback (most recent call last):

  File "D:\alchemy\c3_error.py", line 15, in <module>
    print(r.password)
AttributeError: Could not locate column in row for column 'password'

IntegrityError:当试图做一些 违反 列约束 或 表约束的事情时,就会出现IntegrityError。比如破坏唯一性约束。

from sqlalchemy import select, insert
from c3_manage import users, connection
ins = insert(users).values(    #之所以再次insert是因为这个数据库建在内存Memory上,之前的insert已经消失掉了
    username='cookiemon',
    email_address='mon@cookie.com',
    phone='111-111-1111',
    password='password'
    )
result = connection.execute(ins)

s = select([users.c.username])
print(connection.execute(s).fetchall())

ins = insert(users).values(
    username='cookiemon',    #重名了
    email_address='mon@cookie.com',
    phone='111-111-1111',
    password='password'
    )
result = connection.execute(ins)
#报错
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: users.username
[SQL: INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (?, ?, ?, ?, ?, ?)]
[parameters: ('cookiemon', 'mon@cookie.com', '111-111-1111', 'password', '2021-04-27 17:05:04.309756', '2021-04-27 17:05:04.309756')]
(Background on this error at: http://sqlalche.me/e/13/gkpj)

处理错误try/except

ins = insert(users).values(
    username='cookiemon',
    email_address='mon@cookie.com',
    phone='111-111-1111',
    password='password'
    )
try:
    result = connection.execute(ins)
except:
    print('error')

try/except 块中的代码 越少越好,并且最好只用它来捕获特定类型的错误。因为不同的错误可能有不同的处理方式。


事务。事务可以看作是一个标志,标志着从begin到commit之间的所有语句,执行时要么全部成功, 要么有一个失败就全都视为失效。

启动事务时,数据库系统 先记录 数据库当前状态,然后再 执行SQL语句,

如果所有SQL语句 执行成功,则提交commit,并丢弃之前的数据库状态;

但只要有一个语句 执行失败,则回滚rollback到先前记录的那个状态。

准备案例环境

from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table, Column, Integer, Numeric, String, ForeignKey, Boolean
from sqlalchemy import CheckConstraint
from datetime import datetime
from sqlalchemy import DateTime
#建库、建表
engine = create_engine('sqlite:///:memory:')
connection = engine.connect()
metadata = MetaData()
cookies = Table('cookies', metadata,
    Column('cookie_id', Integer(), primary_key=True),
    Column('cookie_name', String(50), index=True),
    Column('cookie_recipe_url', String(225)),
    Column('cookie_sku', String(55)),
    Column('quantity', Integer()),
    Column('unit_cost', Numeric(12,2)),  #长度12、精度2位小数
    CheckConstraint('quantity > 0', name='quantity_positive')
)

users = Table('users', metadata,
    Column('user_id', Integer(), primary_key=True),
    Column('username', String(15), nullable=False, unique=True),
    Column('email_address', String(255), nullable=False),
    Column('phone', String(20), nullable=False),
    Column('password', String(25), nullable=False),
    Column('created_on', DateTime(), default=datetime.now),
    Column('updated_on', DateTime(), default=datetime.now, onupdate=datetime.now),
    )

orders = Table('orders', metadata,
    Column('order_id', Integer(), primary_key=True),
    Column('user_id', ForeignKey('users.user_id')),
    Column('shipped', Boolean(), default=False),
    )

line_items = Table('line_items', metadata,
    Column('line_items_id', Integer(), primary_key=True),
    Column('order_id', ForeignKey('orders.order_id')),
    Column('cookie_id', ForeignKey('cookies.cookie_id')),
    Column('quantity', Integer()),
    Column('extended_cost', Numeric(12,2)),
    )

metadata.create_all(engine)
#插入users、cookies数据
from sqlalchemy import select, insert, update
ins = insert(users).values(
    username='cookiemon',
    email_address='mon@cookie.com',
    phone='111-111-1111',
    password='password',
    )
result = connection.execute(ins)
ins = cookies.insert()
inventory_list = [
    {
        'cookie_name': 'chocolate chip',
        'cookie_recipe_url': 'http://some.aweso.me/cookie/recipe.html',
        'cookie_sku': 'cc01',
        'quantity':'12',
        'unit_cost':'0.50',
    },
    {
        'cookie_name': 'dark chocolate chip',
        'cookie_recipe_url': 'http://some.aweso.me/cookie/recipe_dark.html',
        'cookie_sku': 'cc02',
        'quantity':'1',
        'unit_cost':'0.75', 
    }
]
result = connection.execute(ins, inventory_list)
#插入两份订单数据
ins = insert(orders).values(user_id=1, order_id='1')
result = connection.execute(ins)
ins = insert(line_items)
order_items = [
    {
        'order_id': 1,
        'cookie_id': 1,
        'quantity': 9,
        'extended_cost': 4.50,
    },
]
result = connection.execute(ins, order_items)
ins = insert(orders).values(user_id=1, order_id='2')
result = connection.execute(ins)
ins = insert(line_items)
order_items = [
    {
        'order_id': 2,
        'cookie_id': 1,
        'quantity': 4,
        'extended_cost': 1.50,
    },
        {
        'order_id': 2,
        'cookie_id': 2,
        'quantity': 1,
        'extended_cost': 4.50,
    },
]
result = connection.execute(ins, order_items)
#发货函数
def ship_it(order_id):
    s = select([line_items.c.cookie_id, line_items.c.quantity])
    s = s.where(line_items.c.order_id==order_id)
    cookies_to_ship = connection.execute(s)
    for cookie in cookies_to_ship:
        u = update(cookies).where(cookies.c.cookie_id == cookie.cookie_id)
        u = u.values(quantity = cookies.c.quantity-cookie.quantity)
        result = connection.execute(u)
    u = update(orders).where(orders.c.order_id == order_id)
    u = u.values(shipped = True)
    result = connection.execute(u)
    print('shipped order ID:'+ str(order_id))
#发第1单货
ship_it(1)
s = select([cookies.c.cookie_id, cookies.c.quantity])
print(connection.execute(s).fetchall())
#发第2单货
ship_it(2)
#此时报错IntegrityError,因为quantity<0
#这可能会导致部分数据被修改,而部分数据不变,但实际上货并没有ship出去

上面的这个问题可以用try/except解决,也可以用 事务。事务型ship_it()

from sqlalchemy.exc import IntegrityError
def ship_it(order_id):
    s = select([line_items.c.cookie_id, line_items.c.quantity])
    s = s.where(line_items.c.order_id==order_id)
    transaction = connection.begin()    #启动事务,记录当前数据库状态
    cookies_to_ship = connection.execute(s).fetchall()
    try:
        for cookie in cookies_to_ship:
            u = update(cookies).where(cookies.c.cookie_id == cookie.cookie_id)
            u = u.values(quantity = cookies.c.quantity-cookie.quantity)
            result = connection.execute(u)
        u = update(orders).where(orders.c.order_id == order_id)
        u = u.values(shipped = True)
        result = connection.execute(u)
        print('shipped order ID:'+ str(order_id))
        transaction.commit()    #若无错误,提交事务
    except IntegrityError as error:
        transaction.rollback()    #若有错误,手动回滚
        print(error)

ship_it(1)
ship_it(2)

<think>我们正在讨论SQLAlchemy中开启事务时`begin`参数的使用。根据引用[1]引用[2],事务管理在SQLAlchemy中是通过会话(Session)来处理的。在创建会话时,可以通过参数控制事务的行为。 在SQLAlchemy中,当我们创建一个会话(Session)时,可以使用参数`begin`来控制事务的开启时机。具体来说: - 如果`begin=True`(默认值),则在会话创建后不会立即开启一个新的事务,而是在会话首次需要数据库连接时自动开启事务(例如执行第一个查询或操作时)。 - 如果`begin=False`,则会话在创建时不会自动开启事务,需要显式调用`session.begin()`来开启事务。 这种设计允许更灵活地控制事务边界。例如,在需要将多个操作组合在一个事务中,但又不想在创建会话后立即开启事务的情况下,可以使用`begin=False`。 ### 详细说明 1. **默认行为(`begin=True`)**: ```python from sqlalchemy.orm import Session engine = create_engine("sqlite://") session = Session(engine) # 等同于 Session(engine, begin=True) # 此时事务并未立即开启,但在第一次操作(如query)时会自动开启 ``` 2. **设置`begin=False`**: ```python session = Session(engine, begin=False) # 此时会话没有事务,需要显式开启 session.begin() ``` 3. **使用上下文管理器(推荐)**: 无论`begin`参数如何,都可以使用上下文管理器来管理事务: ```python with Session(engine) as session: # 在进入上下文时,如果begin=True(默认),则自动开始事务 # 执行操作 session.add(some_object) # 退出上下文时,如果没有异常则提交,有异常则回滚 ``` 如果设置`begin=False`,则需要在上下文管理器中显式开启事务: ```python with Session(engine, begin=False) as session: with session.begin(): # 在这个内层上下文中,事务开始 session.add(some_object) # 退出内层上下文时,事务提交(或异常时回滚) ``` 或者显式调用`begin()``commit()`/`rollback()`,但上下文管理器更安全。 ### 示例代码 以下示例展示了`begin`参数的不同设置使用方式: #### 示例1:默认行为(`begin=True`) ```python from sqlalchemy import create_engine from sqlalchemy.orm import Session engine = create_engine("sqlite:///:memory:") # 默认begin=True,在需要时自动开启事务 with Session(engine) as session: # 进入上下文时,并没有立即开启事务 # 第一次操作(如add)时开启事务 user = User(name=&#39;Alice&#39;) session.add(user) # 在退出上下文时,如果没有异常,则提交事务;有异常则回滚 ``` #### 示例2:设置`begin=False`并显式开启事务 ```python with Session(engine, begin=False) as session: # 此时没有事务,需要显式开启 session.begin() try: user = User(name=&#39;Bob&#39;) session.add(user) session.commit() # 显式提交 except: session.rollback() # 显式回滚 raise ``` #### 示例3:使用嵌套上下文管理器(`begin=False`时) ```python # 使用嵌套上下文管理器,更简洁 with Session(engine, begin=False) as session: with session.begin(): # 在此块中,事务已开启 user = User(name=&#39;Charlie&#39;) session.add(user) # 退出内层上下文时,事务已提交(或回滚) ``` ### 注意事项 - 在大多数情况下,使用默认的`begin=True`并结合`with`上下文管理器是最简单且推荐的方式,因为它自动处理了事务的提交回滚。 - 当需要更细粒度地控制事务(例如,在一个会话中执行多个独立的事务)时,可以使用`begin=False`,然后通过多次调用`session.begin()`来开启多个事务(但注意,SQLAlchemy的Session通常设计为一次只进行一个事务,多个事务需要以非重叠的方式使用,或者使用“分离”模式)。 - 在设置`begin=False`后,如果不显式开启事务就执行数据库操作,会引发错误(因为不在事务中)。 ### 引用说明 根据引用[1]引用[2]中关于事务嵌套管理的描述,SQLAlchemy事务管理是分层的,而`begin`参数正是控制这种层次结构的一种方式[^1][^2]。 ### 相关问题 1. SQLAlchemy中如何实现嵌套事务? 2.SQLAlchemy中,事务的提交回滚有哪些最佳实践? 3. 如何设置SQLAlchemy会话的事务隔离级别? 4. 使用SQLAlchemy时,如何处理长事务? 5. SQLAlchemy中的事务与数据库连接(Connection)的事务有何关系?</think>### SQLAlchemy开启事务时`begin`参数的详细说明 在SQLAlchemy中,`begin`参数用于显式控制事务的开启时机行为,特别是在使用`Session`对象时。以下是关于`begin`参数的详细说明使用方法: #### 1. `begin`参数的核心作用 - **控制事务启动时机**:决定会话(`Session`)创建时是否**立即开启事务** - **默认行为**:当`begin=True`(默认值)时,会话创建后**不会立即开启事务**,而是在首次数据库操作时自动开启 - **显式控制**:当`begin=False`时,需要手动调用`session.begin()`来启动事务 #### 2. 参数取值及效果 | 取值 | 行为说明 | 适用场景 | |-----------|--------------------------------------------------------------------------|-----------------------------------| | `True` | 会话创建后不立即开启事务,在首次数据库操作时自动开启(默认行为) | 大多数常规操作 | | `False` | 会话创建后不开启事务,必须显式调用`session.begin()`启动事务 | 需要精确控制事务边界的情况 | | `None` | 使用SQLAlchemy的默认行为(等同于`True`) | | | 其他值 | 抛出`ArgumentError`异常 | | #### 3. 使用示例 ```python from sqlalchemy.orm import Session # 场景1:默认行为(begin=True) with Session(engine) as session: # 事务未开启 user = session.get(User, 1) # 首次操作时自动开启事务 user.name = "Updated" session.commit() # 提交事务 # 场景2:显式控制事务(begin=False) with Session(engine, begin=False) as session: session.begin() # 手动开启事务 try: user = User(name="New User") session.add(user) session.commit() # 提交事务 except: session.rollback() # 回滚事务 raise # 场景3:使用上下文管理器自动提交 with Session(engine) as session, session.begin(): # 进入块时自动开启事务 user = User(name="Auto Commit") session.add(user) # 退出块时自动提交(异常时自动回滚) ``` #### 4. 事务嵌套处理 当使用嵌套事务时(如引用[2]所述),`begin`参数的行为: ```python with Session(engine) as outer_session: # 外层事务自动开启 with outer_session.begin_nested(): # 创建保存点 # 内层事务操作 user = User(name="Nested") outer_session.add(user) # 外层事务继续 ``` #### 5. 最佳实践 1. **常规操作**:使用默认`begin=True` + 上下文管理器 ```python with Session(engine) as session, session.begin(): # 自动事务管理 ``` 2. **复杂事务**:使用`begin=False` + 显式控制 ```python session = Session(engine, begin=False) session.begin() try: # 多步骤操作 session.commit() except: session.rollback() finally: session.close() ``` 3. **连接重用**:当需要复用现有连接时 ```python with engine.connect() as conn: with Session(bind=conn, begin=False) as session: session.begin() # 使用现有连接开启事务 ``` #### 6. 注意事项 1. 提交/回滚后事务自动结束,需重新开启 2. 使用`begin=False`时忘记调用`session.begin()`会引发`InvalidRequestError` 3. 在异步环境中(如`AsyncSession`),需使用`await session.begin()` 4. 事务隔离级别可通过`session.begin(isolation_level=&#39;REPEATABLE_READ&#39;)`设置 > 关键点:`begin`参数不决定事务是否存在,而是控制**事务开启的时机方式**。SQLAlchemy事务管理遵循"会话即事务"模型,每个会话默认关联一个事务[^1][^2]。 ### 相关问题 1. SQLAlchemy中如何设置事务的隔离级别? 2.SQLAlchemy中如何处理嵌套事务(如保存点)? 3. 使用`AsyncSession`时事务管理有哪些不同? 4. SQLAlchemy事务回滚机制是如何工作的? 5. 如何在Flask应用中集成SQLAlchemy事务管理?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值