我是Slick的新手,因此我不确定错误使用implicits或Slick引起的问题是否不允许我做我想做的事情。
简而言之,我在Postgres中使用Slick-pg扩展名来支持JSONB。我还使用spray-json
将JSONB字段反序列化为案例类。
为了自动将列转换为对象,我编写了通用隐式JsonColumnType
,您可以在下面看到。它允许我有任何case类,我将json formatter定义为转换为jsonb字段。
另一方面,我希望同一列的别名为JsValue
,以便我可以使用JSONB-operators。
import com.github.tminglei.slickpg._
import com.github.tminglei.slickpg.json.PgJsonExtensions
import org.bson.types.ObjectId
import slick.ast.BaseTypedType
import slick.jdbc.JdbcType
import spray.json.{JsValue, RootJsonWriter, RootJsonReader}
import scala.reflect.ClassTag
trait MyPostgresDriver extends ExPostgresDriver with PgArraySupport with PgDate2Support with PgRangeSupport with PgHStoreSupport with PgSprayJsonSupport with PgJsonExtensions with PgSearchSupport with PgNetSupport with PgLTreeSupport {
override def pgjson = "jsonb" // jsonb support is in postgres 9.4.0 onward; for 9.3.x use "json"
override val api = MyAPI
private val plainAPI = new API with SprayJsonPlainImplicits
object MyAPI extends API with DateTimeImplicits with JsonImplicits with NetImplicits with LTreeImplicits with RangeImplicits with HStoreImplicits with SearchImplicits with SearchAssistants { //with ArrayImplicits
implicit val ObjectIdColumnType = MappedColumnType.base[ObjectId, Array[Byte]](
{ obj => obj.toByteArray }, { arr => new ObjectId(arr) }
)
implicit def JsonColumnType[T: ClassTag](implicit reader: RootJsonReader[T], writer: RootJsonWriter[T]) = {
val columnType: JdbcType[T] with BaseTypedType[T] = MappedColumnType.base[T, JsValue]({ obj => writer.write(obj) }, { json => reader.read(json) })
columnType
}
}
}
object MyPostgresDriver extends MyPostgresDriver
以下是我的表格定义方式(最小化版本)
case class Article(id : ObjectId, ids : Ids)
case class Ids(doi: Option[String], pmid: Option[Long])
class ArticleRow(tag: Tag) extends Table[Article](tag, "articles") {
def id = column[ObjectId]("id", O.PrimaryKey)
def idsJson = column[JsValue]("ext_ids")
def ids = column[Ids]("ext_ids")
private val fromTuple: ((ObjectId, Ids)) => Article = {
case (id, ids) => Article(id, ids)
}
private val toTuple = (v: Article) => Option((v.id, v.ids))
def * = ProvenShape.proveShapeOf((id, ids) <> (fromTuple, toTuple))(MappedProjection.mappedProjectionShape)
}
private val articles = TableQuery[ArticleRow]
最后,我有按json字段值查找文章的功能
def getArticleByDoi(doi : String): Future[Article] = {
val query = (for (a <- articles if (a.idsJson +>> "doi").asColumnOf[String] === doi) yield a).take(1).result
slickDb.run(query).map { items =>
items.headOption.getOrElse(throw new RuntimeException(s"Article with doi $doi is not found"))
}
}
可悲的是,我在运行时
中遇到异常java.lang.ClassCastException: spray.json.JsObject cannot be cast to server.models.db.Ids
问题发生在SpecializedJdbcResultConverter.base,其中ti.getValue
被错误调用ti
。它应该是slick.driver.JdbcTypesComponent$MappedJdbcType
,而是它com.github.tminglei.slickpg.utils.PgCommonJdbcTypes$GenericJdbcType
。结果错误的类型被传递到我的元组转换器。
是什么让Slick为列选择不同的类型,即使在表行类中有明确的投影定义?
演示该问题的示例项目是here。