以编程方式处理 Grails 事务
Posted
技术标签:
【中文标题】以编程方式处理 Grails 事务【英文标题】:Handling Grails transactions programmatically 【发布时间】:2014-03-15 09:07:42 【问题描述】:当我需要保存一个对象列表,并且每个对象都应该保存在它自己的事务中(这样如果一个失败,它们不会全部失败),我会这样做:
List<Book> books = createSomeBooks()
books.each book ->
Book.withNewSession
Book.withTransaction TransactionStatus status ->
try
book.save(failOnError: true)
catch (ex)
status.setRollbackOnly()
我使用Book.withNewSession
,因为如果一本书保存失败并且事务回滚,会话将无效,这将阻止后续书籍保存。但是,这种方法存在几个问题:
-
有点冗长
将始终为每本书创建一个新会话,即使前一本书成功了
有没有更好的方法?我想到的一种可能性是依赖注入 Hibernate SessionFactory
并改为这样做
List<Book> books = createSomeBooks()
books.each book ->
try
Book.withTransaction
book.save(failOnError: true)
catch (ex)
// use the sessionFactory to create a new session, but how....?
【问题讨论】:
为什么需要为每次迭代创建一个新会话?恕我直言,在每次迭代中只执行 withTransaction 块就足够了,但所有这些都可以在一个会话中发生.. @lukelazarovic 如果发生回滚,我只需要一个新会话。我遇到的问题是我不知道如何在 catch 块中创建一个新会话 【参考方案1】:应该这样做:
List<Book> books = createSomeBooks()
books.each book ->
Book.withNewTransaction TransactionStatus status ->
try
book.save(failOnError: true)
catch (ex)
status.setRollbackOnly()
回滚会话不是无效的,它只是被清除。因此,任何访问从数据库读取的实体的尝试都会失败,但写入尚未持久化的实体会很好。但是,您确实需要使用单独的事务来防止一次失败回滚所有内容,因此需要使用 withNewTransaction。
【讨论】:
知道withNewTransaction
和withTransaction
之间的区别是什么吗?前者没有记录。
Difference mentioned here 和 plan is to move this to documents in 2.4。寻找 Graeme 的 cmets。我想知道为什么它没有做成文件。 Burt 在他的书中介绍了这种方法。 @唐
如果您转储 TransactionStatus 对象,则有一个名为 newTransaction 的布尔属性。显然对于 withTransaction,这设置为 false。并且基于行为(仅使用任意 setRollbackOnly 来模拟失败),它似乎确实使用单个事务,除非您使用 withNewTransaction。
另外,我看到你之前***.com/questions/21638706/… 也问过类似的问题,似乎更详细。您是否因为试图访问以前从数据库中读取的实体而得到延迟初始化异常,因为您试图更新它们?如果是这样,也许您可以检索每个现有记录作为事务的一部分,而不是在开始时一次全部检索,以确保在写入失败时它们不会全部被清除?【参考方案2】:
您可以尝试先验证它们,然后保存所有通过的吗?我不确定它是否更高效,但它可能会更干净一些。比如:
List<Book> books = createSomeBooks()
List<Book> validatedBooks = books.findAll it.validate()
validatedBooks*.save()
虽然我不确定.validate()
是否承诺保存不会因其他原因而失败,以及数据是否独立(即,唯一的约束通过,直到下一本书也尝试保存)。
【讨论】:
我不认为这是可靠的,因为正如您所指出的,无法保证经过验证的对象会实际保存,因为数据库的状态在对象被验证时和它被保存了。【参考方案3】:也许您可以使用 groovy 元编程和 grails 动态域方法?
在引导中:
def grailsApplication
def init =
List.metaClass.saveCollection =
ApplicationContext context = (ApplicationContext) ServletContextHolder.getServletContext().getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT);
SessionFactory sf = context.getBean('sessionFactory')
Session hsession = sf.openSession()
def notSaved = []
delegate.each
if(!it.trySave())
notSaved << it
hsession.close()
hsession = sf.openSession()
hsession.close()
return notSaved
grailsApplication.getArtefacts("Domain")*.clazz.each clazz ->
def meta = clazz.metaClass
meta.trySave =
def instance = delegate
def success = false
clazz.withTransaction TransactionStatus status ->
try
instance.save(failOnError: true) // ', flush: true' ?
success = true
catch (ex)
status.setRollbackOnly()
return success
然后:
class TheController
def index()
List<Book> books = createSomeBooks()
def notSaved = books.saveCollection()
books.retainAll !notSaved.contains(it)
println "SAVED: " + books
println "NOT SAVED: " + notSaved
当然必须执行一些检查(例如列表包含域类等)。您还可以将特定参数传递给闭包以使其更灵活(例如flush
、failOnError
、deepValidate
等)
【讨论】:
以上是关于以编程方式处理 Grails 事务的主要内容,如果未能解决你的问题,请参考以下文章
作为管理员,如何使用 Grails Spring Security 以编程方式更改用户密码?
Grails 2.3 IntegrationSpec 不能是事务性错误
Grails:在控制器中设置啥以达到 URL 映射中的错误处理?