FastAPI之各种模型类之间的关联关系
一、一对多和多对一关联
比如:作者和⽂章之间,部门和员工之间都是一对多的关联关系。反过来就是:多对一的关联关系。
1、定义外键约束
定义关系的第⼀步是创建外键。外键是(foreign key)⽤来在A表存储B表的主键值以便和B表建⽴联系的关系字段。
因为外键只能存储单⼀数据(标量),所以外键总是在“多”这⼀侧定义,多篇⽂章属于同⼀个作者,所以我们需要为每篇⽂章添加外键存储作者的主键值以指向对应的作者。在Article模型中,我们定义⼀个author_id字段作为外键:
注意ForeginKey的参数是<表名>.<键名>,⽽不是<类名>.<字段名>
多对⼀关联的外键
dept_id:Mapped[int]=mapped_column(ForeignKey(‘t_dept.id’))
2、定义关联属性和双向关联
我们在Author 类中定义了集合关系属性articles ,⽤来获取某个作者拥有的多篇⽂章记录。
在某些情况下,你也许希望能在Article 类中定义⼀个类似的author 关系属性,当被调⽤时返回对应的作者记录,⽽这种两侧都添加关系属性获取对⽅记录的关系我们称之为双向关系。双向关系并不是必须的,但在某些情况下会⾮常⽅便。
多对⼀关联的属性, back_populates写对⽅模型类中的关联属性名字
dept:Mapped[Optional[‘Dept’]]=relationship(back_populates=“emp_list”)
⼀对多的关联属性
emp_list:Mapped[List[‘Employee’]]=relationship(back_populates=‘dept’)
3、级联操作
cascade,默认选项为save-update:
1:save-update:默认选项,在添加⼀条数据的时候,会把其他和次数据关联的数据都添加到数据库中,这种⾏为就是save-update属性决定的。
• 2:delete:表⽰当删除某⼀个模型中的数据的时候,也删除掉使⽤relationship和此数据关联的数据。
• 3:delete-orphan:表⽰当对⼀个ORM对象解除了⽗表中的关联对象的时候,⾃⼰便会被删除,
如果⽗表的数据被删除,同样⾃⼰也会被删除,这个选项只能⽤在⼀对多上,不能⽤在多对多和多对⼀上,并且使⽤的时候还需要在⼦模型的relationship中增加参数:single_parent=True。
• 4:merge(合并):默认选项,当在使⽤session.merge合并⼀个对象的时候,会将使⽤了relationship相关联的对象也进⾏merge操作
• 5:expunge:移除操作的时候,会将相关联的对象也进⾏移除,这个操作只是从session中移除,并不会正则从数据库删除
• 6:all:对save-update、merge、refresh-expire、expunge、delete这⼏种的缩写
import enum
from datetime import date
from decimal import Decimal
from typing import Optional
from sqlalchemy import String, DECIMAL, Boolean, func, insert, select, text, update, delete, and_, or_, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, sessionmaker, relationship
from ch04.db_main import Base, engine
from ch05.models import Dept
class SexValue(enum.Enum):
"""通过枚举,可以给一些属性(字段)设置预设值"""
MALE = "男"
FEMALE = "女"
class Employee(Base):
"""员工的模型类"""
__tablename__ = "t_emp"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(40), name="emp_name", unique=True, nullable=False) # 不允许为空
# DECIMAL:10:总位数,2:小数点后位数
sal: Mapped[Decimal] = mapped_column(DECIMAL(10, 2), nullable=True, comment="员工的基本薪资")
bonus: Mapped[int] = mapped_column(default=0, comment="员工的津贴")
is_leave: Mapped[bool] = mapped_column(Boolean, default=False, comment="员工是否离职,True表示离职,False表示在职")
gender: Mapped[SexValue]
entry_date: Mapped[date] = mapped_column(insert_default=func.now(), nullable=False, comment="入职时间")
# todo 和部门表关联的外键
dept_id:Mapped[Optional[int]]=mapped_column(ForeignKey('t_dept.id'),nullable=True)
# 定义关联属性:该员工所属的部门
dept:Mapped[Optional['Dept']]=relationship(back_populates="emp_list")
# 需要在Employee模型类中增加⼀个__str__函数
def __str__(self):
return f'{self.name}, {self.gender.value}, {self.sal}, {self.entry_date},{self.bonus}'
import enum
from datetime import date
from decimal import Decimal
from typing import Optional, List
from sqlalchemy import String, DECIMAL, Boolean, func, insert, select, text, update, delete, and_, or_
from sqlalchemy.orm import Mapped, mapped_column, sessionmaker, relationship
from ch04.db_main import Base, engine
class Dept(Base):
"""部门模型类"""
__tablename__='t_dept'
id:Mapped[int] =mapped_column(primary_key=True,autoincrement=True)
name:Mapped[str]=mapped_column(String(20),name="dname",unique=True,nullable=False)
city:Mapped[str]=mapped_column(String(50))
#todo 定义一个关联属性:表示一个部门下的所有员工
emp_list:Mapped[List['Employee']]=relationship(back_populates='dept')
4、增删改查
新增
with sessionmaker(engine).begin() as session:
# 新增
dept = Dept(name="北京总公司", city='北京')
emp1 = Employee(name="kd", sal=1000, bonus=1000, gender=SexValue.MALE, entry_date=date(2019, 1, 1))
emp2 = Employee(name="ad", sal=2000, bonus=2000, gender=SexValue.MALE, entry_date=date(2018, 1, 1))
emp1.dept = dept # 把emp1放到dept的部门下
emp2.dept = dept # 把emp2放到dept的部门下
with sessionmaker(engine).begin() as session:
# 新增
dept = Dept(name="北京总公司", city='北京')
emp1 = Employee(name="kd", sal=1000, bonus=1000, gender=SexValue.MALE, entry_date=date(2019, 1, 1))
emp2 = Employee(name="ad", sal=2000, bonus=2000, gender=SexValue.MALE, entry_date=date(2018, 1, 1))
dept.emp_list=[emp1,emp2] #把emp1,emp2都放到dept部门下
session.add(dept)
修改:给kobe设置一个部门为:北京总公司
emp=session.get(Employee,1)
dept=session.get(Dept,1)
# 方法1
# emp.dept=dept
# 方法2
emp.dept_id=dept.id
查询部门下所有的员工
dept = session.get(Dept, 1)
print(dept.name)
for emp in dept.emp_list:
print(emp)
该员工的部门
emp = session.get(Employee, 1)
print(emp.dept)
print(emp.dept.id)
删除,设置kobe的部门为空
方法1
emp = session.get(Employee, 1)
# emp.dept=None
emp.dept_id=None
方法2
session.execute(update(Employee).where(Employee.id==1).values(dept_id=None))
5、树形结构的自己关联
import enum
from datetime import date
from decimal import Decimal
from typing import Optional, List
from sqlalchemy import String, DECIMAL, Boolean, func, insert, select, text, update, delete, and_, or_, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, sessionmaker, relationship
from ch04.db_main import Base, engine
from ch04.emp_manager.models import Employee, SexValue
class Dept(Base):
"""部门模型类"""
__tablename__ = 't_dept'
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(20), name="dname", unique=True, nullable=False)
city: Mapped[str] = mapped_column(String(50))
# todo 定义一个关联属性:表示一个部门下的所有员工
emp_list: Mapped[List['Employee']] = relationship(back_populates='dept',cascade='save-update')
#定义外键约束:关联到父机构
pid:Mapped[Optional[int]] = mapped_column(ForeignKey('t_dept.id'),nullable=True)
#定义一个关联属性
children:Mapped[List["Dept"]]=relationship(back_populates="parent")
# 定义一个关联属性,自关联
parent:Mapped[Optional["Dept"]]=relationship(back_populates="children",remote_side=[id])
def __str__(self):
return f'{self.name}, {self.id}, {self.city}'
维护一对多自关联关系的时候(relationship),必须加⼊:remote_side=[id]
a、树形结构操作
#插入部门
d1=Dept(name="技术部",pid=1,city="北京")
d2=Dept(name="湖南分公司",pid=1,city="长沙")
d3 = Dept(name="人力部",parent=d2,city="长沙")
session.add(d1)
session.add(d2)
# session.add(d3) #可加可不加
二、一对一关联关系
一对一关系实际上是通过建立双向关系的一对多关系的基础上转化而来。
比如:一个用户对应一张⾝份证,一张⾝份证属于一个用户。
class IdCard(Base):
"""身份证的模型类,与员工一对一关联关系"""
__tablename__="t_id_card"
id:Mapped[int]=mapped_column(primary_key=True,autoincrement=True)
card_number:Mapped[str]=mapped_column(String(18),unique=True,nullable=False,comment="身份证")
origin:Mapped[Optional[str]]=mapped_column(String(20),nullable=True,comment="籍贯")
emp_id:Mapped[int]=mapped_column(ForeignKey("t_emp.id"))
#todo 一对一的关联属性 其中: single_parent=True 表⽰⼦表,只关联⽗表的⼀⾏记录。
emp:Mapped['Employee']=relationship(single_parent=True,back_populates="idc")
其中: single_parent=True 表⽰⼦表,只关联⽗表的⼀⾏记录。
#给kobe新增身份证
idc1=IdCard(card_number="11111111111",emp_id=1)
session.add(idc1)
emp1=session.get(Employee,1)
print(emp1.idc.card_number)
三、多对多关联关系
在SQLAlchemy 中,要想表⽰多对多关系,除了关系两侧的模型外,我们还需要创建⼀个关联表(middle_table)。关联表不存储数据,只⽤来存储关系两侧模型的外键对应关系。
多对多关联关系,先定义中间表
#多对多关联关系,先定义中间表
middle_table=Table(
"t_user_role",
Base.metadata,
Column('user_id',ForeignKey('t_user.id'),primary_key=True),
Column('role_id',ForeignKey('t_role.id'),primary_key=True), #联合主键
)
用户模型类
class User(Base):
"""用户模型类"""
__tablename__="t_user"
id:Mapped[int]=mapped_column(primary_key=True,autoincrement=True)
username:Mapped[str]=mapped_column(String(20),unique=True,nullable=False,comment="用户名称")
password:Mapped[str]=mapped_column(String(20),nullable=False,comment="密码")
#多对多的关联属性
roles:Mapped[Optional[List['Role']]]=relationship(back_populates="users",secondary=middle_table)
角色模型类
class Role(Base):
"""角色模型类,它和用户之间是多对多关联"""
__tablename__="t_role"
id:Mapped[int]=mapped_column(primary_key=True,autoincrement=True)
name:Mapped[str]=mapped_column(String(20),unique=True,nullable=False,comment="角色名字")
# 多对多的关联属性
users: Mapped[Optional[List['User']]] = relationship(back_populates="roles", secondary=middle_table)
新增关联关系数据
#多对多操作测试
u1=User(username="zs",password="123123")
u2=User(username="ls",password="123123")
r1=Role(name="仓库管理员")
r2=Role(name="系统管理员")
#zs有2个角色,ls有一个系统管理员
u1.roles=[r1,r2]
u2.roles=[r2]
session.add(u1)
查询
user=session.get(User,1)
print(user.roles[0].name)