Ở bài viết trước, tôi đã giới thiệu sơ qua về NoSQL và MongoDB. Hy vọng bạn đã bỏ chút thời gian để tìm đọc các thông tin về chúng.

Trong bài viết này, tôi sẽ đi vào việc sử dụng MongoDB trong lập trình Java để thao tác dữ liệu (CRUD: Create Read Update Delete).

Qua bài viết này, bạn sẽ học được cách tương tác với MongoDB thông qua driver được cung cấp bởi thư viện có sẵn và thao tác dữ liệu trên cơ sở dữ liệu MongooDB.

Để bắt đầu cho bài viết này, không phải là 1 vài dòng code hay kỹ thuật nào đó liên quan tới lập trình Java mà là 1 vài thông tin về MongoDB IDEs.

Ở bài viết trước, tôi sử dụng console để làm việc với MongoDB, như bạn thấy ban đầu khá phức tạp và giao diện có vẻ không trực quan và gây nhiều khó khăn, hạn chế trong quá trình làm việc. Bởi thế, chúng ta cần phải tìm 1 IDE nào đó giao diện đồ họa đẹp mắt để quản lý và thao dữ liệu với MongoDB. Tôi có 2 đề xuất với IDE để làm việc như sau:

  1. MongoVUE
  2. Rock Mongo (nên dùng)
Tôi khuyên bạn nên dùng Rock Mongo vì nó miễn phí và là Web App giống phpMyAdmin nên chúng ta không cần mất quá nhiều thời gian để học cách sử dụng nó.

Bạn có thể tải Rock Mongo trên trang chủ, và lựa chọn bản cài cho Windows. Tải về xong, bạn giải nén và chạy file rockstart.bat khi đã bật mongo server. (Nếu bạn chưa biết cách bật mongo server, bạn có thể xem bài viết này). Đây là giao diện của Rock Mongo khi chúng ta chạy file rockstart.bat - 1 giao diện có form đăng nhập. Đăng nhập mặc định với tài khoản là admin - admin.(Nếu bạn chỉ định đăng nhập vào cơ sở dữ liệu nào thì phải cung cấp tài khoản đúng với cơ sở dữ liệu đó).


Sau khi đăng nhập thành công, bạn sẽ được chuyển đến trang quản trị, thoạt nhìn ta thấy nó khá giống với phpmyadmin.

Bây giờ chúng ta cần tạo 1 cơ sở dữ liệu mới để lát nữa thực hiện việc thao tác dữ liệu bằng Java. Đơn giản, tôi sẽ click vào menu Database ở góc trên cùng và chọn Create New Database.



Tôi đặt tên cho cơ sở dữ liệu mới là PhoneBook, chúng ta sẽ làm 1 dự án nho nhỏ cho việc quản lý danh sách số điện thoại và chủ thuê bao.



Tiếp đến, tôi tạo 1 Collection để lưu trữ những thông tin về thuê bao điện thoại và chủ thuê bao. Tạm đặt tên là phone_book_list:



Sau khi tạo xong, tôi tạm insert vào trong collection này vài bản ghi mẫu để thực hiện tính năng lấy thông tin trong cơ sở dữ liệu sẽ được thực hiện ngay sau đây. (Chọn menu Insert và gõ vào dữ liệu sau):



Tôi đã có 1 mẫu tin trong Collection, nhiệm vụ coi như xong, tôi chuyển sang thao tác bên Java.

Bật Netbeans và tải về thư viện MongoDB rồi add vào dự án để chuẩn bị tiến hành thiết lập kết nối, thao tác dữ liệu với MongoDB.

Tôi đặt tên dự án và có kèm theo PartX để quy định phiên bản demo mà chúng ta đang theo dõi và cùng làm việc vì kế hoạch tôi sẽ viết các bài liên quan tới MongoDB cũng như những issues liên quan tới Java.



Tôi tiến hành add thư viện mongo đã tải về (version 2.10) vào dự án, và tạo 1 Interface tên là IMongoDAO chứa các phương thức thao tác với cơ sở dữ liệu (Có thể bạn chưa biết DAO là gì, bạn không cần lo lắng, bạn cứ code theo đúng mẫu và tôi sẽ giải thích mẫu DAO trong các bài viết khác). Cấu trúc dự án sẽ như sau:


