Python-SQLAlchemy:第4節:級聯

Mark發表於2019-02-16

上一篇文章:Python-SQLAlchemy:第3節:關係操作

級聯是在一對多關係中父表與子表進行聯動操作的資料庫術語。因為父表與子表通過外來鍵關聯,所以對父表或子表的增、刪、改操作會對另一張表產生相應的影響。適當的利用級聯可以開發出更優雅、健壯的資料庫程式。本節學習SQLAlchemy中級聯的操作方法。

注意:SQLAlchemy級聯獨立於SQL本身針對外來鍵的級聯定義。即使在資料庫表定義中沒有定義on delete等屬性,也不影響開發者在SQLAlchemy中使用級聯。

1、級聯定義

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的功能進行舉例說明。

2、save-update級聯

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_!“

3、delete級聯

顧名思義,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

4、delete-orphan級聯

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

相關文章