在经过前面的学习,我们已经学会使用pymysql对mysql数据库进行操作,但是随着数据库表的增多,字段增多,sql语句愈加的复杂和冗长,有没有什么工具可以使我们避免取直接写sql语句呢?这里还真有,就是ORM模式。本章节同时使用peewee模块实现ORM模式,peewee 虽说不是最好的但是是轻量级的。(实际上博主直接触了这个)
一、认识ORM
ORM,全称Object Relational Mapping,中文意思是对象关系映射,是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式——百度百科,我的理解就是将我们数据库中的表以及一些约束和联系等等,以类的形式封装,每个表好比一个类,表的字段就是对象属性,当然实际上不是这么简单,我推荐一个 大佬的解释 阮一峰 ORM。使用ORM的好处就是 我们不再关心你使用什么样的数据库,对于不同的数据库的操作,使用同样代码 即可在不同数据库实现同样的功能,这里用的peewee支持很多的数据库。
二、将已有的数据库,转换成 model
ok,话不多说,点到为止,如果大家有兴趣再深入研究,这里简单掠过,实际上博主也不是很懂,不敢造次,下面介绍如何将已有的数据库转换为py文件,也就是类文件,数据库模型,便于我们去调用类对象,从而对数据库进行操作。
1、如果没安装请安装peewee
pip install peewee 如果双环境 依据各人配置觉得 反正是python3环境嘛。同时奉上 peewee官网
2、开始导出,生成数据库模型
使用的语句,这里我们不指定表,直接指定test数据库,全部生成一个文件model.py,当然你也可以指定具体的表
python3 -m pwiz -e mysql -H localhost -p 3306 -u root -P root test > model.py
奉上具体参数解释:有些参数是不必要用的
选项 | 解释 | 举例 |
-h | help 显示帮助 | -h |
-e | engine 数据库引擎 | -e mysql |
-H | host 数据库地址 | -H localhost |
-p | port 数据库端口 | -p 3306 |
-u | user 数据库用户名 | -u root |
-P | password 数据库密码 | -P root |
-s | schema 模式 | -s public |
-t | tables 指定生成来自表格 | -t users,classes |
-v | views 指定生成来自视图 | -v |
-i | info 添加原信息 | -i |
-o | order 保留表格列顺序 | -o |
执行这个命令的时候我建议在数据库本机上允许,拉取要输入数据库密码的,注意不同操作系统获取的py文件编码格式不一样,容易报错误。
因为我的是云服务器,那我就从云服务器上拉取,同时如果没有安装python3 的环境和peewee、pymysql的包的请提前安装哦,毕竟Centos安装python3也不是件容易的事情,可以自行查阅也可以 查考 我的另一个 博客
ok,我们安装好之后,来拉取吧
可以看到,已经拉去成功啦,那我们把它放在我们工程目录下的handler目录下把吧。
可以打开model.py感受一下 ORM。对于这个文件重点说一下 连接数据库的连接属性,首先是参数 因为从数据库本机拉的,因此host为localhost 如果我们在自己的机器上使用云端的就得改成公网ip了;其次是对数据库操作的时候,peewee是有隐形连接的,但是为了防止数据库断开连接 我们自己主动去连接数据库和断开数据库。其中的类我们看英文就知道是什么意思了非常好懂,下面我们就开始改写我们之前的代码了。
from peewee import *
database = MySQLDatabase('test', **{'charset': 'utf8', 'sql_mode': 'PIPES_AS_CONCAT', 'use_unicode': True, 'host': 'localhost', 'port': 3306, 'user': 'root', 'password': 'root'})
class UnknownField(object):
def __init__(self, *_, **__): pass
class BaseModel(Model):
class Meta:
database = database
# 对应了我们之前创建的classes表
class Classes(BaseModel):
classname = CharField()
createtime = DateTimeField(constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")])
id = BigAutoField()
class Meta:
table_name = 'classes'
# 对应了我们之前创建的users表
class Users(BaseModel):
class_ = ForeignKeyField(column_name='class_id', field='id', model=Classes)
createtime = DateTimeField(constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")])
id = BigAutoField()
password = CharField(constraints=[SQL("DEFAULT '11111111'")])
username = CharField()
class Meta:
table_name = 'users'
三、改写代码
在改写前,我们得在开发电脑上装上Python的依赖库peewee
1、改写userhandler.py文件作为例子,看看ORM模式下的增删改查,如果你还想留着原先的代码的话,建议备份一下
from basehandler import BaseHandler
# 从model中导出Users类 就是users表 以及数据库连接对象
from model import Users,database
# 这个模块能够将 model类型与字典互换 例如:users的一条记录 转换成 数据和字段一一对应的字典
# 这里我们只用到了 model_to_dict 字面的意思 model转换成字典
from playhouse.shortcuts import model_to_dict
# 这是一个处理新建用户的请求类,继承BaseHandler
class NewUserHandler(BaseHandler):
# 重写post请求函数,处理post请求
def post(self):
# 为了防止出错导致程序崩溃,我们需要捕获异常函数,并处理
try:
# 从请求中获取参数,如果获取不到,我们赋值 None
username = self.get_argument('username', None)
password = self.get_argument('password', None)
class_id = self.get_argument('class_id', None)
# 对传入的参数进行判断,如果为空我们回复 error,并终止执行下去
if not username:
# self.write()函数的参数为标准的json类型
self.write({"error":"用户账号为空!"})
return
if not password:
# self.write()函数的参数为标准的json类型
self.write({"error":"用户密码为空!"})
return
if not class_id:
# self.write()函数的参数为标准的json类型
self.write({"error":"班级没有选择!"})
return
database.connect() # 连接数据库
# 下面这句话的意思是根据 username获取用户信息 我们作做这一部是为了防止用户已存在
# 如果用户不存在会抛出异常DoesNotExist 我们要捕获异常,否则程序报错
user = Users.get(Users.username==username)
self.write({"error":"用户已存在!"})
except Users.DoesNotExist:
# 下面的代码就是插入的操作,当然peewee不止一种插入方式,请学习peewee文档
# 执行成功后会返回新插入的id
user_id=Users.insert({
'username':username,
'password':password,
'class_id':class_id
}).execute()
self.write({"success":"新建用户信息成功!"})
except Exception as e:
# 如果出现异常我们打印出来
print(e)
self.write({"error":"服务器出错!"})
finally:
# 不论是否出现异常,关闭数据库
database.close()
# 这是一个处理获取用户信息的请求类,继承BaseHandler
class GetUserHandler(BaseHandler):
# 重写get请求函数,处理get请求
def get(self):
# 为了防止出错导致程序崩溃,我们需要捕获异常函数,并处理
try:
# 从请求中获取参数,如果获取不到,我们赋值 None
user_id = self.get_argument('id', None)
# 对传入的参数进行判断,如果为空我们回复 error,并终止执行下去
if not user_id:
# self.write()函数的参数为标准的json类型
self.write({"error":"用户id为空!"})
return
database.connect() # 连接数据库
user=Users.get_by_id(user_id) # 通过id得到用户信息
user_dir=model_to_dict(user) # model转换成字典
# 转换时间格式
print(user_dir) #我们可以打印看看返回的是啥,这是必须的,不然你不知道他的数据结构,无法获取指定数据
user_dir['createtime']=user.createtime.strftime('%Y-%m-%d %H:%M:%S')
# 对于外键处理 它会自己把外键的相关信息封装一起,我们需要什么拿什么,在查询的时候无需联合查询
user_dir['classname']=user.class_.classname
del user_dir['class_'] # 将冗余的删除,只剩下一层json
del user_dir['password']
self.write({"success":user_dir})
except Users.DoesNotExist:
self.write({"error":"查无此人!"})
except Exception as e:
# 如果出现异常我们打印出来
print(e)
self.write({"error":"服务器出错!"})
finally:
database.close()
# 这是一个处理获取用户列表的请求类,继承BaseHandler
class ListUserHandler(BaseHandler):
# 重写get请求函数,处理get请求
def get(self):
# 为了防止出错导致程序崩溃,我们需要捕获异常函数,并处理
try:
database.connect()
# select查询会返回多条记录,并且支持指定字段和条件查询以及排序
users = Users.select().order_by(Users.createtime.asc())
result_list=[]
# 这个是返回查询条数的
effect_row=users.count()
# 这个是支持分页的,我们给的是一页10条数据,现在取首页
users=users.paginate(1,10)
for user in users:
user_dir=model_to_dict(user)
# 转换时间格式
user_dir['createtime']=user.createtime.strftime('%Y-%m-%d %H:%M:%S')
user_dir['classname']=user.class_.classname
del user_dir['class_'] # 将冗余的删除,只剩下一层json
del user_dir['password']
result_list.append(user_dir)
self.write({"success":result_list,"length":effect_row,"page":1,"limit":10})
except Exception as e:
print('user list post',e)
self.write({"error":"服务器出错!"})
finally:
database.close()
# 这是一个处理更改用户信息的请求类,继承BaseHandler
# 这里我们只更改密码,如果你有想法的可以自己增加
class UpdateUserHandler(BaseHandler):
# 重写get请求函数,处理get请求
def post(self):
# 为了防止出错导致程序崩溃,我们需要捕获异常函数,并处理
try:
# 从请求中获取参数,如果获取不到,我们赋值 None
user_id = self.get_argument('id', None)
password = self.get_argument('password', None)
# 对传入的参数进行判断,如果为空我们回复 error,并终止执行下去
if not user_id:
# self.write()函数的参数为标准的json类型
self.write({"error":"用户账号为空!"})
return
if not password:
# self.write()函数的参数为标准的json类型
self.write({"error":"用户密码为空!"})
return
database.connect()
# 下面代码是更新用户信息
user=Users.get(Users.id==user_id) # 首先获取到用户
# 其次更改用户字段
user.password=password
# 然后保存提交
user.save()
self.write({"success":"更新密码成功!"})
except Users.DoesNotExist:
self.write({"error":"用户密码错误!"})
except Exception as e:
# 如果出现异常我们打印出来
print(e)
self.write({"error":"服务器出错!"})
finally:
database.close()
# 这是一个处理删除用户信息的请求类,继承BaseHandler
class DeleteUserHandler(BaseHandler):
# 重写get请求函数,处理get请求
def get(self):
# 为了防止出错导致程序崩溃,我们需要捕获异常函数,并处理
try:
# 从请求中获取参数,如果获取不到,我们赋值 None
user_id = self.get_argument('id', None)
# 对传入的参数进行判断,如果为空我们回复 error,并终止执行下去
if not user_id:
# self.write()函数的参数为标准的json类型
self.write({"error":"用户账号为空!"})
return
database.connect()
# 下面代码是删除用户
Users.delete().where(Users.id == user_id).execute()
self.write({"success":"删除用户成功!!"})
except Exception as e:
print('user delete',e)
self.write({"error":"服务器出错!"})
finally:
database.close()
2、其他文件代码无需动,当然如果你熟练的话是可以清理其他文件的
别忘记将之前的main.py文件中注释掉的关于user的路由解禁哦,否则会报404错误
import sys
# 将handler目录下的文件放到和main.py文件同级目录下,便于我们引用
sys.path.append("./handler")
# 添加必要的tornado的模块
import tornado.ioloop
import tornado.web
# 从classhandler.py文件中导出写好的 类
from classhandler import NewClassHandler
from userhandler import NewUserHandler,GetUserHandler,ListUserHandler,UpdateUserHandler,DeleteUserHandler
# 这是操作 postgresql 的路由
# from psqlhandler import NewUserHandler,GetUserHandler,ListUserHandler,UpdateUserHandler,DeleteUserHandler
# 写程序入口函数 main函数
def main():
# 定义请求的路径和响应的请求类,此类会根据你发出的请求区分get 还是post而给予不同的处理
application = tornado.web.Application([
(r"/class/new", NewClassHandler),
(r"/user/new", NewUserHandler),
(r"/user/info", GetUserHandler),
(r"/user/list", ListUserHandler),
(r"/user/update", UpdateUserHandler),
(r"/user/delete", DeleteUserHandler),
# (r"/psql/new", NewUserHandler),
# (r"/psql/info", GetUserHandler),
# (r"/psql/list", ListUserHandler),
# (r"/psql/update", UpdateUserHandler),
# (r"/psql/delete", DeleteUserHandler),
])
# 绑定端口,单进程启动
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
四、现在我们来测试一下吧
1、建用户
相同的信息再建一次,看看如何
2、查看信息
看一下peewee返回的数据结构 ,class是不是整个信息都返回了呢?
3、查看列表
4、更新信息
5、删除用户
五、总结
OK,到此为止,是否发现 我们在心得userhandler.py文件中就没有涉及到pymysql驱动,也没有sql语句,由此看来ORM在实际使用中不关心你数据库是啥,它们都已经封装好了。