Flask Web | SQLAlchemy 数据库关联

使用外键进行多表关联,保证数据一致性和实现一些级联操作

Posted by Haauleon on November 29, 2022

本篇所有操作均在基于 Ubuntu 16.04 LTS 的虚拟机下完成,且使用 Vagrant 来操作虚拟机系统,虚拟机系统 VirtualBox Version: 7.0



一、数据库关联

环境准备:
Python 2.7.11+
pip==9.0.3
flask==0.11.1
httpie==0.9.4
werkzeug==0.11.10

  InnoDB 类型的表可以使用外键进行多表关联,保证数据的一致性和实现一些级联操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# coding=utf-8
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship

from consts import DB_URI

eng = create_engine(DB_URI)
Base = declarative_base()


class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50))


class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True, autoincrement=True)
    email_address = Column(String(128), nullable=False)
    # 使用 ForeignKey 外键关联 users 表的 id,所以 address.user_id 其实就是 users.id,而 address 表不需要独立存储一份 user_id 数据
    user_id = Column(Integer, ForeignKey('users.id'))
    # user 字段关联到 User 模型类
    user = relationship('User', back_populates='addresses')


# User.addresses 字段需要放在 Address 模型类定义之后
User.addresses = relationship('Address', order_by=Address.id,
                              back_populates='user')


Base.metadata.drop_all(bind=eng)
Base.metadata.create_all(bind=eng)

Session = sessionmaker(bind=eng)
session = Session()

user = User(name='xiaoming')

user.addresses = [Address(email_address='a@gmail.com', user_id=user.id),
                  Address(email_address='b@gmail.com', user_id=user.id)]
session.add(user)
session.commit()

  通过 > ipython -i 来执行上面的脚本并继续验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(venv) ❯ ipython --no-banner -i chapter3/section3/rel_sql.py

In [1]: for u, a in session.query(User, Address).\
   ...: filter(User.id==Address.user_id).\
   ...: filter(Address.email_address=='b@gmail.com').\
   ...: all():
   ...:     print 'User ID: {}'.format(u.id)
   ...:     print 'Email Address: {}'.format(a.email_address)
   ...:
User ID: 1
Email Address: b@gmail.com

In [2]: session.query(Address).join(User).filter(Address.id.in_([2,])).all()[0].email_address
Out[2]: 'b@gmail.com'

In [3]: session.query(User).join(Address).filter(Address.email_address=='a@gmail.com').one().name
Out[3]: 'xiaoming'


  虽然使用外键可以降低开发成本,减少数据量,但是在 用户量大、并发度高 的时候,不推荐使用外键来关联,数据的一致性和完整性问题可以通过 事务 来保证。