从 React->ExpressJS->Postgres 修补 Postgres JSONB 列的最佳方法是啥?

Posted

技术标签:

【中文标题】从 React->ExpressJS->Postgres 修补 Postgres JSONB 列的最佳方法是啥?【英文标题】:What's the best way to PATCH Postgres JSONB column from React->ExpressJS->Postgres?从 React->ExpressJS->Postgres 修补 Postgres JSONB 列的最佳方法是什么? 【发布时间】:2020-03-27 18:51:12 【问题描述】:

我有一个大数据表(使用 AG-Grid 渲染),我想在 Postgres 后端更新它,但就工作量而言,下一部分的最佳方法让我推诿,最好行动的过程。

使用fast-json-patch 库,我可以很容易地在客户端获得一个JSON 补丁列表,然后大致如下:

import * as jsonpatch from 'fast-json-patch'

postData = jsonpatch.compare(originalData, updatedData)

const request = new Request(url, 
    method: 'PATCH',
    body: JSON.stringify(postData),
    headers: new Headers(
      Accept: 'application/json',
      'Content-Type': 'application/json-patch',
      Authorization: 'Bearer ' + user.token,
    ),
  )

然后在 ExpressJS 的“后端”中迭代一堆 jsonb_set 查询以更新 Postgres。

或者,我可以从 Postgres 获取要更新的记录,然后使用 fast-json-patch 修补 ExpressJS 后端中的 JSONB 数据,然后一次性更新 Postgres 记录?

这不是我以前做过的事情,但我敢肯定这种事情一定很常见。最好的通用方法是什么?


更新

我尝试实施第二种方法 - 我现在的问题是当我有 JSONB 字段要更新时锁定/解锁 Postgres。我现在的问题与从 express 端实际实现记录锁定和更新有关,特别是尝试处理使用 pg 后端的异步性质。

我只是想知道是否有人能在这个笨拙的尝试中发现(非故意的)错误:

const express = require('express')
const bodyParser = require('body-parser')
const SQL = require('sql-template-strings')
const  Client  = require('pg')
const dbConfig = require('../db')
const client = new Client(dbConfig)
const jsonpatch = require('fast-json-patch')

// excerpt for patch records in 'forms' postgres table

const patchFormsRoute = (req, res) => 
  const  id  = req.body
  const jsonFields = [ 'sections', 'descriptions' ]
  const possibleFields = [ 'name','status',...jsonFields ]
  const parts = []
  const params = [id] // stick id in as first param
  let lockInUse = false

  // find which JSONB fields are being PATCHed.
  // incoming JSONB field values are already JSON 
  // arrays of patches to apply for that particular field

  const patchList = Object.keys(req.body)
    .filter(e => jsonFields.indexOf(e) > -1)

  client.connect()

  if (patchList.length > 0) 
    const patchesToApply = pullProps(req.body, jsonFields)
    lockInUse = getLock('forms',id)
    // return record from pg as object with just JSONB field values
    const oldValues = getCurrentValues(patchList, id)
    // returns record with patches applied
    const patchedValues = patchValues( oldValues , patchesToApply )
  

  possibleFields.forEach(myProp => 
    if (req.body[myProp] != undefined) 
      parts.push(`$myProp = $$params.length + 1`)
      if (jsonFields.indexOf(myProp) > -1) 
        params.push(JSON.stringify(patchedValues[myProp]))
       else 
        params.push(req.body[myProp])
      
    
  )

  result = runUpdate(client, 'forms', parts, params)

  if(lockInUse) 
    releaseLock(client, 'forms', id)
  

  client.end()

  return result


// helper functions to try and cope with async nature of pg

function async getLock(client, tableName, id ) 
  await client.query(SQL`SELECT pg_advisory_lock($tableName::regclass::integer, $id);`)
  return true


function async releaseLock(client, tableName, id) 
  await client.query(SQL`SELECT pg_advisory_unlock($tableName::regclass::integer, $id);`)


function async getCurrentValues(client, fieldList, id) 
  const fl = fieldList.join(', ')
  const currentValues = await client
    .query(SQL`SELECT $fl FROM forms WHERE id = $id`)
    .then((result) => return result.rows[0])
  return currentValues


function pullProps(sourceObject, propList) 
  return propList.reduce((result, propName) => 
    if(sourceObject.hasOwnProperty(propName)) result[propName] = sourceObject[propName]
    return result
  , )


function patchValues(oldValues, patches) 
  const result = 
  Object.keys(oldValues).forEach(e => 
    result[e] = jsonpatch.apply( oldValues[e], patches[e] );
  )
  return result


function async runUpdate(client, tableName, parts, params) 
  const updateQuery = 'UPDATE ' + tableName + ' SET ' + parts.join(', ') + ' WHERE id = $1'
  const result = await client
    .query(updateQuery, params)
    .then(result => 
      res.json(result.rowCount)
    )
  return result

【问题讨论】:

我猜你可能需要在UPDATE之后START TRANSACTIONCOMMIT它,你可以通过SELECT FOR UPDATE语句锁定行,它会给你数据并将它锁定在直到你COMMIT,所以你不需要getLockreleaseLock 【参考方案1】:

使用第二种方法。 PostgreSQL 没有针对 JSONB 的就地编辑功能。它总是包括制作完整的副本。您不妨在客户端中执行此操作,这似乎有更好的工具。

如果补丁很小而 JSONB 很大并且您的网络很慢,则可能会例外。

【讨论】:

这些都是好点。我唯一的想法是,可能有某种方法可以使 Postgres 端的操作原子化。但是,我会尝试 2 次,希望 ;-) 谢谢。 不确定这是否重要,但不能使用jsonb_set 命令就地编辑吗? jsonb_set(target jsonb, path text[], new_value jsonb [, create_missing boolean]) ?无论如何,尝试在 ExpressJS 应用程序中使用 Postgres 咨询锁定来解决它……看起来很有趣 ;-) 它并不是真正的就地编辑,因为 jsonb_set 返回一个编辑后的副本。它不会通过引用接收其第一个参数并在适当位置编辑该引用。它可以节省网络流量,但不会为了更改其中的一部分而获取数据的完整副本。此外,您必须将它们链接在一起才能更改多项内容。我认为 SELECT...FOR UPDATE 就足够了,为此使用建议锁似乎没有必要。 除了... postgres JSONB 字段包含一个 JSON 对象数组,n x m,由多个用户同时更新 - 他们每个人都有自己的列,并且只能更新该列。因此必须在客户端生成差异,以确保仅记录其列的更改,然后在 PATCH 请求中发送。

以上是关于从 React->ExpressJS->Postgres 修补 Postgres JSONB 列的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 expressjs 发送 jwt 令牌以响应应用程序? [关闭]

从 javascript promise (expressjs + neDB) 中提取函数

如何使用 expressjs 在 React 中上传图像

服务器渲染的 React ExpressJS 前端泄露用户的 Redux 存储数据

expressjs:打字稿:“typeof <express.Router>”类型的参数不可分配给“RequestHandlerParams”类型的参数

从 ReactJS 外部渲染组件