关于Slick原生SQL中使用TableQuery[Foo]及GetResult推导的问询
这个问题问得很到位!我来一步步帮你梳理清楚Slick里的这些细节:
答案是默认不行。原因在于Slick的「提升嵌入(lifted embedding)」和「原生SQL查询」是两套独立的机制,依赖的类型类完全不同:
TableQuery[Foo]对应的是Slick的类型安全查询体系,它依赖Shape、Column等类型类来构建和执行类型化的查询。- 而原生SQL的
as[Foo]操作需要的是GetResult[Foo]隐式实例,这个类型类负责把JDBC结果集的行映射到Foo对象上。
两者的设计目标不同:提升嵌入是为了让你用Scala代码构建类型安全的SQL,而原生SQL是为了支持复杂的手写SQL,所以Slick不会自动把TableQuery的定义转换成GetResult实例。
不需要重复定义Foo本身,但你确实需要为Foo提供GetResult实例——不过这个实例不一定需要手动写死。
举个例子,假设你已经有了这样的Foo case class和Table定义:
case class Foo(id: Int, name: String) class FooTable(tag: Tag) extends Table[Foo](tag, "foo") { def id = column[Int]("id", O.PrimaryKey) def name = column[String]("name") // 这里的投影已经把列和Foo的构造函数绑定了 def * = (id, name) <> (Foo.tupled, Foo.unapply) } val fooTable = TableQuery[FooTable]
你不需要重新定义Foo,只需要为它提供GetResult实例即可,而这个实例可以自动推导生成。
当然可以!有几种实用的方式:
方式1:利用Slick的宏自动推导
如果你使用的是Slick 3.x及以上版本,并且Foo是case class,同时查询返回的列顺序和Foo的构造函数参数顺序完全一致,那么可以直接用Slick提供的宏来自动生成GetResult:
import slick.jdbc.GetResult implicit val fooGetResult: GetResult[Foo] = GetResult.derive[Foo]
这个宏会自动根据case class的参数列表生成结果集映射逻辑,本质上和你手动写GetResult(r => Foo(r.<<, r.<<))是一样的。
方式2:基于Table的投影来构建
如果你想严格对齐FooTable里的列定义(避免列顺序不一致的问题),可以利用Table的投影(就是def *定义的部分)来生成GetResult:
implicit val fooGetResult: GetResult[Foo] = { // 拿到FooTable的投影,也就是和Foo绑定的列组合 val projection = fooTable.* // 利用投影的createGetResult方法生成对应的GetResult实例 projection.createGetResult() }
这种方式的好处是,只要你修改了FooTable里的列投影定义,GetResult会自动同步,不需要手动修改映射逻辑,避免了维护不一致的问题。
方式3:手动基于Table的列列表构建
如果需要更灵活的控制,也可以遍历FooTable的列来构建GetResult:
implicit val fooGetResult: GetResult[Foo] = GetResult { r => val values = fooTable.columns.map(col => r.nextValue(col.baseType)) // 把列值转换成Foo对象,这里需要确保列顺序和Foo构造函数匹配 Foo.tupled(values.asInstanceOf[(Int, String)]) }
不过这种方式需要处理类型转换,不如前两种简洁,适合特殊场景下的自定义映射。
总结一下:
- 原生SQL不能直接用
TableQuery替代GetResult,但可以基于Table的定义自动推导GetResult - 不需要重复定义
Foo,只需要为它提供GetResult实例,而这个实例可以自动生成 - 利用Slick的宏或者
Table的投影,就能从TableQuery[Foo]的定义推导出GetResult[Foo]
内容的提问来源于stack exchange,提问作者Klugscheißer

