21

如何使用带有 Slick 映射表的 AutoInc 键将记录插入 PostgreSQL?如果我在我的案例类中使用 and Option 作为 id 并将其设置为 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
4

7 回答 7

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)) })

这是来自Slick git 存储库中的测试用例

于 2012-11-04T03:35:34.603 回答
11

我以不同的方式解决了这个问题。因为我希望我的User对象在我的应用程序逻辑中总是有一个 id,并且唯一没有它的地方是在插入数据库期间,所以我使用了一个NewUser没有 id 的辅助案例类。

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"))

同样,如果您想避免使用额外的 case 类,可以User将 1:1 映射到数据库条目/行,而NewUser可以用元组替换,因为它仅用作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

在这种情况下,您将首先将模型更改应用于特征(包括您可能需要的任何混合),并可选择将默认值添加到NewUser.

作者的观点:我仍然更喜欢 no-trait 解决方案,因为它更紧凑,对模型的更改只需复制粘贴User参数,然后删除id(auto-inc 主键),无论是在案例类声明中还是在表中预测。

于 2013-03-17T00:21:07.353 回答
2

我们正在使用稍微不同的方法。我们没有创建进一步的投影,而是请求表的下一个 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
  }

您所有的实体案例类都需要这样的方法(也应该在特征中定义):

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,但除非您一次插入数千个条目,否则不应引起注意。

主要优点是不需要第二次投影,这使得代码不易出错,特别是对于具有许多列的表。

于 2013-09-05T08:58:58.500 回答
2

最简单的解决方案是像这样使用 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
}
于 2019-12-15T05:19:17.553 回答
1

当我将数据库更改为 Postgres 时,我在尝试从 play-slick-3.0 制作计算机数据库示例时遇到了同样的问题。解决问题的方法是将进化文件 /conf/evolutions/default/1.sql 中的 id 列(主键)类型更改为 SERIAL(最初是在 BIGINT 中)。查看https://groups.google.com/forum/?fromgroups=#%21topic/scalaquery/OEOF8HNzn2U
以了解整个讨论。干杯,ReneX

于 2015-09-27T04:44:46.143 回答
1

另一个技巧是将案例类的 id 设置为 var

case class Entity(var id: Long)

要插入一个实例,像下面这样创建它 Entity(null.asInstanceOf[Long])

我已经测试过它可以工作。

于 2016-08-01T15:01:50.533 回答
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)

WherePK的定义类似于“Essential Slick”一书中的示例:

final case class PK[A](value: Long = 0L) extends AnyVal with MappedTo[Long]
于 2019-07-06T01:20:20.423 回答