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 menggunakanreplaceOne()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
MongoClientbaru di setiap request.- Gunakan
Filters,Updates,Aggregatesdari packagecom.mongodb.client.model— jangan bangun query secara manual denganDocumentkarena 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
MongoRepositoryuntuk operasi sederhana danMongoTemplateuntuk 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.