Aplikasi toko buku sering menghadapi percabangan yang menjemukan: “kalau judulnya sudah ada, perbarui harganya; kalau belum, sisipkan”. Tanpa bantuan khusus, kita harus findOne() dulu, memeriksa hasilnya, lalu memilih updateOne() atau insertOne() — dua bolak-balik ke server dan celah kondisi berubah di tengah jalan. Opsi { upsert: true } memangkas semuanya menjadi satu perintah: update bila cocok, insert bila tidak.
update atau insert dalam Satu Perintah #
Kita mulai dari katalog yang belum memuat Rumah Kaca:
use latihan_crud2
db.buku.drop()
db.buku.insertMany([
{ judul: "Laskar Pelangi", penulis: "Andrea Hirata", harga: 95000, stok: 12, kategori: "Fiksi", tag: ["novel", "bestseller"], rating: 4.6 },
{ judul: "Bumi Manusia", penulis: "Pramoedya Ananta Toer", harga: 110000, stok: 8, kategori: "Fiksi", tag: ["novel", "klasik"], rating: 4.8 },
{ judul: "Atomic Habits", penulis: "James Clear", harga: 135000, stok: 20, kategori: "Pengembangan Diri", tag: ["nonfiksi", "bestseller"], rating: 4.7 },
{ judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, stok: 0, kategori: "Pengembangan Diri", tag: ["nonfiksi"], rating: 4.5 },
{ judul: "Clean Code", penulis: "Robert C. Martin", harga: 165000, stok: 5, kategori: "Teknologi", tag: ["pemrograman", "bestseller"], rating: 4.4 }
])Output yang Dihasilkan:
switched to db latihan_crud2
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad38839df7d7bd42c35aa25'),
'1': ObjectId('6ad38839df7d7bd42c35aa26'),
'2': ObjectId('6ad38839df7d7bd42c35aa27'),
'3': ObjectId('6ad38839df7d7bd42c35aa28'),
'4': ObjectId('6ad38839df7d7bd42c35aa29')
}
}Kirim upsert untuk judul yang belum ada:
db.buku.updateOne(
{ judul: "Rumah Kaca" },
{ $set: { penulis: "Pramoedya Ananta Toer", harga: 98000 } },
{ upsert: true }
)
db.buku.findOne({ judul: "Rumah Kaca" })Output yang Dihasilkan:
{
acknowledged: true,
insertedId: ObjectId('6ad388399d08d4e3fde1a3b0'),
matchedCount: 0,
modifiedCount: 0,
upsertedCount: 1
}
{
_id: ObjectId('6ad388399d08d4e3fde1a3b0'),
judul: 'Rumah Kaca',
harga: 98000,
penulis: 'Pramoedya Ananta Toer'
}Mari kita bedah hasilnya:
matchedCount: 0, upsertedCount: 1— filter tidak menemukan document, sehingga upsert menyisipkan document baru.insertedIdmemuat_iddocument baru itu (mongosh menamai field-nyainsertedId; pada driver resmi field ini disebutupsertedId).- Document baru terbentuk dari gabungan field filter (
judul) dan dokumen update (penulis,harga) —findOne()membuktikan ketiganya menyatu.
Jalankan perintah yang sama lagi — kini document sudah ada, sehingga upsert berlaku sebagai update biasa:
db.buku.updateOne(
{ judul: "Rumah Kaca" },
{ $set: { harga: 95000 } },
{ upsert: true }
)Output yang Dihasilkan:
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}Sifat ini membuat upsert idempoten: perintah yang sama boleh diulang berkali-kali — entri pertama menyisipkan, entri berikutnya memperbarui — tanpa pernah menghasilkan duplikat. Inilah alasan upsert menjadi fondasi pola sinkronisasi data.
upsert pada replaceOne dan updateMany #
Opsi yang sama berlaku pada replaceOne() dan updateMany():
db.buku.replaceOne(
{ judul: "Arus Balik" },
{ judul: "Arus Balik", penulis: "Pramoedya Ananta Toer", harga: 92000 },
{ upsert: true }
)Output yang Dihasilkan:
{
acknowledged: true,
insertedId: ObjectId('6ad3883a9d08d4e3fde1a3b1'),
matchedCount: 0,
modifiedCount: 0,
upsertedCount: 1
}Pada replaceOne(), dokumen pengganti menyediakan seluruh isi document baru — tidak ada penggabungan dengan field lama, sesuai aturan Chapter replaceOne(). Adapun updateMany() dengan upsert: true menyisipkan paling banyak satu document — dibentuk dari gabungan filter dan update, bukan satu per kecocokan:
db.buku.updateMany({ kategori: "Puisi" }, { $set: { harga: 60000 } }, { upsert: true })
db.buku.find({ kategori: "Puisi" })Output yang Dihasilkan:
{
acknowledged: true,
insertedId: ObjectId('6ad3883a9d08d4e3fde1a3b2'),
matchedCount: 0,
modifiedCount: 0,
upsertedCount: 1
}
[
{
_id: ObjectId('6ad3883a9d08d4e3fde1a3b2'),
kategori: 'Puisi',
harga: 60000
}
]Perhatikan isi document hasilnya: hanya kategori dan harga — field yang tidak disebut filter maupun dokumen update tidak ikut dibuat, bahkan judul sekalipun. Bila skema menuntut field tertentu selalu ada, sertakan nilainya di filter atau di update; penegakan aturan otomatis dibahas pada Section Schema Validation.
Race Condition dan Unique Index #
Sisi lemah upsert muncul saat dua client menulis bersamaan. Keduanya memeriksa filter di saat yang hampir sama, sama-sama mendapati document belum ada, lalu sama-sama menyisipkan — hasilnya dua document kembar. Perlombaan semacam ini tidak bisa dihindari dari sisi aplikasi, tetapi bisa dijaga dari sisi data: unique index pada field kunci mengubah sisipan kembar menjadi galat E11000 yang tertangkap:
db.buku.createIndex({ judul: 1 }, { unique: true })
db.buku.insertOne({ judul: "Rumah Kaca", harga: 50000 })Output yang Dihasilkan:
judul_1
Uncaught
MongoServerError: E11000 duplicate key error collection: latihan_crud2.buku index: judul_1 dup key: { judul: "Rumah Kaca" }Kapan Memakai Upsert #
Empat pola yang paling umum:
- Sinkronisasi data eksternal — data dari pemasok masuk berulang; upsert menjadikan proses aman dijalankan berkali-kali.
- Penghitung — statistik kunjungan per judul naik satu tiap peristiwa, dan baris pertama dibuat otomatis.
- Keranjang belanja — menambah item membuat keranjang bila belum ada, memperbarui bila sudah.
- Konfigurasi aplikasi — pengaturan dibuat dengan nilai bawaan saat pertama kali dibutuhkan.
Untuk field yang cukup diisi sekali saat penyisipan — seperti cap waktu pembuatan — pasangkan dengan operator $setOnInsert yang sudah kita uji di Chapter Update Operators. Terakhir, bersihkan database latihan:
db.dropDatabase()Output yang Dihasilkan:
{ ok: 1, dropped: 'latihan_crud2' }Rangkuman #
{ upsert: true }mengubahupdateOne(),updateMany(), danreplaceOne()menjadi “update bila ada, insert bila tidak”.- Document baru terbentuk dari gabungan field filter dan dokumen update;
_id-nya dilaporkan padainsertedId(mongosh). updateMany()dengan upsert menyisipkan paling banyak satu document.- Upsert idempoten — aman diulang; itulah dasar pola sinkronisasi.
- Dua client paralel bisa menyisipkan kembar; unique index menjaganya dengan galat
E11000yang tinggal diulang.
Kita sudah menulis dan membaca hasilnya; Chapter penutup Section ini membahas lapisan jaminannya: write concern dan struktur hasil penulisan.