کار با MongoDB در جاوا (اتصال +‌ CRUD) - قسمت اول

کار با mongodb در جاوا
کار با mongodb در جاوا

سلام و عرض ادب خدمت جاوا کاران عزیز.

با کمترین مقدمه به سراغ نحوه‌ی اتصال به MongoDB از طریق درایور جاوا می‌روم. امیدوارم این پست مقدمه‌ای شود تا بتوانم یک‌سری از نوشته‌ها درمورد چگونگی کار با MongoDB و همچنین مباحث تئوری آن بنویسم. در حال حاضر Mongo تکنولوژی‌است که در مورد آن مطالعه می‌کنم و به آن علاقه‌مند شدم.
پیش از شروع مبحث، باید اشاره کنم که در این نوشته مفاهیم ابتدایی MongoDB را شرح نمی‌دهم. پیش‌فرض من بر این است که شما آن را نصب کرده‌اید، معانی کلماتی نظیر database، collection و document را در MongoDB می‌دانید. خیلی خوب است اگر با پوسته‌ی جاوااسکریپت آن کار هم کرده باشید.

خب، شروع کنیم!


پیش‌نیازها

  • وجود سرویس فعال MongoDB روی لوکال‌هاست با پورت پیش‌فرض ۲۷۰۱۷ (غیر از این هم بود مهم نیست، مهم وجود سرویس فعال است)
  • درایور MongoDB


نصب MongoDB

نصب MongoDB به‌عهده‌ی خود شماست. بسته به نوع پلتفرمی که استفاده می‌کنید بسته‌ی موردنظر را از سایت خود Mongo دانلود کنید و نصب کنید. البته توجه داشته باشید که سایت Mongo برای ما ایرانیان عزیز بسته است و می‌بایست آی‌پی خودتان را با کمک ابزارهایی مثل VPN تغییر دهید. پس از نصب از فعال بودن سرویس آن مطمئن شوید.
در ابونتو با دستور mongod می‌توانید پوسته‌ی جاوااسکریپت آن را اجرا کنید.



نصب درایور MongoDB برای اتصال به آن از طریق جاوا

راه توصیه شده برای اضافه کردن پکیج درایور به پروژه استفاده از سیستم‌های مدیریت وابستگی نظیر Maven و Gradle است. نکته‌ی حائز اهمیت در اینجا وجود ۲ آرتیفکت متفاوت است. کدام را باید استفاده کرد؟
یکی از آرتیفکت‌ها mongodb-driver است و دیگری mongo-java-driver. آرتیفکت mongodb-driver جدیدتر است و ما از آن استفاده می‌کنیم (یک اینترفیس بیشتر دارد!).

<dependencies>  
   <dependency>   
         <groupId>org.mongodb</groupId> 
         <artifactId>mongodb-driver</artifactId>         
         <version>3.4.3</version>
     </dependency> 
</dependencies> 

بلاک بالا وابستگی مربوط به درایور mongo را به پروژه maven اضافه می‌کند.

ایجاد کانکشن

از MongoClient() برای ایجاد کانکشن از یک instance فعال مونگو استفاده‌ می‌کنیم. شی MongoClinet نماینده‌ی استخری از کانکشن‌ها به دیتابیس است. به عبارت دیگر فقط به یک شی از کلاس MongoClient نیاز دارید، حتا اگر در چندین ترد مختلف می‌خواهید استفاده کنید.

اتصال به یک Single MongoDB instance

خب من در اینجا چندین راه مختلف برای اتصال به دیتابیس "virgool" روی ماشین لوکال را نشان می‌دهم. توجه داشته باشید در همه‌ی این روش‌ها اگر دیتابیس وجود نداشته باشد، MongoDB آن را ایجاد خواهد کرد.

روش ۱. مقداردهی شی MongoClient بدون هیچ پارامتری برای اتصال به اینستنس MongoDB که بصورت پیشفرض روی localhost و پورت ۲۷۰۱۷ قرار گرفته است:

MongoClient mongoClient = new MongoClient();


روش ۲. می‌توان hostname را مشخص کرد. بطور مثال زمانی که اینستنس MongoDB روی ماشین محلی قرار ندارد. اما همچنان پورت بصورت پیش‌فرض ۲۷۰۱۷ می‌باشد:

MongoClient mongoClient = new MongoClient( "localhost" );


روش ۳. مشخص کردن hostname و همچنین port بصورت شفاف:

MongoClient mongoClient = new MongoClient( "localhost" , 27017 );


روش ۴. شما همچنین می‌توانید از MongoClinetURI برای ایجاد connection string استفاده کنید:

 MongoClientURI connectionString = new MongoClientURI("mongodb://localhost:27017");
 MongoClient mongoClient = new MongoClient(connectionString);

