Slick 3.1中声明日期/时间列触发编译错误
这个问题我之前也碰到过!本质是多个隐式转换之间的歧义让Slick的编译器在解析表映射的投影(也就是def *)时懵了,不知道该选哪个隐式来处理LocalDateTime的映射。
为什么会报错?
你看到的anyToToShapedValue错误提示,本质是编译器在尝试解析def *里的字段元组时,发现有多个隐式转换都能作用于LocalDateTime相关的类型,导致它无法确定正确的转换路径。通常是因为你定义了两个针对LocalDateTime的隐式——比如一个是Slick需要的列类型映射隐式,另一个是多余的、方向或者作用重复的隐式转换。
解决方案:精简隐式,明确映射
1. 只保留Slick所需的列类型隐式
Slick对Java 8的LocalDateTime没有内置支持,所以我们只需要定义一个BaseColumnType隐式来完成LocalDateTime和数据库原生类型(比如Timestamp)的转换,不需要额外的其他隐式:
import slick.jdbc.MySQLProfile.api._ // 换成你用的数据库Profile(比如PostgresProfile) import java.time.LocalDateTime // 这是唯一需要的隐式:把LocalDateTime映射到数据库的Timestamp implicit val localDateTimeColumnType: BaseColumnType[LocalDateTime] = MappedColumnType.base[LocalDateTime, java.sql.Timestamp]( localDateTime => java.sql.Timestamp.valueOf(localDateTime), timestamp => timestamp.toLocalDateTime )
删掉你之前定义的另一个隐式,这个就足够让Slick处理LocalDateTime的读写了。
2. 确保def *的投影正确对应Case类
你的表类的def *需要准确对应MyEmail的构造函数,用<>操作符完成元组到Case类的双向映射:
case class MyEmail(id: Int, sender: String, receiver: String, sendTime: LocalDateTime) class MyEmailDB(tag: Tag) extends Table[MyEmail](tag, "my_emails") { // 定义表字段 def id = column[Int]("id", O.PrimaryKey, O.AutoInc) def sender = column[String]("sender") def receiver = column[String]("receiver") def sendTime = column[LocalDateTime]("send_time") // 这里会自动用上上面的隐式 // 正确的投影:元组对应Case类字段,双向映射 def * = (id, sender, receiver, sendTime) <> (MyEmail.tupled, MyEmail.unapply) }
3. 如果确实需要多个隐式:缩小作用域
如果你有特殊场景必须保留两个隐式,那要把它们放在不同的作用域里(比如不同的object/trait),或者在def *里用implicitly明确指定需要的隐式,不过这种情况很少见,大部分时候精简到一个列类型隐式就够了。
总结
核心就是消除隐式歧义——Slick只需要一个BaseColumnType隐式来处理LocalDateTime和数据库类型的转换,多余的隐式会让编译器迷失方向。删掉重复的隐式,确保def *的投影和Case类正确对应,问题就能解决。
内容的提问来源于stack exchange,提问作者ps0604

