在 PostgreSQL 和 Slick 中使用自动递增字段
Posted
技术标签:
【中文标题】在 PostgreSQL 和 Slick 中使用自动递增字段【英文标题】:Using Auto Incrementing fields with PostgreSQL and Slick 【发布时间】:2012-10-23 08:18:38 【问题描述】:如何使用带有 Slick 映射表的 AutoInc 键将记录插入 PostgreSQL?如果我在我的案例类中对 id 使用和 Option 并将其设置为 None,那么 PostgreSQL 将在插入时抱怨该字段不能为空。这适用于 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
【问题讨论】:
这个例子可能对某人有用:github.com/slick/slick-examples/blob/2.0.0-RC1/src/main/scala/… 【参考方案1】:这是在这里工作:
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));
更新:
关于用户映射表的具体问题(如问题中)可以解决如下:
def forInsert = first ~ last <>
( (f, l) => User(None, f, l) , u:User => Some((u.first, u.last)) )
这是来自test cases in the Slick git repository。
【讨论】:
对不起,我是 scala 初学者,我想我误解了你的例子。我正在使用驱动程序版本 9.1-901-1.jdbc4。我已经编辑了我的答案,以准确说明我在这里做什么。希望对您有所帮助。 @JacobusR 您是否解决了在发送问题时 id 为空的问题,None
?因为我也有这个问题...
嗨,克里斯蒂安,我不确定我是否理解...您是否可以将其作为一个单独的问题提出,以便您可以发布大部分代码。运气好的话,其他一些聪明的 Slick 大师偶然发现了你的问题,让我们俩都挺直了 :-)
只是一个注释。如果你的数据类型是 int 或 bigint 并且你想使用 AutoInc,你需要创建一个序列并设置默认使用序列。
表变大了怎么办?也许 15-20 列...forInsert
方法变成了怪物!【参考方案2】:
我以不同的方式解决了这个问题。因为我希望我的 User
对象在我的应用程序逻辑中始终有一个 id,并且唯一没有它的地方是在插入数据库期间,所以我使用了一个辅助 NewUser
案例类,它没有身份证。
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 类,因为它仅用作insert
调用。
编辑: 如果你想要更多的安全性(稍微增加冗长),你可以使用 case 类的 trait,如下所示:
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
在这种情况下,您将首先将模型更改应用于特征(包括您可能需要的任何混合),并可选择将默认值添加到 NewUser
。
作者观点:我还是更喜欢 no-trait 解决方案,因为它更紧凑,对模型的更改只需复制粘贴 User
参数,然后删除 id
(auto-inc主键),无论是在案例类声明中还是在表投影中。
【讨论】:
+1 感谢分享。成员的重复并不理想(可以考虑特征或继承),但它确实使代码非常可读。【参考方案3】:我们使用的方法略有不同。我们没有创建进一步的投影,而是请求表的下一个 id,将其复制到案例类中,并使用默认投影“*”插入表条目。
对于 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
所有实体案例类都需要这样的方法(也应该在 trait 中定义):
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,但除非您一次插入数千个条目,否则不应引起注意。
主要优点是不需要第二次投影,这使代码不易出错,特别是对于具有许多列的表。
【讨论】:
有趣的方法,但将应用程序直接绑定到 Postgres。独立于数据库的方法是使用 Slick 的Sequence
类并让每个驱动程序根据序列命名约定实现(例如 Postgres 是“tableName_pkName_seq”)。不幸的是,批量插入在 nextval 中效果不佳。不确定当前的 Slick (3.0) 是否解决了已接受答案 cmets 中引用的样板问题;如果不是,那么不得不手动将案例类映射为元组 sans pk 列……【参考方案4】:
最简单的解决方案是像这样使用 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】:当我将数据库更改为 Postgres 时,我在尝试从 play-slick-3.0 制作计算机数据库示例时遇到了同样的问题。解决问题的方法是将进化文件/conf/evolutions/default/1.sql(最初在BIGINT中)中的id列(主键)类型更改为SERIAL。看看https://groups.google.com/forum/?fromgroups=#%21topic/scalaquery/OEOF8HNzn2U 整个讨论。 干杯, ReneX
【讨论】:
【参考方案6】:另一个技巧是将案例类的 id 设为 var
case class Entity(var id: Long)
要插入一个实例,如下创建它
Entity(null.asInstanceOf[Long])
我已经测试过它可以工作。
【讨论】:
【参考方案7】:我找到的解决方案是在列定义中使用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]
【讨论】:
以上是关于在 PostgreSQL 和 Slick 中使用自动递增字段的主要内容,如果未能解决你的问题,请参考以下文章
删除不适用于 Play! 2.4、Slick 3 和 PostgreSQL
为啥使用 Slick 和 PostgreSQL 播放操作失败并显示“找不到合适的驱动程序”?
Slick 2 中的多个 PostgreSQL 数据库(同一服务器)访问
使用 Play Slick 在 PostgreSQL 中持久化 UUID - java.sql.BatchUpdateException