如何使用带有Slick映射表的AutoInc键将记录插入PostgreSQL?如果我在我的case类中使用和选择id并将其设置为None,那么PostgreSQL会在插入时抱怨该字段不能为null。这适用于H2,但不适用于PostgreSQL:
//import scala.slick.driver.H2Driver.simple._
//import scala.slick.driver.BasicProfile.SimpleQL.Table
import scala.slick.driver.PostgresDriver.simple._
import Database.threadLocalSession
object TestMappedTable extends App{
case class User(id: Option[Int], first: String, last: String)
object Users extends Table[User]("users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def first = column[String]("first")
def last = column[String]("last")
def * = id.? ~ first ~ last <> (User, User.unapply _)
def ins1 = first ~ last returning id
val findByID = createFinderBy(_.id)
def autoInc = id.? ~ first ~ last <> (User, User.unapply _) returning id
}
// implicit val session = Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver").createSession()
implicit val session = Database.forURL("jdbc:postgresql:test:slicktest",
driver="org.postgresql.Driver",
user="postgres",
password="xxx")
session.withTransaction{
Users.ddl.create
// insert data
print(Users.insert(User(None, "Jack", "Green" )))
print(Users.insert(User(None, "Joe", "Blue" )))
print(Users.insert(User(None, "John", "Purple" )))
val u = Users.insert(User(None, "Jim", "Yellow" ))
// println(u.id.get)
print(Users.autoInc.insert(User(None, "Johnathan", "Seagul" )))
}
session.withTransaction{
val queryUsers = for {
user <- Users
} yield (user.id, user.first)
println(queryUsers.list)
Users.where(_.id between(1, 2)).foreach(println)
println("ID 3 -> " + Users.findByID.first(3))
}
}
使用上面的H2成功,但如果我发表评论并改为PostgreSQL,那么我得到:
[error] (run-main) org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint
org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint
答案 0 :(得分:15)
这是在这里工作:
object Application extends Table[(Long, String)]("application") {
def idlApplication = column[Long]("idlapplication", O.PrimaryKey, O.AutoInc)
def appName = column[String]("appname")
def * = idlApplication ~ appName
def autoInc = appName returning idlApplication
}
var id = Application.autoInc.insert("App1")
这就是我的SQL外观:
CREATE TABLE application
(idlapplication BIGSERIAL PRIMARY KEY,
appName VARCHAR(500));
<强>更新强>
关于带有User的映射表(如问题中)的具体问题可以解决如下:
def forInsert = first ~ last <>
({ (f, l) => User(None, f, l) }, { u:User => Some((u.first, u.last)) })
答案 1 :(得分:11)
我以不同的方式解决了这个问题。由于我希望我的User
对象在我的应用程序逻辑中始终具有id,并且在插入数据库期间唯一没有它的地方,我使用辅助NewUser
case case没有身份。
case class User(id: Int, first: String, last: String)
case class NewUser(first: String, last: String)
object Users extends Table[User]("users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def first = column[String]("first")
def last = column[String]("last")
def * = id ~ first ~ last <> (User, User.unapply _)
def autoInc = first ~ last <> (NewUser, NewUser.unapply _) returning id
}
val id = Users.autoInc.insert(NewUser("John", "Doe"))
同样,User
将1:1映射到数据库条目/行,而NewUser
可以替换为元组,如果你想避免使用额外的case类,因为它只用作a insert
调用的数据容器。
修改强> 如果你想要更安全(稍微增加冗长度),你可以使用案例类的特征,如下所示:
trait UserT {
def first: String
def last: String
}
case class User(id: Int, first: String, last: String) extends UserT
case class NewUser(first: String, last: String) extends UserT
// ... the rest remains intact
在这种情况下,您首先将模型更改应用于特征(包括您可能需要的任何mixin),并可选择将默认值添加到NewUser
。
作者的观点:我仍然更喜欢无特性解决方案,因为它更紧凑,对模型的更改是复制粘贴User
参数然后删除id
的问题(auto-inc主键),包括案例类声明和表格投影。
答案 2 :(得分:2)
我们使用的方法略有不同。我们不是创建进一步的投影,而是请求表的下一个id,将其复制到case类中,并使用默认投影“*”来插入表项。
对于postgres,它看起来像这样:
让你的表对象实现这个特性
trait TableWithId { this: Table[_] =>
/**
* can be overriden if the plural of tablename is irregular
**/
val idColName: String = s"${tableName.dropRight(1)}_id"
def id = column[Int](s"${idColName}", O.PrimaryKey, O.AutoInc)
def getNextId = (Q[Int] + s"""select nextval('"${tableName}_${idColName}_seq"')""").first
}
所有实体案例类都需要这样的方法(也应该在特征中定义):
case class Entity (...) {
def withId(newId: Id): Entity = this.copy(id = Some(newId)
}
现在可以通过以下方式插入新实体:
object Entities extends Table[Entity]("entities") with TableWithId {
override val idColName: String = "entity_id"
...
def save(entity: Entity) = this insert entity.withId(getNextId)
}
代码仍然不是DRY,因为您需要为每个表定义withId方法。此外,您必须在插入可能导致性能影响的实体之前请求下一个ID,但除非您一次插入数千个条目,否则不应该是值得注意的。
主要优点是不需要第二次投影,这使得代码不易出错,特别是对于有很多列的表。
答案 3 :(得分:1)
另一个技巧是使案例类的id为var
case class Entity(var id: Long)
要插入实例,请按以下方式创建
Entity(null.asInstanceOf[Long])
我已经测试过它有效。
答案 4 :(得分:1)
最简单的解决方案是使用SERIAL类型,如下所示:
def id = column[Long]("id", SqlType("SERIAL"), O.PrimaryKey, O.AutoInc)
这是一个更具体的块:
// A case class to be used as table map
case class CaseTable( id: Long = 0L, dataType: String, strBlob: String)
// Class for our Table
class MyTable(tag: Tag) extends Table[CaseTable](tag, "mytable") {
// Define the columns
def dataType = column[String]("datatype")
def strBlob = column[String]("strblob")
// Auto Increment the id primary key column
def id = column[Long]("id", SqlType("SERIAL"), O.PrimaryKey, O.AutoInc)
// the * projection (e.g. select * ...) auto-transforms the tupled column values
def * = (id, dataType, strBlob) <> (CaseTable.tupled, CaseTable.unapply _)
}
// Insert and get auto incremented primary key
def insertData(dataType: String, strBlob: String, id: Long = 0L): Long = {
// DB Connection
val db = Database.forURL(jdbcUrl, pgUser, pgPassword, driver = driverClass)
// Variable to run queries on our table
val myTable = TableQuery[MyTable]
val insert = try {
// Form the query
val query = myTable returning myTable.map(_.id) += CaseTable(id, dataType, strBlob)
// Execute it and wait for result
val autoId = Await.result(db.run(query), maxWaitMins)
// Return ID
autoId
}
catch {
case e: Exception => {
logger.error("Error in inserting using Slick: ", e.getMessage)
e.printStackTrace()
-1L
}
}
insert
}
答案 5 :(得分:0)
当我将db更改为Postgres时,我试图从play-slick-3.0制作计算机数据库样本时面临同样的问题。解决问题的方法是在进化文件/conf/evolutions/default/1.sql中更改id列(主键)类型为SERIAL(最初是在BIGINT中)。看看https://groups.google.com/forum/?fromgroups=#%21topic/scalaquery/OEOF8HNzn2U
整个讨论。
干杯,
RENEX
答案 6 :(得分:0)
我发现的解决方案是在列定义中使用SqlType("Serial")
。我尚未对其进行广泛的测试,但是到目前为止,它似乎仍然可以正常工作。
所以不是
def id: Rep[PK[SomeTable]] = column[PK[SomeTable]]("id", O.PrimaryKey, O.AutoInc)
您应该这样做:
def id: Rep[PK[SomeTable]] = column[PK[SomeTable]]("id", SqlType("SERIAL"), O.PrimaryKey, O.AutoInc)
在PK
的定义方式与《 Essential Slick》一书中的示例相同:
final case class PK[A](value: Long = 0L) extends AnyVal with MappedTo[Long]