MongoDB

MongoDB #

Aplikasi modern sering berhadapan dengan data yang strukturnya tidak seragam — profil pengguna dengan atribut berbeda-beda, log event yang terus berubah formatnya, atau katalog produk dengan ratusan variasi. SQL dan skema relasional yang kaku membuat perubahan model data menjadi lambat dan mahal. MongoDB hadir sebagai solusi: database berbasis dokumen yang menyimpan data dalam format JSON-like (BSON), sehingga struktur data bisa berubah tanpa perlu migrasi skema. Artikel ini membahas cara menggunakan MongoDB di Java — mulai dari setup koneksi dengan MongoDB Java Driver, operasi CRUD, query dan filter, aggregation pipeline, hingga integrasi dengan Spring Data MongoDB untuk kode yang lebih idiomatik.

Konsep Dasar MongoDB #

Sebelum menulis kode, penting untuk memahami bagaimana MongoDB mengorganisasi data. MongoDB menggunakan terminologi yang berbeda dari database relasional.

Relational DB MongoDB Keterangan
Database Database Sama — wadah untuk collections
Table Collection Kumpulan dokumen
Row Document Satu entri data dalam format BSON
Column Field Atribut dalam sebuah dokumen
Primary Key _id Identifier unik, auto-generated jika tidak disediakan
JOIN $lookup Agregasi untuk menggabungkan data antar collection
Index Index Sama — mempercepat query

Dokumen MongoDB berbentuk BSON (Binary JSON), yang mendukung tipe data lebih kaya dibanding JSON biasa: ObjectId, Date, Decimal128, array bersarang, dan dokumen bersarang.

// Contoh struktur dokumen MongoDB
{
  "_id": ObjectId("64f3a1b2c3d4e5f6a7b8c9d0"),
  "nama": "Laptop Pro X",
  "harga": 15000000,
  "spesifikasi": {
    "ram": "16GB",
    "storage": "512GB SSD",
    "layar": "15.6 inch"
  },
  "tags": ["laptop", "gaming", "high-end"],
  "tersedia": true,
  "dibuat_pada": ISODate("2024-01-15T08:00:00Z")
}

Perhatikan bahwa dokumen bisa punya field bersarang (spesifikasi) dan field array (tags) secara native, tanpa butuh tabel relasi tambahan.


Instalasi dan Dependency #

MongoDB Java Driver tersedia dalam dua varian: driver sync (blocking) dan driver reactive (non-blocking). Artikel ini fokus pada driver sync yang lebih umum digunakan.

Tambahkan dependency berikut ke pom.xml (Maven):

<!-- MongoDB Java Driver -->
<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-sync</artifactId>
    <version>4.11.1</version>
</dependency>

<!-- Jika menggunakan Spring Boot -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

Atau dengan Gradle:

// build.gradle
implementation 'org.mongodb:mongodb-driver-sync:4.11.1'

// Jika menggunakan Spring Boot
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
MongoDB Java Driver versi 4.x ke atas menggunakan API yang berbeda dari versi 3.x. Pastikan kamu menggunakan versi 4.x karena versi 3.x sudah end-of-life.

Koneksi ke MongoDB #

Koneksi ke MongoDB dibuat melalui MongoClient, yang merupakan thread-safe dan bisa di-share di seluruh aplikasi. Jangan membuat MongoClient baru di setiap request.

Koneksi dengan Connection String #

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

public class MongoDBConnection {

    // ANTI-PATTERN: membuat MongoClient baru di setiap method
    // Ini memboros koneksi dan memperlambat aplikasi
    public void antiPattern() {
        MongoClient client = MongoClients.create("mongodb://localhost:27017");
        // lakukan sesuatu
        client.close(); // harus selalu ditutup, rawan lupa
    }

    // BENAR: buat MongoClient sekali sebagai singleton atau bean
    private static final MongoClient CLIENT =
        MongoClients.create("mongodb://localhost:27017");

    public MongoDatabase getDatabase() {
        return CLIENT.getDatabase("toko_online");
    }
}

Connection String dengan Authentication #

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClients;

public class MongoDBConfig {

