简体   繁体   English

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

[英]Transactional method in Scala Play with Slick (similar to Spring @Transactional, maybe?)

I know scala, as a funcional language, is supposed to work differently from a common OO language, such as Java, but I'm sure there has to be a way to wrap a group of database changes in a single transaction, ensuring atomicity as well as every other ACID property. 我知道scala,作为一种功能语言,应该与Java等常见的OO语言不同,但我确信必须有一种方法可以在一个事务中包装一组数据库更改,确保原子性为以及其他所有ACID财产。

As explained in the slick docs ( http://slick.lightbend.com/doc/3.1.0/dbio.html ), DBIOAction allows to group db operations in a transaction like this: 正如光滑的文档( 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)

However, my use case (and most real world examples I can think of), I have a code structure with a Controller, which exposes the code for my REST endpoint, that controller calls multiple services and each service will delegate database operations to DAOs. 但是,我的用例(以及我能想到的大多数现实世界的例子),我有一个带有Controller的代码结构,它暴露了我的REST端点的代码,该控制器调用多个服务,每个服务都将数据库操作委托给DAO。

A rough example of my usual code structure: 我常用代码结构的一个粗略示例:

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)
  }
}

This is a simple example, but most have more complex business logic in the service layer. 这是一个简单的示例,但大多数服务层中的业务逻辑更复杂。

I don't want: 我不想要:

  1. My DAOs to have business logic and decide which database operations to run. 我的DAO拥有业务逻辑并决定运行哪些数据库操作。
  2. Return DBAction from my DAOs and expose the persistency classes. 从我的DAO返回DBAction并公开持久性类。 That completely defeats the purpose of using DAOs in the first place and makes further refactorings much harder. 这完全违背了首先使用DAO的目的,并使进一步的重构变得更加困难。

But I definitely want a transaction around my entire Controller, to ensure that if any code fails, all the changes done in the execution of that method will be rolled back. 但我肯定想要围绕我的整个Controller进行事务处理,以确保如果任何代码失败,那么在该方法的执行中完成的所有更改都将被回滚。

How can I implement full controller transactionality with Slick in a Scala Play application? 如何在Scala Play应用程序中使用Slick实现完整的控制器事务性? I can't seem to find any documentation on how to do that. 我似乎无法找到有关如何做到这一点的任何文档。

Also, how can I disable auto-commit in slick? 另外,如何在光滑中禁用自动提交? I'm sure there is a way and I'm just missing something. 我确信有一种方法,我只是遗漏了一些东西。

EDIT: 编辑:

So reading a bit more about it, I feel now I understand better how slick uses connections to the database and sessions. 因此,阅读更多关于它,我觉得现在我更好地理解光滑如何使用数据库和会话的连接。 This helped a lot: http://tastefulcode.com/2015/03/19/modern-database-access-scala-slick/ . 这有很大帮助: http//tastefulcode.com/2015/03/19/modern-database-access-scala-slick/

What I'm doing is a case of composing in futures and, based on this article, there's no way to use the same connection and session for multiple operation of the kind. 我正在做的是在期货中撰写的一个案例,根据这篇文章,没有办法对同类的多个操作使用相同的连接和会话。

Problem is: I really can't use any other kind of composition. 问题是:我真的不能使用任何其他类型的组合。 I have considerable business logic that needs to be executed in between queries. 我有相当多的业务逻辑需要在查询之间执行。

I guess I can change my code to allow me to use action composition, but as I mentioned before, that forces me to code my business logic with aspects like transactionality in mind. 我想我可以改变我的代码以允许我使用动作组合,但正如我之前提到的,这迫使我用事务性等方面编写我的业务逻辑。 That shouldn't happen. 这不应该发生。 It pollutes the business code and it makes writing tests a lot harder. 它污染了业务代码,这使得编写测试变得更加困难。

Any workaround this issue? 有没有解决这个问题? Any git project out there that sorts this out that I missed? 任何git项目在那里排除了我错过了吗? Or, more drastic, any other persistence framework that supports this? 或者,更激烈的,任何其他持久性框架支持这个? From what I've read, Anorm supports this nicely, but I may be misunderstanding it and don't want to change framework to find out it doesn't (like it happened with Slick). 从我读过的内容来看,Anorm很好地支持了这一点,但是我可能会误解它并且不想改变框架以找出它没有(就像它在Slick中发生的那样)。

There is no such thing as transactional annotations or the like in slick. 光滑中没有交易注释等。 Your second "do not want" is actually the way to go. 你的第二个“不想要”实际上是要走的路。 It's totally reasonable to return DBIO[User] from your DAO which does not defeat their purpose at all. 从你的DAO返回DBIO[User]是完全合理的,这根本不会破坏他们的目的。 It's the way slick works. 这是光滑的工作方式。

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)
  }
}
  • The signature of save in your service class is still the same. save在服务类中的签名仍然相同。
  • No db related stuff in controllers. 控制器中没有与db相关的东西。
  • You have full control of transactions. 您可以完全控制交易。
  • I cannot find a case where the code above is harder to maintain / refactor compared to your original example. 我找不到上述代码与原始示例相比难以维护/重构的情况。

There is also a quite exhaustive discussion that might be interesting for you. 还有一个非常详尽的讨论可能对您有意义。 See Slick 3.0 withTransaction blocks are required to interact with libraries . 请参阅Slick 3.0 withTransaction块与库交互

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM