Katalog toko buku tidak pernah berhenti berubah: penerbit merevisi harga, stok berkurang setiap ada pesanan, dan rating pembaca terus bergerak. Setelah insert dan find, kebutuhan berikutnya adalah mengubah data yang sudah tersimpan tanpa harus menghapus lalu memasukkan ulang. Method updateOne(filter, update) menjawab kebutuhan itu: menemukan document yang cocok dengan filter, lalu mengubah isinya lewat update operators.

Peta materi Chapter ini:

  • Anatomi updateOne() dan peran wajib operator $set.
  • Membaca hasil matchedCount dan modifiedCount.
  • Galat yang muncul bila dokumen update dikirim tanpa operator.
  • Pratinjau opsi upsert.

Mengubah Satu Document dengan $set #

updateOne() menerima dua argument utama: filter untuk menemukan document, dan dokumen update yang menyatakan perubahan. Dokumen update wajib dibungkus update operator — $set adalah yang paling sering dipakai karena mampu mengganti nilai field maupun membuat field baru. Kita mulai dengan menyiapkan data contoh:

js
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:

text
switched to db latihan_crud2
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad3882da9fbaec463d48902'),
    '1': ObjectId('6ad3882da9fbaec463d48903'),
    '2': ObjectId('6ad3882da9fbaec463d48904'),
    '3': ObjectId('6ad3882da9fbaec463d48905'),
    '4': ObjectId('6ad3882da9fbaec463d48906')
  }
}

Tiga baris pembuka ini akan menjadi pola tetap sepanjang Section CRUD: use latihan_crud2 berpindah ke database latihan, db.buku.drop() membersihkan collection dari percobaan sebelumnya, dan insertMany() mengisi lima data buku. Sekarang kita turunkan harga Laskar Pelangi menjadi Rp89.000:

js
db.buku.updateOne({ judul: "Laskar Pelangi" }, { $set: { harga: 89000 } })
db.buku.findOne({ judul: "Laskar Pelangi" })

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  _id: ObjectId('6ad3882da9fbaec463d48902'),
  judul: 'Laskar Pelangi',
  penulis: 'Andrea Hirata',
  harga: 89000,
  stok: 12,
  kategori: 'Fiksi',
  tag: [ 'novel', 'bestseller' ],
  rating: 4.6
}

Mari kita bedah hasilnya:

  1. Filter { judul: "Laskar Pelangi" } mencocokkan tepat satu document — pola filter yang sama persis dengan find().
  2. { $set: { harga: 89000 } } hanya mengganti field harga; findOne() membuktikan penulis, stok, kategori, tag, dan rating tetap utuh.
  3. Hasil matchedCount: 1, modifiedCount: 1 melapor: satu document cocok, satu document berubah.

Membaca Hasil matchedCount dan modifiedCount #

Dua angka pada hasil di atas adalah alat diagnosis utama untuk operasi update. matchedCount menghitung document yang cocok dengan filter, sedangkan modifiedCount menghitung yang benar-benar berubah isinya. Tiga skenario yang paling sering muncul:

SkenariomatchedCountmodifiedCount
Filter cocok dan nilai baru berbeda11
Filter cocok, tetapi nilai sudah sama10
Filter tidak cocok dengan document mana pun00

Skenario kedua sering mengejutkan pemula. Coba jalankan kembali update yang sama persis:

js
db.buku.updateOne({ judul: "Laskar Pelangi" }, { $set: { harga: 89000 } })

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 0,
  upsertedCount: 0
}

Server menemukan document yang dicari, membandingkan nilai baru dengan nilai lama, lalu memutuskan tidak ada yang perlu ditulis — sehingga modifiedCount tetap 0. Ini bukan galat. Sebaliknya, bila filter tidak menemukan apa pun:

js
db.buku.updateOne({ judul: "Buku yang Tidak Ada" }, { $set: { harga: 50000 } })

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 0,
  modifiedCount: 0,
  upsertedCount: 0
}

Kombinasi 0/0 juga bukan galat — perintah sukses dieksekusi, hanya tidak menemukan sasaran. Ketika hasil 0/0 muncul tanpa diduga, periksa kembali filter: kesalahan ketik pada nama field atau nilainya adalah penyebab paling umum.

Dokumen Update Harus Memakai Operator #

Godaan besar bagi pemula adalah menulis dokumen update seperti document biasa pada insertOne() — langsung berisi field dan nilai. MongoDB menolak pendekatan itu:

js
db.buku.updateOne({ judul: "Laskar Pelangi" }, { harga: 50000 })

Output yang Dihasilkan:

text
Uncaught 
MongoInvalidArgumentError: Update document requires atomic operators

Perhatikan jenisnya: MongoInvalidArgumentError, bukan MongoServerError. Validasi ini terjadi di sisi klien — mongosh memeriksa dokumen update sebelum perintah dikirim ke server, sehingga permintaan yang keliru tidak pernah meninggalkan shell. Pesan Update document requires atomic operators menegaskan aturannya: perubahan harus dinyatakan eksplisit lewat operator seperti $set.

Pratinjau Opsi upsert #

Skenario matchedCount: 0 tadi berakhir tanpa efek — kecuali kita menambahkan opsi { upsert: true }. Dengan opsi itu, MongoDB menyisipkan document baru yang dibentuk dari gabungan field filter dan dokumen update:

js
db.buku.updateOne(
  { judul: "Rumah Kaca" },
  { $set: { penulis: "Pramoedya Ananta Toer", harga: 98000 } },
  { upsert: true }
)

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedId: ObjectId('6ad3882d9d08d4e3fde1a3ae'),
  matchedCount: 0,
  modifiedCount: 0,
  upsertedCount: 1
}

upsertedCount: 1 menandakan satu document baru dibuat, dan _id-nya dilaporkan pada field insertedId. Logika “update bila ada, insert bila tidak” ini penting untuk dibahas tuntas di Chapter Upsert — sekarang kita cukup tahu pintunya ada.

Terakhir, bersihkan database latihan:

js
db.dropDatabase()

Output yang Dihasilkan:

text
{ ok: 1, dropped: 'latihan_crud2' }

Rangkuman #

  • updateOne(filter, update) mengubah document pertama yang cocok dengan filter; field lain tidak tersentuh.
  • Dokumen update wajib memakai operator — $set untuk mengganti atau membuat field.
  • matchedCount menghitung yang cocok, modifiedCount yang benar-benar berubah; 0/0 bukan galat.
  • Dokumen update polos ditolak MongoInvalidArgumentError di sisi klien sebelum mencapai server.
  • Opsi { upsert: true } mengubah kegagalan cocok menjadi penyisipan document baru.

Satu document sudah terkendali. Chapter berikutnya memperluas kemampuan yang sama ke banyak document sekaligus lewat updateMany().