SQLAlcyemy relationship
最佳实践方案:
- 使用字符串引用(推荐): ```python from sqlalchemy import Column, Integer, String from sqlalchemy.orm import relationship from app.db.base_class import Base
class User(Base): tablename = “users” id = Column(Integer, primary_key=True) name = Column(String(100)) # 使用字符串引用,不需要导入 Post 模型 posts = relationship(“Post”, back_populates=”author”)
1
2
3
4
5
6
7
8
9
10
11
12
13
```python
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from app.db.base_class import Base
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String(255))
author_id = Column(Integer, ForeignKey("users.id"))
# 同样使用字符串引用
author = relationship("User", back_populates="posts")
- 推荐使用第一种方法(字符串引用),因为:
- 简单直观
- 不需要额外的初始化步骤
- SQLAlchemy 官方推荐的方式
- 目录结构建议:
1 2 3 4 5 6 7
app/ models/ __init__.py # 导出所有模型 base.py # 基础模型类 user.py # 用户模型 post.py # 文章模型 comment.py # 评论模型
- 在
__init__.py中统一导出: ```python:app/models/init.py from .user import User from .post import Post from .comment import Comment
all = [“User”, “Post”, “Comment”]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
4. **关系定义的一般原则**:
- 在"多"的一方定义外键
- 在更重要的实体上定义 relationship
- 使用 back_populates 而不是 backref(更明确)
- 使用字符串引用避免循环导入
5. **示例使用**:
```python
# 在其他地方使用模型
from app.models import User, Post
# 创建用户和文章
user = User(name="Alice")
post = Post(title="Hello")
user.posts.append(post)
这样的组织方式可以:
- 避免循环引用问题
- 保持代码清晰和模块化
- 便于维护和扩展
- 提供良好的类型提示支持
对于多对多关系,我来展示几种常见的实现方式:
- 最简单的多对多关系: ```python from sqlalchemy import Table, Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from app.db.base_class import Base
关联表定义
post_tags = Table( “post_tags”, Base.metadata, Column(“post_id”, Integer, ForeignKey(“posts.id”), primary_key=True), Column(“tag_id”, Integer, ForeignKey(“tags.id”), primary_key=True) )
class Tag(Base): tablename = “tags” id = Column(Integer, primary_key=True) name = Column(String(50), unique=True) # 通过 secondary 指定关联表 posts = relationship(“Post”, secondary=post_tags, back_populates=”tags”)
1
2
3
4
5
6
7
8
9
10
11
12
```python
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from app.db.base_class import Base
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String(255))
# 反向关联
tags = relationship("Tag", secondary="post_tags", back_populates="posts")
- 带额外字段的多对多关系: ```python from datetime import datetime from sqlalchemy import Column, Integer, ForeignKey, DateTime from sqlalchemy.orm import relationship from app.db.base_class import Base
class PostTag(Base): tablename = “post_tags” post_id = Column(Integer, ForeignKey(“posts.id”), primary_key=True) tag_id = Column(Integer, ForeignKey(“tags.id”), primary_key=True) # 额外字段 created_at = Column(DateTime, default=datetime.utcnow) created_by = Column(Integer, ForeignKey(“users.id”))
1
2
3
# 关联关系
post = relationship("Post", back_populates="tag_associations")
tag = relationship("Tag", back_populates="post_associations") ```
1
2
3
4
5
6
7
8
9
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String(255))
# 通过关联表访问标签
tag_associations = relationship("PostTag", back_populates="post")
# 便捷属性直接访问标签
tags = relationship("Tag", secondary="post_tags", viewonly=True)
1
2
3
4
5
6
7
8
9
class Tag(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
# 通过关联表访问文章
post_associations = relationship("PostTag", back_populates="tag")
# 便捷属性直接访问文章
posts = relationship("Post", secondary="post_tags", viewonly=True)
- 使用示例: ```python
简单多对多关系使用
post = Post(title=”SQLAlchemy Guide”) tag1 = Tag(name=”Python”) tag2 = Tag(name=”Database”)
添加标签
post.tags.append(tag1) post.tags.append(tag2)
或者
post.tags.extend([tag1, tag2])
带额外字段的多对多关系使用
post_tag = PostTag( post=post, tag=tag1, created_by=current_user.id ) db.session.add(post_tag)
查询示例
获取文章的所有标签
tags = post.tags
获取标签的所有文章
posts = tag1.posts
获取带创建信息的关联
for assoc in post.tag_associations: print(f”Tag: {assoc.tag.name}, Created at: {assoc.created_at}”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
4. **查询优化**:
```python
class Post(Base):
__tablename__ = "posts"
# 预加载标签的查询
@classmethod
def get_with_tags(cls, db: Session, post_id: int):
return db.query(cls).options(
joinedload(cls.tags)
).filter(cls.id == post_id).first()
# 按标签筛选文章
@classmethod
def filter_by_tag(cls, db: Session, tag_name: str):
return db.query(cls).join(cls.tags).filter(
Tag.name == tag_name
).all()
- 性能考虑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class Post(Base): # 使用 lazy='selectin' 优化加载性能 tags = relationship( "Tag", secondary="post_tags", back_populates="posts", lazy="selectin" # 自动预加载标签 ) # 使用 lazy='dynamic' 延迟加载,支持进一步查询 tag_query = relationship( "Tag", secondary="post_tags", lazy="dynamic" )
- 常用查询模式: ```python
获取特定标签的文章数量
tag_counts = ( db.query(Tag.name, func.count(Post.id)) .join(post_tags) .join(Post) .group_by(Tag.name) .all() )
获取至少有一个标签的文章
posts_with_tags = ( db.query(Post) .join(Post.tags) .distinct() .all() )
获取同时包含多个标签的文章
posts_with_all_tags = ( db.query(Post) .join(Post.tags) .filter(Tag.name.in_([‘Python’, ‘SQLAlchemy’])) .group_by(Post.id) .having(func.count(distinct(Tag.id)) == 2) .all() ) ```
这些实现方式各有优势:
- 简单多对多关系:代码简洁,适合简单场景
- 带额外字段的关联:更灵活,可以存储关联的元数据
- 查询优化:根据实际需求选择合适的加载策略
选择哪种方式主要取决于:
- 是否需要在关联中存储额外信息
- 查询模式和性能要求
- 代码的复杂度和可维护性