Understanding Foreign Key with Slick and Scala

Understanding foreign Key with Slick and Scala.md

Understanding Foreign Key with Slick and Scala

Currently, I am trying to build a simple web app with Play framework while learning the framework and scala language. Then I later found out that I need to learn Slick in order to store data. Since I am learning Slick, I might just as well know a little about relational database. And then I realize that how am I support to relate two tables in Slick. That’s where I found Foreign Key. One key to rule them all. Maybe.

Github reference I refer from:

  1. UnderscoreIo

1. Setup database config

This is the typesafe config at ‘application.conf’. You can refer to this doc. BTW, You don’t need to setup any database like MySQL or Postgresql because we are using h2 driver.

scalaxdb = {
connectionPool = disabled
url = "jdbc:h2:mem:scalaxdb"
driver = "org.h2.Driver"
keepAliveConnection = true
}

2. Setup Tables

Here we config how the album and song table look like. Take note of def album = foreignKey("album_fk", albumId, AlbumTable)(_.id, onDelete = ForeignKeyAction.Cascade) in the song table configuration. We will store ablum id as foreign key in the song table.

case class Album(artist: String,
                   title: String,
                   id: Long = 0L)

case class Song(name: String,
                 albumId: Long,
                 id: Long = 0L)

  class AlbumTable(tag: Tag) extends Table[Album](tag, "albums") {
    def artist = column[String]("artist")
    def title = column[String]("title")
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)

    def * = (artist, title, id) <> (Album.tupled, Album.unapply)
  }

  class SongTable(tag: Tag) extends Table[Song](tag, "songs") {
    def name = column[String]("title")
    def albumId = column[Long]("albumId")
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
    def * = (name, albumId, id) <> (Song.tupled, Song.unapply)

    def album = foreignKey("album_fk", albumId, AlbumTable)(_.id, onDelete = ForeignKeyAction.Cascade)
  }

3. Create convenient functions

There are convenient functions to query. Take note here: albumId insertAlbum += Album("Keyboard", "Hit it Bro"). This action will generate auto increment ID after Album is created. Then this ID will be stored in Song("Hit me one more time", albumId), as albumId so that they can link together.

lazy val AlbumTable = TableQuery[AlbumTable]
lazy val SongTable = TableQuery[SongTable]
lazy val ddl = AlbumTable.schema ++ SongTable.schema
lazy val insertAlbum = AlbumTable returning AlbumTable.map(_.id)


 // Actions ------------------------------------
val createTables = (AlbumTable.schema ++ SongTable.schema).create

val populateFirstTime =
    for {
      albumId <- insertAlbum += Album("Keyboard", "Hit it Bro")
      count <- SongTable ++= Seq(
        Song("Hit me one more time", albumId),
        Song("Press them all", albumId)
      )
    } yield count

val populateSecondTime =
    for {
      albumId <- insertAlbum += Album("Mouse", "Click it Bro")
      count <- SongTable ++= Seq(
        Song("Jumping around", albumId),
        Song("Sneaking around", albumId)
      )
    } yield count


val join = for {
    song <- SongTable
    album <- song.album
  } yield (album.artist, song.name)


 // Database -----------------------------------

val db = Database.forConfig("scalaxdb")

  // Let's go! ----------------------------------
def exec[T](action: DBIO[T]): T =
  Await.result(db.run(action), 2 seconds)

4. Executing Actions

Finally, we can run these actions to see the results. Again, take note of this exec(deleteHitItBroAlbum). Once the album is deleted, songs in that album are deleted as well. That makes sense.

def main(args: Array[String]): Unit = {
    exec(createTables) //create tables
    println("start")
    exec(populateFirstTime) //populate table with data
    exec(populateSecondTime)
    println("finish init")
    println("\nResult of foreign key join:")
    println(exec(join.result)) // join artist and song title

    exec(SongTable.result).foreach(println) //print out songs on SongTable
    exec(AlbumTable.result).foreach(println) //print out albums on AlbumTable

    val deleteHitItBroAlbum = for {
      mouseAlbumId <- AlbumTable.filter(_.title === "Hit it Bro").map(_.id).result.headOption
      rowsAffected <- SongTable.filter(_.albumId === mouseAlbumId).delete
    } yield rowsAffected

    exec(deleteHitItBroAlbum) //Delete a album// this action will delete songs associated with that album

    println("after delete")
    exec(SongTable.result).foreach(println)
    exec(AlbumTable.result).foreach(println)
  }

5. As One Whole file

Here, I added everything that was written before as one whole file. This file can be run provided sbt and database config are setup correctly. Good luck.

package ForeignKeyTest

import slick.driver.H2Driver.api._
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

object Main {

  // Tables -------------------------------------
  case class Album(artist: String,
                   title: String,
                   id: Long = 0L)

  case class Song(name: String,
                  albumId: Long,
                  id: Long = 0L)
                  
  class AlbumTable(tag: Tag) extends Table[Album](tag, "albums") {
    def artist = column[String]("artist")
    def title = column[String]("title")
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)

    def * = (artist, title, id) <> (Album.tupled, Album.unapply)
  }

  class SongTable(tag: Tag) extends Table[Song](tag, "songs") {
    def name = column[String]("title")
    def albumId = column[Long]("albumId")
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
    def * = (name, albumId, id) <> (Song.tupled, Song.unapply)

    def album = foreignKey("album_fk", albumId, AlbumTable)(_.id, onDelete = ForeignKeyAction.Cascade)
  }

  lazy val AlbumTable = TableQuery[AlbumTable]
  lazy val SongTable = TableQuery[SongTable]
  lazy val ddl = AlbumTable.schema ++ SongTable.schema
  lazy val insertAlbum = AlbumTable returning AlbumTable.map(_.id)

  // Actions ------------------------------------
  val createTables = (AlbumTable.schema ++ SongTable.schema).create

  val populateFirstTime =
    for {
      albumId <- insertAlbum += Album("Keyboard", "Hit it Bro")
      count <- SongTable ++= Seq(
        Song("Hit me one more time", albumId),
        Song("Press them all", albumId)
      )
    } yield count

  val populateSecondTime =
    for {
      albumId <- insertAlbum += Album("Mouse", "Click it Bro")
      count <- SongTable ++= Seq(
        Song("Jumping around", albumId),
        Song("Sneaking around", albumId)
      )
    } yield count

  val join = for {
    song <- SongTable
    album <- song.album
  } yield (album.artist, song.name)

  // Database -----------------------------------
  val db = Database.forConfig("scalaxdb")

  // Let's go! ----------------------------------
  def exec[T](action: DBIO[T]): T =
  Await.result(db.run(action), 2 seconds)

  def main(args: Array[String]): Unit = {

    exec(createTables) //create tables
    println("start")
    exec(populateFirstTime) //populate table with data
    exec(populateSecondTime)
    println("finish init")
    println("\nResult of foreign key join:")
    println(exec(join.result)) // join artist and song title

    exec(SongTable.result).foreach(println) //print out songs on SongTable
    exec(AlbumTable.result).foreach(println) //print out albums on AlbumTable

    val deleteHitItBroAlbum = for {
      mouseAlbumId <- AlbumTable.filter(_.title === "Hit it Bro").map(_.id).result.headOption
      rowsAffected <- SongTable.filter(_.albumId === mouseAlbumId).delete
    } yield rowsAffected

    exec(deleteHitItBroAlbum) //Delete a album// this action will delete songs associated with that album

    println("after delete")
    exec(SongTable.result).foreach(println)
    exec(AlbumTable.result).foreach(println)
  }
}

1 comment:

  1. Nội dung bài viết thật tuyệt vời. Mình cũng muốn giới thiệu về một Công ty dịch thuật uy tín - Công ty dịch thuật miền trung - MIDtrans trụ sở chính chính tại địa chỉ 02 Hoàng Diệu, TP Đồng Hới, tỉnh Quảng Bình có Giấy phép kinh doanh số 3101023866 cấp ngày 9/12/2016 là đơn vị chuyên cung cấp dịch vụ dịch thuật, phiên dịch dành các cá nhân. Hệ thống thương hiệu và các Công ty dịch thuật con trực thuộc: dịch thuật đà nẵng - dịch thuật miền trung tại địa 54/27 Đinh Tiên Hoàng, Quận Hải Châu, TP Đà Nẵng là địa chỉ chuyên cung cấp dịch vụ dịch thuật chuyên nghiệp tại Đà Nẵng và các khu vực lân cận ; dịch thuật tiếng đức tại sài gòn địa chỉ 47 Điện Biên Phủ, Phường Đakao, Quận 1 TP HCM, dịch thuật nha trang, khánh hòa : địa 101 Trần Hưng Đạo, TP Nha Trang là nhà cung ứng dịch vụ dịch thuật uy tín hàng đầu tại Khánh Hòa; Công ty dịch thuật Viettrans và dịc vụ ben tre translation: dịch vụ dịch thuật tại Bến Tre cho người nước ngoài có nhu cầu, giao diện tiếng Anh dễ sử dụng; dịch thuật tiếng nga tại sài gòn: nhà cung ứng dịch vụ dịch vụ dịch thuật phiên dịch hàng đầu tại TP Hồ Chí Minh; công ty dịch thuật đà nẵng midtrans : Địa chỉ 54 Đinh Tiên Hoàng, Quận Hải Châu, TP Đà Nẵng chuyên cung cấp dịch vụ dịch thuật công chứng, dịch thuật đa ngôn ngữ, đa chuyên ngành tại Đà Nẵng; Viettrans 43 Điện Biên Phủ, Quận 1 Sài Gòn: chuyên cung cấp dịch vụ dịch thuật đa chuyên ngành toàn quốc; Công ty dịch thuật Hà Nội MIDtrans chuyên cung cấp dịch vụ Viettravel - vietnamese tourist information and travel tips tại 46 Trần Cao Vân, TP Huế chuyên trang về thông tin du lịch và các tour đặc sắc tại Việt Nam, y dược (bao gồm bệnh lý), xây dựng (kiến trúc), hóa chất, thủy nhiệt điện, ngân hàng, tài chính, kế toán. Các dự án đã triển khai của Công ty dịch thuật chuyên nghiệp MIDtrans đều được Khách hàng đánh giá cao và đạt được sự tín nhiệm về chất lượng biên phiên dịch. Đó là kết quả của một hệ thống quản lý chất lượng dịch thuật chuyên nghiệp, những tâm huyết và kinh nghiệm biên phiên dịch nhiều năm của đội ngũ dịch giả của chúng tôi. Hotline: 0947688883. email: info@dichthuatmientrung.com.vn . Các bạn ghé thăm site ủng hộ nhé.

    ReplyDelete