[JAVA-607] Stackoverflow when querying document with cycle reference using DBRef Created: 23/Jul/12  Updated: 25/Jun/13  Resolved: 23/Jul/12

Status: Closed
Project: Java Driver
Component/s: API
Affects Version/s: 2.7.1
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Ludovic PRAUD Assignee: Jeffrey Yemin
Resolution: Done Votes: 0
Labels: driver
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

MongoDB 2.0.4, Linux Mint 13



 Description   

I first created an issue at spring-data-mongo https://jira.springsource.org/browse/DATAMONGO-488 and it appears that the issue come from the mongo Java Driver.

Following my test code :

Mongo mongo = new Mongo("localhost" , 27017);
DB db = mongo.getDB("test");
 
DBCollection sites = db.getCollection("sites");
DBCollection users = db.getCollection("users");
 
BasicDBObject site = new BasicDBObject();
ObjectId siteId = new ObjectId();
site.put("_id", site);
site.put("name", "site");
WriteResult result = sites.insert(site);
 
BasicDBObject user = new BasicDBObject();
ObjectId userId = new ObjectId();
user.put("_id", userId);
user.put("name", "user");
user.put("site", new DBRef(db, site)); // throws a StackoverflowError

Is this a proper use of DBRef or does cycle reference not allowed ?

The incriminated stacktrace :

java.lang.StackOverflowError
	at java.util.LinkedHashMap$LinkedHashIterator.<init>(LinkedHashMap.java:362)
	at java.util.LinkedHashMap$LinkedHashIterator.<init>(LinkedHashMap.java:362)
	at java.util.LinkedHashMap$KeyIterator.<init>(LinkedHashMap.java:400)
	at java.util.LinkedHashMap$KeyIterator.<init>(LinkedHashMap.java:400)
	at java.util.LinkedHashMap.newKeyIterator(LinkedHashMap.java:413)
	at java.util.HashMap$KeySet.iterator(HashMap.java:887)
	at com.mongodb.DBCollection._checkKeys(DBCollection.java:1170)
	at com.mongodb.DBCollection._checkKeys(DBCollection.java:1174)
	at com.mongodb.DBCollection._checkKeys(DBCollection.java:1174)
	at com.mongodb.DBCollection._checkKeys(DBCollection.java:1174)
	at com.mongodb.DBCollection._checkKeys(DBCollection.java:1174)
	at com.mongodb.DBCollection._checkKeys(DBCollection.java:1174)
	at com.mongodb.DBCollection._checkKeys(DBCollection.java:1174)
        ...



 Comments   
Comment by Ludovic PRAUD [ 23/Jul/12 ]

Your right, this works very well. Thanks. I've misread your first comment

Comment by Scott Hernandez (Inactive) [ 23/Jul/12 ]

You can have a cyclic reference using a DBRef but it doesn't have any issue since they are never resolved by the driver. The query is done by the user (spring in this case) so the driver can store this and it won't be a problem. I have updated the spring case with some details since that is where the stack-overflow is caused.

Mongo mongo = new Mongo("localhost" , 27017);
DB db = mongo.getDB("test");
 
DBRef meRef = new DBRef(db, "me", 1);
BasicDBObject me = new BasicDBObject();
me.put("_id", 1);
me.put("meRef", meRef);
db.getCollection("me").insert(me);
 
BasicDBObject meAgain = (BasicDBObject) db.getCollection("me").findOne();
System.out.println("me");
System.out.println(meAgain);
 
meAgain = (BasicDBObject) ((DBRef) meAgain.get("meRef")).fetch();
System.out.println("me again");
System.out.println(meAgain);
 
meAgain = (BasicDBObject) ((DBRef) meAgain.get("meRef")).fetch();
System.out.println("and again");
System.out.println(meAgain);
 
//for good measure
meAgain = (BasicDBObject) ((DBRef) meAgain.get("meRef")).fetch();

produces this:

me
{ "_id" : 1 , "meRef" : { "$ref" : "me" , "$id" : 1}}
me again
{ "_id" : 1 , "meRef" : { "$ref" : "me" , "$id" : 1}}
and again
{ "_id" : 1 , "meRef" : { "$ref" : "me" , "$id" : 1}}

Comment by Scott Hernandez (Inactive) [ 23/Jul/12 ]

Here is my working code using your example:

Mongo mongo = new Mongo("localhost" , 27017);
DB db = mongo.getDB("test");
 
DBCollection sites = db.getCollection("sites");
DBCollection users = db.getCollection("users");
 
BasicDBObject site = new BasicDBObject();
ObjectId siteId = new ObjectId();
site.put("_id", siteId);
site.put("name", "site");
WriteResult result = sites.insert(site);
 
BasicDBObject user = new BasicDBObject();
ObjectId userId = new ObjectId();
user.put("_id", userId);
user.put("name", "user");
user.put("site", new DBRef(db, sites.getName(), siteId));
users.insert(user);
 
System.out.write("success".getBytes());

And it produces the one word "success" when run.

Comment by Ludovic PRAUD [ 23/Jul/12 ]

Ok, the problem is not that I want to use directly a DBRef instead of using an ID. I use spring-data and when I want to have a User object in my Site object AND a Site object in my User object, it requires an @DBRef to load the entity document from mongo. So I've just wanted to reproduce the case using the java driver directly.

Maybe spring data should allow shallow reference as you suggest, ie: directly store the ID. For now, I have broken the cycle but I will try to store the site ID as a String.

Comment by Scott Hernandez (Inactive) [ 23/Jul/12 ]

In general you could just store the siteId directly, no dbref. The only advantage to storing a dbref would be to allow you to use a different db/collection but since you know that already (and it won't change for some users and not others) I can't think of a good reason to use a dbref here.

Comment by Ludovic PRAUD [ 23/Jul/12 ]

My bad, I've changed

user.put("site", new DBRef(db, site));

by

user.put("site", siteId);

but the error is still the same.

Even with your recommendation

user.put("site", new DBRef(db, "sites", siteId));

Comment by Scott Hernandez (Inactive) [ 23/Jul/12 ]

This code is incorrect. You should create the DBRef with the siteId, just like the _id field for the site. Both use the wrong variable.

Here is the fixed up code:

site.put("_id", siteId);
...
user.put("site", new DBRef(db, "collectionName", siteId));

Generated at Thu Feb 08 08:52:40 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.