Использование автоматического увеличения полей с PostgreSQL и Slick

как вставлять записи в PostgreSQL с помощью ключей AutoInc с гладкими сопоставленными таблицами? Если я использую и параметр для id в моем классе case и установлю его в None, то PostgreSQL будет жаловаться на insert, что поле не может быть 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

5 ответов


Это здесь:

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

Это тестовые примеры в репозитории Slick git.


я решил эту проблему по-другому. Так как я ожидаю моего User объекты всегда имеют идентификатор в моей логике приложения и единственный момент, когда его не было бы во время вставки в базу данных, я использую вспомогательный NewUser класс 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, так как он используется только в качестве данных контейнер для insert ссылка.

EDIT: Если вы хотите больше безопасности (с несколько увеличенной многословностью) , вы можете использовать признак для классов case следующим образом:

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.

мнение автора: я по-прежнему предпочитаю решение без признаков, поскольку оно более компактно и изменения в модели являются вопросом копирования-вставки User params, а затем удаление id (первичный ключ auto-inc), как в объявлении класса case, так и в табличных проекциях.


мы используем немного другой подход. Вместо создания дальнейшей проекции мы запрашиваем следующий идентификатор таблицы, копируем его в класс 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
  }

все ваши классы Entity case нуждаются в таком методе (также должны быть определены в черте):

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

код все еще не сухой, потому что вам нужно определить метод withId для каждой таблицы. Кроме того, вы должны запросить следующий идентификатор перед вставкой объекта, который может привести к влиянию на производительность, но не должен быть заметным, если вы не вставляете тысячи записей одновременно.

главным преимуществом является то, что нет необходимости во второй проекции, что делает код менее подверженным ошибкам, в частности для таблиц, имеющих много столбцы.


еще один трюк-сделать идентификатор класса case var

case class Entity(var id: Long)

вставить экземпляр, создайте его, как показано ниже Entity(null.asInstanceOf[Long])

Я проверил, что это работает.


Я столкнулся с той же проблемой, пытаясь сделать образец базы данных компьютера из play-slick-3.0, когда я изменил БД на Postgres. Проблема заключалась в том, чтобы изменить тип столбца id (первичный ключ) на SERIAL в файле evolution /conf/evolutions/default/1.sql (первоначально был в BIGINT). Взгляните на https://groups.google.com/forum/?fromgroups=#%21topic/scalaquery/OEOF8HNzn2U
на всю дискуссию. Овации, ReneX