【问题标题】:strategy for loading related entities with slick 2使用 slick 2 加载相关实体的策略
【发布时间】:2014-07-07 15:18:03
【问题描述】:

我正在使用 play 2.3 和 slick 2.1

我有两个相关实体 - MessageUser(一个简化的示例域)。消息由用户编写。 表达这种关系的A recommended way (the only way?)是在Message中使用显式userId

我的类和表映射如下所示:

case class Message (
    text: String,
    userId: Int,
    date: Timestamp = new Timestamp(new Date().getTime()),
    id: Option[Int] = None) {}

case class User (
    userName: String,
    displayName: String,
    passHash: String,
    creationDate: Timestamp = new Timestamp(new Date().getTime()),
    lastVisitDate: Option[Timestamp] = None,
    // etc
    id: Option[Int] = None){}

class MessageTable(tag: Tag) extends Table[Message](tag, "messages") {
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def text = column[String]("text")
    def userId = column[Int]("user_id")
    def date = column[Timestamp]("creation_date")

    def * = (title, text, userId, date, id.?) <> (Post.tupled, Post.unapply 
    def author = foreignKey("message_user_fk", userId, TableQuery[UserTable])(_.id)
}

class UserTable(tag: Tag) extends Table[User](tag, "USER") {
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def username = column[String]("username")
    def passHash = column[String]("password")
    def displayname = column[String]("display_name")

    def * = (username, passHash,created, lastvisit, ..., id.?) <> (User.tupled, User.unapply)
}

还有一个方便的辅助对象:

object db {
    object users extends TableQuery(new UserTable(_)) {
        // helper methods, compiled queries
    }
    object messages extends TableQuery(new MessageTable(_)) {
        // helper methods, compiled queries
    }
}

现在,这在内部已经很完美了,但是如果我想显示实际的消息,我希望消息类在模板中使用时能够返回它的作者姓名。

以下是我的考虑:

  1. 我不希望(而且我无论如何也不能)在不属于它的地方传递隐式光滑 Session - 例如模板引擎和模型类
  2. 在这种特殊情况下,我希望避免为消息逐一请求额外数据

我对 Hibernate 比对 Slick 更熟悉;在 Hibernate 中,我会使用 join-fetching。对于 Slick,我想出的最佳想法是使用另一个数据保持器类进行显示:

class LoadedMessage (user:User, message:Message) {
    // getters
    def text = message.text
    def date = message.date
    def author = user.displayName
}
object LoadedMessage {
    def apply( u:User , m:Message ) = new LoadedMessage(u, m)
}

并用连接查询的结果填充它:

val messageList: List[LoadedMessage] = (
    for (
        u <- db.users;
        m <- db.messages if u.id === m.userId
    ) yield (u, m))
    .sortBy({ case (u, m) => m.date })
    .drop(n)
    .take(amount)
    .list.map { case (u, m) => LoadedMessage(u, m) }

然后将它传递到任何地方。我关心的是这个额外的类 - 不是很干燥,所以不必要的转换(而且似乎我不能让它隐含),很多样板。

常用的方法是什么? 有没有办法减少额外的类并真正使模型能够返回它的关联?

【问题讨论】:

  • 只是一个考虑因素,Hibernate 是一个 ORM,而Slick 是一个允许您编写类型安全查询的层,您不能指望相同的功能,在这种情况下尤其是您可以除非您在使用连接的查询中指定,否则不要期望 Slick 为您获取对象。如果您更喜欢 ORM,请查看 SQueryl 例如。
  • 我不喜欢编写查询或加入表格。我对如何以有效的方式表示结果感到更加困惑
  • 感谢您的提示,我也会看看 Squeryl。我刚开始玩 Play,几乎整个堆栈都不熟悉。

标签: scala playframework-2.0 slick-2.0


【解决方案1】:

按照我的评论:

在我看来,如何处理连接结果是个人喜好问题,你的方法也是我会使用的,你有一个特殊的数据结构,它封装了你的数据并且可以很容易地传递(例如在视图中)并访问。

想到的另外两种方法是