    public MongoClient createClient() {
        // Format: mongodb://username:password@host:port/database
        String connectionString = "mongodb://admin:secret123@localhost:27017/toko_online";

        MongoClientSettings settings = MongoClientSettings.builder()
            .applyConnectionString(new ConnectionString(connectionString))
            .applyToConnectionPoolSettings(builder ->
                builder.maxSize(20)           // maksimal 20 koneksi dalam pool
                       .minSize(5))            // minimal 5 koneksi aktif
            .applyToSocketSettings(builder ->
                builder.connectTimeout(5000, java.util.concurrent.TimeUnit.MILLISECONDS))
            .build();

        return MongoClients.create(settings);
    }
}

Arsitektur Koneksi #

flowchart TD
    A[Java Application] --> B[MongoClient Singleton]
    B --> C[Connection Pool]
    C --> D[MongoDB Server :27017]
    D --> E[(Database: toko_online)]
    E --> F[(Collection: produk)]
    E --> G[(Collection: pengguna)]
    E --> H[(Collection: pesanan)]

Operasi CRUD #

Semua operasi data di MongoDB beroperasi pada MongoCollection<Document>. Driver Java menggunakan Document sebagai representasi dokumen BSON.

Create — Menyimpan Dokumen #

import com.mongodb.client.MongoCollection;
import com.mongodb.client.result.InsertOneResult;
import com.mongodb.client.result.InsertManyResult;
import org.bson.Document;
import org.bson.types.ObjectId;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class ProdukRepository {

    private final MongoCollection<Document> collection;

    public ProdukRepository(MongoDatabase database) {
        this.collection = database.getCollection("produk");
    }

    // Insert satu dokumen
    public ObjectId simpanProduk(String nama, int harga, String kategori) {
        Document dokumen = new Document()
            .append("nama", nama)
            .append("harga", harga)
            .append("kategori", kategori)
            .append("stok", 0)
            .append("aktif", true)
            .append("dibuat_pada", new Date());

        InsertOneResult result = collection.insertOne(dokumen);

        // _id di-generate otomatis jika tidak disediakan
        return (ObjectId) result.getInsertedId().asObjectId().getValue();
    }

    // Insert banyak dokumen sekaligus
    public void simpanBanyakProduk(List<Document> produkList) {
        InsertManyResult result = collection.insertMany(produkList);
        System.out.println("Berhasil menyimpan " + result.getInsertedIds().size() + " produk");
    }
}

Read — Membaca Dokumen #

Query di MongoDB menggunakan Filters dari package com.mongodb.client.model. Hindari membangun filter secara manual dengan Document karena rawan typo dan sulit dibaca.

import com.mongodb.client.FindIterable;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Projections;
import com.mongodb.client.model.Sorts;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;

import java.util.ArrayList;
import java.util.List;

public class ProdukQuery {

    private final MongoCollection<Document> collection;

    public ProdukQuery(MongoCollection<Document> collection) {
        this.collection = collection;
    }

    // Cari berdasarkan ID
    public Document cariById(String id) {
        Bson filter = Filters.eq("_id", new ObjectId(id));
        return collection.find(filter).first();
    }

    // Cari semua produk aktif dengan harga di rentang tertentu
    public List<Document> cariProdukAktif(int hargaMin, int hargaMax) {
        Bson filter = Filters.and(
            Filters.eq("aktif", true),
            Filters.gte("harga", hargaMin),
            Filters.lte("harga", hargaMax)
        );

        // Projection: ambil hanya field yang dibutuhkan
        Bson projection = Projections.fields(
            Projections.include("nama", "harga", "kategori"),
            Projections.excludeId()
        );

        return collection.find(filter)
            .projection(projection)
            .sort(Sorts.ascending("harga"))
            .limit(20)
            .into(new ArrayList<>());
    }

    // Cari dengan text search (butuh text index)
    public List<Document> cariByKeyword(String keyword) {
        Bson filter = Filters.text(keyword);
        return collection.find(filter)
            .sort(Sorts.metaTextScore("score"))
            .into(new ArrayList<>());
    }
}

Update — Memperbarui Dokumen #

import com.mongodb.client.model.Updates;
import com.mongodb.client.result.UpdateResult;

import java.util.Date;

public class ProdukUpdate {

    private final MongoCollection<Document> collection;

