如何在Scala Slick按ID查询前将外部ID转换为数据库主键?
在Scala Slick中自动转换外部哈希ID为数据库主键的方案
当然可以实现自动转换!你之前用case class的apply/unapply处理的是数据映射阶段(从数据库行转成case class对象,或者反过来),但filter是查询构造阶段,Slick会把这里的表达式翻译成SQL,所以那套逻辑没法覆盖到查询环节。不过我们可以通过扩展Slick的查询API来封装转换逻辑,下面给你几个实用的方案:
方案1:给特定TableQuery添加自定义查询方法
这是最直接的方案,针对你的items查询对象封装一个专用方法,内部自动完成ID转换:
首先先定义你的哈希ID转内部主键的函数:
// 假设你的外部ID是字符串,内部主键是Long类型 def externalIdToInternal(externalId: String): Long = { // 这里写你的哈希ID解析逻辑,比如解码哈希得到真实主键 // 示例:Base64解码或者哈希反向计算 java.util.Base64.getDecoder.decode(externalId).toLong }
然后给你的TableQuery添加隐式扩展方法:
// 先定义你的Table和TableQuery class Items(tag: Tag) extends Table[Item](tag, "items") { def id = column[Long]("id", O.PrimaryKey, O.AutoInc) // 其他列定义... def * = (id, /* 其他列字段 */) <> (Item.tupled, Item.unapply) } val items = TableQuery[Items] // 用隐式类扩展TableQuery的功能 implicit class ItemsQueryExtensions(query: TableQuery[Items]) { def byExternalId(externalId: String) = { val internalId = externalIdToInternal(externalId) query.filter(_.id === internalId) } }
现在你查询的时候就不用手动转换了,直接调用封装好的方法:
// itemId是外部哈希ID,直接传入即可 val res = items.byExternalId(itemId)
这个方案的好处是逻辑清晰,针对特定表定制,调用起来非常直观。
方案2:扩展列的比较操作符,支持直接传入外部ID
如果你希望保留filter的写法,让i.id === itemId直接生效,可以扩展Slick列的比较操作符,让它能自动识别外部ID并转换:
// 先定义外部ID的类型(可选,用来区分外部ID和普通字符串) case class ExternalId(value: String) // 扩展Column[Long]的===操作符,支持ExternalId类型 implicit class IdColumnComparisons(col: Column[Long]) { def ===(externalId: ExternalId): Column[Boolean] = { col === externalIdToInternal(externalId.value) } }
现在查询的时候,只需要把外部ID包装成ExternalId传入:
val res = items.filter(i => i.id === ExternalId(itemId))
这个方案让查询语法更自然,和原生的Slick写法几乎一致,适合习惯用原生filter语法的场景。
方案3:通用化处理(多表复用)
如果你的项目里多个表都需要处理外部ID转内部主键的逻辑,可以定义一个通用的扩展方案:
首先定义一个标识表有ID列的特质:
trait HasInternalId { type InternalIdType def id: Column[InternalIdType] }
让你的表实现这个特质:
class Items(tag: Tag) extends Table[Item](tag, "items") with HasInternalId { type InternalIdType = Long def id = column[Long]("id", O.PrimaryKey, O.AutoInc) // 其他列... } // 其他表也可以类似实现,比如Users表 class Users(tag: Tag) extends Table[User](tag, "users") with HasInternalId { type InternalIdType = Long def id = column[Long]("id", O.PrimaryKey, O.AutoInc) // 其他列... }
然后定义通用的隐式扩展,同时为每个表提供对应的ID转换器:
// 通用的TableQuery扩展方法 implicit class HasIdQueryExtensions[T <: Table[_] with HasInternalId](query: TableQuery[T]) { def byExternalId(externalId: String)(implicit converter: String => T#InternalIdType) = { val internalId = converter(externalId) query.filter(_.id === internalId) } } // 为Items表提供转换器 implicit val itemsIdConverter: String => Long = externalIdToInternal // 为Users表提供转换器(如果逻辑不同可以单独写) implicit val usersIdConverter: String => Long = externalIdToInternal
现在所有实现了HasInternalId的表都可以用byExternalId方法:
val itemRes = items.byExternalId(itemId) val userRes = users.byExternalId(userId)
这个方案适合多表场景,避免重复写转换逻辑,维护起来更方便。
内容的提问来源于stack exchange,提问作者rysv
相关产品推荐
相关产品推荐

