欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Flask-SQLAlchemyrelationship中的lazy屬性详细介绍

程序员文章站 2022-06-23 15:43:24
每次看flask web開發中的關注者這章,總是被relationship中的lazy弄得一頭霧水 lazy: 指定sqlalchemy什么时候加载数据 select:就是访问到属性的时候,就会全部...

每次看flask web開發中的關注者這章,總是被relationship中的lazy弄得一頭霧水

lazy: 指定sqlalchemy什么时候加载数据

select:就是访问到属性的时候,就会全部加载该属性的数据joined: 对关联的两个表使用联接

subquery: 与joined类似,但使用子子查询

dynamic: 不加载记录,但提供加载记录的查询,也就是生成query对象以学生与课程关系举例,数据库选择mysql, 里面的用户与密码 记得换成自己的建立一对多关系的测试,class是一这一端,对应student是多那一端,意思是,同一个课程,可以有很多学生选这门课[python]from flask_sqlalchemy import sqlalchemyfrom flask import flaskfrom datetime import datetime

app = flask(__name__)app.config['secret_key'] = 'hard to guess string' #use secret_key to realize crsf protection#config sql databaseapp.config['sqlalchemy_database_uri'] = \'mysql+pymysql://root:9@127.0.0.1:3306/test_lazy'app.config['sqlalchemy_database_commit_on_teardown'] = trueapp.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 '' %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 '' %self.name

if db: db.drop_all()db.create_all()

#insert class and student one-to-many relationships1 = 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 dbdb.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")

同樣來看結果:

Flask-SQLAlchemyrelationship中的lazy屬性详细介绍

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 '' %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 '' %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()

下面看看表結構:

Flask-SQLAlchemyrelationship中的lazy屬性详细介绍

同樣執行結果可以看到:

可以看到这个跟一对多关系中的很类似,只不过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")

同樣執行結果爲:

Flask-SQLAlchemyrelationship中的lazy屬性详细介绍

这个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

Flask-SQLAlchemyrelationship中的lazy屬性详细介绍

此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 '' %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 '' %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()

查看表結構:

Flask-SQLAlchemyrelationship中的lazy屬性详细介绍

同樣執行結果:

可以看到返回值是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')

Flask-SQLAlchemyrelationship中的lazy屬性详细介绍

十分简单的sql语句,仅仅查询返回了 registration对象, 虽然结果一样,但是每一个registration对象访问_class属性时,又各自都查询了一遍数据库! 这是很重的! 比如一个class有100个student, 那么获取class.students需要额外查询100次数据库! 每一次数据库的查询代价很大,因此这就是joined的作用了。