    public ProdukUpdate(MongoCollection<Document> collection) {
        this.collection = collection;
    }

    // Update satu field
    public boolean updateHarga(String id, int hargaBaru) {
        Bson filter = Filters.eq("_id", new ObjectId(id));
        Bson update = Updates.combine(
            Updates.set("harga", hargaBaru),
            Updates.set("diperbarui_pada", new Date())
        );

        UpdateResult result = collection.updateOne(filter, update);
        return result.getModifiedCount() > 0;
    }

    // Increment nilai numerik
    public boolean tambahStok(String id, int jumlah) {
        Bson filter = Filters.eq("_id", new ObjectId(id));
        Bson update = Updates.inc("stok", jumlah); // atomik, aman untuk concurrent update
        UpdateResult result = collection.updateOne(filter, update);
        return result.getModifiedCount() > 0;
    }

    // Update banyak dokumen sekaligus
    public long nonaktifkanKategori(String kategori) {
        Bson filter = Filters.eq("kategori", kategori);
        Bson update = Updates.set("aktif", false);
        UpdateResult result = collection.updateMany(filter, update);
        return result.getModifiedCount();
    }

    // Upsert: update jika ada, insert jika tidak ada
    public void upsertProduk(String sku, String nama, int harga) {
        Bson filter = Filters.eq("sku", sku);
        Bson update = Updates.combine(
            Updates.setOnInsert("dibuat_pada", new Date()),
            Updates.set("nama", nama),
            Updates.set("harga", harga),
            Updates.set("diperbarui_pada", new Date())
        );

        com.mongodb.client.model.UpdateOptions options =
            new com.mongodb.client.model.UpdateOptions().upsert(true);
        collection.updateOne(filter, update, options);
    }
}
Hindari menggunakan replaceOne() jika kamu hanya ingin mengubah beberapa field. replaceOne() mengganti seluruh dokumen, termasuk field yang tidak kamu sertakan — field lama akan hilang.

Delete — Menghapus Dokumen #

import com.mongodb.client.result.DeleteResult;

public class ProdukDelete {

    private final MongoCollection<Document> collection;

    public ProdukDelete(MongoCollection<Document> collection) {
        this.collection = collection;
    }

    // Hapus satu dokumen
    public boolean hapusProduk(String id) {
        Bson filter = Filters.eq("_id", new ObjectId(id));
        DeleteResult result = collection.deleteOne(filter);
        return result.getDeletedCount() > 0;
    }

    // Hapus banyak dokumen
    public long hapusProdukTidakAktif() {
        Bson filter = Filters.eq("aktif", false);
        DeleteResult result = collection.deleteMany(filter);
        return result.getDeletedCount();
    }
}

Query Lanjutan #

MongoDB mendukung query yang jauh lebih ekspresif dari sekadar equality check. Beberapa pola query yang sering digunakan dalam aplikasi produksi:

Query pada Field Bersarang dan Array #

public class AdvancedQuery {

    private final MongoCollection<Document> collection;

    public AdvancedQuery(MongoCollection<Document> collection) {
        this.collection = collection;
    }

    // Query field bersarang menggunakan dot notation
    public List<Document> cariLaptopRam16GB() {
        // "spesifikasi.ram" merujuk ke field ram di dalam object spesifikasi
        Bson filter = Filters.eq("spesifikasi.ram", "16GB");
        return collection.find(filter).into(new ArrayList<>());
    }

    // Query dokumen yang array-nya mengandung nilai tertentu
    public List<Document> cariProdukDenganTag(String tag) {
        // $elemMatch tidak diperlukan untuk array of primitives
        Bson filter = Filters.eq("tags", tag);
        return collection.find(filter).into(new ArrayList<>());
    }

    // Query menggunakan $in — cari produk dari kategori manapun dalam list
    public List<Document> cariProdukByKategori(List<String> kategoriList) {
        Bson filter = Filters.in("kategori", kategoriList);
        return collection.find(filter).into(new ArrayList<>());
    }

    // Query berdasarkan keberadaan field
    public List<Document> cariProdukDenganDiskon() {
        Bson filter = Filters.exists("diskon");
        return collection.find(filter).into(new ArrayList<>());
    }

