Slick 3.1.x 以事务方式将所有详细信息从一个主控插入另一个主控?
Slick 3.1.x Insert all details from one master to the other, transactionally?
我有一个有趣的用例。基本上我有一个 sourceUser: UserRow
和 targetUser: UserRow
需要合并。合并意味着将 sourceUser
标记为不活动并将所有源链接帐户(详细信息)复制到目标用户。所有缺失的细节,例如UserRow
可以检查 slick 生成的类型等 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 和看不到如何以事务方式组合的 slick 动作的混合体。我也找不到同时插入 select 和插入的方法,即此尝试无法编译:
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)
}
UPDATE 我已经走到这一步了,但是我遇到了最后一个编译器错误,基本上是想将 targetUser.id
分配给 sourceUser.id
[= 的链接帐户结果23=]
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] ^
我现在在 phone,但我认为您应该在列元组上使用 <>
运算符而不是 as[LinkedAccountRow]
。
<>
将列元组和目标对象之间的函数对转换作为两个参数。对于案例 类,这是 apply.tupled
、unapply
函数。
调用它最合适的地方应该是在 selectAction
理解的 yield
子句中。
如果你只是改变这个,它会不会全部工作:
...
} yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result
...
沿着这些方向:
...
} yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result
.map(LinkedAcount.tuppled)
...
您还可以使用巧妙的技巧在一次 SQL
操作中执行 INSERT...SELECT
。这将模仿您想要的 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
显然是这里的关键。您基本上可以在其中包含任何您想要的东西(例如具有多个连接的查询等),只要它最终产生与原始 map
(forceInsertQuery
之前的那个)匹配的投影。
补充说明:
因为 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) )
我有一个有趣的用例。基本上我有一个 sourceUser: UserRow
和 targetUser: UserRow
需要合并。合并意味着将 sourceUser
标记为不活动并将所有源链接帐户(详细信息)复制到目标用户。所有缺失的细节,例如UserRow
可以检查 slick 生成的类型等 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 和看不到如何以事务方式组合的 slick 动作的混合体。我也找不到同时插入 select 和插入的方法,即此尝试无法编译:
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)
}
UPDATE 我已经走到这一步了,但是我遇到了最后一个编译器错误,基本上是想将 targetUser.id
分配给 sourceUser.id
[= 的链接帐户结果23=]
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] ^
我现在在 phone,但我认为您应该在列元组上使用 <>
运算符而不是 as[LinkedAccountRow]
。
<>
将列元组和目标对象之间的函数对转换作为两个参数。对于案例 类,这是 apply.tupled
、unapply
函数。
调用它最合适的地方应该是在 selectAction
理解的 yield
子句中。
如果你只是改变这个,它会不会全部工作:
...
} yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result
...
沿着这些方向:
...
} yield (targetUser.id, linkedAccount.providerKey, linkedAccount.providerPassword, linkedAccount.modified)).result
.map(LinkedAcount.tuppled)
...
您还可以使用巧妙的技巧在一次 SQL
操作中执行 INSERT...SELECT
。这将模仿您想要的 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
显然是这里的关键。您基本上可以在其中包含任何您想要的东西(例如具有多个连接的查询等),只要它最终产生与原始 map
(forceInsertQuery
之前的那个)匹配的投影。
补充说明:
因为 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) )