اجازه دهید کمی این بخش را بیشتر باز کنیم.
شمای استاندارد برای کانکشن‌استرینگ به فرم زیر است:

mongodb://[username:password@]host1[:port1][,...hostN[:portN]]][/[database][?options]]

که در آن mongodb:// مشخص می‌کند که این رشته از نوع استاندارد اتصال به دیتابیس است.
و username:password@ بیانگر نام‌کاربری و رمزعبور است (اگر نیاز بود) و توجه داشته باشید چنانچه در نام‌کاربری و رمزعبور کارکترهایی نظیر @، : و امثالهم دارید، باید‌ آن‌ها را به کمک percent encoding بنویسید.
سپس host[:port] را مشخص می‌‌کنید که در مثال ما localhost (یا همان 127.0.0.1) و پورت 27017 می‌باشد.
و در نهایت دیتابیس را باید مشخص کرد. مشخص کردن دیتابیس اختیاری است، چنانچه نام‌کاربری‌ای که با آن لاگین می‌کنید مختص به یک دیتابیس خاص است، می‌بایست حتما آن دیتابیس را مشخص کرد.
و اما بخش options! می‌توان خصوصیاتی را به کانکشن نسبت داد، چه خصوصیاتی؟ Connection String Options را ببینید!

یک مثال از کانکشن‌استرینگ:

mongodb://myDBReader:D1fficultP%40ssw0rd@mongodb0.example.com:27017/admin


اتصال به دیتابیس

هنگامی که شی MongoClinet به دیتابیس MongoDB متصل شد، می‌توان به کمک متد MongoClinet.getDatabase() به یک دیتابیس مشخص متصل شد. باید نام آن دیتابیس را در متد getDatabase مشخص کرد، چنانچه وجود هم نداشته باشد خود Mongo آن را ایجاد خواهد کرد (در زمان ذخیره اولین دیتا).

مثال زیر نحوه اتصال به دیتابیس "virgool" را نشان ‌می‌دهد:

MongoDatabase database = mongoClient.getDatabase("virgool");

نکته: شی MongoDatabase بصورت immutable است!


دسترسی به کالکشن

خب، حالا که شی‌ای از MongoDatabase داریم، برای دسترسی به یک کالکشن خاص از متد getCollection() استفاده می‌کنیم. باید نام آن کالکشن را در متد مشخص کرد، اگر وجود نداشته باشد نیز Mongo آن را ایجاد خواهد کرد (در زمان ذخیره اولین دیتا).
برای مثال، من می‌خواهم به کمک شی database به کالکشن posts در دیتابیس virgool دسترسی داشته باشم:

MongoCollection<Document> collection = database.getCollection("posts");

شی MongoCollection نیز immutable است.


ایجاد یک Document

برای ایجاد یک داکیومنت از کلاس Document استفاده می‌کنیم،‌ برای مثال JSON زیر را در نظر بگیرید:

{    
    "name" : "MongoDB",   
    "type" : "database",    
    "count" : 1,    
    "versions": [ "v3.2", "v3.0", "v2.6" ],    
    "info" : 
        { 
            x : 203, 
            y : 102 
        }   
}

برای ایجاد یک داکیومنت با استفاده از Java driver باید یک شی از کلاس Doucument را با field و value مقداردهی کرد. همچنین می‌توان به کمک متد append() فیلدها و مقادیر بیشتری را به یک شی داکیومنت نسبت داد. حتا مقدار (value) می‌تواند یک شی Document دیگر باشد (برای اسنادی تو در تو). به مثال زیر توجه کنید که چگونه سند JSON بالا را تبدیل به یک شی از کلاس Document می‌کنیم:

Document doc = new Document("name", "MongoDB")                 
                .append("type", "database")                 
                .append("count", 1)                 
                .append("versions", Arrays.asList("v3.2", "v3.0", "v2.6"))                 
                .append("info", new Document("x", 203).append("y", 102)
                );


درج یک داکیومنت

حال که یک شی از کلاس MongoCollection داریم، می‌توانیم به کمک آن داکیومنت‌هایی که ایجاد کردیم را در یک کالکشن مشخص insert کنیم. برای این‌کار از متد insertOne() استفاده خواهیم کرد. توجه کنید:

collection.insertOne(doc);

نکته: چنانچه فیلد _id را در سند مشخص نکرده باشیم، MongoDB بصورت خودکار آن را در سند ما قرار می‌دهد!

