Использование полей 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
Ответы
Ответ 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]