    // Query dengan regex
    public List<Document> cariProdukByNama(String keyword) {
        // Case-insensitive search pada field nama
        Bson filter = Filters.regex("nama", keyword, "i");
        return collection.find(filter).into(new ArrayList<>());
    }
}

Sequence Alur Query #

sequenceDiagram
    participant App as Java Application
    participant Driver as MongoDB Driver
    participant DB as MongoDB Server

    App->>Driver: collection.find(filter)
    Driver->>DB: Kirim query BSON
    DB->>DB: Scan index (jika ada)
    DB->>DB: Filter dokumen
    DB-->>Driver: Cursor
    Driver-->>App: FindIterable<Document>
    App->>Driver: .sort().limit().into()
    Driver->>DB: Fetch batch dokumen
    DB-->>Driver: Dokumen BSON
    Driver-->>App: List<Document>

Aggregation Pipeline #

Aggregation adalah fitur paling powerful di MongoDB. Pipeline memproses dokumen melalui serangkaian stage — setiap stage mengubah, memfilter, atau mengelompokkan dokumen sebelum dikirim ke stage berikutnya.

Konsep Pipeline #

flowchart LR
    A[(Collection)] --> B["$match\nFilter dokumen"]
    B --> C["$group\nKelompokkan & hitung"]
    C --> D["$sort\nUrutkan hasil"]
    D --> E["$limit\nBatasi jumlah"]
    E --> F[Hasil Akhir]

Contoh Aggregation #

import com.mongodb.client.AggregateIterable;
import com.mongodb.client.model.Accumulators;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.BsonField;
import org.bson.Document;

import java.util.Arrays;
import java.util.List;

public class ProdukAggregation {

    private final MongoCollection<Document> collection;

    public ProdukAggregation(MongoCollection<Document> collection) {
        this.collection = collection;
    }

    // Hitung total produk dan rata-rata harga per kategori
    public List<Document> statistikPerKategori() {
        List<Bson> pipeline = Arrays.asList(
            // Stage 1: Filter hanya produk aktif
            Aggregates.match(Filters.eq("aktif", true)),

            // Stage 2: Kelompokkan berdasarkan kategori
            Aggregates.group(
                "$kategori",
                Accumulators.sum("total_produk", 1),
                Accumulators.avg("rata_rata_harga", "$harga"),
                Accumulators.min("harga_termurah", "$harga"),
                Accumulators.max("harga_termahal", "$harga")
            ),

            // Stage 3: Urutkan berdasarkan total produk (descending)
            Aggregates.sort(Sorts.descending("total_produk")),

            // Stage 4: Ambil 10 kategori teratas
            Aggregates.limit(10)
        );

        return collection.aggregate(pipeline).into(new ArrayList<>());
    }

    // $lookup: join dengan collection lain
    public List<Document> produkDenganDetailPenjual() {
        List<Bson> pipeline = Arrays.asList(
            Aggregates.lookup(
                "penjual",       // collection yang di-join
                "penjual_id",    // field lokal
                "_id",           // field di collection target
                "info_penjual"   // nama field hasil join
            ),
            // Unwind: pecah array hasil lookup menjadi dokumen terpisah
            Aggregates.unwind("$info_penjual"),
            // Project: tentukan field yang dikembalikan
            Aggregates.project(
                Projections.fields(
                    Projections.include("nama", "harga"),
                    Projections.computed("nama_penjual", "$info_penjual.nama"),
                    Projections.computed("kota_penjual", "$info_penjual.kota")
                )
            )
        );

        return collection.aggregate(pipeline).into(new ArrayList<>());
    }

    // $facet: jalankan multiple pipeline sekaligus (berguna untuk faceted search)
    public Document facetedSearch(String kategori, int hargaMin, int hargaMax) {
        Bson matchFilter = Filters.and(
            Filters.eq("kategori", kategori),
            Filters.gte("harga", hargaMin),
            Filters.lte("harga", hargaMax)
        );

        List<Bson> pipeline = Arrays.asList(
            Aggregates.match(matchFilter),
            Aggregates.facet(
                new com.mongodb.client.model.Facet("hasil",
                    Aggregates.sort(Sorts.ascending("harga")),
                    Aggregates.limit(20)
                ),
                new com.mongodb.client.model.Facet("total",
                    Aggregates.count("jumlah")
                ),
                new com.mongodb.client.model.Facet("harga_range",
                    Aggregates.group(null,
                        Accumulators.min("min", "$harga"),
                        Accumulators.max("max", "$harga")
                    )
                )
            )
        );

        return collection.aggregate(pipeline).first();
    }
}

