7

我不确定从哪里开始或哪些信息是相关的,请让我知道哪些附加信息可能对解决此问题有用。

我正在开发一个简单的 cometd 应用程序,并且使用 mongodb 作为我的存储后端。当应用程序启动时,我获得了一个 mongodb 实例,并将该实例用于所有查询。这实际上是由 mongo java 驱动程序文档推荐的,如下所述:http ://www.mongodb.org/display/DOCS/Java+Driver+Concurrency 。我抓住了稻草,认为这个问题与线程安全有关,但根据该链接,mongodb 是完全线程安全的。

这就是有趣的地方。我有一个扩展类BasicDBObject

public class MyBasicDBObject {

    private static final String MAP = "map";

    public boolean updateMapAnd(String submap, String key, byte[] value) {
         Map topMap = (Map)this.get(MAP);
         Map embeddedMap = topMap.get(submap);
         byte[] oldValue = embeddedMap.get(key);

         newValue = UtilityClass.binaryAnd(oldValue, value);

         embeddedMap.put(key, newValue);
         topMap.put(submap, embeddedMap);
         this.put(MAP, topMap);
    }

    public boolean updateMapXor(String submap, String key, byte[] value) {
         Map topMap = (Map)this.get(MAP);
         Map embeddedMap = topMap.get(submap);
         byte[] oldValue = embeddedMap.get(key);

         newValue = UtilityClass.binaryXor(oldValue, value);

         embeddedMap.put(key, newValue);
         topMap.put(submap, embeddedMap);
         this.put(MAP, topMap);
    }
}

接下来是两个扩展的骨架类MyBasicDBObject

public class FirstDBObject extends MyBasicDBObject { //no code }

public class SecondDBObject extends MyBasicDBObject { //no code }

我以这种方式设置类的唯一原因是提高代码在处理同一范围内的这两个对象时的可读性。这让我可以执行以下操作...

//a cometd service callback
public void updateMapObjectsFoo(ServerSession remote, Message message) {

    //locate the objects to update...
    FirstDBObject first = (FirstDBObject) firstCollection.findOne({ ... });
    SecondDBObject second = (SecondDBObject) secondCollection.findOne({ ... });

    //update them as follows
    first.updateMapAnd("default", "someKey1", newBinaryData1);
    second.updateMapAnd("default", "someKey2", newBinaryData2);

    //save (update) them to their respective collections
    firstCollection.save(first);
    secondCollection.save(second);
}

public void updateMapObjectsBar(ServerSession remote, Message message) {

    //locate the objects to update...
    FirstDBObject first = (FirstDBObject) firstCollection.findOne({ ... });
    SecondDBObject second = (SecondDBObject) secondCollection.findOne({ ... });

    /** 
     * the only difference is these two calls 
     */
    first.updateMapXor("default", "someKey1", newBinaryData1);
    second.updateMapXor("default", "someKey2", newBinaryData2);

    //save (update) them to their respective collections
    firstCollection.save(first);
    secondCollection.save(second);
}

通过遍历传递的字节数组,按位和按位UtilityClass命名方法完全一样。&^

这是我完全迷路的地方。 updateMapObjectsFoo()完全按预期工作,既first反映second了数据库中的变化。 updateMapObjectsBar()另一方面,只能设法正确更新first

通过调试检查updateMapObjectsBar()表明二进制对象实际上在两个对象上都正确更新了,但是当我前往 mongo shell 调查问题时,我发现first数据库中已更新但second没有更新。我从哪里得到线程安全与它有关的想法?让我烦恼的唯一区别是secondCollection其他cometd服务使用了它,而firstCollection没有使用。一方面这似乎相关,但另一方面则不相关,因为Foo有效且Bar无效。

我已经将代码拆开并将其重新组合在一起,并且我不断地回到同样的问题。这里到底发生了什么?

似乎我遗漏了最相关的部分,即 java 泛型的噩梦和 mongodb 驱动程序对语言这一特性的依赖。 BasicDBObject本质上是 a 的包装器Map<String, Object>。问题是,一旦您在该地图中存储了一个对象,您必须将其转换回您将其放入其中时的状态。是的,这可能看起来很明显,在发布这个问题之前我就很清楚了。

我无法确定到底发生了什么,但我会向 java + mongodb 用户提供这个建议。您将进行大量转换,并且您的数据结构越复杂,您需要的转换就越多。长话短说,不要这样做:

DBObject obj = (DBObject) collection.findOne(new BasicDBObject("_id", new ObjectId((String)anotherObj.get("objId"))));

当你在做快速原型时,一个班轮很诱人,但当你开始一遍又一遍地做这件事时,你肯定会犯错误。现在写更多代码,以后少受挫折:

DBObject query = new DBObject();
String objId = (String) anotherObj.get("objId");
query.put("_id", new ObjectId(objId));
obj = (DBObject) collection.findOne(query);

我认为这很烦人,但我应该期望直接与 Mongo 交互,而不是使用某种库来让我的生活更轻松。我在这件事上自欺欺人,但希望有人能从我的错误中吸取教训,为自己省去很多挫折。

感谢大家的帮助。

4

1 回答 1

2

这很容易成为多线程问题。如果只有一个 Mongo 实例,那么 Mongo、DB 和 DBCollection 对象是线程安全的,这是正确的,但 DBObjects不是线程安全的。但即使它们是线程安全的,您的 updateMapObjectsFoo/Bar 方法也无法确保它们是数据库上的原子操作。

不幸的是,您需要对代码进行的更改比仅仅散布一些“同步”关键字更加激烈。看看http://www.mongodb.org/display/DOCS/Atomic+Operations是否不能帮助您了解问题的范围和一些潜在的解决方案。

于 2012-10-22T18:21:21.910 回答