Interface IMongoDAO:
package com.blogspot.code4lifevn.dao;

import com.mongodb.DBCollection;
import com.mongodb.DBObject;

/**
 * CRUD (CREATE READ UPDATE DELETE)
 * @author Code4LifeVn
 */
public interface IMongoDAO<T> {
    //CREATE
    void insert(T object);

    //READ
    T readItem(DBObject object);

    //UPDATE
    void update(T oldPbject, T newObj);

    //DELETE
    void delete(T object);

    //SELECT
    T findDocumentById(String _id);

    DBCollection getDBCollection();
}

Class PhoneBook:
package com.blogspot.code4lifevn.model;

/**
 *
 * @author Code4LifeVn
 */
public class PhoneBook {
    private String _id;
    private double mobi;
    private UserInfo info;

    public class UserInfo {
        private String firstName;
        private String lastName;
        private int age;

        public String getFirstName() {
            return firstName;
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public void setLastName(String lastName) {
            this.lastName = lastName;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

    }

    public double getMobi() {
        return mobi;
    }

    public void setMobi(double mobi) {
        this.mobi = mobi;
    }

    public UserInfo getInfo() {
        return info;
    }

    public void setInfo(UserInfo info) {
        this.info = info;
    }

    public void setId(String _id) {
        this._id = _id;
    }

    public String getId() {
        return _id;
    }

    @Override
    public String toString() {
        return String.format("_id: %s \nmobi: %.0f \nuser.first_name: %s\nuser.last_name: %s\nuser.age: %d",
                getId(), getMobi(), getInfo().getFirst_name(), getInfo().getLast_name(), getInfo().getAge());
    }


}


Ta sẽ lưu thông tin trong database vào 1 model.

Tiếp đến tôi sẽ viết 1 lớp implements Interface DAO kia và lần lượt viết mã cho từng chức năng.

Khi bạn muốn duyệt web để tra cứu thông tin và lấy dữ liệu, bạn cần phải cung cấp cho trình duyệt 1 địa chỉ website mà bạn muốn truy cập và trình duyệt sẽ tiến hành kết nối tới địa chỉ website đó, bạn có được thông tin mà bạn cần.

Tương tự, chúng ta muốn kết nối đến mongo server, chúng ta cần phải biết địa chỉ cần kết nối đến bao gồm Host Address, Port.

Trong dự án này, chúng ta làm việc trên localhost nên dễ dàng xác định ngay Host Address = "localhost", Port = 27017.

Có được thông tin này rồi, chúng ta cần 1 đối tượng nào đó giúp chúng ta kết nối tới mongo server. Tôi sử dụng lớp MongoClient (phiên bản cũ dùng lớp Mongo).

Class PhoneBookDAO sẽ được viết như sau:
package com.blogspot.code4lifevn.dao;

import com.blogspot.code4lifevn.converter.ModelConverter;
import com.blogspot.code4lifevn.converter.PhoneBookConverter;
import com.blogspot.code4lifevn.model.PhoneBook;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import java.net.UnknownHostException;
import org.bson.types.ObjectId;

/**
 *
 * @author Code4LifeVn
 */
public class PhoneBookDAO implements IMongoDAO<PhoneBook> {
    private MongoClient mongoClient;
    private DB phoneBookDB;
    private DBCollection phoneBookListCollection;
    private ModelConverter<PhoneBook> converter;

    public PhoneBookDAO() throws UnknownHostException {
        mongoClient = new MongoClient("localhost", 27017);
        phoneBookDB = mongoClient.getDB("PhoneBook");
        phoneBookListCollection = phoneBookDB.getCollection("phone_book_list");
        converter = new PhoneBookConverter();
    }