حال چگونه لیستی از داکیومنت‌ها را درج کنیم؟ ابتدا بیاید آن را ایجاد کنیم سپس با کمک متد insertMany() از کلاس MongoCollection آن‌ها را درج کنیم.

برای ایجاد لیستی داکیومنت‌ها یک حلقه‌ی for ایجاد می‌کنیم تا ۱۰۰ شی امتحانی ایجاد کنیم:

List<Document> documents = new ArrayList<Document>(); 
        for (int i = 0; i < 100; i++) {     
                documents.add(new Document("i", i)); 
}

سپس با کمک متد insertMany() شی documents را وارد کالکشن مربوطه می‌کنیم:

collection.insertMany(documents);

شمارش تعداد اسناد در یک کالکشن

برای شمارش تعداد اسناد در یک کالکشن از متد count استفاده می‌کنیم. انتظار داریم خط زیرین عدد ۱۰۱ را چاپ کند (۱۰۰ شی را با متد insertMany و یک شی را با insertOne درج کردیم):

System.out.println(collection.count());


بازیابی اسناد

برای کوئری زدن به یک کالکشن، می‌توانیم از متد find() استفاده کنیم. اگر بدون هیچ آرگومانی فراخوانی شود، تمام اسناد یک کالکشن را درخواست می‌کند و اگر آرگومانی به آن بدهیم می‌توانیم اسناد را بر اساس آن فیلتر کنیم. اگر گیج کننده شده است عذرخواهی می‌کنم، به مثال‌ها توجه کنید، گویای مطلب است.

متد find() یک شی از نوع FindIterable ایجاد می‌کند که می‌توان متدهای دیگری را بصورت آبشاری (زنجیری) در آن فراخوانی کرد.


پیدا کردن یک داکیومنت در کالکشن

برای بازیابی اولین سند در کالکشن از متد find بدون هیچ آرگومانی استفاده می‌کنیم که بعد از آن بصورت آبشاری متد first() را فراخوانی می‌کنیم.
نکته: اگر کالکشن خالی باشد، null برخواهد گشت!

Document myDoc = collection.find().first();
System.out.println(myDoc.toJson());


بازیابی تمامی داکیومنت‌های یک کالکشن

برای بازیابی تمام داکیومنت‌های یک کالکشن‌ هم از متد find() بدون هیچ پارامتری استفاده می‌کنیم. بمنظور اینکه بتوانیم نتیجه را پیمایش کنیم باید متد iterator() را به متد find() زنجیر کنیم.
مثال زیر تمام داکیومنت‌های یک کالکشن را برمی‌گرداند و آن‌ها را در کنسول چاپ می‌کند (۱۰۱ داکیومنت):

MongoCursor<Document> cursor = collection.find().iterator(); 
    try {     
        while (cursor.hasNext()) {         
            System.out.println(cursor.next().toJson());     
        } 
    } finally {     
        cursor.close(); 
    }


فیلتر کردن خروجی

برای درخواست داکیومنت‌هایی که با شروط مشخصی هماهنگ هستند (بعبارتی match هستند)، آبجکت فیلتر را به متد find() پاس می‌دهیم. برای راحتی ایجاد آبجکت‌های فیلتر، Java driver کلاس کمکمی‌ای بنام Filters ایجاد کرده است.

برای مثال، برای یافتن اولین سندی که فیلد i آن مقدار 71 را داشته باشد، فیلتر "eq" را برای مشخص کردن شرط برابری به متد find() پاس می‌دهیم:

myDoc = collection.find(eq("i", 71)).first();
System.out.println(myDoc.toJson());

مثال زیر، تمام داکیومنت‌هایی که مقدار i آنها بزرگتر از ۵۰ هست ("i" > 50) را برمی‌گرداند:

Block<Document> printBlock = new Block<Document>() {
     @Override
     public voidapply(final Document document) {
         System.out.println(document.toJson());
     }
};

collection.find(gt("i", 50)).forEach(printBlock);

برای مشخص کردن یک بازه نظیر i بزرگتر از ۵۰ و کوچک‌تر و مساوی ۱۰۰ باشد (50 < i <= 100) می‌توانید از کمکی and استفاده کرد:

collection.find(and(gt("i", 50), lte("i", 100))).forEach(printBlock);

آپدیت کردن داکیومنت

