前期准备
python可以通过不同的第三方库去连接各种数据库。但程序可能会使用多个数据库,需要做到一次编写适配不同的DBMS。且直接用python调用SQL的写法并不是很好,容易收到注入攻击,所以有必要使用ORM(Object Relational Mapping)
来简化代码和使代码便于维护,而python中最著名的ORM为SQLAlchemy
,pip install SQLalchemy
即可。
前提知识
什么是注入攻击?如python直接调用数据库并插入数据:1
self.query.execute("insert into major values(\"{}\", \"{}\")".format(major_name, college_name))
但major_name
的字符内容为:计算机"self.query.execute("delete table;")"
,很容易直接删除数据表或停止软件的运行,造成不可描述的后果。即:肯能执行用户的非法输入,而这在Web
开发中更为致命,甚至可通过植入javascript代码操控你的浏览器,达到不可描述的行为。
更详细的数据库攻击、及其避免方式可以参考《Flask Web开发实战 入门、进阶与原理解析》这本书(作者是李辉),算是一本不错的技术书籍。
也可以粗暴的看看这篇文章的第一部分,了解为何不能用代码直接操作数据库。
启动数据库
我是安装的便携版的MySQL
,所以去对应文件的位置,命令行mysql -uroot -p
启动即可:
连接数据库
此部分的代码建议在jupyter-lab
这款IDE内编写,因为涉及那种命令行交互的代码较多,就是写一句执行一句,不能从头开始全部执行。
首先导入必要工具库:
1 | from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey |
创建数据库test
, 命令为:CREATE DATABASE test;
Engine 是访问数据库的入口,通过它连接test
数据库:
1 | # 创建一个连接引擎 |
定义数据库表的Schema
如果我们需要一张 users 的表,那么我们可以通过一个 User 类来声明它的元数据。
- primary_key 设置为 true 表示为主键
- unique 设置为 true 后,表示这个字段不能重复
- index 设置为 true ,表示对该字段建立索引
- nullable 默认为 true,表示可空
- default 设置该字段的默认值
1 | Base = declarative_base() |
在代码中创建用户完毕后,将这个表映射到MySql数据库中:
1 | Base.metadata.create_all(bind=engine) |
在MySql中查看创建信息,其中 use test;
表示使用当前数据库,show tables;
表示查看当前数据库的所有数据表。
此外,SQLAlchemy的数据类型和python的数据类型的对应关系如下:
数据类型 | python数据类型 | 说明 |
---|---|---|
Integer | int | 整型 |
String | str | 字符串 |
Float | float | 浮点型 |
Boolean | bool | 布尔型 |
Date | datetime.date | 日期 |
DateTime | datetime.datetime | 日期和时间 |
Time | datetime.time | 时间 |
Text | str | 文本类型 |
LongText | str | 长文本类型 |
增删改查
众所周知,数据库用户的核心功能就是增删改查这四大金刚,而SQLalchemy
也很好的提供了支持。查询是其中最重要的功能,所以,关于SQLAlchemy中查询的官方文档如下:https://docs.sqlalchemy.org/en/13/orm/query.html
通常情况下会使用session
完成数据库的增删改查
- session创建和管理数据库连接的会话
- model object 通过session对象访问数据库,并把访问到的数据存入(或修改)提交到数据库中。任意数量的model object被创建,并绑定到session中,session会管理这些对象,而一旦session 里面的objects 有变化,便可以commit/rollback提交或者放弃changs。
- rollback 的使用条件是:假如session第一次add了一个对象,第二次又add了
同一个
对象,此时一定要session.rollback(),直接commit会报错。
一般来说,session在需要访问数据库的时候创建,在session访问数据库的时候,准确来说,应该是“add/update/delete”数据库的时候,会开启database transaction
, 假设没有修改autocommit的默认值(False), 那么,database transaction
一直会保持,只有等到session rolled back, committed, or closed(完成状态)的时候才结束。
所以一般建议,当database transaction
结束的时候,同时close session, 保证,每次发起请求,都创建一个新的session。
此外需要保证session是一个全局的对象,所以和数据库通信的session在任何时候只有一个,且管理一个session对象远比管理两个对象简单
推荐做法如下,而不是一个类创建一个session:
1 | class ThingOne(object): |
session线程安全
session 不是线程安全的。在保证session object是全局变量时,在多线程的环境中,默认情况下,多个线程将会共享同一个session, 试想一下,假设A线程正在使用session处理数据库,B线程已经执行完成,把session给close了,那么此时A在使用session就会报错。所以必须保证每个线程使用的session都不一样。
1 | from sqlalchemy.orm import scoped_session |
- 使用Session()创建的session对象都是一样的,这可以保证代码在多次调用不同的session()依然获得到相同的session 对象。
- 使用Session()创建的session对象 是 Thread-local, session在线程与线程之间没有任何联系。
那么session.remove()
即可关闭此次会话。session创建和管理对数据库的连接,当调用close的时候,注意,sqlalchemy不会关闭与mysql的连接,而是把连接返回到连接池。
增
1 | test = User(username='test', email='lanling@gmail.com', location='Beijing/China') |
改
1 | print(muyu.username) |
删
1 | # muyu1 不合法 |
查
session 接受到query查询语句, 执行的结果或保持或者关联到session中
首先pip install faker
用于生成一些虚假数据。
1 | # 使用 faker 这个模块来生成一些假数据 |
可以在命令行内select *
一下,查看是否生成成功。
返回所有查询结果
1 | session.query(User).all() |
增加查询条件
1 | from sqlalchemy import text |
注意事项,text
模式的查询,更多的用于打印查询命令。我尝试了在python3.8下,这个输出不了查询结果,但是别人的可以,所以我还是不要用text
模式的查询了。
回滚
1 | bob = User(username='test', email='test@gmail.com', location='BeiJing/China') |
代码链接
https://github.com/muyuuuu/SQLAlchemy
后续的东西实在太多了,决定分开写了。
本文参考
Access denied for user ‘test’@’localhost’ (using password: YES) except root user
Session参考
SQLAlchemy参考
vscode 中一键规范化 python 代码