表映射定义
表的映射定义有两种模式:声明式定义(SQLAlchemy ORM)和经典式定义(SQLAlchemy Core)。
声明式定义
声明式定义的表映射需要继承一个基类,这个基类是固定的,可以使用以下语句获得。
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
之后就可以用来定义映射类了。
from sqlalchemy import Column, String, Integer
class User(Base):
__tablename__ = 'user'
id = Column(String(200), primary_key=True, nullable=False)
name = Column(String(200), nullable=False)
password = Column('pass', String(50), nullable=False)
age = Column(Integer)
Column()
的第一个参数是指定要映射的数据表列名,这里可以允许映射类的属性与数据表列名不一致。如果不指定列名,则会使用映射类属性名作为数据表列名。
映射类中还可以存在一个__mapper_args__
属性,这个属性的类型是一个字典,用以对映射进行配置。这个属性常用的配置项有:
column_prefix
,指定列名前缀。include_properties
,列表类型,用来指定要映射的属性。exclude)properties
,列表类型,用来指定不进行映射的属性。primary_key
,列表类型,用来指定表的主键列。polymorphic_identity
,字符串类型,用来指定区分值,用于单表存储按照类型区分的多个映射类,每个子类都需要指定。polymorphic_on
,Column类型,用来指定用于区分多个映射的列,只需要在基类中指定。
经典式定义
经典式定义的表映射则是需要将表描述与映射类手工绑定,上一节的映射示例使用经典式定义则是下面这样的景象。
from sqlalchemy import Table, MetaData, Column, Integer, String
from sqlalchemy.orm import mapper
metadata = MetaData()
user = Table('user', metadata,
Column('id', String(200), primary_key=True, nullable=False),
Column('name', String(200), nullable=False),
Column('pass', String(50), nullable=False),
Column('age', Integer)
)
class User(object):
def __init__(self, id, name, pass, age):
self.id = id
self.name = name
self.password = pass
self.age = age
mapper(User, user)
在表映射定义上,声明式定义已经简洁了很多,这里就不再对经典式定义过多介绍了,后文也主要以声明式定义为主。
注意,如果仅使用Table()
来定义模型,不将其与一个类建立映射关系,则是SQLAlchemy Core的定义方法,这时需要采用SQLAlchemy Core的查询方法,不能采用SQLAlchemy ORM的查询方法。两种查询方法不能通用。
计算列
计算列有两种定义方式,一种是使用修饰器,一种是使用column_property
。
以下使用修饰器定义了一个用户表示字段。
from sqlalchemy.ext.hybrid import hybrid_property
class User(Base):
__tablename__ = 'user'
id = Column(String(200), primary_key=True, nullable=False)
name = Column(String(200), nullable=False)
password = Column('pass', String(50), nullable=False)
age = Column(Integer)
@hybrid_property
def standardname(self):
return "[{}]{}".format(self.id, self.name)
而使用column_property
则更加简单一些。
from sqlalchemy.orm import column_property
class User(Base):
__tablename__ = 'user'
id = Column(String(200), primary_key=True, nullable=False)
name = Column(String(200), nullable=False)
password = Column('pass', String(50), nullable=False)
age = Column(Integer)
standardname = column_property("[{}]{}".format(id, name))
一对多映射
SQLAlchemy中的关联映射并不复杂,只需要逐项定义关联项即可。前面给出的两个表,即是一个一对多的经典关联,下面就利用这两个表来演示一对多映射的定义方法。
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(String(200), primary_key=True, nullable=False)
name = Column(String(200), nullable=False)
password = Column('pass', String(50), nullable=False)
age = Column(Integer)
roles = relationship("Role")
class Role(Base):
__tablename__ = 'role'
id = Column(Integer, primary_key=True, nullable=False),
user_id = Column(String(200), nullable=False, ForeignKey('user.id'))
role = Column(String(20), nullable=False)
使用relationship()
可以定义指定属性为关联属性,建立单向的关联关系,示例中使用back_populates
参数将关联关系指向了对向的属性,从而就建立了双向关联关系。这个参数可以根据需要选择使用。当relationship()
只关联单一的字段时,可以使用backref
参数来指定逆向关联,该关联是在内存中实现的,使用backref
可以不必在被映射的对方定义关联字段。
ForeignKey()
函数给映射列指出了其参考的外键列,外键列使用表名.列名
的方式表示。外键列不需要在数据库中声明为外键,这里只需要虚拟的设定外键即可。
在一对多映射中,外键一般都放置在子表中,来与主表建立关联。relationship()
放置在主表来代表子表的实例集合。
对于关联常用的级联操作,可以使用cascade
参数在relationship()
函数中定义,需要级联的功能使用英文逗号隔开连接成字符串即可,例如cascade=""save-update, merge, delete"
。对于使用backref
定义的逆向关联,可以使用cascade_backrefs
来定义是否使用逆向级联操作。
常用的级联操作有:
save-update
delete
delete-orphan
merge
refresh-expire
expunge
多对一映射
其实在形成一对多映射时,子表对于主表的关联就可以同时形成多对一的映射关系。多对一的映射通常将外键设置在主表中,relationship()
也在主表声明,来定义对子表的关联。
同样使用back_populates
可以建立双向的关联,这与一对多的关系是相同的。
一对一映射
realtionship()
默认会创建使用列表的“多”映射,如果是一对一映射,就需要在不需要出现列表的一方的relationship()
中使用uselist
参数来将其转换成单一属性。
也就是说,谁那一方的属性应该是单数,就在谁的relationship()
中添加uselist=False
参数。
多对多映射
多对多映射通常会用到中间表来定义关联关系,这在映射定义时,就需要在relationship()
中定义secondary
参数。例如以下关联映射的定义:
association_table = Table('association', Base.metadata,
Column('left', Integer, ForeignKey('left.id')),
Column('right', Integer, ForeignKey('right.id'))
)
class Left(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
right = relationship('Right', secondary=association_table, backref="left")
class Right(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
其中Left
中的secondary
参数指定了多对多映射使用的中间表,并且使用了backref
在Right
中定义了映射字段left
来完成双向映射。
当中间表中还有其他要使用的字段时,可以将中间表也定义为一个类,此时上例就变成了下面这样。
class Association(Base):
__tablename__ = 'association_table'
left_id = Column('left', Integer, ForeignKey('left.id'), primary_key=True)
right_id = Column('right', Integer, ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(200))
right = realationship('Right')
class Left(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
right = realtionship('Association')
class Right(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
自关联映射
在数据表设计的时候,经常会使用数据表自己与自己关联来产生级联数据,这种情况下就不能使用前面的关联方法。如果使用前面的一对多的映射方法,就会得到一个异常。这时就需要relationship()
函数的另一个参数remote_side
来定义循环映射主键。
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True),
parent_id = Column(Integer, ForeignKey('node.id'))
data = Column(String(100))
children = relationship('Node', remote_side=[id])
示例中定义了一个单向的映射,如果需要改为双向映射,只需要将关联映射一句改为children = relationship('Node', backref=backref('parent', remote_side=[id]))
。
如果存在多个键的复杂自关联映射,则需要将关联键都列举在remote_side
中。
自定义关联条件
在默认情况下,关联条件都是采用相等的处理方式。但是个别情况下可能会使用其他的关联方式,比如like
或者是数组包含(PostgreSQL支持的操作)等。在这种情况下可以使用relationship()
的primaryjoin
参数来指定关联条件。
primaryjoin
接受一个布尔表达式。
from sqlalchemy.orm import foreign, remote
class Host(Base):
__tablename__ = 'host'
id = Column(Integer, primary_key=True)
ip_address = COlumn(INET)
content = Column(String(50))
parent_host = relationship('Host', primaryjoin=remote(ip_address) == cast(foreign(content), INET))
如果是使用PostgreSQL的特殊比较操作符,则需要使用op
方法,格式为属性名.op('操作符', is_comparision=True)(外表字段)
。例如:
class IPA(Base):
__tablename__ = 'ip_address'
id = Column(Integer, primary_key=True)
v4address = Column(INET)
network = relationship('Network',
primaryjoin="IPA.v4address.op('<<', is_comparision=True)(foreign(Network.v4representation))",
viewonly=True
)
class Network(Base):
__tablename__ = 'network'
id = Column(Integer, primary_key=True)
v4representation = Column(CIDR)