  1. 查询字段而不是对象,它不太易读,我通常讨厌使用元组(在这种情况下是三元组),因为我发现符号 _._1MyClass.myField 更易读。这意味着做这样的事情:

    val messageList = (
      for (
        u <- db.users;
        m <- db.messages if u.id === m.userId
      ) yield (u.displayName, m.date, m.text))
      .sortBy({ case (name, date, text) => date })
      .drop(n)
      .take(amount)
      .list()
    

    这将返回一个三元组,然后可以在您的视图中传递,这是可能的,但我永远不会这样做。

  2. 另一个选项是传递对象的元组,除了最后一个map之外,与您的方法非常相似

    val messageList: List[(User, Message)] = (
      for (
        u <- db.users;
        m <- db.messages if u.id === m.userId
      ) yield (u, m))
      .sortBy({ case (u, m) => m.date })
      .drop(n)
      .take(amount)
      .list()
    

    在这里你可以像这样在视图中传递:

    @(messagesAndAuthors: List[(User, Message)])
    

    然后使用元组和类访问功能访问数据,但是您的模板又将是 _1s 的集合,而且这又是一个难以阅读的内容,而且您必须执行类似 messagesAndAuthors._1.name 之类的操作才能获得一个价值。

最后,我更喜欢在我的观点中尽可能干净地传递变量(我猜这是计算机科学中为数不多的普遍接受的原则之一)并隐藏模型中使用的逻辑,为此使用了一个临时案例在我看来,上课是要走的路。案例类是一个非常强大的工具,很高兴拥有它,对您来说,它可能看起来并不枯燥并且比其他方法更冗长(例如您谈到的Hibernate),但认为当开发人员阅读您的代码时,它将是很容易,因为它可以在这一刻得到,额外的小班将节省数小时的头撞。

对于Session 麻烦请参阅this related question,目前无法避免传递它(据我所知),但您应该能够包含这种情况并避免在模板中传递会话,我通常在我的入口点创建一个会话(大多数时候是一个控制器)并将其传递给模型。

注意:代码未经测试,可能有一些错误。

【讨论】:

  • 我也使用 play-slick,所以控制器中隐含的Session 并不麻烦。我最初的尝试是实现关联的延迟加载,但在视图中传递隐式Session 并不是很成功。现在想起来,这是一个可怕的想法。我知道,通过 play 和 slick,必须先加载所有内容,然后才能将其传递给视图。
  • 关于您的第二种方法 - 我还可以考虑制作一个简单的 case class MessageWithAuthor (u:User, m:Message){} 之类的代码,代码较少,但访问它并不那么漂亮。虽然仍然比元组好,但我担心它可能会像MessageWithAuthorMessageWithResponsesMessageWithParent 等一样以地狱告终。或者可能使MessageWithAllRelatedStuff,以及相关的东西Optional - 但我会输编译时检查我是否实际加载了我使用的东西。
  • 您可以使用封装UserMessage 的案例类,但这不会比您以前的方法更干燥,而是访问会更冗长(类似于MessageWithAuthor.u.name)。如果你真的担心每种情况都会有一个案例类,你应该使用元组,最后,如果你需要 3 或 4 个案例类来完成这项工作,这是一个权衡,这就是我会做的,对我来说易读性是比 DRYness(到一定长度)更重要,那么如果您的用例具有所有可能的组合,那么元组就是要走的路。
  • 我用元组试过了;这并不是那么糟糕,因为我可以将它们分解为带有理解和匹配的模板。最后,将元组用于奇怪的组合并将类用于常见组合是最有意义的。
猜你喜欢
  • 1970-01-01
  • 2012-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-28
  • 2014-10-23
相关资源
最近更新 更多