    @Override
    public DBCollection getDBCollection() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void insert(PhoneBook object) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public PhoneBook readItem(DBObject object) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void update(PhoneBook oldObject, PhoneBook newObj) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void delete(PhoneBook object) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public PhoneBook findDocumentById(String _id) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}

Đầu tiên, tôi sử dụng lớp MongoClient để giúp kết nối tới mongo server thông qua địa chỉ host và port được truyền vào trong constructor.
Lớp DB đảm nhận nhiệm vụ lưu giữ đối tượng mà được lấy về bởi phương thức getDB() xác định database nào mà ta sẽ làm việc.
Lớp DBCollection tương tự như DB, xác định Collection nào mà ta làm việc.

Sau khi có đầy đủ "công cụ" để bắt tay vào implements các method bên dưới, tôi tiến hành viết mã cho phương thức insert đầu tiên và lần lượt các phương thức khác

Class PhoneBookDAO được viết tiếp như sau:
package com.blogspot.code4lifevn.dao;

import com.blogspot.code4lifevn.converter.ModelConverter;
import com.blogspot.code4lifevn.converter.PhoneBookConverter;
import com.blogspot.code4lifevn.model.PhoneBook;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import java.net.UnknownHostException;
import org.bson.types.ObjectId;

/**
 *
 * @author Code4LifeVn
 */
public class PhoneBookDAO implements IMongoDAO<PhoneBook> {
    private MongoClient mongoClient;
    private DB phoneBookDB;
    private DBCollection phoneBookListCollection;
    private ModelConverter<PhoneBook> converter;

    public PhoneBookDAO() throws UnknownHostException {
        mongoClient = new MongoClient("localhost", 27017);
        phoneBookDB = mongoClient.getDB("PhoneBook");
        phoneBookListCollection = phoneBookDB.getCollection("phone_book_list");
        converter = new PhoneBookConverter();
    }

    @Override
    public DBCollection getDBCollection() {
        return phoneBookListCollection;
    }

    @Override
    public void insert(PhoneBook object) {
        BasicDBObject newPhoneBook = converter.convertModelToBasicObject(object);
        phoneBookListCollection.insert(newPhoneBook);
    }

    @Override
    public PhoneBook readItem(DBObject object) {
        return converter.convertBasicObjectToModel((BasicDBObject)object);
    }

    @Override
    public void update(PhoneBook oldObject, PhoneBook newObj) {
        BasicDBObject old = new BasicDBObject("mobi", oldObject.getMobi());
        BasicDBObject query = new BasicDBObject().append("$set", new BasicDBObject().append("mobi", newObj.getMobi()));
        phoneBookListCollection.update(old, query, false, true);
    }

    @Override
    public void delete(PhoneBook object) {
        BasicDBObject query = new BasicDBObject("_id", new ObjectId(object.getId()));
        phoneBookListCollection.remove(query);
    }

    @Override
    public PhoneBook findDocumentById(String _id) {
        BasicDBObject obj = (BasicDBObject) phoneBookListCollection.find(new BasicDBObject("_id", new ObjectId(_id))).next();
        return converter.convertBasicObjectToModel(obj);
    }
}
Lớp Converter có nhiệm vụ chuyển đổi dữ liệu BSON vào trong 1 Model, tôi thiết kế 1 Interface đảm nhận việc đó có tên là ModelConverter. Interface này được viết như sau:

package com.blogspot.code4lifevn.converter;

import com.blogspot.code4lifevn.model.PhoneBook;
import com.mongodb.BasicDBObject;

/**
 *
 * @author Code4LifeVn
 */
public interface ModelConverter<T extends PhoneBook> {
    public T convertBasicObjectToModel(BasicDBObject object);

    public BasicDBObject convertModelToBasicObject(T model);
}

Lớp PhoneBookConverter thực thi Interface ModelConverter. Viết như sau:
package com.blogspot.code4lifevn.converter;

import com.blogspot.code4lifevn.model.PhoneBook;
import com.mongodb.BasicDBObject;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @author Code4LifeVn
 */
public class PhoneBookConverter implements ModelConverter<PhoneBook> {

