玩 Scala 和 Slick3,在 "NOT NULL DEFAULT CURRENT_TIMESTAMP" 列出现 "Column xx cannot be null" 错误
Play Scala & Slick3, got "Column xx cannot be null" error at a column with "NOT NULL DEFAULT CURRENT_TIMESTAMP"
我正在使用带有 slick 3.2.1 的 Play Framework 2.6 创建一个网络应用程序。
当我尝试向 table "USER" 中插入一条记录时,它有 created_at 列和 "NOT NULL DEFAULT CURRENT_TIMESTAMP"(我正在使用 MySQL),数据库抛出一个错误 "Column 'created_at' cannot be null".
我知道slick生成的SQL是错误的。该语句试图将 null 插入 created_at 列。让 slick 生成不包含 created_at 列的 SQL 的正确方法是什么?
scala 代码摘录。
import org.joda.time.DateTime
case class User(
id: Option[Long],
accountId: Long,
name: String,
description: Option[String] = None,
createdAt: Option[DateTime: = None,
)
class UserTable(tag: Tag) extends Table[User](tag, "user") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def accountId = column[Long]("account_id")
def name = column[String]("name")
def description = column[Option[String]]("description")
def createdAt = column[Option[DateTime]]("created_at")
def * = (id.?, accountId, name, description, createdAt) <> (User.tupled, User.unapply)
}
object Users extends TableQuery(new UserTable(_)) {
}
val user = User(None, accountId, name, description)
val insert = for {
newId <- (Users returning Users.map(_.id)) += user
} yield newId
db.run(insert)
生成SQL
[debug] s.j.J.statement - Preparing insert statement (returning: id): insert into `user` (`account_id`,`name`,`description`,`created_at`) values (?,?,?,?)
[debug] s.j.J.parameter - /------+--------+---------+-----------\
[debug] s.j.J.parameter - | 1 | 2 | 3 | 4 |
[debug] s.j.J.parameter - | Long | String | VARCHAR | TIMESTAMP |
[debug] s.j.J.parameter - |------+--------+---------+-----------|
[debug] s.j.J.parameter - | 1 | user01 | NULL | NULL |
[debug] s.j.J.parameter - \------+--------+---------+-----------/
重新思考你的例子。您的 table 不接受 null
作为列 created_at
的值。但是您的域模型允许此字段可以是 None
,它不能以 null
以外的任何其他方式在数据库中表示。因此,如果您想要 slick 生成正确的查询,则必须将 created_at
的类型更改为 DateTime
。
createdAt
不能是 Option
,你可以放 DateTime.now
以避免使用 Option
。还需要 DateTime
列映射器:
import slick.lifted.MappedTypeMapper
import java.sql.Date
import org.joda.time.DateTime
import slick.lifted.TypeMapper.DateTypeMapper
object DateTimeMapper {
implicit def date2dt = MappedTypeMapper.base[DateTime, Date] (
dt => new Date(dt.getMillis),
date => new DateTime(date)
)
}
或者您很可能会在 Timestamp
时需要它,而不是 Date
:
implicit def dateTime =
MappedColumnType.base[DateTime, Timestamp](
dt => new Timestamp(dt.getMillis),
ts => new DateTime(ts.getTime)
)
如果你不想处理DateTime.now
,你可以这样做:
def created = column[DateTime]("createdAt", SqlType("timestamp not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"))
此处的其他信息:
http://queirozf.com/entries/scala-slick-dealing-with-datetime-timestamp-attributes
如果你看你的投影,你必须举起 created_at
,就像你举起你的身份证一样。
所以你的投影会变成:
def * = (id.?, accountId, name, description, createdAt.?) <> (User.tupled, User.unapply)
注意 createdAt 后的 .?
。
我找到了两种解决(破解)这个问题的方法(使用 Slick 3.2.3)。幸运的是,对于我的用例,我没有使用 Slick 来创建 table 或生成 table 类,所以我并不关心它生成的模式是否有效.
假设我们有一个简单的用户 model/table:
case class User(id: Long, name: String, createdAt: Timestamp, updatedAt: Timestamp)
class UsersDAO(tag: Tag) extends Table[User](tag, "users") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def createdAt = column[Timestamp]("created_at", O.AutoInc)
def updatedAt = column[Timestamp]("updated_at", O.AutoInc)
override def * = (id, name, createdAt, updatedAt) <>
(User.tupled, User.unapply)
}
选项 1:使用自定义插入方法
object UsersDAO extends TableQuery(new UsersDAO(_)) {
def create(u: User) = this map { c =>
(c.name) // only include columns you want to send
} returning this.map(_.id) += (u.name)
}
选项 2:将字段标记为 O.AutoInc
如果你用 O.AutoInc
标记 createdAt
和 updatedAt
列(就像我上面做的那样),你可以简单地使用 +=
语法:
object UsersDAO extends TableQuery(new UsersDAO(_)) {
def create(u: User) = this returning this.map(_.id) += user
}
目前似乎有几个与此相关的未解决问题。希望一旦他们得到解决,就会有更好的方法。
https://github.com/slick/slick/issues/1448
https://github.com/slick/slick/pull/1533
我正在使用带有 slick 3.2.1 的 Play Framework 2.6 创建一个网络应用程序。 当我尝试向 table "USER" 中插入一条记录时,它有 created_at 列和 "NOT NULL DEFAULT CURRENT_TIMESTAMP"(我正在使用 MySQL),数据库抛出一个错误 "Column 'created_at' cannot be null".
我知道slick生成的SQL是错误的。该语句试图将 null 插入 created_at 列。让 slick 生成不包含 created_at 列的 SQL 的正确方法是什么?
scala 代码摘录。
import org.joda.time.DateTime
case class User(
id: Option[Long],
accountId: Long,
name: String,
description: Option[String] = None,
createdAt: Option[DateTime: = None,
)
class UserTable(tag: Tag) extends Table[User](tag, "user") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def accountId = column[Long]("account_id")
def name = column[String]("name")
def description = column[Option[String]]("description")
def createdAt = column[Option[DateTime]]("created_at")
def * = (id.?, accountId, name, description, createdAt) <> (User.tupled, User.unapply)
}
object Users extends TableQuery(new UserTable(_)) {
}
val user = User(None, accountId, name, description)
val insert = for {
newId <- (Users returning Users.map(_.id)) += user
} yield newId
db.run(insert)
生成SQL
[debug] s.j.J.statement - Preparing insert statement (returning: id): insert into `user` (`account_id`,`name`,`description`,`created_at`) values (?,?,?,?)
[debug] s.j.J.parameter - /------+--------+---------+-----------\
[debug] s.j.J.parameter - | 1 | 2 | 3 | 4 |
[debug] s.j.J.parameter - | Long | String | VARCHAR | TIMESTAMP |
[debug] s.j.J.parameter - |------+--------+---------+-----------|
[debug] s.j.J.parameter - | 1 | user01 | NULL | NULL |
[debug] s.j.J.parameter - \------+--------+---------+-----------/
重新思考你的例子。您的 table 不接受 null
作为列 created_at
的值。但是您的域模型允许此字段可以是 None
,它不能以 null
以外的任何其他方式在数据库中表示。因此,如果您想要 slick 生成正确的查询,则必须将 created_at
的类型更改为 DateTime
。
createdAt
不能是 Option
,你可以放 DateTime.now
以避免使用 Option
。还需要 DateTime
列映射器:
import slick.lifted.MappedTypeMapper
import java.sql.Date
import org.joda.time.DateTime
import slick.lifted.TypeMapper.DateTypeMapper
object DateTimeMapper {
implicit def date2dt = MappedTypeMapper.base[DateTime, Date] (
dt => new Date(dt.getMillis),
date => new DateTime(date)
)
}
或者您很可能会在 Timestamp
时需要它,而不是 Date
:
implicit def dateTime =
MappedColumnType.base[DateTime, Timestamp](
dt => new Timestamp(dt.getMillis),
ts => new DateTime(ts.getTime)
)
如果你不想处理DateTime.now
,你可以这样做:
def created = column[DateTime]("createdAt", SqlType("timestamp not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"))
此处的其他信息: http://queirozf.com/entries/scala-slick-dealing-with-datetime-timestamp-attributes
如果你看你的投影,你必须举起 created_at
,就像你举起你的身份证一样。
所以你的投影会变成:
def * = (id.?, accountId, name, description, createdAt.?) <> (User.tupled, User.unapply)
注意 createdAt 后的 .?
。
我找到了两种解决(破解)这个问题的方法(使用 Slick 3.2.3)。幸运的是,对于我的用例,我没有使用 Slick 来创建 table 或生成 table 类,所以我并不关心它生成的模式是否有效.
假设我们有一个简单的用户 model/table:
case class User(id: Long, name: String, createdAt: Timestamp, updatedAt: Timestamp)
class UsersDAO(tag: Tag) extends Table[User](tag, "users") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def createdAt = column[Timestamp]("created_at", O.AutoInc)
def updatedAt = column[Timestamp]("updated_at", O.AutoInc)
override def * = (id, name, createdAt, updatedAt) <>
(User.tupled, User.unapply)
}
选项 1:使用自定义插入方法
object UsersDAO extends TableQuery(new UsersDAO(_)) {
def create(u: User) = this map { c =>
(c.name) // only include columns you want to send
} returning this.map(_.id) += (u.name)
}
选项 2:将字段标记为 O.AutoInc
如果你用 O.AutoInc
标记 createdAt
和 updatedAt
列(就像我上面做的那样),你可以简单地使用 +=
语法:
object UsersDAO extends TableQuery(new UsersDAO(_)) {
def create(u: User) = this returning this.map(_.id) += user
}
目前似乎有几个与此相关的未解决问题。希望一旦他们得到解决,就会有更好的方法。 https://github.com/slick/slick/issues/1448 https://github.com/slick/slick/pull/1533