前文
接手同事的一个Django项目,项目虽然没有基于drf框架来写,不过也是用了serializer和restful规范来进行开发的项目,而在近期在用django的orm里的get来操作数据库的时候就产生了bug,当get获取到对象后,改变其中的一个值,save后却导致其他值变化了。
问题描述
这个bug是由两点来导致的,首先在接收到post请求的时候,会将参数送到一个处理类去处理,比如这个处理类是处理person的一个eat apple的行为,而在数据库里保存着一个苹果的数据表,里面有苹果的状态和苹果的颜色,代码如下:
class personEatApple:
def __init__(self,apple_id):
self.apple_id = apple_id #apple_id是数据库里的主键
self.obj = Apple.objects.get(id=self.apple_id)
def eat(self):
self.obj.status = "eat"
obj.save()
self.changeColor()
self.obj.status = "finish"
obj.save()
def changeColor(self):
obj = Apple.objects.get(id=self.apple_id)
obj.color = 'red'
obj.save()
可以看到通过eat方法将苹果从eat改为finish了,中间当然还执行了一些操作,而在中间我又写了另一个代码将苹果的颜色改成红色(假定原来是绿色),此时查看这张表的内容,颜色和状态分别是什么呢?答案是:颜色:绿色,状态:finish
解决
解决这个问题要弄懂两点,第一点就是get的保存会保存所有内容,也就是即使你只改变了status状态这个字段,当你save的时候,通过官方文档我们清楚是会插入一条数据,所以除了你改变的那条数据,其他数据保持不变地插入一条数据(可能是删了原来的数据,不过这个文档里没找到),文档(https://docs.djangoproject.com/en/3.0/ref/models/instances/#django.db.models.Model.save)如下:
所以从第一点我们知道了只要是save就会插入一条数据,而在插入之前Django会判断是update还是insert操作。而第二点更重要的是,我的对象是在init里创建的,所以self.obj保存的状态是在eat动作刚开始就保存着了,此时这里color是绿色,而我在中间用changeColor这个独立的方法改变了color为red,但是在self.obj里color还是绿色,此时改变status为finish并save,则color由于save的插入还是绿色。
可以说这个bug非常隐秘,当然期间你也可以在changeColor里用self.obj来改变状态,不过这个非常不方便,比如中间要做的事非常复杂,又需要一个额外的类来处理,那此时就要继承吗?还有比如别人接手(比如我)代码,那就很容易造成bug,所以建议对象还是不要再init里创建,而是每次都重新创建比较好!
总结
解决方法很简单,即把对象创建从init里移除,每次都重新创建即可。