    @Override
    public PhoneBook convertBasicObjectToModel(BasicDBObject object) {
        String _id =  object.getObjectId("_id").toString();
        double mobi = object.getDouble("mobi");

        //Get User Info
        BasicDBObject userInfo = (BasicDBObject) ((BasicDBObject) object).get("user");
        String firstName = userInfo.getString("first_name");
        String lastName = userInfo.getString("last_name");
        int age = userInfo.getInt("age");

        //Construct UserInfo Object
        PhoneBook.UserInfo userInfoObject = new PhoneBook().new UserInfo();
        userInfoObject.setFirstName(firstName);
        userInfoObject.setLastName(lastName);
        userInfoObject.setAge(age);

        //Construct PhoneBook Object
        PhoneBook phoneBook = new PhoneBook();
        phoneBook.setId(_id);
        phoneBook.setMobi(mobi);
        phoneBook.setInfo(userInfoObject);

        return phoneBook;
    }

    @Override
    public BasicDBObject convertModelToBasicObject(PhoneBook model) {
        //Java 6
        //Map<String, Object> userInfo = new HashMap<String, Object>();

        //Only Java 7,
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("first_name", model.getInfo().getFirstName());
        userInfo.put("last_name", model.getInfo().getLastName());
        userInfo.put("age", model.getInfo().getAge());

        return new BasicDBObject()
                .append("mobi", model.getMobi())
                .append("user", userInfo);
    }

}

Sau khi hoàn tất các quá trình về xử lý nghiệp vụ, tôi viết 1 lớp Main để chạy những chức năng vừa làm để thao tác với dữ liệu trong MongoDB. Lớp PhoneBookRunner được viết như sau:

import com.blogspot.code4lifevn.dao.PhoneBookDAO;
import com.blogspot.code4lifevn.model.PhoneBook;
import com.blogspot.code4lifevn.model.PhoneBook.UserInfo;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import java.net.UnknownHostException;

/**
 *
 * @author Code4LifeVn
 */
public class PhoneBookRunner {
    public static void main(String[] args) throws UnknownHostException {
        PhoneBookDAO mongoDAO = new PhoneBookDAO();

        //SELECT ALL DOCUMENTS
        DBCursor cursor = mongoDAO.getDBCollection().find();
        while(cursor.hasNext()) {
            DBObject obj = cursor.next();
            PhoneBook model = mongoDAO.readItem(obj);
            System.out.println(model);
        }
        //INSERT A NEW DOCUMENT
        PhoneBook newPhoneBook = new PhoneBook();
        newPhoneBook.setMobi(1679997777);
        PhoneBook.UserInfo userInfo = new PhoneBook().new UserInfo();
        userInfo.setFirstName("Manh");
        userInfo.setLastName("Do");
        userInfo.setAge(23);

        newPhoneBook.setInfo(userInfo);
        mongoDAO.insert(newPhoneBook);

        //Update DOCUMENT
        PhoneBook query = new PhoneBook();
        query.setMobi(1696221490);
        PhoneBook query2 = new PhoneBook();
        query2.setMobi(1111111111);
        mongoDAO.update(query, query2);

        //DELETE Document
        PhoneBook deleteObj = mongoDAO.findDocumentById("5157e080f6655c57214be159");
        mongoDAO.delete(deleteObj);
    }
}
Với ví dụ này, bạn là người mới bắt đầu và là lần đầu tiên sử dụng MongoDB, nên tôi muốn bạn làm quen với các API trong thư viện MongoDB bằng các phương thức get/set để lấy ra các thông tin cần thiết, làm quen với các lớp đối tượng xử lý và vai trò của từng lớp đối ượng đảm nhận trách nhiệm của riêng nó...

Các đoạn mã trong bài ví dụ mẫu này vẫn chưa thực sự tối ưu và còn khá rườm rà, nhưng không sao, nghiệp vụ của chúng ta chạy tốt, thao tác tốt, và bạn đã phần nào làm quen được với MongoDB trong Java. Khi mọi thứ ok rồi, chúng ta sẽ tiến hành Refactor dọn dẹp và tối ưu đoạn mã trên. Ở bài viết sau, tôi sẽ hướng dẫn bạn cách Refactor dọn dẹp lại những đoạn mã mà mình viết ra.

Hy vọng qua bài viết này, đã giúp bạn hiểu thêm chút nào đó về công nghệ, về MongoDB và lập trình tương tác với cơ sở dữ liệu NoSQL trong Java.


Xem bài viết: http://code4lifevn.blogspot.com/2013/03/mongodb-nosql-la-gi.html
Xem bài viết: http://code4lifevn.blogspot.com/2013/08/10-dieu-ban-nen-biet-ve-nosql-database.html
Xem bài viết: http://code4lifevn.blogspot.com/2013/06/cai-dat-mongodb-nosql-trong-ubuntu.html

11 nhận xét:

