在grails/hibernate事务中,多个save()调用如何交互?

在grails/hibernate事务中,多个save()调用如何交互?,hibernate,grails,transactions,Hibernate,Grails,Transactions,我正在尝试创建一个新的数据库对象,保存它,并检查保存是否成功。正确的方法是什么: class Document { String externalId; static constraints { externalId(blank: false, unique: true); } } def createDocuments(List<String> ids) { Document.withTransaction()

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

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(空白:false,唯一:true);
}        
}
def createDocuments(列表ID){
Document.withTransaction(){
ids.each{String id->
新文档(externalId:id).save();
}
}
//要在此处测试事务是否成功。
}
此事务可能会失败,因为其他用户可能同时创建了其中一个文档。因此,虽然实例是有效的(即其externalId不是空的),但它可能不是唯一的。但如果不运行事务,就无法判断

怎么办

更新

根据迄今为止提供的答案,我的问题的症结在于:

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

  • save
    调用返回时
  • 当事务提交时
  • 其他一些不确定的时间(可能在#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
        }
    }
    

    嗯。在第n次阅读之后,它确实有意义

    externalId
    文档的主键吗?如果是,则此处使用的优化方法是在保存文档之前检查文档的
    存在性

    //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.
    }
    
    //使用Set避免提交过程中的重复
    def createDocuments(设置ID){
    文件.withTransaction{
    ids.each{String id->
    //如果'id'不是主键,则使用
    //如果(!Document.findByExternalId(id)){}
    //这样可以保持文档的完整性。
    如果(!Document.exists(id)){
    如果(!新文档(外部id:id).save()){
    //约束验证失败。请在此处处理它们
    }
    //您可能不需要在每个文档上调用save,
    //除非您需要验证约束
    //因为所有创建的文档都将自动保存到
    //缓存并最终在会话结束时刷新到db,这
    //在本例中,正在退出操作方法。
    }
    }
    }
    //如果事务成功,则不需要在此处进行测试。
    }
    
    .save在成功保存的情况下返回持久化对象本身。你可以告诉我们关于那件事的绝妙真相。请看一下grails文档中的内容,我认为在所有内容都执行之前,事务不会提交,所以我不清楚save()是否能够检测到重复的违规行为。我不想在保存时指定
    flush:true
    ,因为我想优化性能:我想为一堆文档指定一个事务。可以理解。但这里的验证甚至在刷新到数据库之前就由hibernate处理,因此如果不使用
    flush:true
    ,您应该能够有效地验证它。我不知道它如何在提交事务之前验证唯一性。这就是像hibernate这样的ORM持久层的全部意义。当您在约束中说
    unique:true
    时,在保存hibernate(会话)时,将检查持久层中是否存在具有相同
    externalId
    的任何其他记录。如果找到,则hibernate save将返回同一会话/事务中的验证消息。根据该验证消息,您可以决定需要执行的操作。两点:如果(!Document.exists(id)),则序列
    。。。新文档(externalId:id).save()不是原子的:另一个线程也可能同时尝试插入同一文档,并且两者都可能通过(非)存在性测试,但其中一个事务将不会成功。第二:你引用的文章说:“如果你只是通过new关键字创建一个新实例,那么在调用save()方法之前,对象不会附加到会话。”我编辑了这个问题(和标题)为了更清楚地关注我所面临的问题的症结所在。@GeneGolovchinsky您是否尝试过使用多线程场景来测试它的行为?我使用两个线程使用save()方法保存相同的文档,结果显示save()是当另一个线程创建文档实例时,将返回null。但我不明白为什么我需要交易。因此有了重新措辞的问题。与@dmahapatro的示例一样,这不是线程安全的,并且似乎没有做任何save()后跟null测试不能做的事情。为了澄清一点,我可以通过同步该方法使该线程安全,但这将引入巨大的性能瓶颈。