Slick 3.1.x在事务上将所有细节从一个主服务器插入另一个主服务器?

时间:2016-12-20 22:26:26

标签: sql scala slick

我有一个有趣的用例。基本上我需要合并sourceUser: UserRowtargetUser: UserRow。合并意味着将sourceUser标记为非活动状态,并将所有源链接帐户(详细信息)复制到目标用户。所有遗漏的细节,例如可以检出UserRow光滑生成的类型等in this github project

我可以在SQL中做到这一点没有问题,例如。

UPDATE "user" SET active=false WHERE user_id=${sourceUserId};
INSERT INTO linked_account(user_id, provider_key, provider_hashed) 
   SELECT ${targetUserId}, provider_key, provider_hashed
   FROM linked_account 
   WHERE user_id=${sourceUserId}

我的Slick尝试是通用dao和光滑动作的混合,无法看到如何以事务方式撰写。我也找不到同时选择和插入的方法,即这种尝试不能编译:

def merge(targetUser: UserRow, sourceUser: UserRow) : Future[Unit] = {
  // deactivate the source user
  update(sourceUser.copy(active = false))

  val action = (for {
    // get the linked accounts to source 
    linkedAccountSource <- LinkedAccount
    // match the source user
    source <- User if source.id === sourceUser.id && linkedAccountSource.userId === source.id
    // insert into target the previously matched source linked 
    // account but having target user as user_id
    // ........................................ doesn't compile here VVVVVVVVVVVVVVVVVVVVVVV
    linkedAccountTarget <- (LinkedAccount += linkedAccountSource.copy(userId = targetUser.id))
    // match the target user attempting to update set active=false
    target <- User if target.id === targetUser.id
  } yield linkedAccountTarget).transactionally
  db.run(action)
}

更新我做到了这一点,但我收到了最后一次编译错误,基本上是想将targetUser.id分配给sourceUser.id

的关联帐户结果
def merge(targetUser: UserRow, sourceUser: UserRow) : Future[Unit] = {
  // define an update DBIOAction to deactivate sourceUser
  val updateAction = User.filter(_.id === sourceUser.id).update(sourceUser.copy(active = false))

  // selects all linkedAccount from sourceUser but yield the userId of the targetUser
  val selectAction = (for {
    linkedAccount <- LinkedAccount
    user <- User if user.id === sourceUser.id && user.id === linkedAccount.userId
  } yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result

  // define an insert DBIOAction to insert all the selected linked accounts from sourceUser to targetUser
  val insertAction = selectAction.flatMap(LinkedAccount ++= _)

  // combine both actions using >> and transactionally
  db.run((updateAction >> insertAction).transactionally).map(_ => ())
}

和最后一个错误,我尝试在不同的地方做.as[LinkedAccountRow],但它不喜欢它:

[error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/UserDao.scala:101: type mismatch;
[error]  found   : Seq[(Long, String, String, Option[java.sql.Timestamp])]
[error]  required: Iterable[generated.Tables.LinkedAccountRow]
[error]       val insertAction = selectAction.flatMap(LinkedAccount ++= _)
[error]                                                                 ^

2 个答案:

答案 0 :(得分:4)

如果你改变了这一点,这一切都不会起作用:

...
} yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result
...

以这些方式:

...
} yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result
.map(LinkedAcount.tuppled)
...

此外,您可以使用巧妙的技巧在一个INSERT...SELECT操作中执行SQL。这将模仿您想要的SQL:

INSERT INTO linked_account(user_id, provider_key, provider_hashed) 
SELECT ${targetUserId}, provider_key, provider_hashed
FROM linked_account 
WHERE user_id=${sourceUserId}

这将是以下几点:

LinkedAccount
      .map(acc => (acc.userId, acc.prividerKey, providerHashed))
      .forceInsertQuery(
           for {
                acc <- LinkedAccount if acc.userId === sourceUser.id
            } yield (acc))
            .map(acc => 
                (acc.userId, acc.privderKey, acc.providerHashed))
       )

forceInsertQuery显然是关键所在。您基本上可以在其中包含任何内容(例如查询多个连接等),只要它最终生成与原始mapforceInsertQuery之前的那个)匹配的投影。

附加说明: 正如forceInsertQuery内的内容实际上是常规Query,您可以执行以下操作:预编译。这将是这样的:

// this would need to be obviously created once, perhaps in your DAO
// as member value
private val query = Compiled(sourceUserId: Rep[Long] => (for {
    acc <- LinkedAccount if acc.userId === sourceUserId
} yield (acc))
    .map(acc => 
        (acc.userId, acc.privderKey, acc.providerHashed)
    )
)
...
// and inside your method
LinkedAccount
      .map(acc => (acc.userId, acc.prividerKey, providerHashed))
      .forceInsertQuery( query(sourceUser.id) )

答案 1 :(得分:1)

我现在正在打电话,但我认为你应该在列元组而不是<>上使用as[LinkedAccountRow]运算符。

<>将两个参数作为在列元组和目标对象之间转换的函数对。对于案例类,这是apply.tupledunapply函数。

最合适的地方应该是yield理解的selectAction条款。