0

I'm having issues with a really simple task, in Java: I need to get "users" array inside an object,

Check if it contains a key ID, and if not, add a new user to the array. I don't get any error, but the user isn't added.

Please, answer using the Java driver. Why is that? Here is the code:

List<DBObject> queryResultList = cursor.toArray(1);
DBObject currentObj = queryResultList.get(0);
Set userIdsKeySet = ((BasicDBObject) currentObj.get("users")).keySet();
BasicDBObject newObj = null;
if(!userIdsKeySet.contains(userId)){
    ((BasicDBList)currentObj.get("users")).add(new BasicDBObject().append(userId, user));
}
if(newObj != null) {
    collection.update(currentObj, new BasicDBObject("users", newObj));
    return true;
}

The document structure looks like that:

{
    "_id": "5de9604ef36d7f394a4ade2f",
    "_index": "sw0dfb0",
    "users": [{
        "e9604ef36d94a4ade7f394": {
            "some_field": "abcde"
        }
    }]
}

Is it a better way to make the users array this way?

"users": [{
     "user_id":"e9604ef36d94a4ade7f394",
     "some_field":"abcde"
}]

Note: I Know there are much prettier and simpler ways to do it, any informative advice is welcome.

prasad_
  • 12,755
  • 2
  • 24
  • 36
Upsilon42
  • 241
  • 2
  • 17
  • 1
    by the way, its considered bad form to use data points (for example `e9604ef36d94a4ade7f394`) as a key in a key-value store. Your second example where "user_id": "e9604ef36d94a4ade7f394" is better because you have a key with a valid name which is not a data point. – barrypicker Dec 05 '19 at 23:17
  • Thanks @barrypicker! added the brackets, let's consider that I do it in the correct way you just mentioned. I can't make that update work! – Upsilon42 Dec 05 '19 at 23:20
  • 1
    OK, so you want to update a single sub document in the array? Or are you simply trying to inject the sub document into the array? – barrypicker Dec 05 '19 at 23:20
  • @barrypicker I'm trying to add a new user object to the array, if it's not present yet (by user_id) – Upsilon42 Dec 05 '19 at 23:24
  • I added an answer with a code snippet. Instead of having import statements I fully qualify each object for clarity. Please let me know if you have any questions... – barrypicker Dec 05 '19 at 23:39

2 Answers2

1

I have a Java example program that I continually hack on to learn. My example may have bits that don't apply, but the basis for the question in here. Notice use of "$push"...

This assumes a record is available to query and push a new array item...

package test.barry;

public class Main {

        public static void main(String[] args) {
                com.mongodb.client.MongoDatabase db = connectToClusterStandAlone();
                InsertArrayItem(db);

                return;
        }


        private static void InsertArrayItem(com.mongodb.client.MongoDatabase db) {
                System.out.println("");
                System.out.println("Starting InsertArrayItem...");

                com.mongodb.client.MongoCollection<org.bson.Document> collection = db.getCollection("people");

                com.mongodb.client.MongoCursor<org.bson.Document> cursor = collection.find(com.mongodb.client.model.Filters.eq("testfield", true)).sort(new org.bson.Document("review_date", -1)).limit(1).iterator();

                if(cursor.hasNext()) {
                        org.bson.Document document = cursor.next();
                        Object id = document.get("_id");

                        System.out.println("Selected Id: " + id.toString());

                        org.bson.Document newDocument = new org.bson.Document("somekey", "somevalue");

                        collection.findOneAndUpdate(
                                com.mongodb.client.model.Filters.eq("_id", id),
                                new org.bson.Document("$push", new org.bson.Document("myarray", newDocument))
                        );  
                }   


                System.out.println("Completed InsertArrayItem.");
        }

        private static com.mongodb.client.MongoDatabase connectToClusterStandAlone() {
                // STANDALONE STILL REQUIRES HOSTS LIST WITH ONE ELEMENT...
                // http://mongodb.github.io/mongo-java-driver/3.9/javadoc/com/mongodb/MongoClientSettings.Builder.html

                java.util.ArrayList<com.mongodb.ServerAddress> hosts = new java.util.ArrayList<com.mongodb.ServerAddress>();
                hosts.add(new com.mongodb.ServerAddress("127.0.0.1", 27017));

                com.mongodb.MongoCredential mongoCredential = com.mongodb.MongoCredential.createScramSha1Credential("testuser", "admin", "mysecret".toCharArray());

                com.mongodb.MongoClientSettings mongoClientSettings = com.mongodb.MongoClientSettings.builder()
                        .applyToClusterSettings(clusterSettingsBuilder -> clusterSettingsBuilder.hosts(hosts))
                        .credential(mongoCredential)
                        .writeConcern(com.mongodb.WriteConcern.W1)
                        .readConcern(com.mongodb.ReadConcern.MAJORITY)
                        .readPreference(com.mongodb.ReadPreference.nearest())
                        .retryWrites(true)
                        .build();

                com.mongodb.client.MongoClient client = com.mongodb.client.MongoClients.create(mongoClientSettings);
                com.mongodb.client.MongoDatabase db = client.getDatabase("test");

                return db;
        }
}

Example document after running twice...

