slick 2 mapping many to one relationship with mapp

2019-08-03 12:02发布

问题:

i'm trying to map my class with slick so i can persist them. My business objects are defined this way

case class Book(id : Option[Long], author : Author, title : String, readDate : Date, review : String){}
case class Author(id : Option[Long], name : String, surname : String) {}

Then I defined the "table" class for authors:

class Authors(tag : Tag) extends Table[Author](tag,"AUTHORS") {
    def id = column[Option[Long]]("AUTHOR_ID", O.PrimaryKey, O.AutoInc)
    def name = column[String]("NAME")
    def surname = column[String]("SURNAME")
    def * = (id, name, surname) <> ((Author.apply _).tupled , Author.unapply)
}

And for Books:

class Books (tag : Tag) extends Table[Book](tag, "BOOKS") {
    implicit val authorMapper = MappedColumnType.base[Author, Long](_.id.get, AuthorDAO.DAO.findById(_))

    def id = column[Option[Long]]("BOOK_ID", O.PrimaryKey, O.AutoInc)
    def author = column[Author]("FK_AUTHOR")
    def title = column[String]("TITLE")
    def readDate = column[Date]("DATE")
    def review = column[Option[String]]("REVIEW")

    def * = (id, author, title, readDate, review) <> ((Book.apply _).tupled , Book.unapply)
}

But when I compile i get this error

Error:(24, 51) No matching Shape found.
Slick does not know how to map the given types.
Possible causes: T in Table[T] does not match your * projection. Or you use an unsupported type in a Query (e.g. scala List).
Required level: scala.slick.lifted.FlatShapeLevel
Source type: (scala.slick.lifted.Column[Option[Long]], scala.slick.lifted.Column[model.Author], scala.slick.lifted.Column[String], scala.slick.lifted.Column[java.sql.Date], scala.slick.lifted.Column[Option[String]])
Unpacked type: (Option[Long], model.Author, String, java.sql.Date, String)
Packed type: Any
def * = (id, author, title, readDate, review) <> ((Book.apply _).tupled , Book.unapply)
                                              ^

and also this one:

Error:(24, 51) not enough arguments for method <>: (implicit evidence$2: scala.reflect.ClassTag[model.Book], implicit shape: scala.slick.lifted.Shape[_ <: scala.slick.lifted.FlatShapeLevel, (scala.slick.lifted.Column[Option[Long]], scala.slick.lifted.Column[model.Author], scala.slick.lifted.Column[String], scala.slick.lifted.Column[java.sql.Date], scala.slick.lifted.Column[Option[String]]), (Option[Long], model.Author, String, java.sql.Date, String), _])scala.slick.lifted.MappedProjection[model.Book,(Option[Long], model.Author, String, java.sql.Date, String)].
Unspecified value parameter shape.
def * = (id, author, title, readDate, review) <> ((Book.apply _).tupled , Book.unapply)
                ^

What's the mistake here? What am I not getting about slick? Thank you in advance!

回答1:

Slick is not an ORM so there's no auto mapping from a foreign key to an entity, the question has been asked many times on SO (here, here just to name two).

Let's assume for a moment that what you are trying to do is possible:

implicit val authorMapper = 
  MappedColumnType.base[Author, Long](_.id.get, AuthorDAO.DAO.findById(_))

So you are telling the projection to use the row id and fetch the entity related to that id, there are three problems in your case, first you don't handle failures (id.get), second you primary key is optional (which shouldn't be).

The third problem is that slick will fetch each entity in a separate way, what I mean by this is, you execute some query and get 100 books, slick will make 100 other queries only to fetch the related entity, performance wise is suicide, you are completely bypassing the SQL layer (joins) which has the best performance only to have the possibility of shortening your DAOs.

Fortunately this doesn't seem to be possible, mappers are used for non supported types by slick (for example different date formats without having to explicitly using functions) or to inject format conversion when fetching/inserting rows, have a look at the documentation on how to use joins (depending on your version).



回答2:

Ende Neu's answer is more knowledgeable and relevant to the use case described in the question, and probably a more proper and correct answer.

The following is merely an observation I made which may have helped tmnd91 by answering the question:

What's the mistake here?

I noticed that:

case class Book( ... review : String){}

does not match with:

def review = column[Option[String]]("REVIEW")

It should be:

def review = column[String]("REVIEW")