Scala编译报错:无法找到doobie.Write隐式值问题排查
解决Doobie编译错误:找不到
doobie.Write[VmdbPlanningUpdate]隐式值 问题背景
编写了一个Doobie查询但编译失败,报错提示无法找到doobie.Write[VmdbPlanningUpdate]类型的隐式参数W。
报错信息
[error] /Users/johannesklauss/Documents/campaign-service/server/src/main/scala/de/x/vmdbplanningvalues/impl/queries/UpdateVmdbPlanningValuesQuery.scala:60:35: could not find implicit value for parameter W: doobie.Write[de.x.vmdbplanningvalues.impl.queries.UpdateVmdbPlanningValuesQuery.VmdbPlanningUpdate] [error] Update[VmdbPlanningUpdate](sql).updateMany(updates) [error]
相关代码
UpdateVmdbPlanningValuesQuery.scala
package de.x.vmdbplanningvalues.impl.queries import de.x.campaignplans.CampaignPlanId import de.x.distributionbrands.DistributionBrandId import de.x.vmdbplanningvalues._ import doobie._ object UpdateVmdbPlanningValuesQuery { case class VmdbPlanningUpdate( creditRatingRejections: Option[Double], goodsCoupons: Option[Double], customerDiscounts: Option[Double], campaignPlanId: String, distributionBrandId: String, country: String, categoryId: String, lane: VmdbLane ) def apply( distributionBrand: DistributionBrandId, country: String, campaignPlanId: CampaignPlanId, category: String, updates: List[VmdbPlanningValuesForVmdbLane] ): ConnectionIO[Unit] = { for { _ <- updateQuery(updates.map(update => VmdbPlanningUpdate( update.creditRatingRejections, update.goodsCoupons, update.customerDiscounts, campaignPlanId.id, distributionBrand.id, country, category, update.lane ) )) } yield () } def updateQuery[T: Write](updates: List[VmdbPlanningUpdate]): ConnectionIO[Int] = { val sql = """ UPDATE vmdb_planning_values as vmdb SET vmdb.credit_rating_rejections = ?, vmdb.goods_coupons = ?, vmdb.customer_discounts = ? FROM campaign_plan cp WHERE cp.id = ? AND vmdb.distribution_brand_id = ? AND vmdb.country_id = ? AND vmdb.year=DATE_PART('year', cp.start_date) AND vmdb.quarter=DATE_PART('quarter', cp.start_date) AND vmdb.category_id = ? AND vmdb.lane = ? """ Update[VmdbPlanningUpdate](sql).updateMany(updates) } }
VmdbLane.scala
package de.x.vmdbplanningvalues import io.circe.Decoder.Result import io.circe._ sealed trait VmdbLane { override def toString: String = VmdbLane.toEnum(this) } object VmdbLane { case object New extends VmdbLane case object CarryOver extends VmdbLane case object Sale extends VmdbLane case object Sum extends VmdbLane def toEnum(e: VmdbLane): String = e match { case New => "new" case CarryOver => "carryOver" case Sale => "sale" case Sum => "sum" } def fromEnum(s: String): Option[VmdbLane] = Option(s) collect { case "new" => New case "carryOver" => CarryOver case "sale" => Sale case "sum" => Sum } implicit val jsonFormat: Encoder[VmdbLane] with Decoder[VmdbLane] = new Encoder[VmdbLane] with Decoder[VmdbLane] { override def apply(a: VmdbLane): Json = Encoder.encodeString(toEnum(a)) override def apply(c: HCursor): Result[VmdbLane] = c.value.asString.flatMap(s => fromEnum(s)) match { case Some(a) => Right(a) case None => Left(DecodingFailure("VmdbLane", c.history)) } } }
错误原因及解决方案
原因解析
Doobie的Update类型依赖隐式Write[A]实例,用于将自定义类型转换为JDBC可识别的参数。
VmdbPlanningUpdate是case class,Doobie能自动为包含基础类型的case class生成Write实例,但前提是所有字段类型都有对应的Write支持。- 问题出在
VmdbLane类型:仅定义了Circe的JSON编解码器,缺少Doobie所需的Meta[VmdbLane]实例(Write依赖Meta实现自定义类型与SQL类型的转换)。
解决方案
- 为
VmdbLane添加Doobie的Meta隐式实例
在VmdbLane对象中新增以下代码,让Doobie知道如何在VmdbLane与SQL字符串之间转换:
import doobie.Meta implicit val vmdbLaneMeta: Meta[VmdbLane] = Meta[String].timap( s => fromEnum(s).getOrElse(throw new IllegalArgumentException(s"无效的VmdbLane值: $s")) )(toEnum)
注:转换失败时抛出异常,可根据业务需求调整错误处理逻辑。
- 导入Doobie自动推导依赖
在UpdateVmdbPlanningValuesQuery中添加Doobie隐式导入,让框架自动为VmdbPlanningUpdate生成Write实例:
import doobie.implicits._
- 优化
updateQuery方法(可选)
原方法定义的类型参数T: Write未实际使用,可修改为更严谨的写法:
def updateQuery(updates: List[VmdbPlanningUpdate]): ConnectionIO[Int] = { // 原有SQL及逻辑代码 }
内容的提问来源于stack exchange,提问作者Johannes Klauß
相关产品推荐
相关产品推荐

