使用本示例需通过docker容器,请先下拉jxTMS的docker镜像并按说明启动tms容器,并从helloWorld开始尝试。
jxTMS的数据库操作
对于编程来说,数据库操作包括:
-
数据表的定义
-
ORM【对象关系映射】操作,即通常的增删改查
-
条件查询
下面我们来演示jxTMS是如何操作前两项,条件查询请参考下一节。
数据表的定义
jxTMS中是通过在功能模块目录下的data文件中定义一个数据类来实现数据表的定义的。大家可以在demo1目录中创建data文件,然后输入:
class demoData:
#jxTMS的数据对象必须有主键才能保存,一般建议使用long型的ID作为主键,否则可能会出现各种意外
field ID long primaryKey
field CreateTime datetime
field Type string len=32 index 2
field Name string len=126 fulltext
field NoUsed bool
;
然后将该文件按用sftp管理jxTMS的代码所述拷贝到tms容器的/home/tms/codeDefine/demo/demo/demo1目录中。
此时可以查看数据库,看看是否有了名为demoData的数据表:
#切换目标数据库为组织的私有数据库:demo_6288
mysql> use demo_6288;
#查看数据表demoData的描述
mysql> desc demoData;
然后执行一次热机刷新。
再次查看数据库,看看是否有了名为demoData的数据表:
#再次查看数据表demoData的描述
mysql> desc demoData;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| ID | bigint(20) | NO | PRI | NULL | |
| NoUsed | tinyint(4) | NO | | NULL | |
| Type | varchar(32) | NO | MUL | NULL | |
| CreateTime | datetime | NO | | NULL | |
| Name | varchar(126) | NO | MUL | NULL | |
+------------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)
可以看到,demo组织所对应的数据库中自动多出了一张demoData的数据表,而且该数据中的列和我们在data文件中的定义一一对应。然后我们再看一下demoData的建表语句:
mysql> show create table demoData;
| demoData | CREATE TABLE `demoData` (
`ID` bigint(20) NOT NULL,
`NoUsed` tinyint(4) NOT NULL,
`Type` varchar(32) NOT NULL,
`CreateTime` datetime NOT NULL,
`Name` varchar(126) NOT NULL,
PRIMARY KEY (`ID`),
KEY `index_demoData_2` (`Type`),
FULLTEXT KEY `index_demoData_F0` (`Name`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
我们进一步看到:demoData表中ID是主键,而在class demoData的定义中:
#ID属性有一个primaryKey的定义
field ID long primaryKey
以Type建立了一个索引,而在class demoData的定义中:
#Type属性有一个2号索引,请注意一下数据库表相关索引的名字恰恰是:index_demoData_2
field Type string len=32 index 2
还以Name建立了一个全文索引,而在class demoData的定义中:
#Name属性有一个全文索引
field Name string len=126 fulltext
注1:全文索引最小分词单位是两个字符【支持中文】,所以在使用全文索引时不能只送入一个字符,必须大于等于两个字符
注2:由于mysql的限制,一个查询语句中只能使用一个全文索引,如果有两个以上的全文索引会什么也查不到
大家对照一下data文件中demoData类的定义和show create table demoData命令的输出,就很容易理解如何用data文件定义一个数据表了。具体的说明,请参考自定义数据类.。
ORM操作
我们现在通过data文件定义了demoData数据类,jxTMS还自动为我们创建好了demoData数据表,那我们就试一下如何操作它们吧。
注:从本节开始,所有对capa.py的修改,都是在demo1类的定义中增加或修改其中的某一个对象函数,大家一定要注意这些函数的缩进,务必使得其一定要是demo1的对象函数
我们修改capa.py中的sayHello函数,创建一个新的demoData对象【对应demoData表中的一行】:
@myModule.event('cmd', 'sayHello')
def sayHello(self, db, ctx):
dd = pyORM.create(db,'demoData')
dd.Type = 'demo'
dd.Name = 'Hello world!'
然后执行一次热机刷新。
然后点击快捷栏中的【演示->helloWorld】,点击【点我】按钮。然后看看demoData表:
mysql> select * from demoData;
+-----------------+--------+------+---------------------+--------------+
| ID | NoUsed | Type | CreateTime | Name |
+-----------------+--------+------+---------------------+--------------+
| 103897269075998 | 0 | demo | 2021-06-11 07:00:30 | Hello world! |
+-----------------+--------+------+---------------------+--------------+
1 row in set (0.00 sec)
注:ID和CreateTime不会和演示中的一样
会发现确实插入了一行数据,其中的Type和Name也确实是我们在sayHello中所指定的,但ID和CreateTime我们并没有指定啊?这是由于jxTMS会默认如果一个数据类定义了ID和CreateTime会自动帮我们初始化。其中的ID会赋予一个唯一值,而CreateTime则赋予create函数调用时的时间值。
注:ID的生成依靠主机号与时间,支持255个主机、每1024毫秒可产生16M个唯一的ID
所有在data文件中定义的数据类,都是pyORM类,然后jxTMS根据data文件中的定义,将之虚拟为demoData等,但对开发者来说,不需要关心这一点,只要给create函数指定定义过的数据类的类名,jxTMS就会自动创建一个虚拟的数据类,然后直接对其属性进行赋值,jxTMS就会将这些值翻译为对应的数据库类型并保存到数据库中。
记住你从数据库中查出来的dataData的ID值【我查出来的是:103897269075998,你要换成你的】,然后我们再次修改capa.py,增加一个helloWorld界面的prepareDisp响应函数:
#helloWorld界面显示后,为其执行界面初始化
@myModule.event('prepareDisp', 'helloWorld')
def helloWorld(self, db, ctx):
#读取刚创建的数据对象
#请将103897269075998替换为你从数据库中查询到的ID值
dd = pyORM.getByID(db.get,'demoData',103897269075998)
#向绑定了outText的控件写信息
self.setOutput('outText',utils.getMsg('{}.{}/from:{}',dd.Type,dd.Name,dd.ID)
然后执行一次热机刷新后,再次点击快捷栏中的【演示->helloWorld】,看看和前一次有何不同?
jxTMS通过getByID函数可以直接从数据库中读取相应的数据行,然后创建一个虚拟的demoData对象,并用读到的数据为该对象的同名属性赋值。然后就可以直接使用该对象的各个属性值了。
现在,我们继续修改sayHello函数:
@myModule.event('cmd', 'sayHello')
def sayHello(self, db, ctx):
#请将103897269075998替换为你从数据库中查询到的ID值
dd = pyORM.getByID(db.getDBConn(),'demoData',103897269075998)
dd.Type = 'test'
dd.Name = 'ok'
db.update(dd,'Type','Name')
然后执行一次热机刷新。
再次点击快捷栏中的【演示->helloWorld】,点击【点我】按钮。然后再次查看数据库中的demoData表:
mysql> select * from demoData;
看看和上一个select有什么变化。
开发者在读到数据对象后,可以修改除ID这个主键之外的任何属性,只要在修改后执行:
db.update(数据对象变量,'被修改的属性名1','被修改的属性名2',......)
jxTMS即会自动将修改提交到数据库中。
注1:所有被修改的属性,必须在update函数中逐一列举,否则jxTMS不会提交该属性的变化
注2:不管是cmd、还是prepareDisp函数,在整个函数的执行过程中,所有对数据库的变动,包括create、update,都被纳入到同一个数据库事务中,在函数执行过程中不管因为任何原因导致的异常,都会导致该数据库事务的回滚,不会对数据库产生实质的影响
注3:由于异常会导致数据库操作的回滚,而cmd、prepareDisp函数都是功能模块中的对象函数,所以如果开发者将数据对象设置为了python对象的对象属性,则在异常时会导致缓存下来的数据对象和数据库中的数据不一致,如:
@myModule.event('cmd', 'sayHello')
def sayHello(self, db, ctx):
#请将103897269075998替换为你从数据库中查询到的ID值
self.cachedORM = pyORM.getByID(db.getDBConn(),'demoData',103897269075998)
self.cachedORM.Type = 'updated 123'
self.cachedORM.Name = 1 / 0
db.update(self.cachedORM,'Type','Name')
则由于【1/0】会掷出除零异常,这时用户点击了同一界面上的其它cmd按钮时执行的响应函数中,就会出现self.cachedORM.Type和数据库中的值不一致的错误。
注:每次通过点击【演示->helloWorld】打开的标签页,都是对应于一个单独的功能对象【调用New函数新创建出来的功能对象】,所以两个同时打开的helloWorld完全独立
所以呢,对于连续的业务操作,使用对象变量作为业务数据的缓存,当然会显著降低用getByID读取数据库的操作,但很可能会诱发数据不一致的严重错误,而如果要化解这一错误,还必须捕获异常然后进行恢复,既大大增加了工作量,也未必能百分百的保证恢复的正确性。所以笔者强烈建议:不要用对象变量跨操作作为业务数据的缓存,需要的时候直接从数据库中读取就好了。
通过其它属性来读取数据对象
上面我们都是通过getByID函数来读取数据对象的,除此之外,我们还可以通过其它属性来读取数据对象:
dd = pyORM.get(db.getDBConn(),'数据类名','属性名','属性值')
但是,请务必牢记:以这种方式读取的该数据类的该属性,必须具有唯一值【如流水号、学号、工号等】,否则读到的数据很可能是随机选择的一个。
如果需要通过多个属性来读取,或读取多个,则必须采用条件查询的方法进行读取,请参考下一节中的说明。
关于删除的说明
由于jxTMS设计的目的之一就是尽可能的降低开发者的门槛,所以开发者的经验未必足够。那么误删除就会成为一个非常恐怖的隐藏炸弹,所以jxTMS非常干脆的取消了所有ORM数据对象的删除能力。
请牢记:所有ORM数据对象都无法删除,除了jxTMS平台内部的两个特殊的ORM数据类:
-
Relation:用于刻画两个ORM对象之间的关系的数据类
-
tag:用于对某ORM数据对象进行标记的数据类
也就是说,只有这两个类提供了删除能力,其它所有的jxTMS的ORM数据类,包括jxTMS内部用java定义的以及用户在data文件中定义的,都是一经创建就无法删除。
那么,如果确实需要删除某数据对象怎么办呢?就如同demoData所演示的,定义一个NoUsed的布尔属性,当需要删除时,将该属性设置为True,然后在查询时过滤掉所有NoUsed=true的数据行即可。