4

Subset看起来像一个有趣的、精简的 MongoDB 包装器。

在给出的示例之一中,有推文和用户。但是,User是 的子文档Tweet在经典 SQL 中,这将被规范化为两个单独的表,其外键从 Tweet 到 User。在 MongoDB 中,这不需要 a DBRef,存储用户的ObjectId就足够了。

在 Subset 和 Salat 中,这将导致这些案例类:

case class Tweet(_id: ObjectId, content: String, userId: ObjectId)
case class User(_id: ObjectId, name: String)

因此,不能保证 Tweet 中的 ObjectId 实际上解析为 User(使其类型安全性降低)。我还必须为每个引用 User 的类编写相同的查询(或将其移动到某些特征)。

所以我想要实现的是case class Tweet(_id: ObjectId, content: String, userId: User)在代码中和ObjectId数据库中拥有 , 。这可能吗?如果可以,怎么做?什么是好的选择?

4

2 回答 2

3

是的,这是可能的。实际上,它甚至比在“推文”中拥有“用户”子文档还要简单。当 "user" 是一个引用时,它只是一个标量值,MongoDB 和 "Subset" 没有查询子文档字段的机制。

我为您准备了一个简单的 REPLable 代码片段(假设您有两个集合——“tweets”和“users”)。

准备...

import org.bson.types.ObjectId
import com.mongodb._
import com.osinka.subset._
import Document.DocumentId

val db = new Mongo("localhost") getDB "test"
val tweets = db getCollection "tweets"
val users = db getCollection "users"

我们的User案例课

case class User(_id: ObjectId, name: String)

推文和用户的许多字段

val content = "content".fieldOf[String]
val user = "user".fieldOf[User]
val name = "name".fieldOf[String]

这里开始发生更复杂的事情。我们需要的是一个ValueReader能够ObjectId基于字段名称获取的,然后转到另一个集合并从那里读取一个对象。

这可以写成一段代码,一次完成所有事情(您可能会在答案历史记录中看到这样的变体),但将其表达为读者的组合会更惯用。假设我们有一个ValueReader[User]从 读取的DBObject

val userFromDBObject = ValueReader({
  case DocumentId(id) ~ name(name) => User(id, name)
})

剩下的是一个泛型,它使用提供的底层读取器从特定集合ValueReader[T]中期望并检索对象:ObjectId

class RefReader[T](val collection: DBCollection, val underlying: ValueReader[T]) extends ValueReader[T] {
  override def unpack(o: Any):Option[T] =
    o match {
      case id: ObjectId =>
        Option(collection findOne id) flatMap {underlying.unpack _}
      case _ =>
        None
    }
}

那么,我们可以说我们从引用中读取 s 的类型类User仅仅是

implicit val userReader = new RefReader[User](users, userFromDBObject)

(我很感谢你提出这个问题,因为这个用例非常罕见,我没有真正的动机来开发一个通用的解决方案。我想我最终需要将这种帮助程序包含到“子集”中。我会很感激你的对此方法的反馈)


这就是你将如何使用它:

import collection.JavaConverters._

tweets.find.iterator.asScala foreach { 
  case Document.DocumentId(id) ~ content(content) ~ user(u) =>
    println("%s - %s by %s".format(id, content, u))
}
于 2012-08-16T16:18:27.513 回答
0

Alexander Azarov 的回答可能很好,但我个人不会这样做。

您所拥有的是一条只有 ObjectId 对用户的引用的推文。并且您希望在推文加载期间加载用户,因为对于您的域而言,它可能更容易操作。在任何情况下,除非您使用子文档(并不总是一个好的选择),否则您必须再次查询数据库以检索用户数据,这就是 Alexander Azarov 所做的。

您宁愿做一个转换函数,将 Tweet 转换为 TweetWithUser 或类似的东西。

  def transform(tweet: Tweet) = TweetWithUser( tweet.id, tweet.content, findUserWithId(tweet.userId) ) 

我真的不明白为什么您会期望一个框架来解决您本可以在一行代码中轻松完成的事情。

请记住,在您的应用程序中,在某些情况下您甚至不需要整个 User 对象,因此查询两次数据库的成本很高,而并非总是需要。当你真的需要用户数据时,你应该只使用包含完整用户数据的案例类,而不是总是加载完整的用户数据,因为它看起来更方便。

或者,如果您想操作 User 对象,您将拥有一个 User 代理,您可以在其上直接访问 id 属性,并且在任何其他访问时,都会完成 db 查询。在 Java/SQL 中,Hibernate 正在处理关系的延迟加载,但我不确定将它与 MongoDB 一起使用是否是一个好主意,它会破坏不变性

于 2013-01-01T20:43:04.970 回答