SQLAlchemy:当 autoflush=False 时,持久对象的更新会自动刷新

SQLAlchemy: Updates on persistent object are auto flushed when autoflush=False

当对持久对象应用更新时,似乎每次更新都会自动推送到事务缓冲区,即使 autoflush 属性 设置为 false。

考虑以下示例。有两个实体 - EmployeeDepartment,它们之间存在多对多关系。 ORM定义如下:

from sqlalchemy.orm import registry, relationship

mapper_registry = registry()

mapper_registry.map_imperatively(
   models.Employee,
   employee_table,
   properties={
       "employee_id": employee_table.c.EmployeeId,
       "first_name": employee_table.c.FirstName,
       "last_name": employee_table.c.LastName,
       "departments": relationship(
           models.Department, secondary=joinEmployeeDepartment
       ),
   },
)

初始数据库状态如下所示:

员工:

EmployeeId FirstName FirstName
1 John Doe

部门:

DepartmentId DepartmentName
1 Sales

加入员工部门:

RelationshipId EmployeeId DepartmentId
1 1 1

然后执行以下代码:

from sqlalchemy.orm.session import Session, sessionmaker

Session = sessionmaker(bind=engine, autocommit=False, autoflush=False, expire_on_commit=False)

session = Session()
session.begin()
try:
   employee: Employee = session.query(Employee).where(Employee.first_name == 'John').one_or_none()
   employee.departments = [Department(department_name='support')]  # 1
   employee.departments = [Department(department_name='IT')]       # 2
   session.commit()
finally:
   session.close()

由于 autoflush 已关闭,我希望当持久性员工对象在 #1 中在内存中更新时,更改不会刷新到事务缓冲区。由于#2 覆盖了#1 中所做的更改,因此在调用 session.commit() 时只有后者会被刷新和提交。然而,我观察到情况并非如此。 #1 中的更改也添加到事务缓冲区中。得到的DB状态如下:

部门:

DepartmentId DepartmentName
1 Sales
2 support
3 IT

加入员工部门:

RelationshipId EmployeeId DepartmentId
2 1 3

我的问题是,当对持久对象进行更新时,是否忽略了 autoflush 设置?

发生的事情是,随着每个新的 Department 实例被创建,它被添加到 session.new 并进入 pending state。待定对象

[isn't] actually flushed to the database yet, but it will be when the next flush occurs.

因此,当会话刷新提交时,它会找到两个挂起的 Department 实例并将它们发送到数据库。

第一个 Department 实例的“删除”未在 session.deleted 中跟踪,因为该实例未处于持久状态。因此在刷新时没有为该实例发送 DELETE 。然而,SQLAlchemy 的属性跟踪将其跟踪为已从关系中删除,因此 SQLAlchemy 修复了多对多映射以反映最终所需状态。

我们可以通过打印 session.new 和关系的历史记录,并在引擎上启用日志记录来看到这一点:

with Session() as s:
    employee: Employee = s.query(Employee).where(Employee.first_name == 'John').one_or_none()
    hist = sa.inspect(employee).attrs.departments.history
    employee.departments = [Department(department_name='support')]
    print(s.new)
    print(sa.inspect(employee).attrs.departments.history)
    employee.departments = [Department(department_name='IT')]
    print(s.new)
    print(sa.inspect(employee).attrs.departments.history)
    s.commit()

输出:

2021-12-08 11:42:40,134 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-12-08 11:42:40,139 INFO sqlalchemy.engine.Engine SELECT employees.id AS employees_id, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name 
FROM employees 
WHERE employees.first_name = ?
2021-12-08 11:42:40,139 INFO sqlalchemy.engine.Engine [generated in 0.00028s] ('John',)
2021-12-08 11:42:40,143 INFO sqlalchemy.engine.Engine SELECT departments.id AS departments_id, departments.department_name AS departments_department_name 
FROM departments, association 
WHERE ? = association.employee_id AND departments.id = association.department_id
2021-12-08 11:42:40,143 INFO sqlalchemy.engine.Engine [generated in 0.00024s] (1,)
IdentitySet([<__main__.Department object at 0x7fdbf4998eb0>])
History(added=[<__main__.Department object at 0x7fdbf4998eb0>], unchanged=[], deleted=[<__main__.Department object at 0x7fdbf4882860>])
IdentitySet([<__main__.Department object at 0x7fdbf4998eb0>, <__main__.Department object at 0x7fdbf4881420>])
History(added=[<__main__.Department object at 0x7fdbf4881420>], unchanged=[], deleted=[<__main__.Department object at 0x7fdbf4882860>])
2021-12-08 11:42:40,145 INFO sqlalchemy.engine.Engine INSERT INTO departments (department_name) VALUES (?)
2021-12-08 11:42:40,145 INFO sqlalchemy.engine.Engine [cached since 0.01444s ago] ('support',)
2021-12-08 11:42:40,145 INFO sqlalchemy.engine.Engine INSERT INTO departments (department_name) VALUES (?)
2021-12-08 11:42:40,145 INFO sqlalchemy.engine.Engine [cached since 0.01477s ago] ('IT',)
2021-12-08 11:42:40,147 INFO sqlalchemy.engine.Engine DELETE FROM association WHERE association.employee_id = ? AND association.department_id = ?
2021-12-08 11:42:40,147 INFO sqlalchemy.engine.Engine [generated in 0.00023s] (1, 1)
2021-12-08 11:42:40,148 INFO sqlalchemy.engine.Engine INSERT INTO association (employee_id, department_id) VALUES (?, ?)
2021-12-08 11:42:40,148 INFO sqlalchemy.engine.Engine [cached since 0.01509s ago] (1, 3)
2021-12-08 11:42:40,148 INFO sqlalchemy.engine.Engine COMMIT

要防止第一个 Department 的插入,必须通过 expunging 从会话中将其从 session.new 中删除:

    employee.departments = [Department(department_name='support')]
    s.expunge(employee.departments[0])
    employee.departments = [Department(department_name='IT')]

请参阅会话文档,object states 了解更多详细信息。