这个问题困扰了我很久,为什么一直没解决,主要是因为有替换方案可以选择,所以一直搁置了。

现在随着业务发展,数据结构、存储算法越来越复杂,绕路的方案在某些时候就变得不可接受了,所以需要去解决这个问题。

什么问题

正常的 ORM 模式,如果需要更新某条数据的话,有两个办法。

user = session.query(User).filter(User.id == 1).first()
user.name = 'KL'
session.commit()

或者

session.query(User).filter(User.id == 1).update({
    'name': 'KL'
})

我目前碰到的问题是,第一种方式不可用,第二种方式可用。

熟悉 ORM 的朋友应该都知道,第二种方式是即时生效的,默认带上了 commit 方法。

这就导致每次更新都会去请求数据库进行插入操作,非常耗时。在业务简单的时候,影响不大,但是随着业务发展,这种更新方式在有些时候就变得不可接受了。

最初我非常懵逼,不明白为什么会不可用,然后我重新看了官方文档里数据更新相关的章节。

找到了两个可以查看 session 缓存的方法。

发现问题关键点

session.dirty
session.new

session.dirty 表示和当前数据表不一致的数据,也就是你修改过的数据。

session.new 表示不再当前数据表的数据,也就是你新增的数据。

当我使用第一种方法去更新数据时,发现 session.dirty 中没有存入任何数据,这让我很惊讶,经验告诉我,这是在赋值的时候就出现了问题。

于是我找到了我的映射类。

class Account(zda_engine.Base):

    __tablename__ = 'account'

    id = Column(Integer, primary_key=True, autoincrement=True, nullable=False, unique=True)
    email = Column(String, nullable=False)
    password = Column(String, nullable=False)

    def __setattr__(self, key, value):
        if key == 'password':
            self.__dict__[key] = hashlib.md5(value.encode(encoding='UTF-8')).hexdigest()
        else:
            self.__dict__[key] = value

为了方便展示,我把__setattr__方法移到了子类中,实际使用时,更复杂一些。

可以看到,我在自己定义的类里面完全重写了__setattr__方法,一直以来这个方法可以正常的更新实例值,所以我一直觉得没有问题。

不过在session.dirty方法中,我判断了一下是设置值时出现的问题,那么再看这段代码时,就感觉处处是问题了。

最关键的就是,它覆盖了 SqlAlchemy 的__setattr__方法,最终导致后续一系列的数据存储动作都出现了问题,因为代码没跑过去。

解决问题

当然,在正式修改成功之前,这都是我的推测,于是我将__setattr__代码修改为下面这样

def __setattr__(self, key, value):
    if key == 'password':
        value = hashlib.md5(value.encode(encoding='UTF-8')).hexdigest()
    super().__setattr__(key, value)

这样的话,既能将特定的字段修改为我想要的值,同时依旧可以调用基类中的__setattr__方法。

经过调试,最终解决这个问题。

总结

基础方法使用时,会带来非常多非常好的体验,同时也可能会和其他广泛使用的三方库发生冲突,在使用这些方法时,最好要重新调用一下基类方法,然后再去实现自己的逻辑。


↙↙↙阅读原文可查看相关链接,并与作者交流