可以熟练使用面向对象编程语言——如Python——的人,一定会非常容易理解关系型数据库的设计原则。以MySQL为例,如果用Python中的一个类来表示数据库中的一张表,用类的属性来表示表的字段,那么类的一个实例就表示表中的一行数据。
按照这种方式,完全可以将表用类来表示。自然地,我们会想,既然数据表可以被映射成类,那么对类的操作是不是也可以同步到相应的表呢?答案是肯定的,这就是所谓的ORM技术。
ORM 是对象关系映射(Object-Relational Mapping)的缩写,它是一种软件开发技术,用于将面向对象的编程语言中的对象与关系数据库中的数据进行映射。ORM 通过将关系数据库中的表和列转换成面向对象编程语言中的类和属性,使得开发人员可以用面向对象的方式来操作数据库,而不必关心底层的 SQL 查询语句和数据库操作细节。ORM 技术旨在提高开发效率,降低代码维护成本,同时也可以减少 SQL 注入等安全问题的出现。常见的 ORM 框架有 Hibernate、Django ORM、SQLAlchemy 等。
——来自ChatGPT
1、利用类定义表并写入数据
1.1 建表
利用ORM技术,可以通过在Python中定义类来定义数据表:
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine
from sqlalchemy.orm import declarative_base
Base = declarative_base()
engine = create_engine('mysql+pymysql://root:123456@127.0.0.1:3306/test')
class User(Base):
__tablename__ = "user_account"
id = Column(Integer, primary_key=True)
name = Column(String(30))
fullname = Column(String(30))
def __repr__(self):
return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
email_address = Column(String(30), nullable=False)
user_id = Column(Integer, ForeignKey("user_account.id"))
def __repr__(self):
return f"Address(id={self.id!r}, email_address={self.email_address!r})"
Base.metadata.create_all(engine)
关注类的定义代码不难看出,表结构是通过类属性定义的。其中,__tablename__
是一个特殊的属性,用于表示该类所对应的表的名称。表的其他字段名称及其约束通过实例化Column
为一个变量的方式进行声明。在实例化时,可以对字段的数据类型、长度、是否为主键等进行限制。
类中还定义了两张表的关系。在此处的情境中,一个用户被认为可以拥有多个邮件地址,而一个邮件地址只能属于一个用户,因此,它们之间的关系是“一对多”的。在address
表中,定义的user_id
字段是引用的主表user_account
的主键id
字段。
在定义指代表的类的时,主要它们都需要继承自Base
——一个具有声明性质的类。只有继承自Base
的类才能用上面的写法定义表结构。最后,通过Base.metadata.create_all(engine)
来将在Python中定义好的类映射成表并写入数据库中。
1.2 插入数据
通过ORM方法来与数据库进行交互时用的最多的是一种被称为Session
的对象。对于熟悉PyMySQL
的读者而言,它非常类似于后者的Connection
。实际上,Session
对象与各种数据库进行交互时,就是通过各个驱动包的Connection
(有可能是其他的名字)来实现的。
首先,通过实例化已定义的类来构造需要写入数据表中的数据行:
user1 = User(id=1, name='Everill', fullname='Everill Glen')
user2 = User(id=2, name='Hughes', fullname='Hughes Adnan')
addr1 = Address(id=1, email_address='everillglen1@example.com', user_id=1)
addr2 = Address(id=2, email_address='everillglen2@example.com', user_id=1)
addr3 = Address(id=3, email_address='hughesadnan@example.com', user_id=2)
这里简单地创建了两个User
实例和三个Address
实例。至此,它们还只是Python中的对象,与数据库还没有建立联系。为了将它们写入数据表,需要用到Session
。通过以下方式,可以创建一个Session
对象:
from sqlalchemy.orm import Session
session = Session(engine)
调用Session
的add()
方法,可以将一个实例“绑定”到session
上。这里有5个实例,为简单起见可以调用Session
的add_all()
将这些实例一次性绑定到session
上:
session.add_all([user1, user2, addr1, addr2, addr3])
上述“绑定”的过程,实际上就是Session
对象积累变更的过程。出于效率和其他因素的考虑,除非用户发出明确地指令,SQLALchemy不会将这些变更自动同步到数据库。
通过调用Session
的flush()
方法,可以将积累的变更同步到数据库:
session.flush()
调用flush()
实际上是创建一个事务以将变更对应的SQL发送到数据库服务器进行执行。如果一切顺利,最后还需要调用commit()
方法来真正完成上述事务。
当然,在实际中,可以直接调用commit()
方法,SQLALchemy会自动进行flush。
1.3 按主键查询
既然User
和Address
已经和数据库中的user_account
和address
表相对应,那么当然可以通过表的主键来查询特定的实例:
session.get(User, 1)
1.4 修改实例
user = session.get(User, 1)
user.fullname = 'Everill Green'
上述两行代码将id为1的实例的fullname属性进行了修改。与插入数据的过程类似,这种修改此时仍积累在Session
对象中,并未同步到数据库。
Session
对象通过一个集合来保存这些变更了的实例:
user in session.dirty
# True
再调用commit()
方法后,上述语句返回False
,表明user
不存在待提交的变更。
如果不想将这些变更提交到数据库,则调用rollback()
方法:
session.rollback()
session.dirty # 回滚后,变更集合为空
1.5 删除实例
类似地,先获取实例,再调用delete()
方法,则可以删除实例:
user = session.get(User, 1)
session.delete(user)
要想删除生效,仍需要调用commit()
方法。
至此,可以总结发现,通过Session
对象对数据库进行增删改查等基本操作时,SQLALchemy都会将其转化为对应的SQL并开启一个事务进行执行。若要执行生效,需要显示地进行提交(commit()
);若要进行回滚,则执行rollback()
。
2、获取已有的数据表
很多时候,我们需要做的是对已经存在于数据库中的表进行增删改查等操作。显然,应对这个问题时,我们希望将数据表批量映射为Python中的类,而不是从头将它们在Python中定义一遍。
在SQLALchemy中,我们可以使用automap_base()
函数可以自动将数据库中的表映射为ORM类:
from sqlalchemy.ext.automap import automap_base
# 自动加载所有现有数据表并创建ORM映射对象
Base = automap_base()
Base.prepare(engine, reflect=True)
# 获取ORM映射对象
User = Base.classes.user_account
Address = Base.classes.address
至此,便可以通过第一部分所说的方法来对数据表进行操作了。