I have written a RESTful API using Apache Jersey. I am using MongoDB as my backend. I used Morphia (v.1.3.4) to map and persist POJO to database. I tried to follow "1 application 1 connection" in my API as recommended everywhere but I am not sure I am successful. I run my API in Tomcat 8. I also ran Mongostat to see the details and connection. At start, Mongostat showed 1 connection to MongoDB server. I tested my API using Postman and it was working fine. I then created a load test in SoapUI where I simulated 100 users per second. I saw the update in Mongostat. I saw there were 103 connections. Here is the gif which shows this behaviour.
I am not sure why there are so many connections. The interesting fact is that number of mongo connection are directly proportional to number of users I create on SoapUI. Why is that? I found other similar questions but I think I have implemented there suggestions.
Mongo connection leak with morphia
Spring data mongodb not closing mongodb connections
My code looks like this.
DatabaseConnection.java
// Some imports
public class DatabaseConnection {
private static volatile MongoClient instance;
private static String cloudhost="localhost";
private DatabaseConnection() { }
public synchronized static MongoClient getMongoClient() {
if (instance == null ) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
ServerAddress addr = new ServerAddress(cloudhost, 27017);
List<MongoCredential> credentialsList = new ArrayList<MongoCredential>();
MongoCredential credentia = MongoCredential.createCredential(
"test", "test", "test".toCharArray());
credentialsList.add(credentia);
instance = new MongoClient(addr, credentialsList);
}
}
}
return instance;
}
}
PourService.java
#Secured
#Path("pours")
public class PourService {
final static Logger logger = Logger.getLogger(Pour.class);
private static final int POUR_SIZE = 30;
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response createPour(String request)
{
WebApiResponse response = new WebApiResponse();
Gson gson = new GsonBuilder().setDateFormat("dd/MM/yyyy HH:mm:ss").create();
String message = "Pour was not created.";
HashMap<String, Object> data = null;
try
{
Pour pour = gson.fromJson(request, Pour.class);
// Storing the pour to
PourRepository pourRepository = new PourRepository();
String id = pourRepository.createPour(pour);
data = new HashMap<String, Object>();
if ("" != id && null != id)
{
data.put("id", id);
message = "Pour was created successfully.";
logger.debug(message);
return response.build(true, message, data, 200);
}
logger.debug(message);
return response.build(false, message, data, 500);
}
catch (Exception e)
{
message = "Error while creating Pour.";
logger.error(message, e);
return response.build(false, message, new Object(),500);
}
}
PourDao.java
public class PourDao extends BasicDAO<Pour, String>{
public PourDao(Class<Pour> entityClass, Datastore ds) {
super(entityClass, ds);
}
}
PourRepository.java
public class PourRepository {
private PourDao pourDao;
final static Logger logger = Logger.getLogger(PourRepository.class);
public PourRepository ()
{
try
{
MongoClient mongoClient = DatabaseConnection.getMongoClient();
Datastore ds = new Morphia().map(Pour.class)
.createDatastore(mongoClient, "tilt45");
pourDao = new PourDao(Pour.class,ds);
}
catch (Exception e)
{
logger.error("Error while creating PourDao", e);
}
}
public String createPour (Pour pour)
{
try
{
return pourDao.save(pour).getId().toString();
}
catch (Exception e)
{
logger.error("Error while creating Pour.", e);
return null;
}
}
}
When I work with Mongo+Morphia I get better results using a Factory pattern for the Datastore and not for the MongoClient, for instance, check the following class:
public DatastoreFactory(String dbHost, int dbPort, String dbName) {
final Morphia morphia = new Morphia();
MongoClientOptions.Builder options = MongoClientOptions.builder().socketKeepAlive(true);
morphia.getMapper().getOptions().setStoreEmpties(true);
final Datastore store = morphia.createDatastore(new MongoClient(new ServerAddress(dbHost, dbPort), options.build()), dbName);
store.ensureIndexes();
this.datastore = store;
}
With that approach, everytime you need a datastore you can use the one provided by the factory. Of course, this can implemented better if you use a framework/library that support factory pattern (e.g.: HK2 with org.glassfish.hk2.api.Factory), and also singleton binding.
Besides, you can check the documentation of MongoClientOptions's builder method, perhaps you can find a better connection control there.
Related
I'm using AWS Keyspace (Cassandra 3.11.2) run on Apache Flink in AWS EMR. Some time below query throws Exception. The same code used on AWS Lambda also had the same Exception NoHost. What did I do wrong?
String query = "INSERT INTO TEST (field1, field2) VALUES(?, ?)";
PreparedStatement prepared = CassandraConnector.prepare(query);
int i = 0;
BoundStatement bound = prepared.bind().setString(i++, "Field1").setString(i++, "Field2")
.setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM);
ResultSet rs = CassandraConnector.execute(bound);
at com.datastax.oss.driver.api.core.NoNodeAvailableException.copy(NoNodeAvailableException.java:40)
at com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures.getUninterruptibly(CompletableFutures.java:149)
at com.datastax.oss.driver.internal.core.cql.CqlRequestSyncProcessor.process(CqlRequestSyncProcessor.java:53)
at com.datastax.oss.driver.internal.core.cql.CqlRequestSyncProcessor.process(CqlRequestSyncProcessor.java:30)
at com.datastax.oss.driver.internal.core.session.DefaultSession.execute(DefaultSession.java:230)
at com.datastax.oss.driver.api.core.cql.SyncCqlSession.execute(SyncCqlSession.java:53)
at com.test.manager.connectors.CassandraConnector.execute(CassandraConnector.java:16)
at com.test.repository.impl.BackupRepositoryImpl.insert(BackupRepositoryImpl.java:36)
at com.test.service.impl.BackupServiceImpl.insert(BackupServiceImpl.java:18)
at com.test.flink.function.AsyncBackupFunction.processMessage(AsyncBackupFunction.java:78)
at com.test.flink.function.AsyncBackupFunction.lambda$asyncInvoke$0(AsyncBackupFunction.java:35)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1596)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
This is my code:
CassandraConnector.java:
Because cost of init preparedStatement is huge, I'm cached this.
public class CassandraConnector {
private static final ConcurrentHashMap<String, PreparedStatement> preparedStatementCache = new ConcurrentHashMap<String, PreparedStatement>();
public static ResultSet execute(BoundStatement bound) {
CqlSession session = CassandraManager.getSessionInstance();
return session.execute(bound);
}
public static ResultSet execute(String query) {
CqlSession session = CassandraManager.getSessionInstance();
return session.execute(query);
}
public static PreparedStatement prepare(String query) {
PreparedStatement result = preparedStatementCache.get(query);
if (result == null) {
CqlSession session = CassandraManager.getSessionInstance();
result = session.prepare(query);
preparedStatementCache.putIfAbsent(query, result);
}
return result;
}
}
CassandraManager.java:
I'm using singleton double-check locking for session object.
public class CassandraManager {
private static final Logger logger = LoggerFactory.getLogger(CassandraManager.class);
private static final String SSL_CASSANDRA_PASSWORD = "password";
private static volatile CqlSession session;
static {
try {
initSession();
} catch (Exception e) {
logger.error("Error CassandraManager getSessionInstance", e);
}
}
private static void initSession() {
List<InetSocketAddress> contactPoints = Collections.singletonList(InetSocketAddress.createUnresolved(
"cassandra.ap-southeast-1.amazonaws.com", 9142));
DriverConfigLoader loader = DriverConfigLoader.fromClasspath("application.conf");
Long start = BaseHelper.getTime();
session = CqlSession.builder().addContactPoints(contactPoints).withConfigLoader(loader)
.withAuthCredentials(AppUtil.getProperty("cassandra.username"),
AppUtil.getProperty("cassandra.password"))
.withSslContext(getSSLContext()).withLocalDatacenter("ap-southeast-1")
.withKeyspace(AppUtil.getProperty("cassandra.keyspace")).build();
logger.info("End connect: " + (new Date().getTime() - start));
}
public static CqlSession getSessionInstance() {
if (session == null || session.isClosed()) {
synchronized (CassandraManager.class) {
if (session == null || session.isClosed()) {
initSession();
}
}
}
return session;
}
public static SSLContext getSSLContext() {
InputStream in = null;
try {
KeyStore ks = KeyStore.getInstance("JKS");
in = CassandraManager.class.getClassLoader().getResourceAsStream("cassandra_truststore.jks");
ks.load(in, SSL_CASSANDRA_PASSWORD.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
return ctx;
} catch (Exception e) {
logger.error("Error CassandraConnector getSSLContext", e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.error("", e);
}
}
}
return null;
}
}
application.conf
datastax-java-driver {
basic.request {
timeout = 5 seconds
consistency = LOCAL_ONE
}
advanced.connection {
max-requests-per-connection = 1024
pool {
local.size = 1
remote.size = 1
}
}
advanced.reconnect-on-init = true
advanced.reconnection-policy {
class = ExponentialReconnectionPolicy
base-delay = 1 second
max-delay = 60 seconds
}
advanced.retry-policy {
class = DefaultRetryPolicy
}
advanced.protocol {
version = V4
}
advanced.heartbeat {
interval = 30 seconds
timeout = 1 second
}
advanced.session-leak.threshold = 8
advanced.metadata.token-map.enabled = false
}
There are two scenarios where the driver would report NoNodeAvailableException:
Nodes are unresponsive/unavailable and the driver has marked all of them as down.
All the contact points provided are invalid.
If some inserts are working but eventually runs into NoNodeAvailableException, that indicates to me that the nodes are getting overloaded and eventually become unresponsive so the driver no longer picks a coordinator since they're all marked as "down".
If none of the requests work at all, it means that the contact points are unreachable or unresolvable so the driver can't connect to the cluster. Cheers!
The NoHostAvailableException is a client side exception thrown by the open source driver after it has retried available hosts. The open source driver encapsulated the root cause for retry, which can be confusing.
I suggest first improving you observability by setting up these CloudWatch metrics. You can follow this prebuild CloudFormation template to get started it only takes a few seconds.
Here is a set up for Keyspace & Table Metrics for Amazon Keyspaces using Cloud Watch:
https://github.com/aws-samples/amazon-keyspaces-cloudwatch-cloudformation-templates
You can also replace retry policy with the following examples found in this helper project. The retry policy in this project will either try or throw the original exception which will remove the occurrences of NoHostAvailableException this will provide you with better transparency to your application. Here's the like to the Github repo: https://github.com/aws-samples/amazon-keyspaces-java-driver-helpers
If you're using the private VPC endpoint you want to add the following permissions to enable more entries in the system.peers table.,
Amazon Keyspaces just announced new functionality that will provide more connection points when establishing a session with a private VPC endpoints.
Here is a link about how Keyspaces now automatically optimizes client connection made through AWS PrivateLink to improve availability and write and read: https://aws.amazon.com/about-aws/whats-new/2021/07/amazon-keyspaces-for-apache-cassandra-now-automatically-optimi/
This link that talks about Using Amazon Keypscaes with Interface VPC Endpoints: https://docs.aws.amazon.com/keyspaces/latest/devguide/vpc-endpoints.html . To enable this new functionality you will need to provide additional permissions to DescribeNetworkInterfaces and DescribeVpcEndpoints.
{
"Version":"2012-10-17",
"Statement":[
{
"Sid":"ListVPCEndpoints",
"Effect":"Allow",
"Action":[
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeVpcEndpoints"
],
"Resource":"*"
}
]
}
I suspect that this:
.withLocalDatacenter(AppUtil.getProperty("cassandra.localdatacenter"))
Pulls back a data center name which either does not match the keyspace replication definition or the configured data center name:
nodetool status | grep Datacenter
Basically, if your connection is defined with a local data center which does not exist, it will still try to read/write with replicas in that data center. This will fail, because it obviously cannot find nodes in a non-existent data center.
Similar question here: NoHostAvailable error in cqlsh console
I want to create an AWS Lambda function in java that writes to a database in Firestore. The short story is that, while the code does what it should when I execute it
on my own computer, using NetBeans (the truth is that it works most of the time, but not always, maybe due to problems with my internet connection), nothing at all
happens when I deploy it as a Lambda function and invoke this. I suspect that this has less to do with Firestore itself, but rather with how AWS Lambda handles asynchronous
operations.
Now to the details!
As a simple example, the method that writes to the Firestore object db reads
public static void writeFirestore(Firestore db){
try{
DateTime now = DateTime.now();
String time = now.toString();
Map<String, String> data = new HashMap<>();
data.put("time", time);
String collTitle = "Notebook";
String docTitle = "Document: "+time;
db.collection(collTitle).document(docTitle).set(data);
System.out.println("wrote to Firestore");
}
catch(Exception e){
System.out.println("Could not write to db: "+e.toString());
}
}
Now, as it takes some time to connect to Firestore and initialize db, I want to make sure that db is not passed as an argument into writeFirestore() before it
has been properly retrieved. So, I define a version of db in the form of a Future object, using ExecutorService, and then retrieve
the object db with the get()-method. For this, I define the class TaskRunner:
public class TaskRunner {
ExecutorService executor;
public TaskRunner(){
executor = Executors.newSingleThreadExecutor();
}
public static interface Callback<T>{
public void onCallback(T result);
}
public <T> void executeAsync(Callable<T> callable, Callback<T> callback) throws Exception{
try{
Future future = executor.submit(callable);
Object result = future.get();
if(result != null){
System.out.println("result is not null; applying callback...");
callback.onCallback((T) result);
}
else{
System.out.println("result is null");
}
}
catch(Exception e){
System.out.println("Problem running executeAsync: "+e.toString());
}
}
}
Writing the example document to my fixed database db now goes as follows:
I define the class FirestoreCreator that implements Callable with the purpose of retrieving the Firestore object db:
public static class FirestoreCreator implements Callable<Firestore>{
#Override
public Firestore call() throws Exception {
String projectId = "myProjectId";
GoogleCredentials credentials =
GoogleCredentials.fromStream(new FileInputStream("myCredentialsFile.json"));
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance()
.toBuilder()
.setProjectId(projectId)
.setCredentials(credentials)
.build();
Firestore db = firestoreOptions.getService();
return db;
}
}
I implement the TaskRunner.Callback interface using writeFirestore().
I create a TaskRunner object, taskRunner, and call its executeAsync() method with the above two objects as parameters.
These three steps are collected in the final method testUpdateFirestore() that does the job:
public static void testUpdateFirestoreInterface(){
FirestoreCreator fsCreator = new FirestoreCreator();
TaskRunner.Callback<Firestore> updateCallback = new TaskRunner.Callback<Firestore>() {
#Override
public void onCallback(Firestore result) {
writeFirestore(result);
}
};
TaskRunner taskRunner = new TaskRunner();
try {
taskRunner.executeAsync(fsCreator, updateCallback);
} catch (Exception ex) {
System.out.println("Failed to run executeAsync");
}
}
As I already mentioned in the introduction, the code works (most times) when I run it on my computer, but not at all in AWS Lambda. No exception is thrown, and yet no document has been written in Firestore.
The discussion about threads in AWS Lambda (https://dzone.com/articles/multi-threaded-programming-with-aws-lambda) made me suspect that reason is that the use of some thread that runs when ExecutorService is used is not being handled properly.
Does anyone know what goes wrong and what a solution could look like?
I am working on true SSO in Java application running on Windows 10. My application already has Kerberos auth using Java's GSSAPI (but it obviously does not work on any modern Windows, especially with strict security policies and domain users), so I would like to replace current authorization system with Waffle with minimal implications to overall app design - which I think should be possible if I can get KerberosTicket instance somehow. I am struggling writing this functionality, so far I managed to request some token, but I don't really know what this token is, it does not match Kerberos ticket format. Here is my (actually more like found online code):
public byte[] getServiceTicketSSPI() {
final String securityPackage = "Kerberos";
final String targetName = "<disclosed>";
IWindowsCredentialsHandle clientCredentials = null;
WindowsSecurityContextImpl clientContext = null;
final String currentUser = WindowsAccountImpl.getCurrentUsername();
try {
clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
clientCredentials.initialize();
// initial client security context
clientContext = new WindowsSecurityContextImpl();
clientContext.setPrincipalName(currentUser);
clientContext.setCredentialsHandle(clientCredentials);
clientContext.setSecurityPackage(securityPackage);
final Sspi.SecBufferDesc continueToken = null;
do {
if(debug)
System.out.println("Using target name: " + targetName);
clientContext.initialize(clientContext.getHandle(), continueToken, targetName);
} while(clientContext.isContinue());
return clientContext.getToken();
} finally {
if (clientContext != null)
clientContext.dispose();
if (clientCredentials != null)
clientCredentials.dispose();
}
}
To be fair I am not even sure if SSPI allows me to actually see real ticket. Am I even going in right direction with this snippet? I will be really happy so see any clues as to what should I do. It would be perfect to have KerberosTicket instance in the end.
Below are the steps to do Single Sign On using Waffle for standalone Java Client without using server.
Create client credentials
Get service ticket using initializeSecurityContext of WindowsSecurityContextImpl.
Get WindowsIdentity using accessSecurityContext of WindowsAuthProviderImpl
Original link https://exceptionshub.com/getting-kerberos-service-ticket-using-waffle-in-java.html
For client-server sso, you should follow https://code.dblock.org/2010/04/08/pure-java-waffle.html The code below depicts the standalone java sso using kerberos.
import com.sun.jna.platform.win32.Sspi;
import waffle.windows.auth.IWindowsCredentialsHandle;
import waffle.windows.auth.IWindowsIdentity;
import waffle.windows.auth.IWindowsSecurityContext;
import waffle.windows.auth.impl.WindowsAccountImpl;
import waffle.windows.auth.impl.WindowsAuthProviderImpl;
import waffle.windows.auth.impl.WindowsCredentialsHandleImpl;
import waffle.windows.auth.impl.WindowsSecurityContextImpl;
public class KerberosSingleSignOn {
public static void main() {
try {
System.out.println(getWindowsIdentity().getFqn());
}
catch (Exception e) {
e.printStackTrace();
}
}
public static IWindowsIdentity getWindowsIdentity() throws Exception {
try {
byte[] kerberosToken = getServiceTicketSSPI();
WindowsAuthProviderImpl provider = new WindowsAuthProviderImpl();
IWindowsSecurityContext securityContext = provider
.acceptSecurityToken("client-connection", kerberosToken, "Kerberos");
return securityContext.getIdentity();
}
catch (Exception e) {
throw new Exception("Failed to process kerberos token");
}
}
public static byte[] getServiceTicketSSPI() throws Exception {
final String securityPackage = "Kerberos";
IWindowsCredentialsHandle clientCredentials = null;
WindowsSecurityContextImpl clientContext = null;
final String currentUser = WindowsAccountImpl.getCurrentUsername();
try {
clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
clientCredentials.initialize();
// initial client security context
clientContext = new WindowsSecurityContextImpl();
clientContext.setCredentialsHandle(clientCredentials.getHandle());
/*OR
clientContext.setCredentialsHandle(clientCredentials);
*/
clientContext.setSecurityPackage(securityPackage);
final Sspi.SecBufferDesc continueToken = null;
do {
System.out.println("Using current username: " + currentUser);
clientContext.initialize(clientContext.getHandle(), continueToken, currentUser);
}
while (clientContext.isContinue());
return clientContext.getToken();
}
catch (Exception e) {
throw new Exception("Failed to process kerberos token");
}
finally {
if (clientContext != null)
clientContext.dispose();
if (clientCredentials != null)
clientCredentials.dispose();
}
}
}
I am trying to implement a singleton pattern for connecting to my mongodb database so as to make sure i have only one connection.I have written following code '
public enum MongoConnector {
CONNECTION;
private MongoClient client = null;
/**
* This function is used to create a single instance of the MongoDb connector
* Thread Pooling is handled internally by MongoDb
*/
private MongoConnector() {
try {
client = new MongoClient();
} catch (Exception e) {
e.printStackTrace();
}
}
public MongoClient getClient() {
if (client == null) {
throw new RuntimeException();
}
return client;
}
}
So i want to know if this is ensuring the singleton pattern.If not please let me know how it should be .Thank you
Yes, the MongoConnector is a singleton.
I am developing a Java Restful web application and planning to use MongoDB with Morphia as ODM . As I am new to MongoDB, I needed a few suggestions.
The best way to handle db connections is to make use of db connection pool, which mongoClient takes care of.
Morphia morphia = new Morphia();
ServerAddress addr = new ServerAddress("127.0.0.1", 27017);
String databaseName = "test";
MongoClient mongoClient = new MongoClient(addr);
Datastore datastore = morphia.createDatastore(mongoClient, databaseName);
So I need to reuse the above datastore and not create a new instance upon every request as it can waste a lot of resources and affect performance. Should I be implementing the above as singleton class? Can someone help me through this?
Also can someone explain as to how I can set up configuration for db connections such as max connections per host, connection timeout in Morphia using MongoClientOptions?
Check out the config in this demo github.com/xeraa/morphia-demo
/**
* MongoDB providing the database connection for main.
*/
public class MongoDB {
public static final String DB_HOST = "127.0.0.1";
public static final int DB_PORT = 27017;
public static final String DB_NAME = "morphia_demo";
private static final Logger LOG = Logger.getLogger(MongoDB.class.getName());
private static final MongoDB INSTANCE = new MongoDB();
private final Datastore datastore;
private MongoDB() {
MongoClientOptions mongoOptions = MongoClientOptions.builder()
.socketTimeout(60000) // Wait 1m for a query to finish, https://jira.mongodb.org/browse/JAVA-1076
.connectTimeout(15000) // Try the initial connection for 15s, http://blog.mongolab.com/2013/10/do-you-want-a-timeout/
.maxConnectionIdleTime(600000) // Keep idle connections for 10m, so we discard failed connections quickly
.readPreference(ReadPreference.primaryPreferred()) // Read from the primary, if not available use a secondary
.build();
MongoClient mongoClient;
mongoClient = new MongoClient(new ServerAddress(DB_HOST, DB_PORT), mongoOptions);
mongoClient.setWriteConcern(WriteConcern.SAFE);
datastore = new Morphia().mapPackage(BaseEntity.class.getPackage().getName())
.createDatastore(mongoClient, DB_NAME);
datastore.ensureIndexes();
datastore.ensureCaps();
LOG.info("Connection to database '" + DB_HOST + ":" + DB_PORT + "/" + DB_NAME + "' initialized");
}
public static MongoDB instance() {
return INSTANCE;
}
// Creating the mongo connection is expensive - (re)use a singleton for performance reasons.
// Both the underlying Java driver and Datastore are thread safe.
public Datastore getDatabase() {
return datastore;
}
}
you can like this:
private MongoClient mongoClient = null;
private String mongoUrl="xxxx";//You can also use spring injection,#Value(value = "${db.mongo.url}")
private static final Morphia MORPHIA = new Morphia();
public Datastore getMORPHIADB(String dbName) {
if (mongoClient == null) {
//初始化
init();
}
return MORPHIA.createDatastore(mongoClient, dbName);
}
#PostConstruct
public void init() {
try {
//此处其它参数我们不做配置,采用默认配置MongoClientOptions.Builder里面配置连接池的参数
MongoClientOptions.Builder options = new MongoClientOptions.Builder()
.connectionsPerHost(30); //连接池大小(默认初始化为100个,原来老版本是10个)
MongoClientURI mongoClientURI = new MongoClientURI(mongoUrl, options);
mongoClient = new MongoClient(mongoClientURI);
} catch (MongoClientException e) {
LOGGER.error("建立MongoClient异常");
}
}
#PreDestroy
public synchronized void closeConnection() {
if (mongoClient != null) {
mongoClient.close();
}
}
when need use, you can like this:
private Datastore datastore = mongoConnService.getMORPHIADB("xxx");