【问题标题】:How do multiple save() calls interact in a grails/hibernate transaction?多个 save() 调用如何在 grails/hibernate 事务中交互?
【发布时间】:2013-06-15 01:59:50
【问题描述】:

我正在尝试创建一个新的数据库对象,保存它,然后检查保存是否成功。这样做的正确方法是什么:

class Document {
    String externalId;

    static constraints {
        externalId(blank: false, unique: true);
    }        
}

def createDocuments(List<String> ids) {

    Document.withTransaction() {
        ids.each { String id ->
            new Document(externalId: id).save();
        }
    }

    // want to test here if transaction succeeded.
}

此事务可能会失败,因为其他用户可能同时创建了其中一个文档。因此,虽然一个实例是有效的(即它的 externalId 不为空),但它可能不是唯一的。但是不运行事务就无法判断。

怎么办?

更新

根据目前提供的答案,这是我的问题的症结所在:

如果我运行一个调用多个保存的事务,保存的对象何时可用于其他休眠会话?一些可能性:

  1. save 调用返回时
  2. 事务提交时
  3. 其他一些不确定的时间(大概在 #2 之前)

如果一个对象上的save 由于唯一性约束而失败,并且我回滚了事务,那么所有其他保存是否也会回滚,即使它们没有冲突?如果不是,那么将所有这些都包含在事务中的意义何在?

【问题讨论】:

  • .save 在成功保存的情况下返回持久对象本身。你可以让我们知道这一点。查看 grails 文档中的 save
  • 我认为事务在所有内容执行之前不会提交,所以我不清楚 save() 是否能够检测到重复违规。我不想在我的保存中指定flush: true,因为我想优化性能:我想对一堆文档进行一次交易。
  • 可以理解。但是这里的验证将在刷新到数据库之前由休眠处理,所以不使用flush: true你应该能够有效地验证它。
  • 我看不出它如何在提交事务之前验证唯一性。
  • 这就是拥有像 Hibernate 这样的 ORM 持久层的全部意义所在。当您在约束中说unique: true 时,在保存休眠(会话)时会检查持久层中是否存在具有相同externalId 的任何其他记录。如果找到,则休眠保存将在同一会话/事务中返回一条验证消息。根据该验证消息,您可以决定需要做什么。

标签: hibernate grails transactions


【解决方案1】:

好的。在第 n 次阅读 my favorite article 之后,它确实有意义。

externalIdDocument 的主键吗?如果是,那么这里使用的优化方法是在保存文档之前检查文档的existence

//Avoid duplication in the submission as well by using Set<String>
def createDocuments(Set<String> ids) { 
    Document.withTransaction {
        ids.each { String id ->
            //If `id` is not the primary key then use
            //if(!Document.findByExternalId(id)){}
            //This way you maintain the integrity of the Document.
            if(!Document.exists(id)){
                if(!new Document(externalId: id).save()){
                    //Validation failed on constraints. Handle them here
                }

                //You may not need to call save on each Document, 
                //unless you need to validate the constraints
                //as all of the Documents created will be automatically saved to 
                //cache and ultimately flushed to db by end of the session, which
                //is in this case exiting the action method.
            }
        }
    }

    // You do not need to test here if transaction succeeded.
}

【讨论】:

  • 两点:序列if(!Document.exists(id)) ... new Document(externalId: id).save() 不是原子的:另一个线程也可能同时尝试插入同一个文档,并且两者都可能通过(不)存在测试,但是其中一个交易不会成功。第二:您引用的文章说“如果您只是通过 new 关键字创建一个新实例,那么在调用 save() 方法之前,该对象不会附加到会话。”
  • 我编辑了问题(和标题),以便更清楚地关注我认为我面临的问题的症结所在。
  • @GeneGolovchinsky 您是否尝试过使用多线程场景对其进行测试,看看它的行为如何?
  • 我测试了两个线程使用 save() 方法保存相同的文档,当另一个线程创建文档实例时,save() 似乎将返回 null。但是后来我不明白为什么我需要交易。因此,重新措辞的问题。
【解决方案2】:

您可以在实际保存到数据库之前验证对象。
您也可以使用异常处理来回滚事务以防万一。
我在这类场景中遵循的一般模式是,

DomainClass.withTransaction{ tx ->
   try{
       def ref = new DomainClass(...)
       if(ref.validate()){
           ref.save()
       }
        else{
           //throw some invalid instance 

        }

    }
    catch(e){
      tx.setRollbackOnly()
      log.error e
    }
}

【讨论】:

  • 与@dmahapatro 的示例一样,这不是线程安全的,并且似乎没有执行 save() 后跟 null 测试不执行的任何操作。
  • 澄清一下,我可以通过同步方法使这个线程安全,但这会带来巨大的性能瓶颈。
猜你喜欢
  • 1970-01-01
  • 2011-02-06
  • 1970-01-01
  • 1970-01-01
  • 2020-10-07
  • 1970-01-01
  • 2014-06-16
  • 1970-01-01
  • 2011-11-20
相关资源
最近更新 更多