Indexing #

Index adalah kunci performa di MongoDB. Tanpa index, MongoDB harus scan seluruh collection (collection scan) untuk setiap query — ini lambat ketika dokumen sudah mencapai jutaan.

Jenis Index #

Tipe Index Kegunaan Contoh
Single Field Query pada satu field { "email": 1 }
Compound Query pada kombinasi field { "kategori": 1, "harga": -1 }
Text Full-text search { "nama": "text", "deskripsi": "text" }
Geospatial Query berbasis lokasi { "lokasi": "2dsphere" }
Partial Index hanya subset dokumen filter: { "aktif": true }
Unique Memastikan nilai unik { "email": 1 }, { unique: true }

Membuat Index #

import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;

public class IndexManager {

    private final MongoCollection<Document> collection;

    public IndexManager(MongoCollection<Document> collection) {
        this.collection = collection;
    }

    public void buatIndex() {
        // Index tunggal ascending
        collection.createIndex(Indexes.ascending("email"));

        // Compound index: sort kategori ascending, harga descending
        // Urutan field dalam compound index sangat penting!
        collection.createIndex(
            Indexes.compoundIndex(
                Indexes.ascending("kategori"),
                Indexes.descending("harga")
            )
        );

        // Unique index untuk memastikan tidak ada email duplikat
        IndexOptions uniqueOptions = new IndexOptions().unique(true);
        collection.createIndex(Indexes.ascending("email"), uniqueOptions);

        // Text index untuk full-text search
        collection.createIndex(Indexes.compoundIndex(
            Indexes.text("nama"),
            Indexes.text("deskripsi")
        ));

        // Partial index: hanya index produk yang aktif
        // Hemat storage, index lebih kecil, query lebih cepat
        IndexOptions partialOptions = new IndexOptions()
            .partialFilterExpression(Filters.eq("aktif", true));
        collection.createIndex(
            Indexes.ascending("harga"),
            partialOptions
        );

        // TTL index: dokumen otomatis terhapus setelah waktu tertentu
        // Berguna untuk session, cache, atau data sementara
        IndexOptions ttlOptions = new IndexOptions().expireAfter(30L, java.util.concurrent.TimeUnit.DAYS);
        collection.createIndex(Indexes.ascending("dibuat_pada"), ttlOptions);
    }
}
Index mempercepat read tapi memperlambat write karena setiap insert/update harus memperbarui semua index. Jangan membuat index untuk setiap field — buat hanya untuk field yang sering digunakan dalam filter query.

Integrasi dengan Spring Data MongoDB #

Jika aplikasi kamu menggunakan Spring Boot, Spring Data MongoDB menyediakan abstraksi yang lebih tinggi dari MongoDB Java Driver langsung. Kode jadi lebih ringkas dan idiomatik.

Konfigurasi #

# application.yml
spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/toko_online
      # Atau dengan authentication:
      # uri: mongodb://admin:secret@localhost:27017/toko_online?authSource=admin

Model Document #

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.index.TextIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Document(collection = "produk") // nama collection di MongoDB
public class Produk {

    @Id
    private String id; // MongoDB akan generate _id sebagai ObjectId

    @TextIndexed // otomatis masuk text index untuk full-text search
    private String nama;

    @Indexed // buat single field index otomatis
    private String kategori;

    private BigDecimal harga;
    private int stok;
    private boolean aktif;

    @TextIndexed(weight = 2) // bobot lebih rendah dari nama
    private String deskripsi;

    private List<String> tags;

    @Field("spesifikasi") // nama field di MongoDB bisa berbeda dari nama property Java
    private Spesifikasi detailSpesifikasi;

    private LocalDateTime dibuatPada;
    private LocalDateTime diperbaruidPada;

    // Constructor, getter, setter...

