tutorial scala slick typesafe-stack

tutorial - scala h2



Usando campos de Auto Incremento con PostgreSQL y Slick (5)

Estamos utilizando un enfoque ligeramente diferente. En lugar de crear una proyección adicional, solicitamos la siguiente identificación para una tabla, la copiamos en la clase de caso y usamos la proyección predeterminada ''*'' para insertar la entrada de la tabla.

Para postgres se ve así:

Deja que tus objetos de mesa implementen este rasgo

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 }

Todas las clases de casos de su entidad necesitan un método como este (también debe definirse en un rasgo):

case class Entity (...) { def withId(newId: Id): Entity = this.copy(id = Some(newId) }

Las nuevas entidades ahora se pueden insertar de esta manera:

object Entities extends Table[Entity]("entities") with TableWithId { override val idColName: String = "entity_id" ... def save(entity: Entity) = this insert entity.withId(getNextId) }

El código aún no está SECO, porque necesita definir el método withId para cada tabla. Además, debe solicitar la siguiente identificación antes de insertar una entidad que podría tener un impacto en el rendimiento, pero no debería ser notable a menos que inserte miles de entradas a la vez.

La principal ventaja es que no hay necesidad de una segunda proyección, lo que hace que el código sea menos propenso a errores, en particular para tablas que tienen muchas columnas.

¿Cómo se insertan registros en PostgreSQL utilizando claves AutoInc con tablas mapeadas Slick? Si uso y Opción para el ID en mi clase de caso y lo configuro como Ninguno, PostgreSQL se quejará al insertar que el campo no puede ser nulo. Esto funciona para H2, pero no para 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)) } }

Usar lo anterior con H2 es exitoso, pero si lo comento y cambio a PostgreSQL, entonces obtengo:

[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


Esto está funcionando aquí:

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

Así es como se ve mi SQL:

CREATE TABLE application (idlapplication BIGSERIAL PRIMARY KEY, appName VARCHAR(500));

Actualizar:

El problema específico con respecto a una tabla asignada con el Usuario (como en la pregunta) se puede resolver de la siguiente manera:

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

Esto es de los casos de prueba en el repositorio Slick git .


He abordado este problema de una manera diferente. Como espero que mis objetos de User siempre tengan una identificación en la lógica de mi aplicación y el único punto donde uno no la tendría es durante la inserción en la base de datos, utilizo una clase de caso auxiliar de NewUser que no tiene una identificación.

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

Nuevamente, el User asigna 1: 1 a la entrada / fila de la base de datos, mientras que NewUser podría ser reemplazado por una tupla si quisiera evitar tener la clase de caso adicional, ya que solo se usa como un contenedor de datos para la invocación de insert .

EDITAR: Si desea más seguridad (con mayor verbosidad incrementada) puede hacer uso de un rasgo para las clases de casos así:

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

En este caso, aplicaría los cambios de modelo al rasgo primero (incluidos los mixins que pudiera necesitar) y, opcionalmente, agregaría los valores predeterminados al NewUser .

Opinión del autor: Sigo prefiriendo la solución sin rasgo, ya que es más compacta y los cambios en el modelo son una cuestión de copiar y pegar los parámetros del User y luego eliminar la id (clave principal auto-inc), tanto en la declaración de clase de caso como En proyecciones de mesa.


He enfrentado el mismo problema al intentar hacer la muestra de la base de datos de computadora desde play-slick-3.0 cuando cambié la base de datos a Postgres. Lo que resolvió el problema fue cambiar el tipo de columna de identificación (clave principal) a SERIAL en el archivo de evolución /conf/evolutions/default/1.sql (originalmente estaba en BIGINT). Eche un vistazo a https://groups.google.com/forum/?fromgroups=#%21topic/scalaquery/OEOF8HNzn2U
para toda la discusión. Saludos, ReneX


Otro truco es hacer que el id de la clase de caso sea una var.

case class Entity(var id: Long)

Para insertar una instancia, Entity(null.asInstanceOf[Long]) como debajo de la Entity(null.asInstanceOf[Long])

He probado que funciona.