在PostgreSQL JSON类型中使用列表与SQLAlchemy

11 投票
3 回答
5286 浏览
提问于 2025-04-18 17:15

我正在使用Pyramid框架,搭配SQLAlchemy、pyramid_tm和PostgreSQL来进行测试。

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()


class MyList(Base):
    id = Column(Integer, primary_key=True)
    lst = Column(JSON)

我使用的是PostgreSQL 9.3及以上版本,并且使用了JSON类型。当我这样做的时候

mylst = MyList(lst=[])

我可以看到数据库里创建了一个空的[]列表,还有

def view(request):
    mylst = DBSession.query(MyList).get(1)
    mylst.lst.append('45')
    print(DBSession.is_active, DBSession.is_modified(mylst))

我可以在数据库中看到['45'],而打印出来的结果是

True, True

接着上面的内容[编辑]在下一个请求中(上面的内容已经提交)

def view(request):
    mylst = DBSession.query(MyList).get(1)
    mylst.lst.append('65')
    print(DBSession.is_active, DBSession.is_modified(mylst))

数据库没有更新,仍然是['45'],而打印出来的结果是

True, False

我是不是做错了什么,还是说这是个bug?

3 个回答

-2

在你执行 mylst.lst.append('45') 之后,试试 DBSession.flush()。这样可以让你在 pyramid_tm 提交之前先更新数据库。

更多信息可以在这里找到: http://docs.sqlalchemy.org/en/latest/orm/session.html#flushing

2

你可以手动标记一个实例为已修改

from sqlalchemy.orm.attributes import flag_modified

def view(session):
    mylst = Session.query(MyList).get(1)
    mylst.lst.append('45')
    flag_modified(mylst, 'lst') # flag its `lst' attribute is modified
    print(Session.is_active, Session.is_modified(mylst))
    # (True, True)
15

默认情况下,SQLAlchemy只关注值本身的变化,这对于简单的值,比如整数和字符串,工作得很好:

alice.name = "Alice"
alice.age = 8

当你给一个“复杂类型”的列,比如字典或列表,赋一个新值时,它也能正常工作:

alice.toys = ['doll', 'teddy bear']

但是,如果你只是修改列表中的某个元素,或者添加/删除一个值,SQLAlchemy就不会察觉到变化:

alice.toys[0] = 'teapot'
alice.toys.append('lego bricks')

为了让它能正常工作,你可以确保每次都赋一个新的列表:

toys = alice.toys[:]  # makes a "clone" of the existing list
toys[0] = 'teapot'
toys.append('lego bricks')
alice.toys = toys

或者你可以看看SQLAlchemy文档中的变更追踪章节,了解如何通过子类化列表或字典来追踪它们元素的修改。

另外,既然你提到你在使用Postgres - Postgres中有一个专门的ARRAY类型,如果你只需要存储列表,可以使用这个类型,而不是JSON。不过,上面提到的关于变更追踪的内容同样适用于ARRAY类型的列。

撰写回答