【问题标题】:Nested transactions with pg-promise带有 pg-promise 的嵌套事务
【发布时间】:2017-05-30 10:39:06
【问题描述】:

我正在使用 NodeJS、PostgreSQL 和惊人的 pg-promise 库。就我而言,我想执行三个主要查询:

  1. 在“tweets”表中插入一条推文。
  2. 如果推文中有标签,请将它们插入另一个表“标签”
  3. 它们在第三个表“hashtagmap”(多对多关系表)中链接推文和主题标签

这是请求正文 (JSON) 的示例:

{
"id":"12344444",
"created_at":"1999-01-08 04:05:06 -8:00",
"userid":"@postman",
"tweet":"This is the first test from postman!",
"coordinates":"",
"favorite_count":"0",
"retweet_count":"2",
"hashtags":{
    "0":{
        "name":"test",
        "relevancetraffic":"f",
        "relevancedisaster":"f"
    },
    "1":{
        "name":"postman",
        "relevancetraffic":"f",
        "relevancedisaster":"f"
    },
    "2":{
        "name":"bestApp",
        "relevancetraffic":"f",
        "relevancedisaster":"f"
    }
}

除了主题标签之外,上述所有字段都应包含在“推文”表中,而主题标签又应包含在“主题标签”表中。

这是我基于 NodeJS 模块内的 pg-promise 文档中的嵌套事务使用的代码。我想我需要嵌套事务,因为我需要知道 tweet_idhashtag_id 才能将它们链接到 hashtagmap 表中。

// Columns
var tweetCols = ['id','created_at','userid','tweet','coordinates','favorite_count','retweet_count'];

var hashtagCols = ['name','relevancetraffic','relevancedisaster'];

//pgp Column Sets
var cs_tweets = new pgp.helpers.ColumnSet(tweetCols, {table: 'tweets'});

var cs_hashtags = new pgp.helpers.ColumnSet(hashtagCols, {table:'hashtags'});
return{
// Transactions
add: body =>
    rep.tx(t => {
        return t.one(pgp.helpers.insert(body,cs_tweets)+" ON CONFLICT(id) DO UPDATE SET coordinates = "+body.coordinates+" RETURNING id")
            .then(tweet => {
                var queries = [];
                for(var i = 0; i < body.hashtags.length; i++){
                    queries.push(
                        t.tx(t1 => {
                            return t1.one(pgp.helpers.insert(body.hashtags[i],cs_hashtags) + "ON CONFLICT(name) DO UPDATE SET fool ='f' RETURNING id")
                                .then(hash =>{
                                    t1.tx(t2 =>{
                                        return t2.none("INSERT INTO hashtagmap(tweetid,hashtagid) VALUES("+tweet.id+","+hash.id+") ON CONFLICT DO NOTHING");
                                    });
                                });
                        }));
                }
                return t.batch(queries);
            });
    })
}

问题在于这段代码我能够成功插入推文,但随后什么也没有发生。我无法插入主题标签,也无法将主题标签链接到推文。

抱歉,我是编码新手,所以我想我不明白如何从交易中正确返回以及如何执行这个简单的任务。希望您能够帮助我。

提前谢谢你。

【问题讨论】:

    标签: node.js postgresql transactions pg-promise


    【解决方案1】:

    对于遇到类似问题的人,我会发布答案。

    首先,我的错误:

    1. 在 for 循环中:body.hashtag.length 不存在,因为我正在处理一个对象(这里是非常基本的错误)。改为Object.keys(body.hashtags).length
    2. 为什么要使用这么多事务?按照vitally-t的回答:Interdependent Transactions with pg-promise我删除了额外的交易。我还不清楚如何打开一个事务并将一个查询的结果用于同一事务中的另一个查询。

    这是最终代码:

        // Columns
    var tweetCols = ['id','created_at','userid','tweet','coordinates','favorite_count','retweet_count'];
    
    var hashtagCols = ['name','relevancetraffic','relevancedisaster'];
    
    //pgp Column Sets
    var cs_tweets = new pgp.helpers.ColumnSet(tweetCols, {table: 'tweets'});
    
    var cs_hashtags = new pgp.helpers.ColumnSet(hashtagCols, {table:'hashtags'});
    
    return {
        /* Tweets */
        // Add a new tweet and update the corresponding hashtags
        add: body =>
            rep.tx(t => {
                return t.one(pgp.helpers.insert(body,cs_tweets)+" ON CONFLICT(id) DO UPDATE SET coordinates = "+body.coordinates+" RETURNING id")
                    .then(tweet => {
                        var queries = [];
                        for(var i = 0; i < Object.keys(body.hashtags).length; i++){
                            queries.push(
                                t.one(pgp.helpers.insert(body.hashtags[i],cs_hashtags) + "ON CONFLICT(name) DO UPDATE SET fool ='f' RETURNING id")
                                    .then(hash =>{
                                        t.none("INSERT INTO hashtagmap(tweetid,hashtagid) VALUES("+tweet.id+","+hash.id+") ON CONFLICT DO NOTHING");
                                    })
                                );
                        }
                        return t.batch(queries);
                    });
            }),
    

    【讨论】:

    • 你有一个问题 `t.none("INSERT INTO hashtagmap...` 你必须从它返回结果,即return t.none("INSERT INTO hashtagmap...,否则你创建一个松散的承诺。每一个查询请求必须是链式的,这是继承自 Promise 的关键规则。
    • It's not yet clear for me how you can open one transaction and use the result of one query into another in the same transaction. - 与一般的 Promise 相同。
    【解决方案2】:

    改进 Jean Phelippe 自己的答案:

    // Columns
    var tweetCols = ['id', 'created_at', 'userid', 'tweet', 'coordinates', 'favorite_count', 'retweet_count'];
    
    var hashtagCols = ['name', 'relevancetraffic', 'relevancedisaster'];
    
    //pgp Column Sets
    var cs_tweets = new pgp.helpers.ColumnSet(tweetCols, {table: 'tweets'});
    
    var cs_hashtags = new pgp.helpers.ColumnSet(hashtagCols, {table: 'hashtags'});
    
    return {
        /* Tweets */
        // Add a new tweet and update the corresponding hash tags
        add: body =>
            db.tx(t => {
                return t.one(pgp.helpers.insert(body, cs_tweets) + ' ON CONFLICT(id) DO UPDATE SET coordinates = ' + body.coordinates + ' RETURNING id')
                    .then(tweet => {
                        var queries = Object.keys(body.hashtags).map((_, idx) => {
                            return t.one(pgp.helpers.insert(body.hashtags[i], cs_hashtags) + 'ON CONFLICT(name) DO UPDATE SET fool = $1 RETURNING id', 'f')
                                .then(hash => {
                                    return t.none('INSERT INTO hashtagmap(tweetid, hashtagid) VALUES($1, $2) ON CONFLICT DO NOTHING', [+tweet.id, +hash.id]);
                                });
                        });
                        return t.batch(queries);
                    });
            })
                .then(data => {
                    // transaction was committed;
                    // data = [null, null,...] as per t.none('INSERT INTO hashtagmap...
                })
                .catch(error => {
                    // transaction rolled back
                })
    },
    

    注意事项:

    • 根据我之前的说明,您必须链接所有查询,否则您将得到松散的承诺
    • 远离嵌套事务,除非您确切了解它们在 PostgreSQL 中的工作原理(read this,特别是 Limitations 部分)。
    • 避免手动查询格式化,这不安全,始终依赖库的查询格式化。
    • 除非您将事务结果传递到其他地方,否则您至少应该提供.catch 处理程序。

    附:对于像+tweet.id 这样的语法,它与parseInt(tweet.id) 相同,只是更短,以防它们是字符串;)

    【讨论】:

    • 感谢您的回答!始终为我们提供有用且有见地的 cmets/answers。问候
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-11
    • 1970-01-01
    • 2017-11-18
    • 1970-01-01
    • 2016-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多