برای بروز کردن (آپدیت) اسنادِ کالکشن از متدهای updateOne و updateMany استفاده می‌کنیم.
چیزهایی که به متدها ارسال می‌کنیم:

  • (اول) آبجکت فیلتر، داکیومنت یا داکیومنت‌ها را برای آپدیت مشخص می‌کند. برای راحتی ایجاد آبجکت فیلتر هم Java driver یک کلاس هلپر (کمکی) بنام Filters ایجاد کرده است. برای مشخص کردن یک فیلتر خالی نیز (بعبارتی تمام داکیومنت‌های یک کالکشن را می‌خواهید)، از آبجکت خالی Document استفاده کنید.
  • (دوم) سند آپدیتی که تغییرات را مشخص می‌کند، برای اینکه لیست عملیات‌های ممکن رو ببینید رو این کلیک کنید. این لیست را من در زیر هم خلاصه میاورم:

$currentDateSets the value of a field to current date, either as a Date or a Timestamp.
$incIncrements the value of the field by the specified amount.
$minOnly updates the field if the specified value is less than the existing field value.
$maxOnly updates the field if the specified value is greater than the existing field value.
$mulMultiplies the value of the field by the specified amount.
$renameRenames a field.
$setSets the value of a field in a document.
$setOnInsertSets the value of a field if an update results in an insert of a document. Has no effect on update operations that modify existing documents.
$unsetRemoves the specified field from a document.

متدهای update یک UpdateResult برمی‌گردانند که شامل اطلاعاتی درمورد تعداد داکیومنت‌های تغییر یافته است. چنانچه می‌خواهید تنها یک داکیومنت را تغییر دهید از updateOne استفاده کنید.
مثال زیر، اولین داکیومنتی که شرط فیلتر را ارضا کند‌ (i مساوی با ۱۰) تغییر می‌دهد (مقدار i را برابر با ۱۱۰ قرار می‌دهد):

 collection.updateOne(eq("i", 10), new Document("$set", new Document("i", 110)));

اما برای آپدیت کردن تمام اسنادی که با فیلتر همخوانی دارند (match) از متد updateMany استفاده خواهیم کرد.
در مثال زیر مقادیر i تمام اسنادی که مقدار i آنها کمتر از ۱۰۰ می‌باشد را ۱۰۰تا افزایش می‌دهیم:

UpdateResult updateResult = collection.updateMany(lt("i", 100), inc("i", 100));
System.out.println(updateResult.getModifiedCount());


حذف کردن داکیومنت

برای حذف کردن اسناد از یک کالکشن می‌توانید از متدهای deleteOne و deleteMany استفاده کنید.
مشابه آپدیت، برای این متدها ابتدا باید آبجکت فیلتر را پاس داد تا مشخص شود کدام سند یا سندها باید حذف شوند. که می‌توان از کلاس کمکی درایور جاوا (Filters) کمک گرفت. مشابه آپدیت، برای حذف تمام داکیومنت‌ها یک آبجکت خالی Document ارسال کنید تا بعنوان بدون فیلتر درنظر گرفته شود.
متدهای حذف هم یک DeleteResult برمی‌گردانند که شامل اطلاعاتی در مورد تعداد اسناد حذف شده است.

برای حذف اولین سندی که مطابق فیلتر است از متد deleteOne استفاده می‌کنیم. مثال زیر سندی را که مقدار i آن برابر با ۱۱۰ می‌باشد را حذف می‌کند:

collection.deleteOne(eq("i", 110));

برای حذف چندین سند هم از deleteMany استفاده می‌کنیم. مثال زیر تمام اسنادی را که مقدار آن‌ها بزرگ‌تر و مساوی ۱۰۰ می‌باشد را حذف می‌کند:

DeleteResult deleteResult = collection.deleteMany(gte("i", 100));
System.out.println(deleteResult.getDeletedCount());

ساخت ایندکس

برای ایجاد ایندکس روی یک فیلد یا چندین فیلد، سند مشخص ایندکس را باید به متد createIndex() پاس داد. سند مشخص ایندکس شامل فیلدهایی که باید ایندکس شوند و همچنین نوع ایندکس هر فیلد می‌باشد، فرمت زیر را نگاه کنید:

new Document(<field1>, <type1>).append(<field2>, <type2>) ...
  • برای نوع ایندکس افزایشی، مقدار 1 را برای <type> قرار دهید.
  • برای نوع ایندکس کاهشی، مقدار -1 را برای <type> قرار دهید.

بعنوان مثال یک ایندکس افزایشی برای فیلد i ایجاد می‌کنیم:

collection.createIndex(new Document("i", 1));

برای دیدن تمام انواع ایندکس‌ها صفحه ایجاد ایندکس‌ها را مشاهده کنید.


خب، به پایان رسید این قصه، اما حکایت همچنان باقیست. بحث مربوط به درایور جاوا برای MongoDB ادامه دارد. می‌توان خیلی بیشتر آن را بسط داد، شما بگویید، قسمت دوم آن را اضافه کنم؟