廖金龙 2019-06-28
级联是在一对多关系中父表与子表进行联动操作的数据库术语。因为父表与子表通过外键关联,所以对父表或子表的增、删、改操作会对另一张表产生相应的影响。适当的利用级联可以开发出更优雅、健壮的数据库程序。本节学习SQLAlchemy中级联的操作方法。
注意:SQLAlchemy级联独立于SQL本身针对外键的级联定义。即使在数据库表定义中没有定义on delete等属性,也不影响开发者在SQLAlchemy中使用级联。SQLAlchemy中的级联通过对父表中的relationship属性定义cascade参数来实现,代码如下:
from sqlalchemy import Table,Column,Integer,ForeignKey,String from sqlalchemy.orm import relationship,backref from sqlalchemy.ext.declarative import declarative_base Base=declarative_base() class Class(Base): __tablename__='class' class_id=Column(Integer,primary_key=True) name=Column(String(50)) level=Column(Integer) address=Column(String(50)) students=relationship("Student",backref="class_",cascade="all") class Student(Base): __tablename__='student' student_id=Column(Integer,primary_key=True) name=Column(String(50)) age=Column(Integer) gender=Column(String(10)) address=Column(String(50)) class_id=Column(Integer,ForeignKey('class.class_id'))
上述代码定义了班级表Class(父表)和学生表Student(子表)。一对多的关系有父表中的relationship属性students进行定义。relationship中的cascade参数定义了要在该关系上实现的级联方法为:all。
SQLAlchemy中另外一种设置级联的方式是在子表的relationship的backref中进行设置。比如上述代码可以写为如下形式,意义保持不变:
from sqlalchemy import Table,Column,Integer,ForeignKey,String from sqlalchemy.orm import relationship,backref from sqlalchemy.ext.declarative import declarative_base Base=declarative_base() class Class(Base): __tablename__='class' class_id=Column(Integer,primary_key=True) name=Column(String(50)) level=Column(Integer) address=Column(String(50)) class Student(Base): __tablename__='student' student_id=Column(Integer,primary_key=True) name=Column(String(50)) age=Column(Integer) gender=Column(String(10)) address=Column(String(50)) class_id=Column(Integer,ForeignKey('class.class_id')) class_=relationship("Class",backref="students",cascade="all")
上述代码没有在父表Class中设置relationship和cascade,而是在子表中设置了。
SQLAlchemy中可选的cascade取值范围如下表所示:
可选值 | 意义 |
---|---|
save-update | 当一个父对象被新增到session中时,该对象当时关联的子对象也自动被新增到session中。 |
merge | Session.merge是一个对数据库对象进行新增或更新的办法。cascade取值为merge时的意义为:当父对象进行merge操作时,该对象当时关联的子对象也会被merge |
expunge | Session.expunge是一种将对象从session中移除的方法。cascade取值为expunge时的意义为:当父对象进行了expunge操作时,该对象当时关联的子对象也会被从session中删除。 |
delete | 当父对象被delete时,会自动将该子对象删除 |
delete-orphan | 当子对象不再与任何父对象关联时,会自动将该子对象删除 |
refresh-expire | Session.expire是一种设置对象已过期、下次引用时需要从数据库即时读取的方法。cascade取值为refredh-expire时的意义为:当父对象进行了expire操作时,该对象当时关联的子对象也进行expire操作。 |
all | 是一个集合值,表示:save-update、merge、refresh-expire、expunge、delete同时被设置 |
多个cascade属性可以通过逗号分隔并同时赋值给cascade。例如:
students=relationship("Student",backref="class_",cascade="save-update,merge,expunge")
在默认情况下,任何relationship的级联属性都被设置为cascade="save-update,merge"。下面就常用的参数:save-update、delet、delete-orphan的功能进行举例说明。
save-update级联是指当一个父对象被新增到session中时,该对象当时关联的子对象也自动被新增到session中。
通过如下代码建立一个父对象class和两个子对象students1与students2
class_=Class() student1,student2=Student(),Student() class_.students.append(student1) class_.students.append(student2)
如果父子级联关系包含save-update,则只需要将父对象保存到session中,子对象会自动被保存。
session.add(class_) if student1 in session: print("The student1 has been added too!")
上面代码将会打印:"The student1 has been added too!"
技巧:”in“语句可以判断某对象是否被关联到了session中。已被关联的对象在session被commit时会被写入到视频库中。即使父对象已经被新增到session中,新关联的子对象仍然可以被添加:
class_=Class() session.add(class_) students3=Student() if student3 in session: print("The student3 is added before append to the class_!") class_.students.append(students) if student1 in session: print("The student3 is added after append to the class_!")
这段代码打印”The student3 is added after append to the class_!“
顾名思义,delete级联是指当父对象被从session中删除时,其关联的子对象也自动被从session中delete。通过一个例子演示delete的作用,假设数据库中class表和students表的内容如下表所示:
class表:
class_id | name | level | address |
---|---|---|---|
1 | 三年二班 | 3 | 理想路520号1楼 |
2 | 五年一班 | 5 | 理想路520号3楼 |
3 | 五年二班 | 5 | 理想路520号3楼 |
student表:
student_id | class_id | name | age | gender | address | contactor |
---|---|---|---|---|---|---|
1 | 1 | 理想 | 10 | 男 | 静安区 | Null |
2 | 1 | mark | 10 | 女 | 静安区 | Null |
3 | 1 | 小马哥 | 9 | 女 | 闸口区 | 张三 |
4 | 2 | 张苗 | 10 | 男 | 宝山区 | NULL |
5 | 2 | 小黑 | 12 | 女 | 静安区 | 李四 |
6 | 2 | 喵喵 | 11 | 男 | 闸北区 | NULL |
7 | 1 | 韩永跃 | 10 | 男 | 静安区 | NULL |
8 | 3 | 小镜镜 | 12 | 男 | 闸北区 | NULL |
9 | 3 | 小镜子 | 12 | 女 | 宝山区 | NULL |
从例表中可知,系统中有3个班级,他们分别有4、3、2个学生。如果SQLAlchemy中没有把它们的relationship的cascade设置为delete,则删除父表内容不会删除相应的子表内容,而是把子表的相应外键设置为空。比如:
class_=session.query(Class).filter(name="三年二班").first() #三年二班的class_id为1 session.delete(class_) #删除class_id为1的班级
当cascade不包含delete时,上述代码中的delete语句相当于执行了如下SQL语句:
UPDATE student SET class_id=None WHERE class_id=1; DELETE FROM class WHERE class_id=1; COMMIT;
执行后数据表class和student的内容变化如下所示:
class表:
class_id | name | level | address |
---|---|---|---|
2 | 五年一班 | 5 | 理想路520号3楼 |
3 | 五年二班 | 5 | 理想路520号3楼 |
student表:
student_id | class_id | name | age | gender | address | contactor |
---|---|---|---|---|---|---|
1 | NULL | 理想 | 10 | 男 | 静安区 | Null |
2 | NULL | mark | 10 | 女 | 静安区 | Null |
3 | NULL | 小马哥 | 9 | 女 | 闸口区 | 张三 |
4 | 2 | 张苗 | 10 | 男 | 宝山区 | NULL |
5 | 2 | 小黑 | 12 | 女 | 静安区 | 李四 |
6 | 2 | 喵喵 | 11 | 男 | 闸北区 | NULL |
7 | 1 | 韩永跃 | 10 | 男 | 静安区 | NULL |
8 | 3 | 小镜镜 | 12 | 男 | 闸北区 | NULL |
9 | 3 | 小镜子 | 12 | 女 | 宝山区 | NULL |
此时将表定义中的relationship的cascade属性设置为delete:
students=relationship("Student",backref="class",cascade="delete")
现在通过如下语句删除“五年一班”:
class_=session.query(Class).filter(name="五年一班").first() #五年一班的class_id为2 session.delete(class_) #删除class_id为2的班级
当cascade包含“delete”时,上述代码中的delete语句相当于执行了如下SQL语句:
DELETE FROM student WHERE class=2; DELETE FROM class WHERE class=2; COMMIT;
执行后数据库表class和student的内容变化如下表所示:
class表:
class_id | name | level | address |
---|---|---|---|
3 | 五年二班 | 5 | 理想路520号3楼 |
student表:
student_id | class_id | name | age | gender | address | contactor |
---|---|---|---|---|---|---|
1 | NULL | 理想 | 10 | 男 | 静安区 | Null |
2 | NULL | mark | 10 | 女 | 静安区 | Null |
3 | NULL | 小马哥 | 9 | 女 | 闸口区 | 张三 |
7 | NULL | 韩永跃 | 10 | 男 | 静安区 | NULL |
8 | 3 | 小镜镜 | 12 | 男 | 闸北区 | NULL |
9 | 3 | 小镜子 | 12 | 女 | 宝山区 | NULL |
delete-orphan级联是指当子对象不再与任何父对象关联时,会自动将该子对象删除。设置父表与子表的relationship中的cascade包含“delete-orphan”:
students=relationship("Student",backref="class",cascade="delete-orphan")
通过如下代码将于班级“五年一班”关联的学生全部脱离:
class_=session.query(Class).filter(name="五年二班").first() unattachedStudent=[] while len(class_.students)>0: unattachedStudent.append(class_.students.pop()) #与父对象脱离 session.commit #显示地提交事务
上述代码中没有显示地删除任何学生,但由于使用了delete-orphan级联,所以被脱离出班级对象的学生会在session事务提交时会自动从数据库中删除。代码执行后数据库表中的内容的变化:
class表:
class_id | name | level | address |
---|---|---|---|
3 | 五年二班 | 5 | 理想路520号3楼 |
student表:
student_id | class_id | name | age | gender | address | contactor |
---|---|---|---|---|---|---|
1 | NULL | 理想 | 10 | 男 | 静安区 | Null |
2 | NULL | mark | 10 | 女 | 静安区 | Null |
3 | NULL | 小马哥 | 9 | 女 | 闸口区 | 张三 |
7 | NULL | 韩永跃 | 10 | 男 | 静安区 | NULL |