{
    "_id" : ObjectId("5de7f472b0ba4011a7caa59c"),
    "name" : "someone somebody",
    "age" : 22,
    "state" : "WA",
    "phone" : "(739) 543-2109",
    "ssn" : "444-22-9999",
    "testfield" : true,
    "versions" : [
        "v1.2",
        "v1.3",
        "v1.4"
    ],
    "info" : {
        "x" : 444,
        "y" : "yes"
    },
    "somefield" : "d21ee185-b6f6-4b58-896a-79424d163626",
    "myarray" : [
        {
            "somekey" : "somevalue"
        },
        {
            "somekey" : "somevalue"
        }
    ]
}

For completeness here is my maven file...

Maven POM File...

<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>test.barry</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>test</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${basedir}</outputDirectory>
                            <finalName>Test</finalName>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>test.barry.Main</mainClass>
                                </transformer>
                            </transformers>
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>3.10.1</version>
        </dependency>
    </dependencies>
</project>
barrypicker
  • 9,740
  • 11
  • 65
  • 79
  • Great answer, thanks! But little problem, I can't user findOneAndUpdate method as it cant get resolved: Cannot resolve method 'findOneAndUpdate(org.bson.conversions.Bson, org.bson.Document)' – Upsilon42 Dec 05 '19 at 23:47
  • @Upsilon42 - are you saying this does not compile, or are you integrating into your code and it does not find the method? – barrypicker Dec 05 '19 at 23:48
  • It cannot compile, because method is not found :) – Upsilon42 Dec 05 '19 at 23:49
  • I know there is a lot of examples floating about using the old driver client. This example is using the 3.10 driver and most recent client. MongoDB now has driver 3.11 released for general use. I have compiled this file via `mvn package` and test run with the command `java -jar Test.jar` and I evaluated the database to ensure it worked as expected. – barrypicker Dec 05 '19 at 23:52
  • @Upsilon42 - I cannot tell by your code example what type 'collection' is. – barrypicker Dec 05 '19 at 23:52
  • It's because the collection I'm using is DBCollection, and not com.mongodb.client.MongoCollection. – Upsilon42 Dec 05 '19 at 23:53
  • @Upsilon42 - OK that makes sense. I believe DBCollection is the old client. Could I convince you to use the more modern client? Everything is headed that way anyways. see https://stackoverflow.com/questions/29364787/mongocollection-versus-dbcollection-java – barrypicker Dec 05 '19 at 23:55
  • Actually I'm using 3.11. I'm trying to use you code – Upsilon42 Dec 06 '19 at 00:02
  • @Upsilon42 - Yeah, Mongo kept the old client in the new driver for backwards compatibility. It also adds a ton of confusion. So both object types exist (`com.mongodb.DBCollection` and `com.mongodb.client.MongoCollection`). It simply depends on which model you elect to use - the new vs. the old. The entire client stack has old and new including document types, collection, database, etc. The new client is the currently supported huckleberry and gets all the attention from the Mongo developers. – barrypicker Dec 06 '19 at 00:04
  • I don't have examples for the old client - I have moved on and adopted the new client only... – barrypicker Dec 06 '19 at 00:07
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/203709/discussion-between-upsilon42-and-barrypicker). – Upsilon42 Dec 06 '19 at 00:16
1

I have a sample document and Java code (using MongoDB Java Driver 3.9.0) to update the users array.

MongoDB Enterprise > db.test.find()
{
        "_id" : 1,
        "index" : "999",
        "users" : [
                {
                        "user11" : {
                                "fld1" : "abcde"
                        }
                },
                {
                        "user22" : {
                                "fld1" : "xyz123"
                        }
                }
        ]
}


Java Code:

import org.bson.Document;
import org.bson.conversions.Bson;
import com.mongodb.client.result.UpdateResult;
import static com.mongodb.client.model.Updates.*;
import static com.mongodb.client.model.Filters.*;
import com.mongodb.client.*;

public class Testing9 {

    public static void main(String [] args) {

        MongoClient mongoClient = MongoClients.create("mongodb://localhost/");
        MongoDatabase database = mongoClient.getDatabase("users");
        MongoCollection<Document> collection = database.getCollection("test");

        String user = "user99"; // new user to be added to the array
        Bson userNotExistsFilter = exists(("users." + user), false);
        Bson idFilter = eq("_id", new Integer(1));
        Document newUser = new Document(user, new Document("fld1", "some_value"));
        Bson pushUser = push("users", newUser);

        UpdateResult result = 
            collection.updateOne(and(idFilter, userNotExistsFilter), pushUser);

        System.out.println(result);
    }
}


Result:

Querying the collection shows the updated array field users with the new user "user99":

MongoDB Enterprise > db.test.find().pretty()
{
        "_id" : 1,
        "index" : "999",
        "users" : [
                {
                        "user11" : {
                                "fld1" : "abcde"
                        }
                },
                {
                        "user22" : {
                                "fld1" : "xyz123"
                        }
                },
                {
                        "user99" : {
                                "fld1" : "some_value"
                        }
                }
        ]
}



Shell Query:

This is the equivalent update query from the mongo shell:

db.test.updateOne(
  { _id: 1, "users.user99": { $exists: false} },
  { $push: { users: { user99: { fld1: "some_value" } } } }
)

The collection's array will be added with the following document:

{
  "user99" : {
      "fld1" : "some_value"
  }
}
prasad_
  • 12,755
  • 2
  • 24
  • 36