Semua data yang kita kelola sejauh ini hidup dalam satu document yang berdiri sendiri. Aplikasi nyata jarang sesederhana itu: toko buku kita memiliki pelanggan yang membuat pesanan atas buku tertentu, dan ketiganya layak menjadi collection terpisah. References adalah cara menautkannya — menyimpan nilai _id (atau field unik lain) dari satu document ke dalam field document lain. Chapter ini membedah anatomi referensi itu sendiri: pilihan kunci, arah penunjukan, dan konvensi lama DBRef yang tidak lagi disarankan.
Peta materinya:
- Referensi sebagai nilai yang disalin — dan konsekuensi tidak adanya foreign key.
- Pilihan kunci:
_idversus natural key sepertiskudannoPesanan. - DBRef, konvensi lama yang ditinggalkan.
- Arah referensi untuk relasi one-to-many dan many-to-many.
Referensi Hanyalah Nilai yang Disalin #
MongoDB tidak memiliki foreign key seperti database relasional. Sebuah referensi hanyalah field biasa yang nilainya kita salin dari document lain — biasanya _id milik document itu. Tidak ada mekanisme server yang otomatis mengikuti referensi, dan tidak ada pemeriksaan yang memastikan document yang ditunjuk masih ada. Seluruh tanggung jawab menjaga dan menyelesaikan referensi berada di tangan aplikasi.
Kita siapkan ketiga collection toko buku. Setiap pesanan menyimpan dua referensi: pelangganId menunjuk document pelanggan, bukuId menunjuk document buku:
use latihan_relasi
db.pelanggan.drop()
db.buku.drop()
db.pesanan.drop()
db.pelanggan.insertMany([
{ nama: "Budi", email: "[email protected]", kota: "Bandung" },
{ nama: "Siti", email: "[email protected]", kota: "Jakarta" },
{ nama: "Dewi", email: "[email protected]", kota: "Surabaya" }
])
db.buku.insertMany([
{ sku: "BK-001", judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000 },
{ sku: "BK-002", judul: "Bumi", penulis: "Tere Liye", harga: 89000 },
{ sku: "BK-003", judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 72000 }
])
db.pesanan.createIndex({ noPesanan: 1 }, { unique: true })
var idBudi = db.pelanggan.findOne({ nama: "Budi" })._id
var idBuku = {}
db.buku.find({}, { sku: 1 }).forEach(b => idBuku[b.sku] = b._id)
db.pesanan.insertMany([
{ noPesanan: "PSN-001", pelangganId: idBudi, bukuId: idBuku["BK-001"], jumlah: 2 },
{ noPesanan: "PSN-002", pelangganId: idBudi, bukuId: idBuku["BK-002"], jumlah: 1 },
{ noPesanan: "PSN-003", pelangganId: db.pelanggan.findOne({ nama: "Siti" })._id, bukuId: idBuku["BK-003"], jumlah: 3 }
])
db.pesanan.find({}, { _id: 0 })Output yang Dihasilkan:
switched to db latihan_relasi
true
true
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad39a172139099756693d50'),
'1': ObjectId('6ad39a172139099756693d51'),
'2': ObjectId('6ad39a172139099756693d52')
}
}
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad39a172139099756693d53'),
'1': ObjectId('6ad39a172139099756693d54'),
'2': ObjectId('6ad39a172139099756693d55')
}
}
noPesanan_1
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad39a172139099756693d56'),
'1': ObjectId('6ad39a172139099756693d57'),
'2': ObjectId('6ad39a172139099756693d58')
}
}
[
{
noPesanan: 'PSN-001',
pelangganId: ObjectId('6ad39a172139099756693d50'),
bukuId: ObjectId('6ad39a172139099756693d53'),
jumlah: 2
},
{
noPesanan: 'PSN-002',
pelangganId: ObjectId('6ad39a172139099756693d50'),
bukuId: ObjectId('6ad39a172139099756693d54'),
jumlah: 1
},
{
noPesanan: 'PSN-003',
pelangganId: ObjectId('6ad39a172139099756693d51'),
bukuId: ObjectId('6ad39a172139099756693d55'),
jumlah: 3
}
]Mari kita bedah bagian-bagian pentingnya:
var idBudi = ... ._id: nilai_idpelanggan Budi disalin ke variabel sebelum dipakai diinsertMany— persis pola yang akan dipakai aplikasi sungguhan.var idBuku = {}+forEach: peta kecil dariskuke_iddibangun sekali, sehingga tiga penyisipan pesanan cukup membacaidBuku["BK-001"]tanpa query tambahan.db.pesanan.createIndex(...):noPesananadalah kode pesanan buatan manusia; unique index memastikan tidak ada dua pesanan bernomor sama.db.pesanan.find(...): hasil akhirnya — setiap pesanan memuatpelangganIddanbukuIdberupaObjectId, salinan dari_iddocument yang ditunjuk.
Perhatikan bahwa pelangganId: ObjectId('...') di hasil tidak punya kekuatan khusus. Bagi MongoDB, field itu sama seperti field angka atau string lainnya — nilainya hanya kebetulan berbentuk ObjectId.
_id atau Natural Key #
Referensi tidak harus memakai _id. Field unik yang bermakna bagi bisnis — disebut natural key — juga bisa dipakai: sku untuk buku, email untuk pelanggan, atau noPesanan untuk pesanan. Keduanya punya kompromi:
| Aspek | _id (ObjectId) | Natural key (sku, noPesanan) |
|---|---|---|
| Ketersediaan | otomatis ada di setiap document | harus dikelola sendiri + unique index |
| Keterbacaan | nilai acak 12-byte, tak bermakna | terbaca manusia, ramah untuk log dan laporan |
| Stabilitas | dijamin tidak pernah berubah | bisa berubah mengikuti proses bisnis |
| Antar environment | berbeda antara dev dan produksi | nilainya sama di mana pun |
| Ukuran | tetap 12 byte | bervariasi, string cenderung lebih panjang |
_id adalah pilihan paling aman untuk referensi karena selalu ada dan tidak pernah berubah. Natural key menarik justru pada titik di mana referensi harus keluar dari database — misalnya pengiriman barang dari gudang yang sistemnya terpisah dan hanya mengenal nomor pesanan, bukan ObjectId internal kita:
db.pengiriman.drop()
db.pengiriman.insertMany([
{ noPesanan: "PSN-001", kurir: "Kilat", status: "dikirim" },
{ noPesanan: "PSN-003", kurir: "Reguler", status: "diproses" }
])
var kirim = db.pengiriman.findOne({ kurir: "Kilat" })
db.pesanan.find({ noPesanan: kirim.noPesanan }, { _id: 0 })Output yang Dihasilkan:
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad39a172139099756693d59'),
'1': ObjectId('6ad39a172139099756693d5a')
}
}
[
{
noPesanan: 'PSN-001',
pelangganId: ObjectId('6ad39a172139099756693d50'),
bukuId: ObjectId('6ad39a172139099756693d53'),
jumlah: 2
}
]Document pengiriman menautkan dirinya ke pesanan lewat noPesanan — tidak ada ObjectId yang tercecer di sistem logistik. Baris db.pesanan.find({ noPesanan: ... }) menyelesaikan referensi itu kembali ke document pesanan aslinya.
DBRef: Konvensi Lama yang Ditinggalkan #
Versi-versi awal MongoDB mengenal DBRef — konvensi yang membungkus referensi dalam subdocument khusus berisi nama collection, nilai _id, dan kadang nama database:
// mdtest: skip
// Bentuk DBRef — hanya ilustrasi sejarah, jangan dipakai
{ noPesanan: "PSN-001", pelanggan: { $ref: "pelanggan", $id: ObjectId("..."), $db: "latihan_relasi" } }DBRef pernah menjanjikan resolusi referensi otomatis oleh driver. Praktiknya, konvensi ini menambah ukuran document, mempersulit query biasa, dan tidak pernah menjadi bagian inti server. Dokumentasi resmi MongoDB saat ini menyatakan manual references sudah memadai untuk hampir semua kasus dan lebih disarankan daripada DBRef. Untuk course ini kita memakai manual references saja — menyimpan _id atau natural key pada field biasa seperti pelangganId di atas.
Arah Referensi #
Untuk relasi one-to-many, referensi diletakkan di sisi “banyak”: banyak pesanan menunjuk satu pelanggan, bukan sebaliknya. Meletakkan array pesananIds pada document pelanggan juga bisa, tetapi array itu tumbuh tanpa batas seiring pesanan baru — menabrak batas 16MB per document dan memperlambat setiap update pada document pelanggan. Penampakan keseluruhan relasi kita:
Diagram di atas terbaca: satu pelanggan bisa memiliki banyak pesanan (||--o{), dan tautannya adalah field pelangganId di sisi pesanan. Relasi many-to-many antara pelanggan dan buku tidak perlu array ganda — collection pesanan itu sendiri berperan sebagai document penghubung (link), tepat seperti tabel perantara pada database relasional.
Rangkuman #
- Referensi adalah field biasa berisi nilai (
_idatau natural key) dari document lain — server tidak mengikuti atau memverifikasinya otomatis. _idpaling aman (selalu ada, tidak berubah); natural key sepertiskuramah manusia dan stabil antar environment, tetapi menuntut unique index.- DBRef adalah konvensi lama yang sudah tidak disarankan — gunakan manual references.
- One-to-many: sisi banyak memegang referensi; many-to-many: gunakan document penghubung seperti
pesanan.
Referensi hanyalah data yang tersimpan. Menggunakannya untuk membaca kembali document yang ditunjuk adalah pekerjaan dua langkah — pola yang kita praktikkan di Chapter Manual References.