example scala slick

scala slick example



Upsert en Slick (2)

Actualizado para soporte nativo de upsert / merge en Slick 2.1

Atención

Tiene que usar incrustación de SQL simple con su base de datos nativa MERGE . Todos los ensayos para simular esta afirmación probablemente conducirán a resultados incorrectos.

Fondo:

Cuando simules la declaración de aumento / fusión, Slick tendrá que usar varias declaraciones para alcanzar ese objetivo (por ejemplo, una selección y luego una inserción o una declaración de actualización). Cuando se ejecutan varias declaraciones en una transacción SQL, generalmente no tienen el mismo nivel de aislamiento que una sola instrucción. Con diferentes niveles de aislamiento, experimentará efectos extraños en situaciones concurrentes masivas. Así que todo funcionará bien durante las pruebas y fallará con efectos extraños en la producción.

Una base de datos generalmente tiene un nivel de aislamiento más fuerte mientras se ejecuta una instrucción entre dos declaraciones en la misma transacción. Mientras que una declaración en ejecución no se verá afectada por otras declaraciones que se ejecutan en paralelo. La base de datos bloqueará todo lo que toca la declaración o detectará la interferencia entre las declaraciones en ejecución y reiniciará automáticamente las declaraciones problemáticas cuando sea necesario. Este nivel de protección no se mantiene cuando se ejecuta la siguiente instrucción en la misma transacción.

Entonces, el siguiente escenario puede (y ocurrirá):

  1. En la primera transacción, la declaración de selección detrás de user.firstOption no encuentra una fila de base de datos para el usuario actual.
  2. Una segunda transacción paralela inserta una fila para ese usuario
  3. La primera transacción inserta una segunda fila para ese usuario (similar a una lectura fantasma )
  4. O terminas con dos filas para el mismo usuario o la primera transacción falla con una infracción de restricción aunque su verificación fue válida (cuando se ejecutó)

Para ser justos, esto no sucederá con el nivel de aislamiento "serializable" . Pero este nivel de aislamiento viene con un gran impacto en el rendimiento que rara vez se utiliza en la producción. Además, la serialización necesitará ayuda de su aplicación: el sistema de administración de la base de datos generalmente no será serializable en todas las transacciones. Pero detectará violaciones contra el reenvío serializable y simplemente abortará las transacciones en problemas. Por lo tanto, su aplicación debe estar preparada para volver a ejecutar la transacción que el DBMS ha abortado (al azar).

Si confía en que se produzca la violación de la restricción, diseñe su aplicación de manera que se ejecute automáticamente la transacción en cuestión sin molestar al usuario. Esto es similar al requisito en el nivel de aislamiento "serializable".

Conclusión

Use SQL simple para este escenario o prepárese para sorpresas desagradables en la producción. Piense dos veces sobre posibles problemas con la concurrencia.

Actualización 5.8.2014: Slick 2.1.0 ahora tiene soporte nativo de MERGE

Con Slick 2.1.0 ahora hay soporte nativo para la declaración MERGE (consulte las notas de la versión : "Soporte de inserción o actualización que hace uso de las funciones de bases de datos nativas cuando sea posible").

El código se verá así (tomado de los casos de prueba de Slick ):

def testInsertOrUpdatePlain { class T(tag: Tag) extends Table[(Int, String)](tag, "t_merge") { def id = column[Int]("id", O.PrimaryKey) def name = column[String]("name") def * = (id, name) def ins = (id, name) } val ts = TableQuery[T] ts.ddl.create ts ++= Seq((1, "a"), (2, "b")) // Inserts (1,a) and (2,b) assertEquals(1, ts.insertOrUpdate((3, "c"))) // Inserts (3,c) assertEquals(1, ts.insertOrUpdate((1, "d"))) // Updates (1,a) to (1,d) assertEquals(Seq((1, "d"), (2, "b"), (3, "c")), ts.sortBy(_.id).run) }

¿Hay alguna manera de que pueda hacer una operación de inserción en Slick? Lo siguiente funciona pero es demasiado oscuro / detallado y debo indicar explícitamente los campos que deben actualizarse:

val id = 1 val now = new Timestamp(System.currentTimeMillis) val q = for { u <- Users if u.id === id } yield u.lastSeen q.update(now) match { case 0 => Users.insert((id, now, now)) case _ => Unit }


Apparently esto no es (¿todavía?) En Slick.

Sin embargo, firstOption probar firstOption para algo un poco más idiomático:

val id = 1 val now = new Timestamp(System.currentTimeMillis) val user = Users.filter(_.id is id) user.firstOption match { case Some((_, created, _)) => user.update((id, created, now)) case None => Users.insert((id, now, now)) }