  1. Bài này quá hay. Cảm ơn bạn đã chia sẽ rất nhiều. Chúc bạn luôn thành công

    Trả lờiXóa
  2. ^^ Cảm ơn bạn đã đánh giá cao bài viết này

    Trả lờiXóa
  3. Bạn có thể cho biết về cách kết hợp MongoDB với Hadoop không ạ... Mục đích của việc kết hợp đó luôn. ^^

    Trả lờiXóa
  4. Hiện tại công việc của mình không cần thiết phải sử dụng quá nhiều công nghệ vào 1 mục đích giải quyết bài toán nghiệp vụ nên mình chưa va chạm với Hadoop. Mình cũng đã biết tới Hadoop nhưng chưa có thời gian để thực nghiệm Hadoop. Có lẽ trong thời gian sắp tới nếu có làm các dự án hay các "cron jobs" nào đụng tới Hadoop thì mình sẽ viết bài chia sẻ.

    Hiện mình không có câu trả lời cho bạn :)

    Trả lờiXóa
  5. Phạm Phương Túlúc 10:05 26 tháng 8, 2013

    Hi, rất ủng hộ bài viết của bạn, mình làm việc mongo cũng lâu rồi, mình thấy bạn nên chia bài viết này thành nhiều bài viết nhỏ, 1-lý thuyết chi tiết về mongo (database, collection, query, index, aggeration framework, ...), 2-Tool view, monitor(Nếu bạn thấy dùng rockmongo hay thì nên chỉ thêm vài điểm nó hơn các tool khác (truy vấn, hint, stats)), 3-Làm việc với ngôn ngữ lập trình, ... :D Chúc bạn thành công.

    Trả lờiXóa
  6. Cảm ơn bạn đã góp ý, mình sẽ chỉnh sửa và tổ chức lại các bài viết sau này ^^

    Trả lờiXóa
  7. thanks vì bài viết của bạn..

    Trả lờiXóa
  8. Bạn có thể cho biết về cách kết hợp MongoDB với Hadoop không ạ... Mục đích của việc kết hợp đó luôn. ^^

    Trả lờiXóa
  9. Hiện tại công việc của mình không cần thiết phải sử dụng quá nhiều công nghệ vào 1 mục đích giải quyết bài toán nghiệp vụ nên mình chưa va chạm với Hadoop. Mình cũng đã biết tới Hadoop nhưng chưa có thời gian để thực nghiệm Hadoop. Có lẽ trong thời gian sắp tới nếu có làm các dự án hay các "cron jobs" nào đụng tới Hadoop thì mình sẽ viết bài chia sẻ.

    Hiện mình không có câu trả lời cho bạn :)

    Trả lờiXóa
  10. em làm theo rồi nhưng nó vẫn bị lỗi, "Ở bài viết sau, tôi sẽ hướng dẫn bạn cách Refactor dọn dẹp lại những đoạn mã mà mình viết ra". Anh viết tiếp chưa ạ, em tìm mà k thấy, cho em xin link với ạ.

    Trả lờiXóa
  11. anh đã viết tiếp bài về sử dụng java làm việc với mongo chưa ạ

    Trả lờiXóa

 

code4lifevn team

Thanh niên nghiêm túc :)

Name: Manh Do

Age: years old

Job: Senior Java and Mobile Developer

Country: VietNam

Name: Hung Nguyen

Age: years old

Job: Android Developer

Country: VietNam

Name: Trung PH

Age: years old

Job: Senior iOS and Android Developer

Country: VietNam

Name: Điệp NT

Age: years old

Job: Senior .Net and Android Developer

Country: VietNam