SQLAlchemy 数据库炼金师-ORM模式
SQLAlchemy 简介
SQLAlchemy 是目前最流行的 Python 语言的SQL工具箱,它为开发人员提供了 SQL 的全部功能同时保留了 Python 的灵活性。
SQLAlchemy 以对象关系映射(ORM)而闻名,使用它可以将 Python 的类映射到数据库,从而允许对象模型和数据库模式从一开始就以一种完全分离的方式分别进行架构。 对象模型可以无需考虑使用什么数据库来存储, 这样给项目开发带来了极大的便利.
开始之前, 先收下几个有用的网址
- 官网 https://www.sqlalchemy.org/
- GitHub https://github.com/sqlalchemy/sqlalchemy
- 文档 https://docs.sqlalchemy.org
什么是ORM?
ORM(对象关系映射)是一种编程技术,用于在面向对象的编程语言中将对象与数据库表进行映射, 从而简化开发过程。
在ORM系统中,每个类都映射到数据库中的表。 使用了 ORM 技术的项目不必在编写繁琐的数据库接口代码, 而是由 ORM 技术自动来解决增删改查这些与数据库相关的操作,因而开发者可以更专注于实现项目的核心功能。
SQLAlchemy 的两种使用模式
SQLAlchemy 由两个主要的模块组成, 分别是 核心模式 与 ORM 模式
核心模式: 是功能齐全的 SQL 语言的抽象工具包,对于熟悉 SQL 语言的开发者来说, 使用 SQLAlchemy 的核心模式可以快速的上手, 而又无需考虑数据库之间的 SQL 方言差异
ORM 模式: 每个类都映射到数据库中的表, 开发者无需在编写繁琐的数据库接口代码, 可以更专注于实现项目的核心功能
课程内容简介
本课程主要讲解 SQLAlchemy 的ORM模式开发,主要围绕着图书管理系统来进行设计,数据包含以下内容
图书表 books
+----+--------+--------------------------+-------+
| id | cat_id | name | price |
+----+--------+--------------------------+-------+
| 1 | 1 | 生死疲劳 | 40.40 |
| 2 | 1 | 皮囊 | 31.80 |
| 3 | 2 | 半小时漫画中国史 | 33.60 |
| 4 | 2 | 耶路撒冷三千年 | 55.60 |
| 5 | 2 | 国家宝藏 | 52.80 |
| 6 | 3 | 时间简史 | 31.10 |
| 7 | 3 | 宇宙简史 | 22.10 |
| 8 | 3 | 自然史 | 26.10 |
| 9 | 3 | 人类简史 | 40.80 |
| 10 | 3 | 万物简史 | 33.20 |
+----+--------+--------------------------+-------+
_
分类表 categories
+----+--------------+
| id | name |
+----+--------------+
| 1 | 文学 |
| 2 | 人文社科 |
| 3 | 科技 |
+----+--------------+
第一关 ORM模式快速案例
接下来我们用一个简短的案例用 SQLAlchemy 实现对数据库的增删改查操作
#coding:utf8
from sqlalchemy import create_engine
# 连接到 SQLite 数据库,通常加上 echo = True 用于调试, 这样 SQLAlchemy 会输出自动生成的 SQL 语句
engine = create_engine('sqlite:///sqlalchemy.db', echo = True )
# 这个时候还没有连接数据, 还需要对 engine 对象调用 connect 方法
cn = engine.connect()
# declarative_base是sqlalchemy内部封装的一个方法,通过其构造一个基类,这个基类和它的子类,可以将Python类和数据库表关联映射起来
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
# ORM的重要特点,我们操作表的时候,就需要通过操作对象来实现,现在我们创建一个类,以用户表为例
from sqlalchemy import Column, Integer, String
# 数据库表模型类通过__tablename__和表关联起来,Column表示数据表的列。
class Users(Base):
__tablename__ = "users"
#通过primary_key设置为主键
id = Column(Integer, primary_key=True)
#unique设置为不重复
name = Column(String(64), unique=True)
email = Column(String(64))
#初始化设置值
def __init__(self, name, email):
self.name = name
self.email = email
#创建表 如果表存在则忽略,执行以下代码发现在db中创建了users表
Base.metadata.create_all(engine)
#sqlalchemy中使用session用于创建程序和数据库之间的会话,所有对象的载入和保存都需要通过session对象 。
from sqlalchemy.orm import sessionmaker
# 创建session
DbSession = sessionmaker(bind=engine)
session = DbSession()
#增
add_user = Users("test", "test123@qq.com")
session.add(add_user)
session.commit()
#查
users = session.query(Users).filter_by(id=1).all()
for item in users:
print(item.name)
#改
session.query(Users).filter_by(id=1).update({'name': "Jack"})
#删
delete_users = session.query(Users).filter(Users.name == "test").first()
if delete_users:
session.delete(delete_users)
session.commit()
同学当你阅读完这个代码以后,你会发现 咦~,没有SQL语句了。嗯,是的,这个就是ORM的魅力所在,只要你掌握了类与对象的概念,即可进行数据的操作了,整个ORM里面,通过操作Users这个类即可数据进行增删改查的操作,完全不依赖SQL语句,对于刚接触不久的你,无疑是发现了一块新大陆。哦也!不用在记那些繁琐的sql语句喽!接下来我们愉快的系统的进行学习ORM框架,进入ORM全新世界。
第二关 连接数据库
首先学习使用 SQLAlchemy 连接数据库
SQLAlchemy 用 create_engine 方法新建一个 Engine 对象, 通过 Engine 对象可以连接数据库,
数据库连接语法: dialect+driver://username:password@host:port/database , 下面通过几个案例讲解如何连接各种类型的数据库
# 连接 SQLite
engine = create_engine('sqlite:///sqlalchemy.db')
# Windows 指定路径
engine = create_engine('sqlite:///C:\\path\\to\\sqlalchemy.db')
# Linux/Unix/Mac 指定路径
engine = create_engine('sqlite:////path/to/sqlalchemy.db')
# MySQL : 缺省驱动 mysql-python
engine = create_engine('mysql://scott:tiger@localhost/foo')
# MySQL : 使用 pymysql 连接
engine = create_engine('mysql+pymysql://scott:tiger@localhost/foo')
# PostgreSQL : 缺省驱动 psycopg2
engine = create_engine('postgresql://scott:tiger@localhost/mydatabase')
重要参数:
- echo = True : 用于调试, 这样 SQLAlchemy 会输出自动生成的 SQL 语句和其他一些调试信息
- encoding = ‘utf-8’ : 指定字符串类型的编码, 缺省值就是 utf-8
- pool_size = 10 : 指定数据库连接池内保持活跃连接的数量,sqlite不支持数据库连接池
创建了 Engine 对象后, 还没有实际连接数据库, 还需要调用 connect 方法连接数据库
# 连接数据库, 返回连接赋值给 cn
cn = engine.connect()
Engine 与数据库连接池: 由于连接数据库是一个较为耗时的操作, 因此 Engine 内部实现了数据库连接池, 当我们创建 Engine 对象的时候, 使用 pool_size 告诉 Engine 对象数据库连接池需要保持多少个活跃连接. 当我们需要连接数据库时, 从连接池中取用一个现成的连接使用即可, SQLAlchemy 将这一切复杂的逻辑为我们封装在 Engine 对象中
# SQLite 不支持连接池, 这里使用 MySQL 数据库进行演示
engine = create_engine('mysql+pymysql://pandas:pandas@localhost/pandas', pool_size= 5, echo=True)
第三关 建表
为了适应不同数据库建表技术的不同之处 , SQLAlchemy 实现了自己的建表方式: MetaData 元数据类,
通过工厂模式declarative_base创建MetaData元数据类,declarative_base是sqlalchemy内部封装的一个方法,通过其构造一个基类,这个基类和它的子类,可以将Python类和数据库表关联映射起来
创建基类Base用于将Python类和数据库表关联映射
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
建表: 用 Category对象新建数据库表, 用 Column 对象映射数据库表的每个字段,值得注意的是sqlalchemy要求每个表是必须有主键值的,无主键值会报错
# 导入需要用的类
from sqlalchemy import Table, Column, Integer, Numeric, String, DateTime
# 数据库表模型类通过__tablename__和表关联起来,Column表示数据表的列。
class Category(Base):
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
name = Column('name', String(30))
def __init__(self,name):
self.name=name
#创建表 如果表存在则忽略,执行以下代码发现在db中创建了users表
Base.metadata.create_all(engine)
建表输出语句
CREATE TABLE categories (
id INTEGER NOT NULL,
name VARCHAR(30),
PRIMARY KEY (id)
)
建立了数据表以后, Category 对象还有大用处, 可以进行增删改查, 所以如果使用 SQLAlchemy 的ORM模式开发项目, 首先就要建立 ORM 对象。
下面列出了 SQLAlchemy 提供的常用数据类型, 供参考使用
- BigInteger
- Boolean
- Date/Time/DateTime
- Enum
- Float
- Integer/SmallInteger
- LargeBinary
- Numeric
- String/Unicode
- Text/UnicodeText
SQLAlchemy 还有一些数据类型是只有部分数据库才支持的, 要了解这些数据类型请参考官方文档 https://docs.sqlalchemy.org/en/13/core/type_basics.html
ForeignKey 外键: 在关系型数据库设计中, 外键是一个重要的特性, 即数据表中的其中一个字段的数据来自于另一个数据表的字段, 通常来自于另一个数据表的主键。 上面我们设计了一个 categories 表, 现在设计一个 books 表, 其中的一个字段 cat_id 来自于 Category 表的主键
from sqlalchemy import create_engine, Numeric
# 连接到 SQLite 数据库通常加上 echo = True 用于调试, 这样 SQLAlchemy 会输出自动生成的 SQL 语句
engine = create_engine('sqlite:///sqlalchemy.db', echo = True )
# 这个时候还没有连接数据, 还需要对 engine 对象调用 connect 方法
cn = engine.connect()
# declarative_base是sqlalchemy内部封装的一个方法,通过其构造一个基类,这个基类和它的子类,可以将Python类和数据库表关联映射起来
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
# ORM的重要特点,我们操作表的时候,就需要通过操作对象来实现,现在我们创建一个类,以用户表为例
from sqlalchemy import Column, Integer, String,ForeignKey
# 数据库表模型类通过__tablename__和表关联起来,Column表示数据表的列。
class Category(Base):
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
name = Column('name', String(30))
def __init__(self,name):
self.name=name
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
cat_id = Column(Integer,ForeignKey("categories.id"))
name = Column('name', String(120))
price = Column('price', Numeric)
def __init__(self,cat_id,name,price):
self.cat_id=cat_id
self.name=name
self.price=price
#创建表 如果表存在则忽略,执行以下代码发现在db中创建了users表
Base.metadata.create_all(engine)
建表输出:
CREATE TABLE books (
id INTEGER NOT NULL,
cat_id INTEGER,
name VARCHAR(120),
price NUMERIC,
PRIMARY KEY (id),
FOREIGN KEY(cat_id) REFERENCES categories (id)
)
上面有2个表,为什么这里只输出了一个表呢?原因是Base.metadata.create_all方法是如果没有表创建表,如果有表,就不在进行创建表
磨刀不误砍柴工,为了方便后续打印数据,对数据做一个封装
def to_dict(self):
return {c.name: getattr(self, c.name, None)
for c in self.__table__.columns}
Base.to_dict = to_dict
第四关 增删改
1. 创建会话
创建了数据表以后, 接下来对数据表进行增删改操作. 使用session会话进行增删改查的操作,我们使用上一关卡创建的engine
#sqlalchemy中使用session用于创建程序和数据库之间的会话,所有对象的载入和保存都需要通过session对象 。
from sqlalchemy.orm import sessionmaker
# 创建session
DbSession = sessionmaker(bind=engine)
session = DbSession()
2. 插入数据
在前面建表关卡, 我们建立了 categories 和 books 数据表, 并分别使用 metadata.create_all 方法,创建了数据表, 下面我们创建categories对象使用session的内置add增加数据
c1=Category('文学')
session.add(c1)
session.commit()
三行代码,就解决了增加数据步骤,怎么样,是不是很惊喜!
在整个操作中,我们会发现id没有设置值,id设置的是主键,获取增加数据后自动生成的主键值
插入数据输出
INSERT INTO categories (name, email) VALUES (?, ?)
如果要添加多个数据
c2=Category('人文社科')
c3=Category('科技')
session.add_all([c2,c3])
session.commit()
#添加数据
book=Book(1,'生死疲劳',40.40)
book1=Book(1,'皮囊',31.80)
book2=Book(2,'半小时漫画中国史',33.60)
book3=Book(2,'耶路撒冷三千年',55.60)
book4=Book(2,'国家宝藏',52.80)
book5=Book(3,'时间简史',31.10)
book6=Book(3,'宇宙简史',22.10)
book7=Book(3,'自然史',26.10)
book8=Book(3,'人类简史',40.80)
book9=Book(3,'万物简史',32.20)
session.add_all([book,book1,book2,book3,book4,book5,book6,book7,book8,book9])
session.commit()
3. 查看数据
查看数据: 使用session的query语句作为查询,使用 Book 类指定查询表,使用filter_by作为查询条件,通过all返回所有符合查询条件的结果,根据返回值遍历,得到每一行数据的内容
book = session.query(Book).filter(Book.id == 1).first()
print(book.to_dict())
查询数据语句输出
SELECT books.id AS books_id, books.cat_id AS books_cat_id, books.name AS books_name, books.price AS books_price
FROM books
WHERE books.id = ?
LIMIT ? OFFSET ?
得到的返回数据如下
{‘id’: 1, ‘cat_id’: 1, ‘name’: ‘生死疲劳’, ‘price’: Decimal(‘40.4000000000’)}
4. 修改数据
查看数据: 使用session的query语句作为查询,使用 Book 类指定查询表,使用filter_by作为查询条件,通过update设置要修改的内容,里面的参数使用字段
session.query(Book).filter_by(id=1).update({'name': "Jack"})
5. 删除数据
删除数据: 使用seesion的query查找的对象,查找对象使用的是Book用于指定是查找哪个表,filter设置查找的内容,first返回的是查找的第一个对象,若这个对象有,则使用session的delete进行删除数据
delete_book = session.query(Users).filter(Book.name == "宇宙简史").first()
if delete_book:
session.delete(delete_book)
session.commit()
第五关 查询数据
上一关中使用了简单的查询语句,查询语句有多种多样的语句来满足不同的应用场景,接下来来看看还有哪些查询场景
1、单条件查询
通过query进行查询,filter进行设置条件,first取出第一个值来进行查询,返回的是单个对象
book = session.query(Book).filter(Book.id == 1).first()
print(book.to_dict())
输出结果如下
{'id': 1, 'cat_id': 1, 'name': '生死疲劳', 'price': Decimal('40.4000000000')}
2、多条件查询
通过query进行查询,filter进行设置条件,使用all返回所有内容,接收的数据结构为一个列表类型
books = session.query(Book).filter(Book.cat_id == 1,Book.price > 35).all()
print([v.to_dict() for v in books])
输出结果如下
[{'id': 1, 'cat_id': 1, 'name': 'Jack', 'price': Decimal('40.4000000000')}]
3、返回记录条数
调用count函数,来获得返回的数据数量
count = session.query(Book).filter(Book.cat_id == 3).count()
print(count)
输出结果如下
5
4、限制返回的记录条数
通过limit来进行限制返回数据的条数
#limit() 限制返回的记录条数
books = session.query(Book).filter(Book.cat_id == 3).limit(3).all()
print([v.to_dict() for v in books])
输出结果如下
[{'id': 6, 'cat_id': 3, 'name': '时间简史', 'price': Decimal('31.1000000000')},
{'id': 7, 'cat_id': 3, 'name': '宇宙简史', 'price': Decimal('22.1000000000')},
{'id': 8, 'cat_id': 3, 'name': '自然史', 'price': Decimal('26.1000000000')}]
5、将记录按照某个字段进行排序
使用order_by设置排序,desc进行设置是依据、哪个字段进行正向排序还是逆向排序
# 图书按 ID 降序排列
# 如果要升序排列,去掉 .desc() 即可
books = session.query(Book.id, Book.name).filter(Book.id > 8).order_by(Book.id.desc()).all()
print([dict(zip(v.keys(), v)) for v in books])
输出结果如下
[{'cat_id': 1}, {'cat_id': 2}, {'cat_id': 3}]
6、查看记录是否存在
查看这个数据是否存在
from sqlalchemy.sql import exists
is_exist = session.query(exists().where(Book.id > 10)).scalar()
print(is_exist)
输出结果如下
False
第六关 事务处理
SQLAlchemy 对于事务处理其实和 DB-API 一样, 因为事务处理的套路是一样的, 就是利用 with 语法
# 连接数据库
engine = create_engine('sqlite:///sqlalchemy.db', echo = True)
# 使用 with 启动一个事务
with engine.begin() as cn:
users = session.query(Users).filter_by(id=1).all()
for item in users:
print(item.name)
第七关 相关拓展-联表查询
什么是联表查询,联表查询是把2个表按照一定关系进行组合后进行查询,比如图书管理系统中,使用Book时候无法直观的看到这个图书的具体分类,但是通过联表查询,就可以把结果比较直观的显示图书的数据分类
联表查询分为内连接和外链接
内连接:(inner join)内连接是把两张表相同的地方匹配出来
外连接:外连接是以左边或右边的表作为主表,把主表的内容显示出来,从表没有的内容置为空处理
1、内连接
使用内连接我们来查询一下分类为科技的并且价格大于40的图书
#内连接 获取分类为「科技」,且价格大于 40 的图书
# 如果 ORM 对象中定义有外键关系
# 那么 join() 中可以不指定关联关系
# 否则,必须要
books = session .query(Book.id,
Book.name.label('book_name'),
Category.name.label('cat_name')) \
.join(Category, Book.cat_id == Category.id) \
.filter(Category.name == '科技',
Book.price > 40) \
.all()
print([dict(zip(v.keys(), v)) for v in books])
输出结果:_
[{'id': 9, 'book_name': '人类简史', 'cat_name': '科技'}]
统计各个分类的图书的数量
# 统计各个分类的图书的数量
from sqlalchemy import func
books = session \
.query(Category.name.label('cat_name'),
func.count(Book.id).label('book_num')) \
.join(Book, Category.id == Book.cat_id) \
.group_by(Category.id) \
.all()
print([dict(zip(v.keys(), v)) for v in books])
_
输出结果
[{'cat_name': '文学', 'book_num': 2},
{'cat_name': '人文社科', 'book_num': 3},
{'cat_name': '科技', 'book_num': 5}]
2、外连接
#为方便说明,我们仅在这一小节中向 books 表中加入如下数据
# +----+--------+-----------------+-------+
# | id | cat_id | name | price |
# +----+--------+-----------------+-------+
# | 11 | 3 | 人性的弱点 | 54.40 |
# +----+--------+-----------------+-------+
# outerjoin 默认是左连接# 如果 ORM 对象中定义有外键关系
# 那么 outerjoin() 中可以不指定关联关系
# 否则,必须要
books = session \
.query(Book.id.label('book_id'),
Book.name.label('book_name'),
Category.id.label('cat_id'),
Category.name.label('cat_name')) \
.outerjoin(Category, Book.cat_id == Category.id) \
.filter(Book.id >= 9) \
.all()
print([dict(zip(v.keys(), v)) for v in books])
_
输出结果
[{'book_id': 9, 'book_name': '人类简史', 'cat_id': 3, 'cat_name': '科技'},
{'book_id': 10, 'book_name': '万物简史', 'cat_id': 3, 'cat_name': '科技'}]
课程练习
设计一个项目 project 的数据库,要求包含以下字段:
- id 整数类型主键自增1
- name 字符串类型
本项目使用了一个 流行歌手的数据集 artist.csv , 其中数据非常简单只有两列: id, name , 刚好与我们设计的 project 数据库一样. 接下来使用数据库实现以下操作:
Step1:将 artist.csv 导入项目数据库,并删除掉前 3 个数据;
Step2:搜索数据库,打印出数据库 id = 10 的数据;
Step3:将数据导出为 project_data.csv 文件中。
好了,利用我们本课程已经学习的知识,动手完成这个小练习吧!
参考答案:
#coding:utf8
# 导入 SQLAlchemy
from sqlalchemy import create_engine, MetaData, Table, Integer, String, Column
# 连接数据库
engine = create_engine('sqlite:///project.db', echo = True)
# 使用 MetaData/Table 建表
# declarative_base是sqlalchemy内部封装的一个方法,通过其构造一个基类,这个基类和它的子类,可以将Python类和数据库表关联映射起来
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Project(Base):
__tablename__='project'
id=Column(Integer, primary_key=True)
# unique设置为不重复
name=Column(String(255))
def __init__(self,name):
self.name=name
Base.metadata.create_all(engine)
# 从 CSV 文件读取数据到列表对象
# 定义一个列表对象
projects = []
# 导入 csv 模块
import csv
# 用 with 语法, 打开文件
with open('artist.csv', newline='') as csvfile:
# 用 DictReader 类, 方便开发
reader = csv.DictReader(csvfile)
# 按行遍历csv 文件
for row in reader:
try:
projects.append({"id": row['id'], "name": row['name'] } )
except Exception as e:
print(e)
print(projects)
#sqlalchemy中使用session用于创建程序和数据库之间的会话,所有对象的载入和保存都需要通过session对象 。
from sqlalchemy.orm import sessionmaker
# 创建session
DbSession = sessionmaker(bind=engine)
session = DbSession()
for i in projects:
print(i['name'])
add_project = Project(i['name'])
add_project.id=int(i['id'])
session.add(add_project)
session.commit()
print(" 成功导入 CSV 数据" )
# 删除掉前3个数据
delete_projects = session.query(Project).filter(Project.id <3).all()
if len(delete_projects)>0:
for i in delete_projects:
session.delete(i)
session.commit()
# 打印 id = 10 的数据
project_10 = session.query(Project).filter_by(id=10).first()
print(project_10)
# 将数据导出为 project_data.csv
with open("project_data.csv", 'w') as csvfile:
header= False
pro_all = session.query(Project).all()
for i in pro_all:
csvfile.write('%d,%s\r\n'%(i.id,i.name))
print(" 成功写入 CSV 文件")
SQLAlchemy 是目前最为流行的 Python 数据库模块, 在本课程中主要讲解了如何使用 SQLAlchemy 的ORM模式操作数据库, 只需要创建对象进行操作即可,对于刚接触程序的你,非常友好
SQLAlchemy 的ORM模式建立在对象的理解上,只要掌握了对象初始化和调用函数即可完成
SQLAlchemy 的核心开发模式和ORM开发模式各有千秋,适应不同人群,根据自身掌握程度,在开发时候除了公司明确要求你使用哪种,剩下的就根据自己最熟悉的来进行使用