在 SQLALCHEMY 中为什么定义中间表时要使用Table,而不是Model ?

112 阅读3分钟

最近做项目中想到了这个问题,原因是我想要直接往一个存放多对多关系的中间表导入数据,发现定义表时使用的是用db.Table定义,而非db.Model。 从而无法通过新建对象,然后调用db.Model.add方法进行数据插入,也无法通过db.Model.Query进行查询(Query方法来源于flask-sqlalchemy,非sqlalchemy)。从而对这个问题进行了一些研究思考。

在 SQLAlchemy 中,当你需要直接定义一个中间表或者操作一个并非基于 ORM 类(即非 db.Model 子类)的现有数据库表时,通常会使用 Table 对象而非 Model。这是因为 db.Model 是用于构建对象关系映射(ORM)模型的基础类,它更适合用于那些我们希望以面向对象的方式进行操作的实体表,这些实体表有对应的 Python 类,并通过 SQLAlchemy 的声明式方式映射到数据库表。

而 Table 主要是用来定义或引用数据库表结构的底层组件,它主要用于不依赖 ORM 层或者说不需要实体类的对象关系描述。以下几种场景中经常使用 Table

  1. 多对多关系(Many-to-Many Relationships) : 在 SQLAlchemy 中建立多对多关系时,通常会有一个中间关联表,这个表往往仅用于存储两个其他表之间的关联关系,而不包含额外的行为或属性。这种情况下,我们不会为此创建一个完整的 ORM Model,而是直接定义一个 Table 来描述这个关联表结构。
python
   association_table = Table('association', Base.metadata,
       Column('left_id', Integer, ForeignKey('left_table.id')),
       Column('right_id', Integer, ForeignKey('right_table.id'))
   )

   class Left(BaseORM):
       # ...
       right = relationship("Right", secondary=association_table)

   class Right(BaseORM):
       # ...
       left = relationship("Left", secondary=association_table)
  1. 现有表无 ORM 映射: 如果你需要操作一个已经在数据库中存在的表,但并不打算为其创建一个完整的 ORM Model,那么你可以用 Table 来定义这个表结构以便进行 CRUD 操作。
python
   existing_table = Table('existing_table', Base.metadata,
       autoload_with=engine)  # 使用引擎自动加载已有表结构
  1. 原生 SQL 查询: 当你只打算执行一些原生 SQL 查询而不涉及 ORM 实体时,可以直接使用 Table 和 SQLAlchemy Core API 来构造查询。
table.insert().values(loads)

总之,在 SQLAlchemy 中,Model 和 Table 各有其适用场景。当需要更精细地控制表结构,或者处理不需完整 ORM 映射的情况时,Table 是更为合适的选择。而对于业务实体,通常建议使用 Model 进行映射,从而利用 ORM 提供的便利性。

实际使用中,感觉不如一开始定义表时使用db.Model更方便。

当我用db.Table定义时,我需要这样构建语句来进行插入

def schema2dbbyORM(filepath, SchemaName):
    with open(filepath, 'r', encoding='utf-8') as file:
        data = json.load(file)
        # schema = SchemaName(many=True)
        try:
            loads = SchemaName(load_instance=False, many=True).load(data)
        except ValidationError as err:
            pprint(err.messages)
        # loads 为字典, 需要用到对应到db.Table对象来构建语句
        stmt = dict_Schema2ORMOrTable.get(SchemaName.__name__).insert().values(loads)
        result = db.session.execute(stmt)
        print(f'{SchemaName.__name__} Inserted {result.rowcount} rows')
        db.session.flush()
        db.session.commit()

当使用db.Model时

def schema2db(filepath, SchemaName):
   with open(filepath, 'r', encoding='utf-8') as file:
       data = json.load(file)
       # schema = SchemaName(many=True)
       try:
           loads = SchemaName(many=True).load(data)
       except ValidationError as err:
           pprint(err.messages)
       # loads 为Model实体
       print(f'{SchemaName.__name__} Inserted {len(loads)} rows')
       db.session.add_all(loads)
       db.session.commit()

如上,实际使用中,感觉不如一开始定义表时使用db.Model更方便。