用SQLAlchemy做ORM也有一段时间了,总结一下用过的一些地方。
连接数据库
SQLAlchemy通过一个url连接数据库,这个url包含了连接数据库相关的信息。
数据库连接URL
以MySQL为例,url的格式是
mysql+{driver}://{username}:{password}@{host}:{port}/{name}
,
其中
driver
是Python的数据库驱动,比如MySQL官方的数据库驱动
mysql-connector-python
,
driver
是
mysqlconnector
;
username
是数据库用户名;
password
是密码;
host
是数据库主机;
port
是数据库端口;
name
是数据库名。
创建数据库引擎,元数据以及会话等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
from sqlalchemy import create_engine, MetaData from sqlalchemy.orm import sessionmaker
db_url = "mysql+mysqlconnector://root:@localhost:3306/test"
engine = create_engine(db_url, echo=True)
metadata = MetaData(engine)
Session = sessionmaker(bind=engine)
session = Session()
|
一个
Session
实例可以理解成一个数据库连接,通过它来操作数据库;
也可以将它理解成一个容器,各种对象的实例存储在其中。
数据库连接池
注意
:以上的连接数据库方式获取数据库连接是使用默认的数据库连接池的,
如果不想使用数据库连接池的话可以用以下方式创建数据库引擎。
1 2 3 4 5
|
from sqlalchemy import create_engine from sqlalchemy.pool import NullPool db_url = "mysql+mysqlconnector://root:@localhost:3306/test"
engine = create_engine(db_url, poolclass=NullPool)
|
操作数据库
声明映射
在数据库中定义一个数据表对应在Python代码中则是定义一个类(也就是通常所说的Model),要维持从类到表的关系,首先就要先声明一个映射。
1 2 3 4
|
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
|
然后就可以通过继承
Base
类来定义映射到数据表中的类了。
注意
:
declarative_base()
生成的类,该类的子类一般都必须与数据库中的一张表对应,
如果想扩充这个基类,让所有子类都能使用,可以用如下方法。
1 2 3 4 5 6 7 8 9 10 11
|
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class BaseModel(Base): __abstract__ = True
pass
|
定义数据表模型
现在尝试创建一个简单的数据表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
from sqlalchemy import Integer, String, Enum, Column from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Student(Base):
__tablename__ = "tb_student"
id = Column(Integer(), primary_key=True) name = Column(String(30), nullable=False) gender = Column(Enum('male', 'female'))
pass
|
Integer
,
String
,
Enum
等数据类型都是SQLAlchemy定义的,会根据使用的数据库不同而使用数据库中对应的类型,
你也可以使用特定数据库的特定类型,以MySQL为例。
1
|
from sqlalchemy.dialects.mysql import INTEGER, ENUM, VARCHAR, TINYINT, TEXT, BLOB, DATETIME
|
创建和删除数据表
上面的代码只是声明这么一个类
Student
映射数据表
tb_student
,创建数据表的操作需要如下代码。
1 2 3
|
Base.metadata.create_all(engine)
|
删除数据表也是一样的操作,值得一提的是,未在代码中定义的数据表和不存在的数据表是不会做删除操作的。
1 2
|
Base.metadata.drop_all(engine)
|
数据状态
以ORM方式来对数据库中的数据做增删查改操作是通过
Session
实例来完成的,
在学习了解如何以ORM方式操作数据之前首先我们要对数据的状态有个基本的了解。
首先在ORM中,数据库中的
数据表
对应于Python中的
类
,而
数据表中的记录
对应于
类的实例对象
。
因此,对数据表中的记录进行增删查改在Python中实际上就是对实例对象的操作。
数据实例对象有四种状态,分别是
Transient - (瞬时的)
表示该实例对象不在session中,当然也没有保存到数据库中,
主键一般情况下为
None
(如果一个
Persistent
状态的对象进行事务回滚后虽然主键有值,但却是
Transient
状态)。
Pending - (挂起的)
调用
session.add()
后,
Transient
状态的对象就会变成
Pending
状态的对象,这个时候它只是在
session
中,
并没有保存到数据库,因此主键依旧为
None
。
只有触发了
session.flush()
操作才会保存到数据库使得主键有值,比如查询操作就会触发flush。
Persistent - (持久的)
session
和数据库中都有对应的记录存在,为持久状态。
Detached - (游离的)
数据库中可能有记录,但是
session
中不存在。对这种对象的操作不会触发任何SQL语句。
要查看数据对象的状态可以用如下方式
1 2 3 4
|
>>> from sqlalchemy import inspect >>> status = inspect(data_object) >>> status.persistent True
|
SQLAlchemy关于数据实例对象状态的官方文档
数据的简单操作
以下面的数据表为例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
from sqlalchemy import Integer, String, Enum, Column from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'tb_user'
id = Column(Integer(), primary_key=True) name = Column(String(30), nullable=False) gender = Column(Enum('male', 'famale'))
def __repr__(self): return "<User(id={})>".format(self.id)
pass
|
用
session
来表示数据库会话实例。
查询操作
查询子句使用
session
的
.query()
方法来获取
Query
查询对象。
查询对象能够使用一些方法来对应一些查询子句,比如
.order_by()
,
.limit()
,
.filter()
等,高级的查询后面会专门讲。
查询对象有这么几种方法
.one()
,
.all()
,
.scalar()
,
.one_or_none()
,
.get()
,以及
.first()
等。
下面对这几个方法的用法及效果做简单解释。
.all()
返回查询到的所有的结果。
这个方法比较危险的地方是,如果数据量大且没有使用
limit
子句限制的话,所有的结果都会加载到内存中。
它返回的是一个
列表
,如果查询不到任何结果,返回的是空列表。
.first()
返回查询到的第一个结果,
如果没有查询到结果,返回
None
。
.scalar()
这个方法与
.one_or_none()
的效果一样。
如果查询到很多结果,抛出
sqlalchemy.orm.exc.MultipleResultsFound
异常。
如果只有一个结果,返回它,没有结果返回
None
。
.one()
如果只能查询到一个结果,返回它,否则抛出异常。
没有结果时抛
sqlalchemy.orm.exc.NoResultFound
,有超过一个结果时抛
sqlalchemy.orm.exc.MultipleResultsFound
。
.one_or_none()
比起
.one()
来,区别只是查询不到任何结果时不再抛出异常而是返回
None
。
.get()
这是个比较特殊的方法。它用于根据主键来返回查询结果,因此它有个参数就是要查询的对象的主键。
如果没有该主键的结果返回
None
,否则返回这个结果。
1 2 3 4 5 6 7 8 9
|
session.query(User).all()
session.query(User).order_by(User.id.asc()).first()
session.query(User).get(user_id) session.query(User).filter(User.id == user_id).scalar() session.query(User).filter(User.id == user_id).one_or_none() session.query(User).filter(User.id == user_id).one()
|
增加操作
删除操作
修改操作