slick - 在 Slick 3.x 中定义和读取可为空的日期列

标签 slick slick-3.0

我有一个列类型为日期的表。此列接受空值,因此,我将其声明为一个选项(请参阅下面的字段 perDate)。问题显然是从/到 java.time.LocalDate 的隐式转换/java.sql.DateperDate 时从该表读取是不正确的为 null 失败并出现错误:

slick.SlickException: Read NULL value (null) for ResultSet column <computed>

这是 Slick 表定义,包括隐式函数:

import java.sql.Date
import java.time.LocalDate

class FormulaDB(tag: Tag) extends Table[Formula](tag, "formulas") {

  def sk = column[Int]("sk", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def descrip = column[Option[String]]("descrip")
  def formula = column[Option[String]]("formula")
  def notes = column[Option[String]]("notes")
  def periodicity = column[Int]("periodicity")
  def perDate = column[Option[LocalDate]]("per_date")(localDateColumnType)

  def * = (sk, name, descrip, formula, notes, periodicity, perDate) <> 
       ((Formula.apply _).tupled, Formula.unapply)


  implicit val localDateColumnType = MappedColumnType.base[Option[LocalDate], Date](
     {
        case Some(localDate) => Date.valueOf(localDate)
        case None => null
     },{
        sqlDate => if (sqlDate != null) Some(sqlDate.toLocalDate) else None
     }
  )

}

最佳答案

实际上,您从/到 java.time.LocalDate/java.sql.Date隐式转换 是正确的。

我遇到了同样的错误,在做一些研究后我发现 Slick SQL 编译器创建的节点实际上是 MappedJdbcType[Scala.Option -> LocalDate] 类型,而不是 选项[本地日期]

这就是为什么当映射编译器为您的 def perDate 创建列转换器时,它正在创建一个 Base/strong> ResultConverter而不是Option ResultConverter

这是基础转换器的 Slick 代码:

def base[T](ti: JdbcType[T], name: String, idx: Int) = (ti.scalaType match {
    case ScalaBaseType.byteType => new BaseResultConverter[Byte](ti.asInstanceOf[JdbcType[Byte]], name, idx)
    case ScalaBaseType.shortType => new BaseResultConverter[Short](ti.asInstanceOf[JdbcType[Short]], name, idx)
    case ScalaBaseType.intType => new BaseResultConverter[Int](ti.asInstanceOf[JdbcType[Int]], name, idx)
    case ScalaBaseType.longType => new BaseResultConverter[Long](ti.asInstanceOf[JdbcType[Long]], name, idx)
    case ScalaBaseType.charType => new BaseResultConverter[Char](ti.asInstanceOf[JdbcType[Char]], name, idx)
    case ScalaBaseType.floatType => new BaseResultConverter[Float](ti.asInstanceOf[JdbcType[Float]], name, idx)
    case ScalaBaseType.doubleType => new BaseResultConverter[Double](ti.asInstanceOf[JdbcType[Double]], name, idx)
    case ScalaBaseType.booleanType => new BaseResultConverter[Boolean](ti.asInstanceOf[JdbcType[Boolean]], name, idx)
    case _ => new BaseResultConverter[T](ti.asInstanceOf[JdbcType[T]], name, idx) {
      override def read(pr: ResultSet) = {
        val v = ti.getValue(pr, idx)
        if(v.asInstanceOf[AnyRef] eq null) throw new SlickException("Read NULL value ("+v+") for ResultSet column "+name)
        v
      }
    }
  }).asInstanceOf[ResultConverter[JdbcResultConverterDomain, T]]

不幸的是,我没有解决这个问题的方法,我建议的解决方法是按如下方式映射您的 perDate 属性:

import java.sql.Date
import java.time.LocalDate

class FormulaDB(tag: Tag) extends Table[Formula](tag, "formulas") {

  def sk = column[Int]("sk", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def descrip = column[Option[String]]("descrip")
  def formula = column[Option[String]]("formula")
  def notes = column[Option[String]]("notes")
  def periodicity = column[Int]("periodicity")
  def perDate = column[Option[Date]]("per_date")

  def toLocalDate(time : Option[Date]) : Option[LocalDate] = time.map(t => t.toLocalDate))  
  def toSQLDate(localDate : Option[LocalDate]) : Option[Date] = localDate.map(localDate => Date.valueOf(localDate)))  


  private type FormulaEntityTupleType = (Int, String, Option[String], Option[String], Option[String], Int, Option[Date])

  private val formulaShapedValue = (sk, name, descrip, formula, notes, periodicity, perDate).shaped[FormulaEntityTupleType]

  private val toFormulaRow: (FormulaEntityTupleType => Formula) = { formulaTuple => {
      Formula(formulaTuple._1, formulaTuple._2, formulaTuple._3, formulaTuple._4, formulaTuple._5, formulaTuple._6, toLocalDate(formulaTuple._7))
     }
  }

  private val toFormulaTuple: (Formula => Option[FormulaEntityTupleType]) = { formulaRow =>
   Some((formulaRow.sk, formulaRow.name, formulaRow.descrip, formulaRow.formula, formulaRow.notes, formulaRow.periodicity, toSQLDate(formulaRow.perDate)))
  }

  def * = formulaShapedValue <> (toFormulaRow, toFormulaTuple)

希望答案来得不算太晚。

关于slick - 在 Slick 3.x 中定义和读取可为空的日期列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37346832/

相关文章:

scala - 如何过滤由左连接生成的可选表

scala - Slick 3.0 通用 CRUD 实现中的类型参数绑定(bind)错误

sql - Slick 提升嵌入 : working with CURRENT_DATE (from PostgreSQL)?

斯卡拉 + 光滑 3 : Inserting the result of one query into another table

scala - 在 Slick 3.0 中,有一种方法可以在不使用特定 JDBC 驱动程序的情况下声明表

scala - 在 Slick 中插入

scala - 如何使用可空列编写光滑的表定义?

playframework - 私有(private)值 dbConfig 作为类型 Repository.this.dbConfig.driver.api.Query[T,E,S] 的一部分转义其定义范围

scala - 一对多的性能光滑查询

Slick 3.0 记录查询性能