MySQL ORM

1 MySQL 连接配置

1.1 default 配置

配置数据库连接方式

conf/config.py

DATABASES = {
    "default": "mysql+retrypool://root:password@127.0.0.1:3306/test?max_connections=300&stale_timeout=300",
}

Butterfly 访问 MySQL 基于 Peewee 库,Peewee 是一个简单小巧的 Python ORM,它非常容易学习,使用起来非常方便。

1.2 自定义配置(仅读取本 region 配置)

conf/servicer/db_x1.py 中,增加 url_x1 = "mysql+retrypool://root:password@127.0.0.1:3306/test?max_connections=300&stale_timeout=300"

from xlib import db
from xlib.db import peewee
from xlib.util import config_util


def _get_db(name):
    db_url = config_util.get_config(None, "db_x1", name)
    db_obj = db.connect(db_url)
    return db_obj
    
class ProcessRecords(peewee.Model):
    """
    X1 task process 记录, 用于耗时统计
    """
    id = peewee.BigAutoField()
    task_id = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")], index=True)
    task_batch_id = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
    work_flow = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
    entity = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
    step = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
    operation = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
    created_at = peewee.DateTimeField(constraints=[peewee.SQL("DEFAULT CURRENT_TIMESTAMP")])
    start_at = peewee.DateTimeField(constraints=[peewee.SQL("DEFAULT 0000-00-00 00:00:00")])
    cost = peewee.IntegerField(constraints=[peewee.SQL("DEFAULT 0")])
    parameters = peewee.TextField(null=True)
    err_msg = peewee.TextField(null=True)

    class Meta(object):
        """
        meta
        """
        database = _get_db("url_x1")
        table_name = 'process_records'

1.3 自定义配置(多 region adapter)

lib_model.py

例子

2 基本知识

编写 model.py

demo

在官方的 Quckstart 中,Peewee 中 Model 类、fields 和 model 实例与数据库的映射关系如下:

Object
Corresponds to…

Model

class Database table

Field

instance Column on a table

Model

instance Row in a database table

也就是说

  • 一个 Model 类代表一个数据库的表

  • 一个 Field 字段代表数据库中的一个字段

  • 一个 model 类实例化对象则代表数据库中的一行。

Peewee 的实现原理可以结合 道生一,一生二,二生三,三生万物

2.1 字段

字段类用于描述模型属性到数据库字段的映射,每一个字段类型都有一个相应的 SQL 存储类型,如 varchar, int。并且 python 的数据类型和 SQL 存储类型之间的转换是透明的。

在创建模型类时,字段被定义为类属性。有一种特殊类型的字段 ForeignKeyField,可以以更直观的方式表示模型之间的外键关系。

这允许你编写如下的代码:

2.1.1 字段类型表

字段类型
Sqlite
Postgresql
MySQL

IntegerField

integer

integer

integer

BigIntegerField

integer

bigint

bigint

SmallIntegerField

integer

smallint

smallint

AutoField

integer

serial

integer

FloatField

real

real

real

DoubleField

real

double precision

double precision

DecimalField

decimal

numeric

numeric

CharField

varchar

varchar

varchar

FixedCharField

char

char

char

TextField

text

text

longtext

BlobField

blob

bytea

blob

BitField

integer

bigint

bigint

BigBitField

blob

bytea

blob

UUIDField

text

uuid

varchar(40)

DateTimeField

datetime

timestamp

datetime

DateField

date

date

date

TimeField

time

time

time

TimestampField

integer

integer

integer

IPField

integer

bigint

bigint

BooleanField

integer

boolean

bool

BareField

untyped

不支持

不支持

ForeignKeyField

integer

integer

integer

2.1.2 字段初始参数

