gpt4 book ai didi

exception - Grails 2.4.4 : How to reliably rollback in a complex service method

转载 作者:行者123 更新时间:2023-12-02 19:32:54 27 4
gpt4 key购买 nike

考虑以下服务(默认是事务性的)。玩家必须始终拥有一个帐户。没有至少一个对应帐户的玩家是错误状态。

class playerService {
def createPlayer() {
Player p new Player(name: "Stephen King")
if (!p.save()) {
return [code: -1, errors:p.errors]
}
Account a = new Account(type: "cash")
if (!a.save()) {
// rollback p !
return [code: -2, errors:a.errors]
}
// commit only now!
return [code: 0, player:p]
}
}

我见过有经验的 grails 开发人员的这种模式,当我告诉他们,如果玩家帐户的创建因任何原因失败,它不会回滚玩家,并使数据库处于无效状态,他们看着我我很生气,因为 grails 处理回滚播放器,因为服务是事务性的,对吗?

那么,作为一名 SQL 专家,我寻找一种在 grails 中调用回滚的方法。没有一个。根据各种帖子,只有两种方法可以强制 grails 在服务中回滚:
  • 抛出一个未经检查的异常。你知道这是对的吗?
  • 不要使用服务方法或事务注释,使用这个结构:

  • .
    DomainObject.withTransaction {status ->
    //stuff
    if (someError) {
    status.setRollbackOnly()
    }
    }

    1. 抛出一个未经检查的异常

    1.1 所以我们必须抛出运行时异常来回滚。这对我来说没问题(我喜欢异常),但是这不会与我们拥有的 grails 开发人员融为一体,他们将异常视为对 Java 的回归并且不酷。这也意味着我们必须改变应用程序当前使用其服务层的整个方式。

    1.2 如果抛出异常,您将丢失 p.errors - 您将丢失验证详细信息。

    1.3 我们的新 grails 开发人员不知道未检查异常和已检查异常之间的区别,也不知道如何区分。这真的很危险。

    1.4.使用 .save(failOnError: true)
    我非常喜欢使用它,但它并不适用于任何地方。有时您需要在进一步操作之前检查原因,而不是抛出异常。它可以生成的异常是始终检查、始终未检查还是两者之一? IE。无论是什么原因,failOnError AWLAYS 都会回滚吗?我问过的没有人知道这个问题的答案,这令人不安,他们正在盲目相信以避免损坏/不一致的数据库。

    1.5 如果 Controller 调用服务 A,服务 A 调用服务 B,然后服务 C,会发生什么。服务 A 必须捕获任何异常并向 Controller 返回格式良好的返回值。如果服务 C 抛出异常,被服务 A 捕获,服务 B 的事务会回滚吗?了解这一点对于构建工作应用程序至关重要。

    更新1:
    做了一些测试后,似乎任何运行时异常,即使在一些不相关的子调用中抛出并捕获,也会导致父级中的所有内容回滚。然而,在父 session 中知道这个回滚已经发生并不容易——你需要确保如果你捕捉到任何异常,你要么重新抛出,要么向调用者传递一些通知以表明它在这种情况下失败了一种方式,其他一切都将回滚。

    2. withTransaction

    2.1 这似乎是一个集市结构。我如何调用它,我为“状态”参数传递什么?什么是“setRollbackOnly”。为什么它不只是称为“回滚”。什么是“唯一”部分?当您的方法可能想要更新几个不同的域对象时,它与域对象相关联。

    2.2 你应该把这段代码放在哪里?在 DomainObject 类中?在源文件夹中(即不在服务或 Controller 中?)?直接在 Controller 中? (我们不想在 Controller 中复制业务逻辑)

    3.理想情况。

    3.1 一般情况是,如果该服务方法中的任何内容因任何原因无法保存,或因任何原因(已检查或未检查)引发任何异常,我们都希望我们在该服务方法中所做的每一件事都能回滚。

    3.2 理想情况下,我希望服务方法“始终回滚,除非我明确调用提交”,这是最安全的策略,但我相信这是不可能的。

    问题是我如何实现理想的情况?

    无论失败的原因是什么,调用 save(failOnError:true) 总是会回滚一切吗?这并不完美,因为调用者不容易知道哪个域对象保存导致了问题。

    或者人们是否定义了许多异常类,这些类是 runtimeException 的子类,然后在 Controller 中显式地捕获它们中的每一个以创建适当的响应?这是旧的 Java 方式,由于我们必须编写大量样板代码,因此我们的 groovy 开发人员不喜欢这种方法。

    人们使用什么方法来实现这一目标?

    最佳答案

    我不会称自己为专家,这个问题已经有一年多了,但我可以回答其中的一些问题,如果只是为了 future 的搜索者。我刚刚重构了一些 Controller 以使用服务以利用事务。

    I have seen this pattern by experienced grails developers, and when I tell them that if creation of the account of the player fails for any reason, it wont rollback the player, and will leave the DB in an invalid state, they look at me like I am mad because grails handles rolling back the player because services are transactional right?



    我没有在文档中看到它明确指出从服务方法返回不会回滚事务,但我无法想象这将是一种非常理智的行为。尽管如此,测试是证明自己的一种简单方法。

    1.2 If an exception is thrown, you lose the p.errors - you lose the validation detail.



    由于您是抛出异常的人,因此您可以同时抛出错误。例如:
    // in service
    if (!email.save()) {
    throw new ValidationException("Couldn't save email ${params.id}", email.errors)
    }

    当您捕获异常时,您重新加载实例(因为抛出异常会清除 session ),将错误放回实例中,然后像往常一样将其传递给 View :
    // in controller
    } catch (ValidationException e) {
    def email = Email.read(id)
    email.errors = e.errors
    render view: "edit", model: [emailInstance: email]
    }

    这在标题“验证错误和回滚”下讨论,从 http://grails.github.io/grails-doc/2.4.4/guide/single.html#transactionsRollbackAndTheSession 开始的页面向下。 .

    1.4. use .save(failOnError: true) I am a big fan of using this, but its not appropriate everywhere. Sometimes you need to check the reason before going further, not throw an exception. Are the exceptions it can generate always checked, always unchecked, or either? I.e. will failOnError AWLAYS rollback, no matter what the cause? No one I have asked knows the answer to this, which is disturbing, they are using blind faith to avoid corrupted/inconsistent DBs.



    failOnError will cause save() to throw a ValidationException ,所以是的,如果您在事务中并且没有检查该异常,则事务将被回滚。

    一般来说,使用 failOnError 似乎不是“Grailsy”很多,可能是由于您列出的原因(例如,缺乏控制)。相反,您检查是否 save()失败( if (!save()) ... ),并据此采取行动。

    1. withTransaction


    我不确定这有什么意义,因为 SpringSource 确实鼓励对所有事情都使用服务。我个人也不喜欢。

    如果你想让一个特定的服务成为非事务性的,然后让它的一个方法成为事务性的,你可以用 @Transactional 注释那个方法。 (除非您的开发人员也不喜欢注释,因为它们太“Java”;))。

    笔记!只要你用 @Transactional 标记一个方法,整体服务将变为非事务性。

    3.1 The general case is we want every thing we do in a service method to roll back if anything in that service method cant be saved for any reason, or throws any exception for any reason (checked or unchecked).



    我觉得检查异常通常被认为不是“Groovy”(这也使它们不是 Grails-y)。不确定这是什么原因。

    但是,看起来您可以通过在 rollbackFor option to @Transactional 中列出它们来告诉您的服务回滚已检查的异常。 .

    Or do people define lots of exception classes which subclass runtimeException, then explicit catch each of them in the controller to create the appropriate response? This is the old Java way, and our groovy devs pooh pooh this approach due to the amount of boiler plate code we will have to write.



    Groovy 的好处在于您可以编写一次样板文件,然后重复调用它。我见过很多并且目前正在使用的模式是这样的:
    private void validate(Long id, Closure closure) {
    try {
    closure()
    } catch (ValidationException e) {
    def email = Email.read(id)
    email.errors = e.errors
    render view: "edit", model: [emailInstance: email]
    } catch (OtherException e) {
    def email = Email.read(id)
    flash.error = "${e.message}: ${e.reasons}"
    render view: "show", model: [emailInstance: email]
    } catch (Throwable t) {
    flash.error = "Unexpected error $t: ${t.message}"
    redirect action: "list"
    }
    }

    然后在每个 Controller 操作中调用它,如下所示:
    def update(Long id, Long version) {
    withInstance(id, version) { Email emailInstance ->
    validate(emailInstance.id) {
    emailService.update(emailInstance, params)
    flash.message = "Email $id updated at ${new Date()}."
    redirect action: "show", id: emailInstance.id
    }
    }
    }

    ( withInstance 是另一种类似的方法,它可以干掉检查是否存在和乐观锁定。)

    这种方法有缺点。您在每个操作中都会获得相同的重定向集;您可能想为每个 Controller 编写一组方法;将闭包传递给方法并期望该方法知道闭包将抛出哪些异常似乎有点愚蠢。但是,嘿,编程就是权衡,对吧?

    无论如何,希望这至少是有趣的。

    关于exception - Grails 2.4.4 : How to reliably rollback in a complex service method,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28242153/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com