数据库进阶实践
级联操作
Cascade意为“级联操作”,就是在操作一个对象的同时,对相关的对象也执行某些操作。我们通过一个Post模型和Comment模型来演示级联操作,分别表示文章(帖子)和评论,两者是一对多关系:
classPost(db.Model):
id= db.Column(db.Integer, primary_key =True)
title= db.Column(db.String(50), unique =True)
body=db.Column(db.Text)
comments= db.relationship('Comment', back_populates = 'post')classComment(db.Model):
id= db.Column(db.Integer, primary_key =True)
body=db.Column(db.Text)
post_id= db.Column(db.Integer, db.ForeignKey('post.id'))
post= db.relationship('Post', back_populates = 'comments')
级联行为通过关系函数relationship()的cascade参数设置。我们希望在操作Post对象时,处于附属地位的Comment对象也被相应执行某些操作,这时应该在Post类的关系函数中定义级联参数。设置了cascade参数的一侧将被视为父对象,相关的对象则被视为子对象。
cascade通常使用多个组合值,级联值之间使用逗号分隔,比如:
classPost(db.Model):
…
comments= db.relationship('Comment', cascade = 'save-update, merge,delete',back_populates = 'post')
常用的配置组合如下所示:
1) save-update、merge(默认值)
2) save-update、merge、delete
3) all
4) all、delete-orphan
当没有设置cascade参数时,会使用默认值save-upgrate、merge。上面的all等同于除了delete-orphan以外所有可用值的组合,即save-update、merge、refresh-expire、expunge、delete。
下面介绍常用的几个级联值:
1、save-update
save-update是默认的级联行为,当cascade参数设为save-update时,如果使用db.session.add()方法将Post对象添加到数据库会话时,那么与Post相关联的Comment对象也将被添加到数据库会话。我们首先创建一个Post对象和两个Comment对象:
>>> post =Post()>>> comment1 =Comment()>>> comment2 =Comment()
将post1添加到数据库会话后,只有post1在数据库会话中:>>>db.session.add(post)>>> post indb.session
True>>> comment1 indb.session
False>>> comment2 indb.session
False
如果我们让post1与这两个Comment对象建立关系,那么这两个Comment对象也会自动被添加到数据库会话中:>>>post.comments.append(comment1)>>>post.comments.append(comment2)>>> comment1 indb.session
True>>> comment2 indb.session
True
当调用db.session.commit()提交数据库会话时,这三个对象都会被提交到数据库中。
2、delete
如果某个Post对象被删除,那么按照默认的行为,该Post对象相关联的所有Comment对象都将与这个Post对象取消关联,外键字段的值会被清空。如果Post类的关系函数中cascade参数设为delete时,这些相关的Comment会在关联的Post对象删除时一并删除,当需要设置delete级联时,我们会将级联值设为all或save-update、merge、delete,比如:
classPost(db.Model):
id= db.Column(db.Integer, primary_key =True)
title= db.Column(db.String(50), unique =True)
body=db.Column(db.Text)
comments= db.relationship('Comment',cascade = 'all', back_populates = 'post')
我们先创建一个文章对象post2和两个评论对象comment3和comment4,并将这两个评论对象与文章对象建立关系,将它们添加到数据库会话并提交:
>>> comment3 = Comment(body = 'very good')>>> comment4 = Comment(body = 'excellent')>>> post2 = Post(title = 'i have a good plan', body = 'tomorrow i will go to climbing')>>>post2.comments.append(comment3)>>>post2.comments.append(comment4)>>>db.session.add(post2)>>> db.session.commit()
现在共有两条Post记录和四条Comment记录:
>>>Post.query.all()
[, ]>>>Commment.query.all()
Traceback (most recent call last):
File"", line 1, in NameError: name'Commment' is notdefined>>>Comment.query.all()
[, , , ]
如果删除文对象Post2,那么对应的两个评论对象也会一并被删除:
>>> post = Post.query.get(2)>>>post
>>>db.session.delete(post)>>>db.session.commit()>>>Post.query.all()
[]>>>Comment.query.all()
[, ]
3、delete-orphan
这个模式是基于delete级联的,必须和delete级联一起使用,通常会设为all、delete-orphan,因为all包含delete。因此当cascade参数设为delete-orphan时,它首先包含delete级联的行为:当某个Post对象被删除时,所有相关的Comment对象都将被删除(delete级联)。除此之外,当某个Post对象(父对象)与某个Comment对象(子对象)解除关系时,也会删除该Comment对象,这个解除关系的对象被称为鼓励对象(orphan object),现在comments属性中的级联值为all、delete-orphan,如下所示:
classPost(db.Model):
id= db.Column(db.Integer, primary_key =True)
title= db.Column(db.String(50), unique =True)
body=db.Column(db.Text)
comments= db.relationship('Comment',cascade = 'all, delete-orphan', back_populates = 'post')
我们先创建一个文章对象post3和两个评论对象comment5和comment6,并将这两个评论对象与文章对象建立关系,将他们添加到数据库会话并提交:
>>> post3 =Post()>>> post3 = Post(title = 'today learn python', body = 'python include class and object')>>> comment5 = Comment(body = 'i also wanna learn python')>>> comment6 = Comment(body = 'python is easy to learn, but you have to pay your time every day')>>>post3.comments.append(comment5)>>>post3.comments.append(comment6)>>>db.session.add(post3)>>>db.session.commit())
File"", line 1db.session.commit())^SyntaxError: invalid syntax>>> db.session.commit()
现在数据库中有两条文章记录和四条评论记录:
>>>Post.query.all()
[, ]>>>Comment.query.all()
[, , , ]
下面我们将comment5和comment6与post3解除关系并提交数据库会话:>>>post3.comments.remove(comment5)>>>post3.comments.remove(comment6)>>> db.session.commit()
默认情况下,相关评论对象的外键会被设为空值。因为我们设置了delete-orphan级联,所以现在你会发现解除关系的两条评论记录都被删除了:
>>>Comment.query.all()
[, ]
delete和delete-orphan通常会在一对多关系模式中,而且“多”这一侧的对象附属于“一”这一侧的对象时使用。尤其是如果“一”这一侧的“父”对象不存在了,那么“多”这一侧的“子”对象不再有意义的情况。比如,文章和评论的关系就是一个典型的示例。当文章被删除了,那么评论也就没必要在留存。在这种情况下,如果不使用级联操作,那么我们就需要手动迭代关系另一侧的所有评论对象,然后一一进行删除操作。
对于这两个级联选项,如果你不会通过列表语义对集合关系属性调用remove()方法等方式来操作关系,那么使用delete级联即可。
虽然级联操作方便,但是容易带来安全隐患,因此要谨慎使用。默认值能够满足大部分情况,所以最好仅在需要的时候才修改它。
在SQLAlchemy中,级联的行为和配置选项等最初衍生自另一个ORM—Hibernate ORM。如果对这部分内容感到困惑,那么引用SQLAlchemy文档中关于Hibernate文档的结论:“The sections we have just covered can be a bit confusing.However, in practice, it all works out nicely. (我们刚刚介绍的这部分内容可能会有一些让人困惑,不过在实际使用中,他们都会工作的很顺利)”