在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
调用返回时如果一个对象上的
保存
由于唯一性约束而失败,并且我回滚事务,那么所有其他保存是否也会回滚,即使它们没有冲突?如果没有,将所有这些包装到事务中有什么意义?您可以在实际保存到数据库之前验证对象。此外,您还可以使用异常处理,以便在发生异常时回滚事务。
我在这类场景中遵循的一般模式如下,
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测试不能做的事情。为了澄清一点,我可以通过同步该方法使该线程安全,但这将引入巨大的性能瓶颈。