Scala中的事务方法与Slick一起玩(类似于Spring @Transactional,也许?)

时间:2016-07-06 09:48:24

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

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

正如光滑的文档(http://slick.lightbend.com/doc/3.1.0/dbio.html)中所解释的那样,DBIOAction允许在这样的事务中对db操作进行分组:

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端点的代码,该控制器调用了多个服务,每个服务都会委托数据库操作对DAOs。

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

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的目的,并使进一步的重构变得更加困难。
  3. 但我绝对想要围绕我的整个Controller进行事务处理,以确保如果任何代码失败,那么在该方法的执行中完成的所有更改都将被回滚。

    如何在Scala Play应用程序中使用Slick实现完整的控制器事务性?我似乎无法找到有关如何做到这一点的任何文档。

    另外,如何在光滑中禁用自动提交?我确定有办法,我只是遗漏了一些东西。

    编辑:

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

    我正在做的是在期货中撰写的一个案例,并且根据这篇文章,没有办法对同类的多个操作使用相同的连接和会话。

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

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

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

1 个答案:

答案 0 :(得分:5)

在光滑中没有交易注释等。你的第二个“不想要”实际上是要走的路。从你的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的签名仍然相同。
  • 控制器中没有数据库相关内容。
  • 您可以完全控制交易。
  • 我找不到上述代码与原始示例相比难以维护/重构的情况。

还有一个非常详尽的讨论可能对你有意义。请参阅Slick 3.0 withTransaction blocks are required to interact with libraries