Slick 3 join查询一对多关系
Slick 3 join query one to many relationship
想象以下关系
一本书包含很多章节,一章恰好属于一本书。经典的一对多关系。
我是这样建模的:
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
}
现在我想做什么:
我想查询所有或特定书籍(按 ID)及其所有章节
翻译成普通 SQL,类似于:
SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10
但在 Slick 中我无法真正让这一切正常工作。
我尝试了什么:
object Books
{
//...
def withChapters(q: Query[Books, Book, Seq]) = q.join(Chapters.all).on(_.id === _.bookID)
}
以及:
object Books
{
//...
val withChapters = for
{
(Books, Chapters) <- all join Chapters.all on (_.id === _.bookID)
} yield(Books, Chapters)
}
但无济于事。 (我使用 ScalaTest,结果为空(def withChapters(...)
)或 val withChapters = for...
的另一个异常)
这件事怎么办?我试图遵守文档,但显然我做错了什么。
另外:有没有一种简单的方法可以将实际查询视为字符串?我只找到了 query.selectStatement
之类的,但这对我的连接查询不可用。非常适合调试以查看实际查询是否错误。
编辑: 我的测试是这样的:
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)
}
}
}
}
像这样我得到一个 IndexOutOfBoundsException
。
我用这个作为我的方法:
object Books
{
def withChapters(id: Long) = Books.all.filter(_.id === id) join Chapters.all on (_.id === _.bookID)
}
还有:
logback.xml
看起来像这样:
<configuration>
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG/>
</configuration>
在哪里可以看到日志?或者我还需要做什么才能看到它们?
要翻译您的查询...
SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10
...为了 Slick 我们可以 filter
books
:
val bookTenChapters =
Books.all.filter(_.id === 10L) join Chapters.all on (_.id === _.bookID)
这将为您提供 returns Seq[(Books, Chapters)]
的查询。如果你想 select 不同的书,你可以使用不同的过滤表达式。
或者,您可能更喜欢对连接进行过滤:
val everything =
Books.all join Chapters.all on (_.id === _.bookID)
val bookTenChapters =
everything.filter { case (book, chapter) => book.id === 10L }
那可能会更接近您的加入。检查使用您使用的数据库生成的 SQL 以查看您喜欢哪个。
您可以通过创建 src/main/resources/logback.xml
文件并设置来记录查询:
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG"/>
我有一个 example project with logging set up。您需要将 xml 文件中的 INFO
更改为 DEBUG
,例如 chapter-01 文件夹。
想象以下关系
一本书包含很多章节,一章恰好属于一本书。经典的一对多关系。
我是这样建模的:
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
}
现在我想做什么:
我想查询所有或特定书籍(按 ID)及其所有章节
翻译成普通 SQL,类似于:
SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10
但在 Slick 中我无法真正让这一切正常工作。
我尝试了什么:
object Books
{
//...
def withChapters(q: Query[Books, Book, Seq]) = q.join(Chapters.all).on(_.id === _.bookID)
}
以及:
object Books
{
//...
val withChapters = for
{
(Books, Chapters) <- all join Chapters.all on (_.id === _.bookID)
} yield(Books, Chapters)
}
但无济于事。 (我使用 ScalaTest,结果为空(def withChapters(...)
)或 val withChapters = for...
的另一个异常)
这件事怎么办?我试图遵守文档,但显然我做错了什么。
另外:有没有一种简单的方法可以将实际查询视为字符串?我只找到了 query.selectStatement
之类的,但这对我的连接查询不可用。非常适合调试以查看实际查询是否错误。
编辑: 我的测试是这样的:
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)
}
}
}
}
像这样我得到一个 IndexOutOfBoundsException
。
我用这个作为我的方法:
object Books
{
def withChapters(id: Long) = Books.all.filter(_.id === id) join Chapters.all on (_.id === _.bookID)
}
还有:
logback.xml
看起来像这样:
<configuration>
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG/>
</configuration>
在哪里可以看到日志?或者我还需要做什么才能看到它们?
要翻译您的查询...
SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10
...为了 Slick 我们可以 filter
books
:
val bookTenChapters =
Books.all.filter(_.id === 10L) join Chapters.all on (_.id === _.bookID)
这将为您提供 returns Seq[(Books, Chapters)]
的查询。如果你想 select 不同的书,你可以使用不同的过滤表达式。
或者,您可能更喜欢对连接进行过滤:
val everything =
Books.all join Chapters.all on (_.id === _.bookID)
val bookTenChapters =
everything.filter { case (book, chapter) => book.id === 10L }
那可能会更接近您的加入。检查使用您使用的数据库生成的 SQL 以查看您喜欢哪个。
您可以通过创建 src/main/resources/logback.xml
文件并设置来记录查询:
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG"/>
我有一个 example project with logging set up。您需要将 xml 文件中的 INFO
更改为 DEBUG
,例如 chapter-01 文件夹。