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

如精美的文档(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如何使用到数据库和 session 的连接。这很有帮助:http://tastefulcode.com/2015/03/19/modern-database-access-scala-slick/

    我正在做的是在将来进行组合的情况,根据本文,无法对同一类型的多种操作使用相同的连接和 session 。

    问题是:我真的不能使用任何其他种类的构图。我有大量的业务逻辑,需要在两次查询之间执行。

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

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

    最佳答案

    光滑的东西没有事务注释之类的东西。您的第二个“不想要”实际上是要走的路。从您的DAO返回DBIO[User]是完全合理的,这完全不会破坏其目的。这就是光滑的工作方式。

    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

    08-04 16:22