【问题标题】:Transactional method in Scala Play with Slick (similar to Spring @Transactional, maybe?)Scala 中的事务方法 Play with Slick(可能类似于 Spring @Transactional?)
【发布时间】:2016-11-08 07:53:35
【问题描述】:

我知道 scala 作为一种函数式语言,其工作方式应该与常见的 OO 语言(如 Java)不同,但我确信必须有一种方法可以将一组数据库更改包装在单个事务中,确保原子性以及所有其他 ACID 属性。

如 slick 文档 (http://slick.lightbend.com/doc/3.1.0/dbio.html) 中所述,DBIOAction 允许在事务中对数据库操作进行分组,如下所示:

val a = (for {
  ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result
  _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally

val f: Future[Unit] = db.run(a)

但是,我的用例(以及我能想到的大多数真实示例),我有一个带有控制器的代码结构,它公开了我的 REST 端点的代码,该控制器调用多个服务,每个服务将委托数据库操作到 DAO。

我常用代码结构的粗略示例:

class UserController @Inject(userService: UserService) {
  def register(userData: UserData) = {
    userService.save(userData).map(result => Ok(result))
  }
}

class UserService @Inject(userDao: UserDao, addressDao: AddressDao) {
  def save(userData: UserData) = {
    for {
      savedUser <- userDao.save(userData.toUser)
      savedAddress <- addressDao.save(userData.addressData.toAddress)
    } yield savedUser.copy(address = savedAddress)
  }
}

class SlickUserDao {
  def save(user: User) = {
    db.run((UserSchema.users returning UserSchema.users)).insertOrUpdate(user)
  }
}

这是一个简单的例子,但大多数在服务层都有更复杂的业务逻辑。

我不想要:

  1. 我的 DAO 拥有业务逻辑并决定运行哪些数据库操作。
  2. 从我的 DAO 返回 DBAction 并公开持久性类。这完全违背了使用 DAO 的初衷,并使进一步的重构变得更加困难。

但我绝对想要一个围绕我的整个控制器的事务,以确保如果任何代码失败,则在执行该方法时所做的所有更改都将回滚。

如何在 Scala Play 应用程序中使用 Slick 实现完整的控制器事务性?我似乎找不到任何有关如何执行此操作的文档。

另外,如何在 slick 中禁用自动提交?我确定有办法,但我只是错过了一些东西。

编辑:

所以阅读更多关于它的内容,我觉得现在我更好地理解了 slick 如何使用与数据库和会话的连接。这很有帮助:http://tastefulcode.com/2015/03/19/modern-database-access-scala-slick/

我正在做的是一个在期货中作曲的案例,根据这篇文章,没有办法将相同的连接和会话用于这种类型的多个操作。

问题是:我真的不能使用任何其他类型的构图。我有相当多的业务逻辑需要在查询之间执行。

我想我可以更改我的代码以允许我使用动作组合,但正如我之前提到的,这迫使我在编写业务逻辑时考虑到事务性等方面。那不应该发生。它污染了业务代码,并且使编写测试变得更加困难。

有没有解决这个问题的方法?有什么 git 项目可以解决我错过的问题吗?或者,更激烈的是,任何其他支持这一点的持久性框架?根据我的阅读,Anorm 很好地支持这一点,但我可能会误解它并且不想更改框架以发现它不支持(就像 Slick 发生的那样)。

【问题讨论】:

    标签: scala playframework playframework-2.0 slick slick-3.0


    【解决方案1】:

    在 slick 中没有事务注释之类的东西。您的第二个“不想要”实际上是要走的路。从你的 DAO 返回 DBIO[User] 是完全合理的,这根本不会违背他们的目的。这就是 slick 的工作方式。

    class UserController @Inject(userService: UserService) {
      def register(userData: UserData) = {
        userService.save(userData).map(result => Ok(result))
      }
    }
    
    class UserService @Inject(userDao: UserDao, addressDao: AddressDao) {
      def save(userData: UserData): Future[User] = {
        val action = (for {
          savedUser <- userDao.save(userData.toUser)
          savedAddress <- addressDao.save(userData.addressData.toAddress)
          whatever <- DBIO.successful(nonDbStuff)
        } yield (savedUser, savedAddress)).transactionally
    
        db.run(action).map(result => result._1.copy(result._2))
      }
    }
    
    class SlickUserDao {
      def save(user: User): DBIO[User] = {
        (UserSchema.users returning UserSchema.users).insertOrUpdate(user)
      }
    }
    
    • save 在您的服务类中的签名仍然相同。
    • 控制器中没有与 db 相关的内容。
    • 您可以完全控制交易。
    • 与您的原始示例相比,我找不到上述代码更难维护/重构的情况。

    还有一个非常详尽的讨论,您可能会感兴趣。见Slick 3.0 withTransaction blocks are required to interact with libraries

    【讨论】:

    • 好吧,我实际上可以发现它有几个问题:使我的服务显式地依赖于 slick 类,因此没有必要使用 DAO(在 DB 层中不可能进行抽象)。这意味着我的业务逻辑和数据库访问处于同一级别。使得测试和模拟变得非常困难。另外,如果我想改变我的数据库访问实现,我需要改变我的服务,所以任何重构都会很痛苦。它还用横切关注点污染了我的业务代码,例如划定事务,这可能再次使测试变得更加困难。不可能是唯一的方法。无论如何,谢谢
    • 我同意避免显式依赖 slick 是可取的,但是如果您想处理交易,AFAIK 没有其他解决方案 ATM。我并不是说这是最终的解决方案,事实上,如果有人证明我错了,我会非常高兴。关于持久性框架的更改:由于 slick 不像您的普通 ORM,因此无论如何它都可能是 PITA。老实说,你有多少次在实时系统中切换持久性框架? :) 无论如何,我会关注这个帖子,也许有人会想出一个更体面的解决方案。
    • 嗯,我最近确实将持久性框架从 Mongo 和 ReactiveMongo 更改为 Postgres 和 Slick(不要问...),我敢肯定,我们将不得不更改其中的一部分返回 Mongo 或其他文档数据库。当这种情况发生时,我有点试图避免另一个巨大的重构。对于第二次重构的大部分时间,事务并不重要,但重构工作本身肯定会。编写单元测试似乎也有点困难。无论如何,让我们希望其他人有一个替代答案。
    • 经过大量研究和挫折尝试,似乎根本不可能完全隔离 slick 3 中的持久层,就像我描述的那样,所以当你说“这是要走的路”时,你是对的光滑”。我仍然觉得这是一个糟糕的架构,我可能永远不会在另一个项目中再次使用 Slick,直到我看到这个限制被排序。但是,我必须奖励赏金,尽管事实上你的答案不是我的问题的解决方案,但它是正确的,并且可能会阻止其他人浪费大量时间尝试做一些根本不可能的事情。
    • 另外值得注意的是,您将能够从 一个 服务层方法协调多个 DAO 调用的操作,但是在应用程序层您倾向于协调多个服务方法来完成任务。您如何确保所有这些服务方法都参与共享事务?您不想从 Service 层传回 Slick 类型...
    猜你喜欢
    • 2011-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-18
    • 2019-03-30
    • 2023-03-20
    • 2021-05-29
    • 2014-07-08
    相关资源
    最近更新 更多