所有字段类型接受的参数与默认值

  • null = False – 布尔值,表示是否允许存储空值

  • index = False – 布尔值,表示是否在此列上创建索引

  • unique = False – 布尔值,表示是否在此列上创建唯一索引

  • column_name = None – 如果和属性名不同,底层的数据库字段使用这个值

  • default = None – 字段默认值,可以是一个函数,将使用函数返回的值

  • primary_key = False – 布尔值,此字段是否是主键

  • constraints = None - 一个或多个约束的列表 例如:[Check('price > 0')]

  • sequence = None – 序列填充字段(如果后端数据库支持)

  • collation = None – 用于排序字段 / 索引的排序规则

  • unindexed = False – 表示虚拟表上的字段应该是未索引的(仅用于 sqlite)

  • choices = None – 一个可选的迭代器,包含两元数组(value, display)

  • help_text = None – 表示字段的帮助文本

  • verbose_name = None – 表示用户友好的字段名

一些字段的特殊参数

字段类型
特殊参数

CharField

max_length

FixedCharField

max_length

DateTimeField

formats

DateField

formats

TimeField

formats

TimestampField

resolution, utc

DecimalField

max_digits, decimal_places, auto_round, rounding

ForeignKeyField

model, field, backref, on_delete, on_update, extra

BareField

coerce

2.1.3 字段默认值

创建对象时,peewee 可以为字段提供默认值,例如将字段的默认值null设置为0

如果想提供一个动态值,比如当前时间,可以传入一个函数

数据库还可以提供字段的默认值。虽然 peewee 没有明确提供设置服务器端默认值的 API,但您可以使用 constraints 参数来指定服务器默认值:

2.1.4 外键字段

foreignkeyfield 是一种特殊的字段类型,允许一个模型引用另一个模型。通常外键将包含与其相关的模型的主键(但您可以通过指定一个字段来指定特定的列)。

可以通过追加 _id 的外键字段名称来访问原始外键值

ForeignKeyField 允许将反向引用属性绑定到目标模型。隐含地,这个属性将被命名为 classname_set,其中 classname 是类的小写名称,但可以通过参数覆盖 backref:

2.1.5 日期字段

DateField TimeFieldDateTimeField 字段

DateField 包含 year month day TimeField 包含 hour minute second DateTimeField 包含以上所有

3 使用说明

而使用过程,分成两步:

  • 定义 Model,建立数据库

  • 操作数据库

3.1 定义 Model,建立数据库

在使用的时候,根据需求先定义好 Model,然后可以通过 create_tables() 创建表,若是已经创建好数据库表了,可以通过 python -m pwiz 脚本工具直接创建 Model。

3.1.1 创建 Model

3.1.1.1 第一种方式

先定义 Model,然后通过 db.create_tables() 创建或 Model.create_table() 创建表。 例如,我们需要建一个 Person 表,里面有 name、birthday 和 is_relative 三个字段,我们定义的 Model 如下:

然后,我们就可以创建表了

其中,CharField、DateField、BooleanField 等这些类型与数据库中的数据类型一一对应,我们直接使用它就行,至于 CharField => varchar(255) 这种转换 Peewee 已经为我们做好了 。

完成之后,就会在数据库中看到 test 数据库中,创建好了 person 表

3.1.1.2 第二种方式

已经存在过数据库,则直接通过 python -m pwiz 批量创建 Model。

例如,上面我已经创建好了 test 库,并且创建了 person 表,表中拥有 id、name、birthday 和 is_relative 字段。那么,我可以使用下面命令:

然后,输入密码,pwiz 脚本会自动创建 Model,内容如下:

3.1.2 Model 定义

3.1.2.1 复合主键约束 (CompositeKey)

3.1.2.2 联合唯一索引 (indexes)

需要注意的是,上面语法,三层元组嵌套, 元组你懂得, 一个元素时需要加个 , 逗号。 别忘了。

3.1.3 设置数据库

使用 Peewee 配置数据库的三种方式

详情

DatabaseProxy

仅当实际数据库驱动程序在运行时变化时才使用 DatabaseProxy

如果只是连接值在运行时发生变化,例如数据库文件的路径或数据库主机,则应改用 Database.init()

故 DatabaseProxy 暂时不需要使用

3.2 操作数据库

操作数据库,就是增、删、改和查。

3.2.1 增 (object.save,Model.create,Model.insert)

批量写数据

3.2.2 删 (object.delete_instance,Model.delete)

删除有两种方式

  • 使用 object.delete_instance

  • 使用 Model.delete

3.2.3 改 (Model.update, object.save)

若是,已经添加过数据的的实例或查询到的数据实例,且表拥有 primary key 时,此时使用 save() 就是修改数据;若是未拥有实例,则使用 update().where() 进行更新数据。

Peewee 中,调用模型的 save() 方法时,若数据内容没有发生变化

若实例中有主键 ID,则 跳过 UPDATE 操作,返回 0,若无主键 ID,则会仍会执行 INSERT ,返回 1。

3.2.3.1 peewee 的 update 是原子的

需要注意的是,在使用 update 的时候千万不要在 Python 中使用计算再更新,要使用 SQL 语句来更新,这样才能具有原子性。

错误做法

正确做法

3.2.3.2 update 的几种方法

3.2.3.3 无则插入,有则更新

两种方式

存在更新,不存在则插入 replace 与 on_conflict_replace() 是等效的

MySQL 提供了一种独有的语法 ON DUPLICATE KEY UPDATE 可以使用以下方法实现。

3.2.4 查(单条 Model.get)

3.2.4.1 get

单条数据使用 Person.get() 就行了,也可以使用 Person.select().where().get()。若是查询多条数据,则使用 Person.select().where(),去掉 get() 就行了。语法很直观,select() 就是查询,where 是条件,get 是获取第一条数据。

3.2.4.2 get_or_none

3.2.4.3 get_or_create

Peewee 有一个辅助方法来执行“获取 / 创建”类型的操作: Model.get_or_create() 首先尝试检索匹配的行。如果失败,将创建一个新行。

参数:

返回值:

3.2.4.4 get_by_id

对于主键查找,还可以使用快捷方法 Model.get_by_id()

3.2.4.5 select

使用 Model.select() 查询获取多条数据。select 后可以添加 where 条件,如果不加则查询整个表。

  • select 代表 sql 语句中 select 后面的语句表示要展示的字段

  • where 代表 where 条件语句 得到一个数据集合

语法:

参数:

示例:

select() 返回结果是一个 ModelSelect 对象,该对象可迭代、索引、切片。当查询不到结果时,不报错,返回 None。并且 select() 结果是延时返回的。如果想立即执行,可以调用 execute() 方法。

注意

3.2.4.6 获取记录条数 count 方法

使用 .count() 方法可以获取记录条数。

也许你会问,用 len() 方法可以吗?当然也是可以的,但是是一种不可取的方法。

这两者的实现方式天差地远。用 count() 方法,执行的 SQL 语句是:

而用 len() 方法执行的 SQL 语句却是:

直接返回所有记录然后获取长度,这种方法是非常不可取的。

3.2.4.7 排序 order_by 方法

排序默认是升序排列,也可以用 + 或 asc() 来明确表示是升序排列:

用 - 或 desc() 来表示降序:

如要对多个字段进行排序,逗号分隔写就可以了

3.2.4.8 查询条件

当查询条件不止一个,需要使用逻辑运算符连接,而 Python 中的 and、or 在 Peewee 中是不支持的,此时我们需要使用 Peewee 封装好的运算符,如下:

逻辑符
含义
样例

&

and

Person.select().where((Person.Name == '张三') & (Person.Age == 30))

`

`

or

Person.select().where((Person.Name == '张三') | (Person.Age == 30))

~

not

Person.select().where(~Person.Name == '张三')

当条件全为 and 时,也可以用逗号分隔,get 和 select 中都可以

3.2.4.9 支持的比较符

3.2.4.10 如何根据查询项,设置不同的搜索条件

搜索项支持 job_reqid,job_id 等

3.3 查看 ORM 对应的原生 SQL 语句

后缀 .sql() 打印对应原生 sql

3.4 常见查询算子

.in_(value)

在查找中(与 << ),如 model.<field_name>.in_(<field_value>.split(",")

.not_in(value)

不在查找中。

.is_null(is_null)

为空或不为空。接受布尔参数。

.contains(substr)

子字符串的通配符搜索。

.startswith(prefix)

搜索以开头的值 prefix .

.endswith(suffix)

搜索以结尾的值 suffix .

.between(low, high)

在哪里搜索 low <= value <= high

.regexp(exp)

正则表达式匹配(区分大小写)。

.iregexp(exp)

正则表达式匹配(不区分大小写)。

.bin_and(value)

二进制和

.bin_or(value)

二进制或

.concat(other)

使用连接两个字符串或对象 || .

.distinct()

为非重复选择标记列。

.collate(collation)

使用给定的排序规则指定列。

.cast(type)

将列的值强制转换为给定的类型。

3.5 一些有用的拓展

3.5.1 模型转换成字典

除了在查询的时候使用 model.dicts 以外,还可以使用 model_to_dict(model) 这个函数。

3.5.2 从数据库生成模型

可以使用 pwiz 工具从已有的数据库产生 peewee 的模型文件

pwiz 传送门:https://github.com/coleifer/peewee/blob/master/pwiz.py

直接拉取最新的 peewee 项目库,在 peewee 项目库中执行产生 peewee 模型文件即可

3.5.3 创建自己的 Field

peewee 中创建自己的 Field, 主要通过继承 Field 或其子类来完成,

  • 如果有 mysql 中对应的字段,则将其赋值给 db_field 即可。这里对 set, enum 并没有找到通用的字段定义,但是对具体的业务可以进行如 GenderField 这样的个性化定制。

  • 如果没有,则使用其父类的 db_field 字段,并定义 db_value 和 python_value 两个方法来完成与数据库中数据类型之间的转化

使用

4 实践

4.1 user

ImportError: No module named dateutil //faker 使用

结果:

4.2 School

4.3 fn.FIND_IN_SET

以【星桥】列表为例, 搜索 job_tags 使用FIND_IN_SET 函数(使用 fn 功能),搜索 is_valid 使用 peewee.NodeList 进行拼接

SQL

4.4 fn.SUM

查询统计总量:

使用的 peewee 的话,使用方法是:

说明,: 结果返回的是一个ModelSelect

示例:

结果:

建议使用 result.dicts()[0]

4.5 JOIN.LEFT_OUTER

可以通过 alias 将 table 自定义名

  • 连表操作: query_cmd = query_cmd.join(cluster_model, on=(unit_model.cluster_id == cluster_model.id), attr='cluster_obj', join_type=peewee.JOIN.LEFT_OUTER)

  • 获取连表的 field:

(1) query_cmd = unit_model.select(unit_model, cluster_model)

(2) cluster_obj = getattr(record, 'cluster_obj', None)

4.6 count

4.7 limit

4.8 time 范围

如:

DateTimeField(column_name="c_time", default=datetime.now)

4.9 输出某个字段的最新记录

使用子查询和 join

在上面的代码中,subquery.c.max_time 是子查询中计算的最大时间列。确保在 JOIN 条件中使用的是 max_time,而不是 create_time

4.10 timestamp 类型

Peewee 的字段类型是逻辑抽象,而非严格绑定

Peewee 的 DateTimeField 是一个逻辑字段,它代表“日期+时间”数据,但具体存储为哪种数据库类型(DATETIMETIMESTAMPTIMESTAMPTZ)取决于数据库后端

  • MySQL/MariaDBDateTimeField 默认映射到 DATETIME,但可以手动改为 TIMESTAMP(通过 ALTER TABLEconstraints)。

数据库中的类型

peewee 定义

插入数据

Last updated