0

How Do I properly Manage mongo connections using multiple classes ?

For example I have 4 classes that manages 4 collections.

Collection1.class Collection2.class Etc..

What I do is creating a connect and close method in each class which by the time slows the connection of some transactions in the app

What would be the best way to connect the app to db once and start using all the classes instances other than creating object of each class and connecting each one separately ?

prasad_
  • 8,610
  • 1
  • 14
  • 24
  • A clarification question: when you say 'properly' do you mean with respect specifically to performance? You mention OO in the question title but your final 2 sentences seem to imply that your primary concern is performance. Please clarify as these are likely to drive radically different answers. – sprinter Feb 12 '20 at 02:20
  • @sprinter well each class instance needs a connection so i will end up with class1.connect class2.connect etc ... so i though of creating one class holding connection as well as db collections and inheriting from it but couldnt do it ... as well definetly performance matters I guess you get my point ? – Raymond Hani Artin Feb 12 '20 at 02:23
  • Ah ok I understand now. Will try to answer. – sprinter Feb 12 '20 at 02:32
  • I posted an answer with some additional details and some Java code. Hope it is useful for your application. – prasad_ Feb 21 '20 at 11:23

2 Answers2

1

In the application, a single MongoClient object with a required number of connections, using connection pooling, will work in this case. The default value of the connection pool of 100, and can be modified (or configured) as needed.

The mongo client object can be created at the start of the application and is closed only when the application is closed. This saves the resources related to creating a connection with the mongo client objects in each collection access class.

The same mongo client object can be used throughout the application. A singleton class (which maintains one instance of the mongo client object) can be accessed by any other object in the application which needs a connection to the MongoDB database server.


What is connection pooling?

In software engineering, a connection pool is a cache of database connections maintained so that the connections can be reused when future requests to the database are required. Connection pools are used to enhance the performance of executing commands on a database. Opening and maintaining a database connection for each user, especially requests made to a dynamic database-driven website application, is costly and wastes resources. In connection pooling, after a connection is created, it is placed in the pool and it is used again so that a new connection does not have to be established. If all the connections are being used, a new connection is made and is added to the pool. Connection pooling also cuts down on the amount of time a user must wait to establish a connection to the database.


Example Code:

/*
 * Manages the MongoClient object and its settings like host, port, connection pool, etc.
 */
public class DBAccess {

  private static MongoClient mongoClient;
  private static DBAccess dbAccess;

  // MongoClient with default settings
  // NOTE: the code will have only one of the constructors
  //private DBAccess() {
  //    final String connectionString = "mongodb://localhost:27017";
  //    this.mongoClient = MongoClients.create(connectionString);
  //}

  // MongoClient with custom settings.
  // Private constructor, so that the class can be instantiated outside this class.
  // NOTE: the code will have only one of the constructors
  private DBAccess() {

      MongoClientSettings settings =
          MongoClientSettings.builder()
              .applyToConnectionPoolSettings(builder ->
                   builder.maxSize(40).minSize(10))
             .applyToClusterSettings(builder ->
                   builder.hosts(Arrays.asList(new ServerAddress("localhost", 27017))))
            .build();

      mongoClient = MongoClients.create(settings);
  }

  public static MongoClient getConnection() {

      if (dbAccess == null) {
           dbAccess = new DBAccess();   
      }

      return mongoClient;
  }

  public static void closeDatabase() {
      mongoClient.close();
  }
}

/*
 * Class manages a collection.
 */
public class CollectionOneAccess {

  public static String COLLECTION_ONE = "collection_one";
  private MongoCollection<Document> collection;

  public CollectionOneAccess(MongoDatabase db) {    
      collection = db.getCollection(COLLECTION_ONE);
  }

  public void printOneDocument() {
      Document myDoc = collection.find().first();
      System.out.println(myDoc.toJson());
  }

  // other CRUD operations ...

}


// Usage of DBAcess and CollectionOneAccess classes:

private static final String APP_DATABASE = "abc_db";

public static void main(String [] args) {
    MongoDatabase database = DBAccess.getConnection().getDatabase(APP_DATABASE);
    CollectionOneAccess one = new CollectionOneAccess(database);
    one.printOneDocument();
    // ...
}

Mongo Client

MongoClient object is used to connect to the MongoDB server, get access to a database using the getDatebase() method and work with collections.

com.mongodb.client.MongoClient interface:

A client-side representation of a MongoDB cluster. Instances can represent either a standalone MongoDB instance, a replica set, or a sharded cluster. Instance of this class are responsible for maintaining an up-to-date state of the cluster, and possibly cache resources related to this, including background threads for monitoring, and connection pools.

From the MongoDB Java documentation:

The MongoClient instance represents a pool of connections to the database; you will only need one instance of class MongoClient even with multiple threads.

