我有一个表user_permissions
,它有46个权限列以及id
和created_date
。这个表有一个相应的UserPermissions
类:
class UserPermission(val id: Long,
val createdDate: Option[Timestamp],
val permission1: Boolean,
val permission2: Boolean,
...
val permission46: Boolean)
和光滑映射表
class UserPermissions(tag: Tag) extends Table[UserPermission](tag, "users_permissions") {
def * = (
id ::
createdDate ::
permission1 ::
permission2 ::
...
permission46 ::
HNil).shaped <> (
{ case x => UserPermission(
x(0), x(1), x(2), ... x(47))
},
{
UserPermission.unapply _
}
}
... <columns defined here>
)
现在我想更新由id
标识的UserPermission集。我的函数是:
object UserPermissions {
val userPermissions = TableQuery[UserPermissions]
def update(userPermission: UserPermission)(implicit session: Session) = {
userPermissions.filter(_.id === userPermission.id.get).update(userPermission)
}
}
This is not working and throw Exception:
play.api.Application$$anon$1: Execution exception[[SQLServerException: Cannot update identity column 'id'.]]
有意义,因为Slick生成的SQL是:
update "users_permissions" set "id" = ?, "created_date" = ?, ...
问题1 所以我的第一个问题是,我无法更新一个完整的UserPermission
对象与光滑。如果我有一个解决这个问题的方法,那就太好了。
因为我无法更新完整的对象,所以我想yield
我想要更新的列,然后触发一个更新查询。代码看起来像这样:
def update(obj: UserPermission)(implicit session: Session) = {
val query = for {
p <- userPermissions
if p.id === obj.id.get
} yield (p.permission1, p.permission2, ... p.permission46)
query.update(obj.permission1, obj.permission2, ... obj.permission46)
}
问题2现在slick没有更新query.update()
函数中的46列。它一次只能处理22列。我如何更新我的UserPermissions
对象?
我能想到的一个糟糕的解决方案是第一次更新22,然后22,然后在第三次查询中更新2。这将是3 db更新查询,我不想。
我的问题有解决方案吗?
依赖关系:
scalaVersion := "2.11.4"
"com.typesafe.play" %% "play-slick" % "0.8.1"
"com.typesafe.slick" %% "slick-extensions" % "2.1.0"
Slick的负责人Stefan Zeiger说我们不能。然而,他建议我们在平面的22+列表上嵌套投影:
// 2 classes for the nested structure
case class Part(i1: Int, i2: Int, i3: Int, i4: Int, i5: Int, i6: Int)
case class Whole(id: Int, p1: Part, p2: Part, p3: Part, p4: Part)
// Note that it's a Table[Int] -- we only map the primary key in *
object T extends Table[Int]("t_wide") {
def id = column[Int]("id", O.PrimaryKey)
def p1i1 = column[Int]("p1i1")
def p1i2 = column[Int]("p1i2")
def p1i3 = column[Int]("p1i3")
def p1i4 = column[Int]("p1i4")
def p1i5 = column[Int]("p1i5")
def p1i6 = column[Int]("p1i6")
def p2i1 = column[Int]("p2i1")
def p2i2 = column[Int]("p2i2")
def p2i3 = column[Int]("p2i3")
def p2i4 = column[Int]("p2i4")
def p2i5 = column[Int]("p2i5")
def p2i6 = column[Int]("p2i6")
def p3i1 = column[Int]("p3i1")
def p3i2 = column[Int]("p3i2")
def p3i3 = column[Int]("p3i3")
def p3i4 = column[Int]("p3i4")
def p3i5 = column[Int]("p3i5")
def p3i6 = column[Int]("p3i6")
def p4i1 = column[Int]("p4i1")
def p4i2 = column[Int]("p4i2")
def p4i3 = column[Int]("p4i3")
def p4i4 = column[Int]("p4i4")
def p4i5 = column[Int]("p4i5")
def p4i6 = column[Int]("p4i6")
// This is just the default projection -- It doesn't have to contain all columns
def * = id
// Instead, we use nested tuples for a full projection:
def all = (
id,
(p1i1, p1i2, p1i3, p1i4, p1i5, p1i6),
(p2i1, p2i2, p2i3, p2i4, p2i5, p2i6),
(p3i1, p3i2, p3i3, p3i4, p3i5, p3i6),
(p4i1, p4i2, p4i3, p4i4, p4i5, p4i6)
)
// And override create_* to get the DDL for all columns.
// Yeah, this is ugly. It used to be much simpler in ScalaQuery.
// We can add a helper method to simplify it.
override def create_* =
all.shaped.packedNode.collect {
case Select(Ref(IntrinsicSymbol(in)), f: FieldSymbol) if in == this => f
}.toSeq.distinct
}
T.ddl.create
// Insert into T.all. The extra ".shaped" call is needed because we cannot
// get the types in an implicit conversion due to SI-3346
T.all.shaped.insert(
0,
(11, 12, 13, 14, 15, 16),
(21, 22, 23, 24, 25, 26),
(31, 32, 33, 34, 35, 36),
(41, 42, 43, 44, 45, 46)
)
// Get the nested tuples in a query
val q1 = T.map(_.all)
println(q1.first)
// Map the result to the case classes
val i2 = q1.mapResult { case (id, p1, p2, p3, p4) =>
Whole(id, Part.tupled.apply(p1), Part.tupled.apply(p2), Part.tupled.apply(p3), Part.tupled.apply(p4))
}
println(i2.first)
现在是Slick的一个测试,包括版本3的测试。至于更新:
val oData = Whole(0,
Part(11, 12, 13, 14, 15, 16),
Part(21, 22, 23, 24, 25, 26),
Part(31, 32, 33, 34, 35, 36),
Part(41, 42, 43, 44, 45, 46)
)
val oData2 = Whole(10,
Part(111, 12, 13, 14, 15, 16),
Part(121, 22, 23, 24, 25, 26),
Part(131, 32, 33, 34, 35, 36),
Part(141, 42, 43, 44, 45, 46)
)
ts.ddl.create
ts.insert(oData)
assertEquals(oData, ts.first)
ts.filter(_.p1i2 === 12).update(oData2)
assertEquals(oData2, ts.first)
带有Slick投影的嵌套对象可以为您引入的单个对象进行平面化,也可以将其移除。
关于问题2的一些建议:
- 你能用Slick 3.0吗?这个版本似乎有一个解决方案
- 你能改变数据库的布局,以这样的方式权限是行而不是列?这似乎更易于扩展
这个问题是由slick-2.0
标记的,但如果你谷歌"光滑更新超过22",它是第一个链接,所以我相信详细描述一下解决方法是值得的。
如前所述,从v3开始,Slick有了自己的异构列表实现。
请在下面找到完整的Slick 3.3.3
示例:
import slick.collection.heterogeneous.HNil
import slick.jdbc.H2Profile.api._
import slick.jdbc.JdbcBackend.Database
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
object HelloWorld {
def main(args: Array[String]): Unit = {
val db = Database.forURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver="org.h2.Driver")
val table = new TableQuery(tag => new ManyColumnTable(tag))
val insertAction = table += Row(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)
val updateAction = table
.map { r => r.c1 :: r.c2 :: r.c3 :: r.c4 :: r.c5 :: r.c6 :: r.c7 :: r.c8 :: r.c9 :: r.c10 :: r.c11 :: r.c12 ::
r.c13 :: r.c14 :: r.c15 :: r.c16 :: r.c17 :: r.c18 :: r.c19 :: r.c20 :: r.c21 :: r.c22 :: r.c23 ::
r.c24 :: HNil }
.update { 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 ::
0 :: 0 :: 0 :: 0 :: HNil }
val f = for {
_ <- db.run(table.schema.create)
insertedRows <- db.run(insertAction)
updatedRows <- db.run(updateAction)
} yield {
println(s"There were $insertedRows rows inserted")
println(s"There were $updatedRows rows updated")
}
Await.ready(f, 10.seconds)
}
case class Row(c1:Int,c2:Int,c3:Int,c4:Int,c5:Int,c6:Int,c7:Int,c8:Int,c9:Int,c10:Int,c11:Int,c12:Int,c13:Int,
c14:Int,c15:Int,c16:Int,c17:Int,c18:Int,c19:Int,c20:Int,c21:Int,c22:Int,c23:Int,c24:Int)
class ManyColumnTable(tag: Tag) extends Table[Row](tag, "many_columns_table") {
def * = (c1 :: c2 :: c3 :: c4 :: c5 :: c6 :: c7 :: c8 :: c9 :: c10 :: c11 :: c12 :: c13 :: c14 :: c15 :: c16 ::
c17 :: c18 :: c19 :: c20 :: c21 :: c22 :: c23 :: c24 :: HNil).mapTo[Row]
def c1 = column[Int]("c1")
def c2 = column[Int]("c2")
def c3 = column[Int]("c3")
def c4 = column[Int]("c4")
def c5 = column[Int]("c5")
def c6 = column[Int]("c6")
def c7 = column[Int]("c7")
def c8 = column[Int]("c8")
def c9 = column[Int]("c9")
def c10 = column[Int]("c10")
def c11 = column[Int]("c11")
def c12 = column[Int]("c12")
def c13 = column[Int]("c13")
def c14 = column[Int]("c14")
def c15 = column[Int]("c15")
def c16 = column[Int]("c16")
def c17 = column[Int]("c17")
def c18 = column[Int]("c18")
def c19 = column[Int]("c19")
def c20 = column[Int]("c20")
def c21 = column[Int]("c21")
def c22 = column[Int]("c22")
def c23 = column[Int]("c23")
def c24 = column[Int]("c24")
}
}