如何在Scala Slick插入数据后返回对象?
Scala Slick 通用插入返回对象函数实现方案
我在使用Scala Slick编写可返回对象的通用插入函数时遇到问题:针对特定实体的插入函数可以正常运行,但通用版本在调用copy方法时报错Cannot resolve symbol copy。
可正常运行的特定实体插入函数
// 仅适用于Employee实体和对应表,运行正常 def insertEmployee(employee: Employee): IO[Employee] = DB.run(db, { def returnObj = (EmployeeTable.instance returning EmployeeTable.instance.map(_.id)).into((obj, id) => obj.copy(id = id)) returnObj += employee })
报错的通用版本代码
// 无法运行,"copy"处报错:Cannot resolve symbol copy def withReturningObj[E <: BaseEntity, T <: BaseTable[E]](query: TableQuery[T]) = (query returning query.map(_.id)).into((obj, id) => obj.copy(id = id))
问题原因
编译器无法确认所有BaseEntity的子类都具备copy方法——虽然实体类(比如Employee)是case class,自带copy方法,但泛型约束仅指定E <: BaseEntity,没有明确要求子类实现该方法。
解决方案
方案1:在BaseEntity中定义copy契约
给BaseEntity添加抽象的copy方法,强制子类实现(case class自动生成的copy方法会自动匹配,只要字段名一致):
// 定义BaseEntity trait trait BaseEntity { def id: Option[Long] // 根据你的实际id类型调整,比如Long/Int等 def copy(id: Option[Long]): this.type } // 实体类继承BaseEntity(case class自动实现copy方法) case class Employee(id: Option[Long], name: String, age: Int) extends BaseEntity
此时通用函数的泛型约束E <: BaseEntity就能保证obj有copy方法,报错会消失。
方案2:增强泛型约束
直接利用case class的特性,给泛型添加Product with Serializable约束(case class默认实现这两个特质),让编译器识别出实体类是case class,从而允许调用copy:
def withReturningObj[E <: BaseEntity with Product with Serializable, T <: BaseTable[E]](query: TableQuery[T]) = (query returning query.map(_.id)).into((obj, id) => obj.copy(id = id))
这种方式无需修改BaseEntity,但依赖case class的隐式特性,适合实体类都是case class的场景。
方案3:使用类型类封装复制逻辑
如果不想修改BaseEntity的结构,可以定义类型类来处理实体的id复制逻辑,灵活性更高:
// 定义类型类,负责实体的id复制 trait EntityCopier[E] { def copyWithId(entity: E, newId: Long): E } // 给所有BaseEntity的case class子类提供默认实现 implicit def caseClassCopier[E <: BaseEntity : Product : Serializable]: EntityCopier[E] = new EntityCopier[E] { override def copyWithId(entity: E, newId: Long): E = { val product = entity.asInstanceOf[Product] // 替换id字段的值,这里假设id是Option[Long],根据实际类型调整 val updatedArgs = product.productIterator.toArray.map { case Some(_: Long) => Some(newId) case arg => arg } // 通过反射创建新实例 product.getClass.getConstructors.head.newInstance(updatedArgs: _*).asInstanceOf[E] } } // 修改通用函数,引入类型类隐式参数 def withReturningObj[E <: BaseEntity, T <: BaseTable[E]](query: TableQuery[T])(implicit copier: EntityCopier[E]) = (query returning query.map(_.id)).into((obj, id) => copier.copyWithId(obj, id))
内容的提问来源于stack exchange,提问作者user9462447
相关产品推荐
相关产品推荐