IMPORTANT: Typically you only create one MongoClient instance for a given MongoDB deployment (e.g. standalone, replica set, or a sharded cluster) and use it across your application. However, if you do create multiple instances:

  • All resource usage limits (e.g. max connections, etc.) apply per MongoClient instance.
  • To dispose of an instance, call MongoClient.close() to clean up resources.

The following code creates a MongoDB client connection object with default settings, like the host ("localhost") and port (27017), connection pooling, etc., and connects to a MongoDB instance and gets access to the testDB database.

MongoClient mongoClient = MongoClients.create();
MongoDatabase database = mongoClient.getDatabase("testDB");

Mongo Client Settings:

You can explicitly specify other settings with the MongoClientSettings to control the behavior of a MongoClient.

MongoClient mongoClient = MongoClients.create(MongoClientSettings settings)

The ConnectionPoolSettings object specifies all settings that relate to the pool of connections to a MongoDB server. The application creates this connection pool when the client object is created. ConnectionPoolSettings.Builder is a builder for ConnectionPoolSettings, has methods to specify the connection pool properties. E.g., maxSize​(int maxSize): The maximum number of connections allowed (default is 100). Other methods include, minSize, maxConnectionIdleTime, etc.

Code to instantiate a MongoClient with connection pool settings:

MongoClientSettings settings = MongoClientSettings.builder()
                                   .applyToConnectionPoolSettings(builder -> 
                                       builder.maxSize(20))
                                   .build();
MongoClient mongoClient = MongoClients.create(settings);
// ...
// Verify the connection pool settings 
System.out.println("Pool size: " + 
    settings.getConnectionPoolSettings().getMaxSize());
prasad_
  • 8,610
  • 1
  • 14
  • 24
0

You are correct that each class (representing a MongoDB collection) should not be managing its own connection to the database. Rather you should be passing the database connection into the class - generally in the constructor. Something like this:

class Animal {
    private String species;
    private String name;
    private int age;

    public Animal(DBObject dbObject) { ... }
}

class AnimalCollection {
    private final DBCollection collection;        

    public AnimalCollection(Database database) {
        collection = database.getCollection("animals");
    }

    public List<Animal> getAll() {
        List<Animal> animals 
        try (DBCursor cursor = collection.find(query)) {
            while (cursor.hasNext()) {
                animals.add(new Animal(cursor.next());
            }
        }
        return animals;
    }
}

Your code to create all the collections should get the MongoClient, connect to the DB and managing closing the connection at exit. That way you have a single connection you manage.

So the class to manage the collections might look like:

class CollectionManager implements AutoCloseable {
    private final Database database;
    private final AnimalCollection animals;

    public CollectionManager(MongoClient client) {
        database = client.getDB("Zoo");
        animals = new AnimalCollection(database);
    }

    @Override
    public void close() {
        database.close();
    }
}

The reason to have this class extend AutoCloseable is that close is called automatically when you exit a try-with-resources block. That'll make your code easier to read and safer.

This approach has another big advantage. You can unit test your classes by passing a mocked Database in the constructor and test behaviour in response to various DB outputs without needing an actual DB with any data in it.

sprinter
  • 24,103
  • 5
  • 40
  • 71
  • Thanks a lot that is exactly what I was looking for ... so basically If you can confirm on using a singleton design pattern for the connection so that I wouldnt need to pass the connection class object each time ... with this I can create as much objects from the collection classes while maintaining only one connection class object ... correct ?? – Raymond Hani Artin Feb 12 '20 at 02:51
  • No not a singleton - that generally means a single global variable that all the collection classes access. I'm recommending that you pass the database object into each collection class. That is much better design for a variety of reasons. I'll add a bit of detail on this to the end of my answer. – sprinter Feb 12 '20 at 02:59
  • Ok great ... however what i meant is creating a class that holds 2 methods connect and closedb and passing it using singleton any where in the app... applying it to your animal collection would be lil this : animalcollection(new connectionobject) which some how will manage to return the previously created connection instance ... – Raymond Hani Artin Feb 12 '20 at 03:10
  • If by 'singleton' you mean a single shared object then yes. Generally that design pattern also implies global access to the object without requiring it to be passed to the client. That's generally (though not always) poor design. – sprinter Feb 12 '20 at 03:12
  • Ok that is not my understanding of singleton ... I thought that it is a design that ensures that only one instance of the class is created all over the app.. however why do you consider it a poor design? – Raymond Hani Artin Feb 12 '20 at 03:18
  • Whether singleton's are a good or bad idea is a fiercely contested topic! For example, take a look at https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons But if you want my personal opinion, there are times when singletons are useful but in most cases I try to avoid them because they make testing harder. – sprinter Feb 12 '20 at 05:36
  • Ok thanks a lot for your support ... but generally speaking what I am doing here is a good design of Db connection case right?? even though all what the collection classes do is containing methods that retreive data for the rest of the App right directly ... I mean if it does not store info in variables and calling them later? – Raymond Hani Artin Feb 12 '20 at 15:22