Подтвердить что ты не робот

Использование полей Auto Incrementing с PostgreSQL и Slick

Как вставить записи в PostgreSQL с помощью клавиш AutoInc со скопированными таблицами Slick? Если я использую и Option для id в моем классе case и устанавливаю его в 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
4b9b3361

Ответ 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));

Update:

Конкретная проблема с сопоставленной таблицей с пользователем (как в вопросе) может быть решена следующим образом:

  def forInsert = first ~ last <>
    ({ (f, l) => User(None, f, l) }, { u:User => Some((u.first, u.last)) })

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

Ответ 2

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

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, а затем с удалением id (первичный ключ auto-inc), как в случае объявления класса, так и в табличных проекциях.

Ответ 3

Мы используем несколько иной подход. Вместо создания дополнительной проекции мы запрашиваем следующий идентификатор таблицы, копируем ее в класс case и используем проекцию по умолчанию "*" для вставки записи в таблицу.

Для postgres это выглядит так:

Пусть ваши Table-Objects реализуют этот признак

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

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

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

Ответ 4

Другой трюк делает идентификатор класса case var

case class Entity(var id: Long)

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

Я тестировал, что он работает.

Ответ 5

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

Ответ 6

Я нашел решение использовать 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]