    public static class Spesifikasi {
        private String ram;
        private String storage;
        private String prosesor;
        // getter setter...
    }
}

Repository #

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

public interface ProdukRepository extends MongoRepository<Produk, String> {

    // Spring Data otomatis generate query dari nama method
    List<Produk> findByKategori(String kategori);

    List<Produk> findByAktifTrue();

    List<Produk> findByHargaBetween(BigDecimal min, BigDecimal max);

    Optional<Produk> findByNama(String nama);

    // Pagination dan sorting bawaan
    Page<Produk> findByKategoriAndAktif(String kategori, boolean aktif, Pageable pageable);

    // Custom query dengan @Query annotation
    @Query("{ 'harga': { $gte: ?0, $lte: ?1 }, 'aktif': true }")
    List<Produk> cariByRentangHarga(BigDecimal min, BigDecimal max);

    // Query dengan projection — hanya ambil field tertentu
    @Query(value = "{ 'kategori': ?0 }", fields = "{ 'nama': 1, 'harga': 1 }")
    List<Produk> namaHargaByKategori(String kategori);

    long countByKategori(String kategori);

    void deleteByAktifFalse();
}

MongoTemplate untuk Query Kompleks #

MongoRepository cocok untuk query sederhana. Untuk query yang lebih kompleks — terutama aggregation — gunakan MongoTemplate.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Service
public class ProdukService {

    @Autowired
    private MongoTemplate mongoTemplate;

    // Query dengan kriteria dinamis
    public List<Produk> cariProdukDenganFilter(
            String kategori, BigDecimal hargaMin, BigDecimal hargaMax) {

        Query query = new Query();

        // Bangun criteria secara dinamis
        Criteria criteria = new Criteria();
        if (kategori != null && !kategori.isEmpty()) {
            criteria.and("kategori").is(kategori);
        }
        if (hargaMin != null) {
            criteria.and("harga").gte(hargaMin);
        }
        if (hargaMax != null) {
            criteria.and("harga").lte(hargaMax);
        }
        criteria.and("aktif").is(true);

        query.addCriteria(criteria);
        return mongoTemplate.find(query, Produk.class);
    }

    // Update parsial
    public void updateStok(String id, int perubahanStok) {
        Query query = new Query(Criteria.where("id").is(id));
        Update update = new Update()
            .inc("stok", perubahanStok)
            .set("diperbaruidPada", LocalDateTime.now());

        mongoTemplate.updateFirst(query, update, Produk.class);
    }

    // Aggregation dengan Spring Data
    public List<StatistikKategori> statistikPerKategori() {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(Criteria.where("aktif").is(true)),
            Aggregation.group("kategori")
                .count().as("totalProduk")
                .avg("harga").as("rataRataHarga")
                .min("harga").as("hargaTerendah")
                .max("harga").as("hargaTertinggi"),
            Aggregation.sort(org.springframework.data.domain.Sort.by(
                org.springframework.data.domain.Sort.Direction.DESC, "totalProduk")),
            Aggregation.limit(10)
        );

        AggregationResults<StatistikKategori> results =
            mongoTemplate.aggregate(aggregation, "produk", StatistikKategori.class);

        return results.getMappedResults();
    }

    // DTO untuk hasil aggregation
    public static class StatistikKategori {
        private String id; // hasil dari $group _id
        private int totalProduk;
        private double rataRataHarga;
        private BigDecimal hargaTerendah;
        private BigDecimal hargaTertinggi;
        // getter setter...
    }
}

Transaksi Multi-Document #

MongoDB mendukung ACID transaction sejak versi 4.0, namun hanya tersedia pada deployment Replica Set atau Sharded Cluster — tidak pada standalone instance.

import com.mongodb.client.ClientSession;
import com.mongodb.TransactionOptions;
import com.mongodb.ReadConcern;
import com.mongodb.WriteConcern;
import com.mongodb.ReadPreference;

public class TransactionExample {

    private final MongoClient client;

    public TransactionExample(MongoClient client) {
        this.client = client;
    }

    // Transaksi: kurangi stok produk dan buat record pesanan secara atomik
    public void buatPesanan(String produkId, int jumlah, String penggunaId) {
        TransactionOptions txnOptions = TransactionOptions.builder()
            .readPreference(ReadPreference.primary())
            .readConcern(ReadConcern.LOCAL)
            .writeConcern(WriteConcern.MAJORITY)
            .build();

        try (ClientSession session = client.startSession()) {
            session.withTransaction(() -> {
                MongoDatabase db = client.getDatabase("toko_online");
                MongoCollection<Document> produkCol = db.getCollection("produk");
                MongoCollection<Document> pesananCol = db.getCollection("pesanan");

                // Cek dan kurangi stok
                Bson filterProduk = Filters.and(
                    Filters.eq("_id", new ObjectId(produkId)),
                    Filters.gte("stok", jumlah) // pastikan stok cukup
                );
                Bson updateStok = Updates.inc("stok", -jumlah);
                Document produk = produkCol.findOneAndUpdate(session, filterProduk, updateStok);

                if (produk == null) {
                    throw new RuntimeException("Stok tidak mencukupi");
                }

                // Buat record pesanan
                Document pesanan = new Document()
                    .append("produk_id", new ObjectId(produkId))
                    .append("pengguna_id", penggunaId)
                    .append("jumlah", jumlah)
                    .append("harga_satuan", produk.get("harga"))
                    .append("status", "pending")
                    .append("dibuat_pada", new Date());
                pesananCol.insertOne(session, pesanan);

                return null;
            }, txnOptions);
        }
    }
}
Transaksi di MongoDB menambahkan overhead performa. Gunakan hanya ketika atomisitas benar-benar dibutuhkan. Untuk banyak kasus, desain skema yang baik dengan embedded documents bisa menghindari kebutuhan transaksi.

Kapan Menggunakan MongoDB vs SQL #

Memilih antara MongoDB dan database relasional bukan soal mana yang lebih baik secara absolut — tapi mana yang lebih cocok untuk use case kamu.

Gunakan MongoDB jika:
  ✓ Struktur data tidak seragam atau sering berubah
  ✓ Data hierarkis yang secara natural bersarang (nested)
  ✓ Perlu scale horizontal dengan sharding
  ✓ Data bersifat document-centric (profil, log, catalog)
  ✓ Tidak ada banyak relasi antar entitas yang kompleks
  ✓ Perlu full-text search dengan Mongo Atlas Search

Pertimbangkan SQL (PostgreSQL/MySQL) jika:
  ✗ Data sangat relasional dan banyak JOIN yang kompleks
  ✗ Butuh ACID transaction yang ketat untuk banyak operasi
  ✗ Tim lebih familiar dan aplikasi sudah ada dengan SQL
  ✗ Data terstruktur dan skema tidak akan banyak berubah
  ✗ Butuh reporting dan analytics yang kompleks (SQL lebih ekspresif)

Ringkasan #

  • Document model — MongoDB menyimpan data sebagai dokumen BSON yang bisa punya field bersarang dan array secara native, tanpa butuh tabel join.
  • MongoClient adalah singleton — buat sekali dan share di seluruh aplikasi. Jangan buat MongoClient baru di setiap request.
  • Gunakan Filters, Updates, Aggregates dari package com.mongodb.client.model — jangan bangun query secara manual dengan Document karena rawan typo.
  • Aggregation pipeline adalah fitur paling powerful — gunakan untuk grouping, joining antar collection ($lookup), transformasi data, dan faceted search.
  • Index adalah kunci performa — tanpa index, query pada collection besar akan lambat. Buat index hanya untuk field yang sering digunakan dalam filter, bukan semua field.
  • Spring Data MongoDB menyederhanakan integrasi: gunakan MongoRepository untuk operasi sederhana dan MongoTemplate untuk query dinamis atau aggregation kompleks.
  • Transaksi multi-document tersedia sejak MongoDB 4.0, tapi hanya pada Replica Set dan Sharded Cluster — tidak pada standalone. Gunakan hanya saat atomisitas benar-benar dibutuhkan.
  • Pilih MongoDB untuk data yang tidak terstruktur, hierarkis, atau sering berubah skemanya. Pilih SQL untuk data yang sangat relasional dengan banyak JOIN kompleks.

← Sebelumnya:PostgreSQL   Berikutnya: Elasticsearch →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact