每次看flask web開發中的關注者這章,總是被relationship中的lazy弄得一頭霧水,今天做筆記記錄下
基本介紹
lazy: 指定sqlalchemy数据库什么时候加载数据
- select: 就是访问到属性的时候,就会全部加载该属性的数据
- joined: 对关联的两个表使用联接
- subquery: 与joined类似,但使用子子查询
- dynamic: 不加载记录,但提供加载记录的查询,也就是生成query对象
以学生与课程关系举例,数据库选择mysql, 里面的用户与密码 记得换成自己的
建立一对多关系的测试,class是一这一端,对应student是多那一端,意思是,同一个课程,可以有很多学生选这门课
[python]
from flask_sqlalchemy import SQLAlchemy
from flask import Flask
from datetime import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string' #use SECRET_KEY to realize CRSF protection
#config sql database
app.config['SQLALCHEMY_DATABASE_URI'] = \
'mysql+pymysql://root:[email protected]:3306/test_lazy'
app.config['SQLALCHEMY_DATABASE_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
class Student(db.Model):
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
class_id = db.Column(db.Integer, db.ForeignKey('classes.id'))
def __repr__(self):
return '<Student: %r>' %self.name
class Class(db.Model):
__tablename__ = 'classes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
students = db.relationship('Student', backref='_class', lazy="select")
def __repr__(self):
return '<Class: %r>' %self.name
if db:
db.drop_all()
db.create_all()
#insert class and student one-to-many relationship
s1 = Student(id=1, name='susan', class_id = 1)
s2 = Student(id=2, name='bob', class_id=2)
s3 = Student(id=3, name='may', class_id=1)
c1 = Class(id=1, name='class1')
c2 = Class(id=2, name='class2')
#update db
db.session.add_all([s1, s2, s3, c1, c2])
db.session.commit()
if __name__ == '__main__':
app.run()
注意Class里面的students是relationship,里面的lazy我选择了select,也就是默认值,backref参数的意义是在Student表中定义一个_class属性,用来访问Class模型对象得到的表结构如下:
访问lazy属性为select值的students, 直接查找出對象,並由對象元素組成一個list
然后,用s1这个实例对象,通过Class里面的backref=_class属性,也能访问Class内容,因为backref=_class默认也是select属性,所以,他也是直接导出结果, 此處結果爲class的實例對象
將lazy值換爲dynamic
students = db.relationship('Student', backref='_class', lazy="dynamic")
同樣來看結果:
lazy爲
dynamic
值的students 得到一個查詢對象,而不是一個列表,可以在上面添加過濾器進行操作,如filter() all()
并且该对象的sql语句也可以看到,就是简单查询了Student。
lazy="dynamic"只可以用在一对多和多对多关系中,不可以用在一对一和多对一中
即 lazy屬性的對向關系模型只能是多的一側,在這裏,Class的對向Student爲多的一側
前面说的都是给当前属性加lazy属性,backref的lazy默认都是select,如果给反向引用backref加lazy属性呢? 直接使用
backref=db.backref('students', lazy='dynamic')
??根據上面原則,那麼反向引用對向側即Class模型必須爲多的一側,而此時模型關系變爲多對多,多對多關系比較復雜,需要關聯表作爲中間表
先看一個基本多對多關系,即一個學生可選擇多個課程 一個課程有多個學生
registrations = db.Table('registrations', db.Column('student_id', db.Integer, db.ForeignKey('students.id')), db.Column('class_id', db.Integer, db.ForeignKey('classes.id'))) class Student(db.Model): __tablename__ = 'students' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64)) #class_id = db.Column(db.Integer, db.ForeignKey('classes.id')) def __repr__(self): return '<Student: %r>' %self.name class Class(db.Model): __tablename__ = 'classes' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64)) students = db.relationship('Student', secondary=registrations, backref='_class', lazy="dynamic") def __repr__(self): return '<Class: %r>' %self.name
手動插入多對多數據:
# insert student and class many-to-many relationship c1 = Class(id=1, name='class1') c2 = Class(id=2, name='class2') s1 = Student(id=1, name='susan') s2 = Student(id=2, name='bob') s3 = Student(id=3, name='may') c1.students = [s1, s3] c2.students = [s1, s2] #update db db.session.add_all([s1, s2, s3, c1, c2]) db.session.commit()
下面看看表結構:
同樣執行結果可以看到:
可以看到这个跟一对多关系中的很类似,只不过s1._class成为了集合形式, 因为
backref="_class"
默认仍然是select
,所以直接返回结果,而c1.students lazy屬性值爲dynamic依然是返回了query對象
如果修改反向引用的lazy爲joined
students = db.relationship('Student', secondary=registrations, backref=db.backref('_class', lazy='joined'), lazy="dynamic")同樣執行結果爲:
这个joined属性很奇怪,因为他没有改变_class这个backref生成的结果,他反而是改变了这个relationship的属性的结果(也就是students属性),
s1._class
还是直接返回数据。有变化的是c1.students
的sql语句
觀察sql語句:
SELECT students.id AS students_id, students.name AS students_name, classes_1.id AS classes_1_id, classes_1.name AS classes_1_name FROM registrations, students LEFT OUTER JOIN (registrations AS registrations_1 INNER JOIN classes AS classes_1 ON classes_1.id = registrations_1.class_id) ON students.id = registrations_1.student_id WHERE %(param_1)s = registrations.class_id AND students.id = registrations.student_id
此sql語句進行聯接操作,對三個表都進行了查詢
上述代码中的
registrations
是直接被sqlalchemy
接管的,程序无法直接访问的。
在下面的多对多例子中,我们可以看到上述的
lazy
方式的优势,我们把关联表改为实体model,并且额外增加一个时间信息。模型代码如下:
class Registration(db.Model): __tablename__ = 'registrations' student_id = db.Column(db.Integer, db.ForeignKey('students.id'), primary_key=True) class_id = db.Column(db.Integer, db.ForeignKey('classes.id'), primary_key=True) create_at = db.Column(db.DateTime, default=datetime.utcnow) class Student(db.Model): __tablename__ = 'students' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64)) _classes = db.relationship('Registration', foreign_keys=[Registration.student_id], backref = db.backref('_student', lazy='joined'), lazy = 'dynamic') def __repr__(self): return '<Student: %r>' %self.name class Class(db.Model): __tablename__ = 'classes' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64)) _students = db.relationship('Registration', foreign_keys=[Registration.class_id], backref=db.backref('_class', lazy='joined'), lazy='dynamic') def __repr__(self): return '<Class: %r>' %self.name手動插入數據:
#insert student and classes many to many relationship c1 = Class(id=1, name='class1') c2 = Class(id=2, name='class2') s1 = Student(id=1, name='susan') s2 = Student(id=2, name='bob') s3 = Student(id=3, name='may') r1 = Registration(student_id=3, class_id=1) r2 = Registration(student_id=2, class_id=2) r3 = Registration(student_id=1, class_id=1) r4 = Registration(student_id=1, class_id=2) #update db db.session.add_all([s1, s2, s3, c1, c2, r1, r2, r3, r4]) db.session.commit()查看表結構 :
同樣執行結果:
可以看到返回值是Registration两个对象, 不再直接返回
Student
和Class
对象了。如果想要获取的话,可以使用给Registration加的反向引用:
那麼在調用Registration的_student _class時是否還會查詢數據庫
我们可以发现: 跟上一个例子一样,
s1._class
不仅查询了对应的class
信息,而且通过join
操作,获取到了相应的Student
和Class
对象,换句话说,把Registration的_student和_class
两个回引属性均指向了对应的对象, 也就是说,s1._class
这一条查询语句就可以把上述操作都完成。这个就是backref=db.backref('_class', lazy='joined')
的作用。
最後,看看下面再看看把
lazy
改为select
的情况:
_students = db.relationship('Registration', foreign_keys=[Registration.class_id], backref=db.backref('_class', lazy='select'), lazy='dynamic')
_classes = db.relationship('Registration', foreign_keys=[Registration.student_id], backref = db.backref('_student', lazy='select'), lazy = 'dynamic')
十分简单的sql语句,仅仅查询返回了
Registration
对象, 虽然结果一样,但是每一个Registration
对象访问_class
属性时,又各自都查询了一遍数据库! 这是很重的! 比如一个class有100个student, 那么获取class.students
需要额外查询100次数据库! 每一次数据库的查询代价很大,因此这就是joined
的作用了。
參考文檔: