简体   繁体   中英

Slick 3 join query one to many relationship

Imagine the following relation

One book consists of many chapters, a chapter belongs to exactly one book. Classical one to many relation.

I modeled it as this:

case class Book(id: Option[Long] = None, order: Long, val title: String)

class Books(tag: Tag) extends Table[Book](tag, "books")
{
  def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
  def order = column[Long]("order")
  def title = column[String]("title")

  def * = (id, order, title) <> (Book.tupled, Book.unapply)
  def uniqueOrder = index("order", order, unique = true)

  def chapters: Query[Chapters, Chapter, Seq] = Chapters.all.filter(_.bookID === id)
}

object Books
{
  lazy val all = TableQuery[Books]
  val findById = Compiled {id: Rep[Long] => all.filter(_.id === id)}

  def add(order: Long, title: String) = all += new Book(None, order, title)
  def delete(id: Long) = all.filter(_.id === id).delete

//  def withChapters(q: Query[Books, Book, Seq]) = q.join(Chapters.all).on(_.id === _.bookID)

  val withChapters = for
  {
    (Books, Chapters) <- all join Chapters.all on (_.id === _.bookID)
  } yield(Books, Chapters)
}

case class Chapter(id: Option[Long] = None, bookID: Long, order: Long, val title: String)

class Chapters(tag: Tag) extends Table[Chapter](tag, "chapters")
{
  def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
  def bookID = column[Long]("book_id")
  def order = column[Long]("order")
  def title = column[String]("title")

  def * = (id, bookID, order, title) <> (Chapter.tupled, Chapter.unapply)
  def uniqueOrder = index("order", order, unique = true)

  def bookFK = foreignKey("book_fk", bookID, Books.all)(_.id.get, onUpdate = ForeignKeyAction.Cascade, onDelete = ForeignKeyAction.Restrict)
}

object Chapters
{
  lazy val all = TableQuery[Chapters]
  val findById = Compiled {id: Rep[Long] => all.filter(_.id === id)}

  def add(bookId: Long, order: Long, title: String) = all += new Chapter(None, bookId, order, title)
  def delete(id: Long) = all.filter(_.id === id).delete
}

Now what I want to do:

I want to query all or a specific book (by id) with all their chapters

Translated to plain SQL, something like:

SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10

but in Slick I can't really get this whole thing to work.

What I tried:

object Books
{
    //...
    def withChapters(q: Query[Books, Book, Seq]) = q.join(Chapters.all).on(_.id === _.bookID)
}

as well as:

object Books
{
    //...
    val withChapters = for
    {
        (Books, Chapters) <- all join Chapters.all on (_.id === _.bookID)
    } yield(Books, Chapters)
}

but to no avail. (I use ScalaTest and I get an empty result (for def withChapters(...) ) or another exception for the val withChapters = for... )

How to go on about this? I tried to keep to the documentation, but I'm doing something wrong obviously.

Also: Is there an easy way to see the actual query as a String? I only found query.selectStatement and the like, but that's not available for my joined query. Would be great for debugging to see if the actual query was wrong.

edit: My test looks like this:

class BookWithChapters extends FlatSpec with Matchers with ScalaFutures with BeforeAndAfter
{
  val db = Database.forConfig("db.test.h2")

  private val books = Books.all
  private val chapters = Chapters.all

  before { db.run(setup) }
  after {db.run(tearDown)}

  val setup = DBIO.seq(
    (books.schema).create,
    (chapters.schema).create
  )

  val tearDown = DBIO.seq(
    (books.schema).drop,
    (chapters.schema).drop
  )

  "Books" should "consist of chapters" in
  {
    db.run(
      DBIO.seq
      (
        Books.add(0, "Book #1"),
        Chapters.add(0, 0, "Chapter #1")
      )
    )

    //whenReady(db.run(Books.withChapters(books).result)) {
    whenReady(db.run(Books.withChapters(1).result)) {
    result => {
      //  result should have length 1
        print(result(0)._1)
      }
    }
  }
}

like this I get an IndexOutOfBoundsException .

I used this as my method:

object Books
{
      def withChapters(id: Long) = Books.all.filter(_.id === id) join Chapters.all on (_.id === _.bookID)
}

also:

logback.xml looks like this:

<configuration>
    <logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG/>
</configuration>

Where can I see the logs? Or what else do I have to do to see them?

To translate your query...

SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10

...to Slick we can filter the books :

val bookTenChapters = 
  Books.all.filter(_.id === 10L) join Chapters.all on (_.id === _.bookID)

This will give you a query that returns Seq[(Books, Chapters)] . If you want to select different books, you can use a different filter expression.

Alternatively, you may prefer to filter on the join:

val everything = 
   Books.all join Chapters.all on (_.id === _.bookID)

val bookTenChapters = 
  everything.filter { case (book, chapter) => book.id === 10L }

That will probably be closer to your join. Check the SQL generated with the database you use to see which you prefer.

You can log the query by creating a src/main/resources/logback.xml file and set:

 <logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG"/>

I have an example project with logging set up . You will need to change INFO to DEBUG in the xml file in, eg, the chapter-01 folder.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM