jxORM是jxWebUI配套的数据库操作库,可以简化python程序员操作数据库。
声明数据类
定义数据类之前,先导入ORM修饰符:
from jxORM import ORM, DBDataType, ColType
然后就可以用ORM修饰符来修饰一个类,从而定义一个数据类:
@ORM
class User:
ID:DBDataType.Long = ColType.PrimaryKey
CreateTime:DBDataType.DataTime = 2
name:DBDataType.Chars = 1
pwd:DBDataType.Chars = 1
info:DBDataType.Json
一个数据类就是一个普通的python类,只不过在定义类时,用了ORM修饰符进行修饰以表明其是一个数据类。
数据类需要定义自己的属性,这些属性对应了数据库中同名数据表的字段。
数据类的属性定义
数据类的属性定义语法如下:
字段名:DBDataType.字段类型 (= 可选的主键或索引定义)?
其中,字段名是数据库中同名数据表的字段名,不能和所使用的数据库的关键字冲突,如说明字段名用desc。
数据类型必须是DBDataType类型中的一种,目前DBDataType的类型如下:
No:不支持的数据类型
Int:4字节的整数类型
Long:8字节的整数类型
Float:4字节的浮点数类型
Double:8字节的浮点数类型
Bool:布尔类型
Chars:短字符串类型
String:字符串类型
DataTime:日期时间类型
Json:json类型
注1:这里的Int、Long、Float、Double、Bool、Chars、String、DataTime、Json都是基于mysql数据库类型的区分,具体需要根据自己所选择的数据库进行对应
注2:mysql数据库,如果需要对某字符串字段【如Type、Name】进行索引,则需要将其定义定义为Chars类型,否则会在创建索引时报错
注3:sqlite数据库中,没有对应DataTime的内置类型,为方便直接用sqlite3命令查看数据,所以jxORM选择了Text类型来映射DataTime类型
任何一个字段,都可以定义为主键或索引,主键和索引的定义语法如下:
#定义字段为主键,可以同时将多个字段指定为联合主键
字段名:DBDataType.字段类型 = ColType.PrimaryKey
#定义字段为某索引
字段名:DBDataType.字段类型 = 整数型索引号
相同索引号的字段将组成一个联合索引。
有几个索引号,就将为数据表建立多少个索引。一般来说,如果索引建的太多,会影响数据库插入与修改的性能,而且在复杂的联合查询时也只有一个索引会起到作用。所以,需要根据实际情况来定义索引。
关于ID
jxORM使用8字节长整数作为实体的ID。同时自带了一个ID生成算法,用于生成ID。只要数据类定义了一个ID的属性,则在创建该数据类的对象时,会自动生成ID。
这个内置的ID生成算法,可以在一秒内生成64K个不同的ID,一般是够用的。如果有多台服务器,可通过给各服务器设置不同的host_id来避免ID冲突。
from jxORM set_host_id
set_host_id(2)
注:默认的host_id是1,最多可设置到65535
使用ID的一般用法:
#在创建用户时为其指定角色
role = get_role(希望分配的角色)
u = User()
#创建一个关系对象
rel_user_role = Rel()
#指定关系对象的user_id
rel_user_role.user_id = u.ID
#指定关系对象的role_id
rel_user_role.role_id = role.ID
#插入关系对象
rel_user_role.insert(db)
默认初始化字段
jxORM在创建一个数据对象时,会自动为其初始化一些字段。这些字段是:
- ID:自动生成的ID
- CreateTime:创建时间
- Timestamp:创建时间
jxORM创建的数据表,其表中的字段都是Not Null的。所以在插入一个数据对象时,如果有字段没有赋值,则会给其设置为相应类型的默认值。
数据字段的默认值:
- 整数型:0
- 浮点数型:0.0
- 字符串型:‘’
- 日期时间型:unix纪元零时:1970年1月1日00:00:00
- 布尔型:False
- Json型:{}
注:数据表中保存的日期时间类型的数据都是无时区信息的,jxORM在读取时会直接为其添加上海时区。也就是说,jxORM默认日期时间数据都是上海时区的。如果开发者需要工作在其它时区,需自行转换
创建数据表
定义完数据类,就可以在数据库中创建数据表:
User.create(db)
这条语句会在数据库中创建一个名为【user】的表,其在mysql库时的建表语句如下:
CREATE TABLE `User` (
`ID` bigint NOT NULL,
`CreateTime` datetime NOT NULL,
`Name` varchar(126) NOT NULL,
`Type` varchar(126) NOT NULL,
`Info` mediumtext NOT NULL,
PRIMARY KEY (`ID`),
KEY `User_index_2` (`CreateTime`),
KEY `User_index_1` (`Name`,`Type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
如果数据库中已经存在名为【User】的表,那么会报错,在日志中记录表已经存在,但不会影响后继的执行。
所以,在数据表创建后,再修改数据类的定义,是无法自动完成数据表的更新的,这也违背了jxORM的设计初衷。
在jxORM中,如果需要增加字段,有更简单而可靠的办法:继承性扩展。详见【继承性扩展数据】章节。
插入数据
user = User()
user.Name = 'admin'
user.Type = 'admin'
user.Info = {'name': 'admin', 'pwd': '123456'}
user.insert(db)
则会在表User中插入一条记录。ID、CreateTime会自动生成。
db就是jxORM中调用get_db函数得到的数据库连接对象;也可以是jxWebUI中各事件响应函数中的db参数,如:
@capa.cmd
def create_user(ci, db, ctx):
...通过ci.getInput获取用户输入的参数并设置user对象的属性,然后执行
user.insert(db)
修改数据
user.Name = 'test'
user.Type = 'normal'
user.update(db)
db就是jxORM中调用get_db函数得到的数据库连接对象;也可以是jxWebUI中各事件响应函数中的db参数,如:
@capa.cmd
def update_user(ci, db, ctx):
...通过ci.getInput获取用户输入的参数并修改user对象的属性,然后执行
user.update(db)
数据库事务
jxORM中,所有的数据库操作都是在事务中进行的。
在jxWebUI中,每个事件响应函数在调用前都会创建事务,当函数正常执行完毕后,会自动提交事务。如果函数掷出异常,则会回滚事务。从而确保了数据的一致性,不会因失败而导致数据异常。
通过get_db函数获取到的数据库连接则需要手动开启事务的自动提交与异常回滚:
db = get_db(数据库名)
with db.transaction():
...调用jxORM的数据操作函数
连接数据库
使用某数据库前,首先需要设置如何连接到该数据库:
from jxORM import jxDB
#连接到本地的mysql服务器中的testDB数据库【testDB需要预先建立】
jxDB.set('testDB', password='123456')
目前,jxORM支持mysql和sqlite两种数据库。
连接到mysql数据库:
jxDB.set(cls, dbName, host='127.0.0.1', port=3306, user='root', password='root', **kwargs)
连接到sqlite数据库:
jxDB.set(cls, dbName, type=DBType.SQLite, **kwargs)
#会在当前的工作目录下创建一个dbName.db的文件,这个文件就是所设置的sqlite数据库
jxORM使用了PooledDB来管理数据库连接池,池参数都采取了默认配置。如果需要自己所需的连接池参数,如mincached、maxconnections等,可自行写入kwargs中,jxORM会将其传递给PooledDB。
多数据库
jxORM支持多数据库,只要在使用时明确指定对应的数据库名即可。如果不指定,则jxORM会使用默认的数据库。
如:
db = get_db('数据库1')
#下述语句将其插入到数据库1中的User表
user.insert(db)
db = get_db('数据库2')
#下述语句将其插入到数据库2中的User表
user.insert(db)
默认数据库
默认数据库就是调用get_db时,如果不指定数据库名时所使用的数据库。大多数情况下,我们都只会用到一个数据库,使用默认数据库,程序员就可以不把要访问的数据库名写到代码中。
当使用jxDB.set设置数据库连接时,最后一个jxDB.set所设置的数据库,会被设置为默认数据库。
当然也可以手动设置默认数据库:
from jxORM import set_default_db
set_default_db('testDB')
就会将默认数据库设置为testDB。
杂项说明
jxORM还有三个函数尚未介绍到,它们是:
from jxORM import get_db, get_default_db, register_create_db
get_db
在jxWebUI的事件响应函数中,会自动获取数据库连接db,然后直接在jxORM的数据操作函数中使用即可。
如果需要手动获取数据库连接对象,则需要使用get_db函数来获得数据库连接。
db = get_db(数据库名)
#数据库名就是通过xDB.set设置的数据库名。如果不指定数据库名,则会返回默认数据库的连接对象
#如果数据库连接只用来查询,则可:
with db:
...调用jxORM的数据操作函数
#如果数据库连接还用来修改数据【insert、update】,则应:
with db.transaction():
...调用jxORM的数据操作函数
注1:jxWebUI的事件响应函数中提供的db参数,会在事件响应函数执行完毕后自动提交数据库事务
注2:如果在with db中对数据进行了修改,因未提交事务,所有的修改无效
注3:with db.transaction()的代码块中如果出现异常,则会回滚事务
get_default_db
get_default_db返回默认数据库的连接对象,其实就是get_db()不给出数据库名参数的简写版。
db = get_default_db()
既然有get_db,那为什么还要再额外给一个get_default_db呢?
这是因为jxWebUI中,其db是jxWebSQLGetDBConnection设置的函数所获得的,而这个函数就是没有数据库名字参数的。所以,为了配合jxWebUI使用,需:
from jxWebUI import jxWebSQLGetDBConnection
from jxORM import get_default_db
jxWebSQLGetDBConnection(get_default_db)
register_create_db
jxORM目前支持mysql和sqlite两种数据库。如果开发者需要支持其他数据库,则需要用register_create_db函数来注册自己的数据库连接对象。
register_create_db(db_type, func)
#db_type:自定义的数据库类型
#func:创建数据库连接对象的函数,其签名为:
#func(db_name) -> db
添加自己的数据库连接的步骤是:
1、设置数据库连接:
jxDB.set(你的数据库名, type=你的数据库类型)
jxORM并不知道你的数据库是否适合自己所使用的PooledDB进行管理,也不清楚该如何设置你的数据库的连接池。所以,jxORM只要求你设置你的数据库类型,和自己所支持的mysql和sqlite区分开即可。
需要说明的是,jxORM使用的是整数型的枚举来标记数据库类型的,为避免混淆,建议使用字符串的方式来标记你的数据库类型。
2、用register_create_db注册你的数据库连接对象创建函数
你需要定义一个你的数据库连接对象创建函数,然后用register_create_db注册它:
def create_db_connection(db_name):
#创建你的数据库连接对象
return db
register_create_db(你的数据库类型, create_db_connection)
和jxWebUI中的jxWebSQLGetDBConnection函数一样,你所创建的db也是一个鸭子类型,需要实现一系列的方法:
class DB_interface:
def __init__(self, dbname):
self._name = dbname
def type(self):
#数据库类型
return 你的数据库类型
def name(self):
return self._name
def need_trans(self):
#是否需要对查询结果进行转换
#mysql的查询结果每行就是一个【列名:列值】的键值对,所以不需要转换
#sqlite的查询结果每行就是一个list,所以需要转换
#如果你的数据库的查询结果也是一个【列名:列值】的键值对,那就不需要转换,此处返回False即可
#如果你的数据库的查询结果需要转换,此处返回True,description返回本次查询的列名信息
return False
def description(self):
#本次查询的列名信息等,此处是PooledDB的返回形式:description每个元素的头一个元素就是列名
#如果你的数据库连接每次查询返回的都是【列名:列值】的键值对,则need_trans返回False,则本函数就不起作用了,返回None即可
#PooledDB管理的数据库连接的示例:
#return self.cursor.description
return None
def _get_create_table_sql(self, table_name, fields:dict, keys:list, indexs:dict):
#建表语句
#table_name:数据表的表名,也是数据类名
#fields:数据表的列,字段名:数据类型
#keys:作为主键的字段名列表
#indexs:数据表的索引,索引号:该条索引的所有字段名列表
#返回建表所需的所有sql语句,如sqlite中建表和建索引分开的,而mysql则是一条语句
return []
def create_table(self, table_name, fields, keys, indexs):
sl = self._get_create_table_sql(table_name, fields, keys, indexs)
for sql in sl:
self.execute(sql)
def get_db_type(self, ty):
#将jxORM中的数据类型转换为数据库类型
#如mysql的是
if ty == DBDataType.Int:
return 'int'
if ty == DBDataType.Long:
return 'bigint'
if ty == DBDataType.Float:
return 'float'
if ty == DBDataType.Double:
return 'double'
if ty == DBDataType.Bool:
return 'tinyint'
if ty == DBDataType.Chars:
return 'varchar(126)'
if ty == DBDataType.String:
return 'mediumtext'
if ty == DBDataType.DataTime:
return 'datetime'
if ty == DBDataType.Json:
return 'mediumtext'
return 'mediumtext'
def commit(self):
#提交数据库事务
def rollback(self):
#回滚数据库事务
def execute(self, sql):
from jxORM import jxORMLogger
jxORMLogger.info(f'execute sql:{sql}')
#开始执行sql
def fetchone(self) -> dict:
#从execute执行后的结果中获取一行
def fetchall(self) -> list:
#从execute执行后的结果中获取全部行
def trans2String(self, vt, v):
#根据vt【jxWebUI导入的jxValueType】将v转换成适合插入数据库的形式:
#典型的,字符串、日期时间等需要用引号括起来
#bool一般需转换为字符1、0【根据get_db_type中将DBDataType.Bool转换成了何种数据库类型】
def __enter__(self):
#数据库连接对象需要实现__enter__和__exit__方法,才能在with语句中使用
#本处是PooledDB管理的数据库连接的示例
if self.conn is not None:
self.cursor = self.conn.cursor()
self.conn.begin()
return self
def __exit__(self, type, value, trace):
if self.conn is not None:
self.cursor.close()
self.conn.close()
定义好了你的数据库连接类后,你就可以将其注册到jxORM中了:
register_create_db(你的数据库类型, lambda dbname:DB_interface(dbname))