【Python】FastAPI之SQLAlchemy、关联关系

第四节:SQLAlchemy操作数据库

一、SQLAlchemy介绍

SQLAlchemy 是一个功能强大且灵活的 Python SQL 工具包及对象关系映射(ORM)库,它提供了全面的数据库访问抽象层。通过 SQLAlchemy,开发者可以使用 Python 代码来定义数据模型,并以面向对象的方式与关系型数据库进行交互,而无需直接编写 SQL 语句。

主要特性

  • 核心 (Core):
  • SQL Expression Language: 提供了一种类似于 SQL 的表达式语言,用于构建和执行 SQL 查询。这使得你可以用 Python 构建复杂的查询,同时保持对 SQL 的完全控制。
  • Schema 和类型系统: 支持定义表结构、列类型、约束等,允许你创建、修改或删除数据库中的对象。
  • ORM (Object-Relational Mapper):
  • 声明性基类: 使用 declarative_base() 创建一个基类,所有模型类都继承自这个基类,从而简化了模型定义。
  • 会话管理: 通过 Session 对象来管理事务和查询上下文,确保线程安全和高效的数据库操作。
  • 关联和关系: 支持一对一、一对多、多对多等多种关系类型,以及懒加载、立即加载等加载策略。
  • 事件监听: 可以为模型实例注册事件监听器,在特定事件发生时触发回调函数。
  • 数据库支持:
  • 支持多种主流的关系型数据库,如 PostgreSQL, MySQL, SQLite, Oracle 等。
  • 提供了针对不同数据库的方言(Dialect),以处理特定于某个数据库的功能和语法差异。
  • 迁移工具 Alembic:
  • 自带的数据库迁移工具 Alembic,可以帮助你管理数据库模式的变化,包括版本控制、升级和降级等功能。
  • 更多功能

  • 事务管理: 支持显式的事务管理,确保数据的一致性和完整性。
  • 性能优化: 提供批量插入、延迟加载等机制来提高性能。
  • 扩展性: 可以轻松集成第三方库和其他中间件,适应复杂的应用需求。
  • SQLAlchemy对象关系映射器提供了一种方法,用于将用户定义的Python类与数据库表相关联,并将这些类(对象)的实例与其对应表中的行相关联。它包括一个透明地同步对象及其相关行之间状态的所有变化的系统,称为工作单元,以及根据用户定义的类及其定义的彼此之间的关系表达数据库查询的系统。

    可以让我们使用类和对象的方式操作数据库,从而从繁琐的 sql 语句中解脱出来。

    ORM 就是 Object Relational Mapper 的简写,就是关系对象映射器的意思。

    pip install sqlalchemy

    from sqlalchemy import create_engine
    
    # 数据库的配置变量
    HOSTNAME = '127.0.0.1'
    PORT     = '3306'
    DATABASE = 'test'
    USERNAME = 'root'
    PASSWORD = '123123'
    DB_URI = 'mysql+mysqldb://{}:{}@{}?charset=utf8mb4:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
    
    
    # pymysql
    engine = create_engine(
        "mysql://user:password@localhost:3306/dbname",
        echo=True,  # echo 设为 True 会打印出实际执行的 sql,调试的时候更方便
        future=True,  # 使用 SQLAlchemy 2.0 API,向后兼容
        pool_size=5, # 连接池的大小默认为 5 个,设置为 0 时表示连接无限制
        pool_recycle=3600, # 设置时间以限制数据库自动断开
    )
    
    # 创建一个 SQLite 的内存数据库,必须加上 check_same_thread=False,否则无法在多线程中使用
    engine = create_engine("sqlite:///:memory:", echo=True, future=True,
        connect_args={"check_same_thread": False})
    
    # pip install mysqlclient
    engine = create_engine('mysql+mysqldb://user:password@localhost/foo?charset=utf8mb4')
    

    二、创建ORM映射

    创建一个类,一个类对应了一个数据库中的一张表,类的数据属性对应了表中的字段名,这个类称为映射类。根据映射类创建出一个一个的对象,每个对象对应了表中的一条实际的数据。

    #1、创建基类
    from sqlalchemy.ext.declarative import declarative_base
          engine = create_engine(DB_URI)
       Base = declarative_base(engine)
    
     #2、用这个`Base`类作为基类来写自己的ORM类。要定义`__tablename__`类属性,来指定这个模型映射到数据库中的表名。
    class Person(Base):
        __tablename__ ='person'
    
    #3. 创建属性来映射到表中的字段,所有需要映射到表中的属性都应该为Column类型:
    class Person(Base):
        __tablename__ ='t_person'
        #2.在这个ORM模型中创建一些属性,来跟表中的字段进行 一一 映射。这些属性必须是sqlalchemy给我们提供好的数据类型
        id = Column(Integer,primary_key=True,autoincrement=True)
        name = Column(String(50))
        age = Column(Integer)
        country = Column(String(50))
    
    # 调用 create_all 创建所有模型
    
    Base.metadata.create_all(engine)
    
    # 如果只需要创建一个模型
    
    User.__table__.create(engine)
    
    #定义一个枚举类
    class   TagEnum(enum.Enum):
         python="PYHTON"
         flask="FLASK"
         django ="DJANGO"
    
    
    #创建一个ORM模型     说明基于sqlalchemy  映射到mysql数据库的常用字段类型有哪些?
    Base = declarative_base(engine)
    class News(Base):
        __tablename__='news'
        id = Column(Integer,primary_key=True,autoincrement=True)
        price1 = Column(Float)  #存储数据时存在精度丢失问题
        price2 = Column(DECIMAL(10,4))
        title = Column(String(50))
        is_delete =Column(Boolean)
        tag1 =Column(Enum('PYTHON','FLASK','DJANGO'))  #枚举常规写法
        tag2 =Column(Enum(TagEnum)) #枚举另一种写法
        create_time1=Column(Date)
        create_time2=Column(DateTime)
        create_time3=Column(Time)
        content1 =Column(Text)
        content2 =Column(LONGTEXT)
    
    # Base.metadata.drop_all()
    # Base.metadata.create_all()
    
    

    三、增删改查操作

    1、建立session链接

    from sqlalchemy.orm import sessionmaker
    
    DBSession = sessionmaker(bind=engine,autocommit=False, autoflush=False,
                                                      future=True)
    
    with DBSession() as sess:
        sess.execute()
    
    with DBSession.begin() as sess:
        sess.execute()
    
    
    

    2、新增操作

    和 1.x API 不同,2.0 API 中不再使用 query,而是使用 select 来查询数据。

    <!--方式一SQL-->
    insert_stmt = insert(User).values(name='name1')
    with DBSession() as sess:
        sess.execute(insert_stmt)
        sess.commit()
      
    <!--未绑定参数-->
    insert_stmt2 = insert(User)
    with DBSession() as sess:
        sess.execute(insert_stmt2,{'name':'name1'})
        sess.commit()
      
    <!--批量-->
    with DBSession() as sess:
        sess.execute(insert_stmt2,[{'name':'name1'},{'name':'name2'}])
        sess.commit()
      
    <!--另一种方式begin-->
    with DBSession.begin() as sess:
        sess.execute(insert_stmt2,[{'name':'name1'},{'name':'name2'}])
    
    <!--方式二对象-->
    obj=User(name='name2')
    with DBSession() as sess:
        sess.add(obj)
        sess.commit()
    
    <!--批量-->
    obj=User(name='name2')
    obj2=User(name='name2')
    with DBSession() as sess:
        sess.add(obj)
        sess.add(obj2)
        # 或者 s.bulk_save_objects([obj,obj2])
        sess.commit()
    

    3、查、改和删除操作

    2.0 API 中不再使用 query,而是使用 select,update,delete 来操作数据。但query仍可使用

    # session.query(User).get(42)
    session.get(User, 42)
    
    # session.query(User).all()
    session.execute(select(User)).scalars().fetchall()
    
    # session.query(User).filter_by(name="some_user").first()
    session.execute(select(User).filter_by(name="some_user")).fetchone()
    
    # session.query(User).from_statememt(text("select * from users")).a..()
    session.execute(select(User).from_statement(text("selct * from users"))).scalars().all()
    
    # session.query(User).filter(User.name == "foo").update({"fullname": "FooBar"}, synchronize_session="evaluate")
    session.execute(update(User).where(User.name == "foo").values(fullname="FooBar").execute_options(synchronize_session="evaluate"))
    # synchronize_session 有三种选项: false, "fetch", "evaluate",默认是 evaluate
    # false 表示完全不更新 Python 中的对象
    # fetch 表示重新从数据库中加载一份对象
    # evaluate 表示在更新数据库的同时,也尽量在 Python 中的对象上使用同样的操作
    
    

    第五节:SQLAlchemy的关联

    一、ORM中的一对多/多对一

    SQLAlchemy提供了一个 relationship,这个类可以定义属性,以后在访问相关联的表的时候就直接可以通过属性访问的方式就可以访问得到了。另外,可以通过 back_populates来指定反向访问的属性名称。

    注意ForeginKey的参数是<表名>.<键名>,而不是<类名>.<字段名>

    cascade,默认选项为save-update:

  • 一:save-update:默认选项,在添加一条数据的时候,会把其他和次数据关联的数据都添加到数据库中,这种行为就是save-update属性决定的
  • 二:delete:表示当删除某一个模型中的数据的时候,也删除掉使用relationship和此数据关联的数据
  • 三:delete-orphan:表示当对一个ORM对象解除了父表中的关联对象的时候,自己便会被删除,如果父表的数据被删除,同样自己也会被删除,这个选项只能用在一对多上,不能用在多对多和多对一上,并且使用的时候还需要在子模型的relationship中增加参数:single_parent=True
  • 四:merge(合并):默认选项,当在使用session.merge合并一个对象的时候,会将使用了relationship相关联的对象也进行merge操作
  • 五:expunge:移除操作的时候,会将相关联的对象也进行移除,这个操作只是从session中移除,并不会正则从数据库删除
  • 六:all:对 save-update、merge、refresh-expire、expunge、delete 这几种的缩写
  • 二、自关联

    我们说数据表关系时,默认说的是数据表之间的关系「一对多、一对一、多对多等等」。而在实际应用中常常会遇到数据表内的关联,比如现在互联中的一个名词「关注者」和「被关注者」,他们都在用户范围内,只是两个用户之间的关系。或者是:父部门,和子部门。

    children: Mapped[List['Dept']] = relationship(back_populates='parent', remote_side=[id])

    维护1对多自关联关系的时候(relationship),必须加入:remote_side= [id]

    三、一对一关联

    关系很简单:一个用户对应一张身份证,一张身份证属于一个用户。

    emp: Mapped['Employee'] = relationship(back_populates='id_card', single_parent=True)

    其中:single_parent=True 表示子表,只关联父表的一行记录。

    四、多对多关联

    数据库中多对多的关系,主要有下面三个步骤

    1. 定义一个中间表保存两个表的主键
    2. 定义多对多关系的两个表的模型
    3. 给每个模型都添加一个访问对方的属性 注意在relationship中指定中间表
    middle_table = Table(  # 中间表
        't_emp_role',
        Base.metadata,
        Column('emp_id', ForeignKey('t_emp.id'), primary_key=True),
        Column('role_id', ForeignKey('t_role.id'), primary_key=True),
    )
    

    作者:道友老李

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Python】FastAPI之SQLAlchemy、关联关系

    发表回复