scala - Scala 中的事务方法 Play with Slick(可能类似于 Spring @Transactional?)

标签 scala playframework playframework-2.0 slick slick-3.0

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

正如 slick docs (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)

然而,我的用例(以及我能想到的大多数真实世界的例子),我有一个带有 Controller 的代码结构,它公开了我的 REST 端点的代码,该 Controller 调用多个服务,每个服务将数据库操作委托(delegate)给 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)
  }
}

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

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

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

    如何在 Scala Play 应用程序中使用 Slick 实现完整的 Controller 事务性?我似乎找不到任何关于如何做到这一点的文档。

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

    编辑:

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

    我正在做的是一个在 future 中作曲的案例,根据这篇文章,没有办法使用相同的连接和 session 来进行这种类型的多个操作。

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

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

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

    最佳答案

    slick 中没有诸如事务注释之类的东西。您的第二个“不想要”实际上是要走的路。返回 DBIO[User] 完全合理来自您的 DAO,这根本不会破坏他们的目的。这就是 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的签名在您的服务类中仍然是一样的。
  • Controller 中没有与数据库相关的东西。
  • 您可以完全控制交易。
  • 与原始示例相比,我找不到上面的代码更难维护/重构的情况。

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

    关于scala - Scala 中的事务方法 Play with Slick(可能类似于 Spring @Transactional?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38221021/

    相关文章:

    java - 如何从 Java 停止 Play Framework

    scala - Akka 流——按流中元素的数量过滤

    scala - 如何从IntelliJ(或其他IDE)运行Spark测试

    Scala 在可变函数中下划线星号?

    scala - Scala 有哪些基于 actor 的 Web 框架?

    java - Playframework - 分包 Controller 和路由问题

    playframework - 如何在Play Framework的配置文件中使用 "war.context"?

    java - 迁移 play 项目时纠正 akka 远程设置

    java - Play 2.1。 WebSocket 没有 EntityManager 绑定(bind)到这个线程

    java - Play Framework 2.1.0 看不到派生类中的静态方法