You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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类型的转换)。

解决方案

  1. 为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)

注:转换失败时抛出异常,可根据业务需求调整错误处理逻辑。

  1. 导入Doobie自动推导依赖
    在UpdateVmdbPlanningValuesQuery中添加Doobie隐式导入,让框架自动为VmdbPlanningUpdate生成Write实例:
import doobie.implicits._
  1. 优化updateQuery方法(可选)
    原方法定义的类型参数T: Write未实际使用,可修改为更严谨的写法:
def updateQuery(updates: List[VmdbPlanningUpdate]): ConnectionIO[Int] = {
  // 原有SQL及逻辑代码
}

内容的提问来源于stack exchange,提问作者Johannes Klauß

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 05:25:44