适当的数据库冗余
现在我们的 mgr/order.py 里面用 listorder 函数列出订单。
如果一个订单里面有多个药品,就会产生多条记录。
为了解决这个问题,我们不得不用python代码来处理冗余,像下面这样
def listorder(request):
# 返回一个 QuerySet 对象 ,包含所有的表记录
qs = Order.objects\
.annotate(
customer_name=F('customer__name'),
medicines_name=F('medicines__name')
)\
.values(
'id','name','create_date','customer_name','medicines_name'
)
# 将 QuerySet 对象 转化为 list 类型
retlist = list(qs)
# 可能有 ID相同,药品不同的订单记录, 需要合并
newlist = []
id2order = {}
for one in retlist:
orderid = one['id']
if orderid not in id2order:
newlist.append(one)
id2order[orderid] = one
else:
id2order[orderid]['medicines_name'] += ' | ' + one['medicines_name']
return JsonResponse({'ret': 0, 'retlist': newlist})
这样做其实有一些问题。
首先,它使得我们的代码增加了去除重复记录的处理,比较麻烦。
另外,还会带来性能问题:当用户大量登录,需要列出订单信息 的时候,服务程序从数据库获取到数据后,还得还要执行去除重复记录的任务。
那么怎样解决这个问题呢?
我们可以修改数据库表的设计, 就在 Order 表里面 直接存入 订单包含的药品信息。
这样,就不需要 从 OrderMedicine 表里面 去获取关联药品信息了,当然也不需要去除重复的代码了。
那么我们怎样在 Order 表 里面存储订单包含的药品信息呢?
这要包括 药品的ID,名称,和数量。而且订单中可能包含多种药品。
关键是:不同的订单记录 药品的数量 是 不同
的。
对于这种情况,我们通常可以使用一个字段, 里面存储 json格式的字符串,记录可变数量的数据信息。
修改 Order 表对应的类, 如下
class Order(models.Model):
# 订单名
name = models.CharField(max_length=200,null=True,blank=True)
# 创建日期
create_date = models.DateTimeField(default=datetime.datetime.now)
# 客户
customer = models.ForeignKey(Customer,on_delete=models.PROTECT)
# 订单购买的药品,和Medicine表是多对多 的关系
medicines = models.ManyToManyField(Medicine, through='OrderMedicine')
# 为了提高效率,这里存放 订单 medicines 冗余数据
medicinelist = models.CharField(max_length=2000,null=True,blank=True)
在最后 添加的 medicinelist 是一个字符串,里面用json格式来存储订单中的药品。像这样:
其中:
id 表示药品的id,
name 表示 药品的名字,
amount 表示 药品的数量
上面的例子就表示该订单中有id 为 1 和 2 的两种药品 数量分别是 20 和 100
这个字段最大长度可达2000个字符,通常足以存储订单中的药品信息了。
修改了数据库类的定义, 别忘了migrate到数据库。 执行下面的命令
我们发现,现在 Order表里面需要存储订单中药品的名字和数量, 而当前的接口的定义是没法满足这个需求的。
API v1.0 文档,添加消息体的格式如下:
这里面只有药品的id,没有药品的 名称和数量。
我们在开发的时候,经常会遇到 当前的接口设计不能满足新的需求,需要修改
的情况。
这时候就要和 接口的设计者 , 以及接口对接的开发团队进行沟通, 说明为什么你需要修改接口。
讨论通过后,接口的设计者 就要修改接口文档。
我们这里,修改后就是的文档:API接口文档1.1, 点击查看
我们这样修改接口定义:
添加订单的格式为
{
"action":"add_order",
"data":{
"name":"华山医院订单002",
"customerid":3,
"medicinelist":[
{"id":16,"amount":5,"name":"环丙沙星"},
{"id":15,"amount":5,"name":"克林霉素"}
]
}
}
列出订单中,每个订单的格式为
{
"id": 2,
"name": "华山医院订单002",
"create_date": "2018-12-27T14:10:37.208Z",
"customer_name": "华山医院",
"medicinelist":[
{"id":16,"amount":5,"name":"环丙沙星"},
{"id":15,"amount":5,"name":"克林霉素"}
]
}
好的,既然接口变动了,前端的开发团队 要根据修改后的接口,修改他们的代码,保证按照新的接口实现消息格式。
想象一下,前端开发人员领命 重新修改代码去了。。。
当然,我们作为后端开发团队,也要立即动工。
修改 mgr/order.py 中 添加订单的代码,如下
def addorder(request):
info = request.params['data']
with transaction.atomic():
medicinelist = info['medicinelist']
new_order = Order.objects.create(name=info['name'],
customer_id=info['customerid'],
# 写入json格式的药品数据到 medicinelist 字段中
medicinelist=json.dumps(medicinelist,ensure_ascii=False),)
batch = [OrderMedicine(order_id=new_order.id,
medicine_id=medicine['id'],
amount=medicine['amount'])
for medicine in medicinelist]
OrderMedicine.objects.bulk_create(batch)
return JsonResponse({'ret': 0, 'id': new_order.id})
基本只有一处修改,把药品数据信息写入到 新增的medicinelist表字段中
还需要修改 列出订单函数,如下:
def listorder(request):
qs = Order.objects \
.annotate(
customer_name=F('customer__name')
)\
.values(
'id', 'name', 'create_date',
'customer_name',
'medicinelist'
)
# 将 QuerySet 对象 转化为 list 类型
retlist = list(qs)
return JsonResponse({'ret': 0, 'retlist': retlist})
哈哈,打完收工了。
好处很明显,列出订单的代码就比较简单了,不需要执行去重的任务。
性能也提高了, 只要查询一张表,并且不要执行去重的任务。
那么有没有什么坏处呢?大家自己先思考一下,再看后面的内容。
当然也有, 就是出现了 冗余数据
在数据库中。
订单里面订购了什么药品,我们本来记录在 OrderMedicine 表 中的, 现在我们还需要记录在 Order 表中。
这就意味着, 我们 修改 订单中关联的药品, 必须修改两张表。
可能你疑惑了, 既然 修改 的时候需要更新2张表,那么这也会造成性能的降低啊? 凭什么你说 这种方案比原来的性能好呢?
这就要看这张表 最频繁的操作是什么, 是查询还是更新订单中的药品。
我们订单数据要被大量的用户查看, 而更新订单中 关联的 药品 的情况相对较少, 所以权衡利弊后,采用冗余数据的方案。
可能你还有一个疑问, 我们能不能就只在 Order 表中记录 订单信息,不需要 OrderMedicine 表 中记录呢。
这样不是更简单了吗?
这样做,最大的问题是, 如果以后我们需要统计药品被采购的信息就非常麻烦了。
因为,现在我们在数据库中存储的订单中的药品信息,是以字符串类型存储的 json信息,不能直接使用SQL语句进行过滤查询,只能把所有的记录读取出来进行分析,那样性能会非常低。
新版本的 PostGresql 和 Mysql 提供了json格式的字段 ( 但是目前 Django 只支持 PostGresql 里面的json字段 ),可以部分解决这个问题,感兴趣的朋友可以自行研究一下。
好的,我们的代码完成了,大家可以先使用 requests构建前端请求,自己测试一下。
等待前端开发人员也修改好前端代码,就可以集成在一起发布了 :)
目前为止,我们系统的完整代码(包括前端发布的更新),在如下百度网盘中的 bysms_11.zip
百度网盘链接:https://pan.baidu.com/s/1nUyxvq6IYykBNtPUf4Ho6w
提取码:9w2u