1. Konsep Dasar Entitas dan Asosiasi
Dalam kerangka kerja ent
, sebuah entitas merujuk pada unit data dasar yang dikelola dalam basis data, yang biasanya sesuai dengan tabel dalam basis data. Bidang-bidang dalam entitas sesuai dengan kolom-kolom dalam tabel, sedangkan asosiasi (sisi) antara entitas digunakan untuk menjelaskan hubungan dan ketergantungan antara entitas. Asosiasi entitas membentuk dasar untuk membangun model data kompleks, memungkinkan representasi hubungan hierarkis seperti hubungan induk-anak dan hubungan kepemilikan.
Kerangka kerja ent
menyediakan seperangkat API yang kaya, memungkinkan pengembang untuk mendefinisikan dan mengelola asosiasi-asosiasi ini dalam skema entitas. Melalui asosiasi-asosiasi ini, kita dapat dengan mudah mengekspresikan dan mengoperasikan logika bisnis yang kompleks antara data.
2. Jenis Asosiasi Entitas dalam ent
2.1 Asosiasi Satu-ke-Satu (O2O)
Asosiasi satu-ke-satu merujuk pada korespondensi satu-ke-satu antara dua entitas. Sebagai contoh, dalam kasus pengguna dan rekening bank, setiap pengguna hanya dapat memiliki satu rekening bank, dan setiap rekening bank juga hanya milik satu pengguna. Kerangka kerja ent
menggunakan metode edge.To
dan edge.From
untuk mendefinisikan asosiasi-asosiasi tersebut.
Pertama, kita dapat mendefinisikan asosiasi satu-ke-satu yang menunjuk ke Card
dalam skema User
:
// Sisi dari Pengguna.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("card", Card.Type). // Menunjuk ke entitas Card, mendefinisikan nama asosiasi sebagai "card"
Unique(), // Metode Unique memastikan ini adalah asosiasi satu-ke-satu
}
}
Selanjutnya, kita mendefinisikan asosiasi balik ke User
dalam skema Card
:
// Sisi dari Kartu.
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type). // Menunjuk kembali ke Pengguna dari Kartu, mendefinisikan nama asosiasi sebagai "owner"
Ref("card"). // Metode Ref menentukan nama asosiasi balik yang sesuai
Unique(), // Ditandai sebagai unik untuk memastikan satu kartu sesuai dengan satu pemilik
}
}
2.2 Asosiasi Satu-ke-Banyak (O2M)
Asosiasi satu-ke-banyak menunjukkan bahwa satu entitas dapat terkait dengan beberapa entitas lain, tetapi entitas-entitas ini hanya dapat menunjuk kembali ke satu entitas. Sebagai contoh, seorang pengguna mungkin memiliki beberapa hewan peliharaan, tetapi setiap hewan peliharaan hanya memiliki satu pemilik.
Dalam ent
, kita tetap menggunakan edge.To
dan edge.From
untuk mendefinisikan jenis asosiasi ini. Contoh di bawah ini mendefinisikan asosiasi satu-ke-banyak antara pengguna dan hewan peliharaan:
// Sisi dari Pengguna.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type), // Asosiasi satu-ke-banyak dari entitas Pengguna ke entitas Hewan Peliharaan
}
}
Dalam entitas Pet
, kita mendefinisikan asosiasi banyak-ke-satu kembali ke User
:
// Sisi dari Hewan Peliharaan.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type). // Asosiasi banyak-ke-satu dari Hewan Peliharaan ke Pengguna
Ref("pets"). // Menentukan nama asosiasi balik dari hewan peliharaan ke pemilik
Unique(), // Memastikan satu pemilik dapat memiliki beberapa hewan peliharaan
}
}
2.3 Asosiasi Banyak-ke-Banyak (M2M)
Asosiasi banyak-ke-banyak memungkinkan dua jenis entitas memiliki beberapa instansi satu sama lain. Sebagai contoh, seorang siswa dapat mendaftar dalam beberapa kursus, dan sebuah kursus juga dapat memiliki beberapa siswa yang mendaftar. ent
menyediakan API untuk menetapkan asosiasi banyak-ke-banyak:
Dalam entitas Mahasiswa
, kita menggunakan edge.To
untuk menetapkan asosiasi banyak-ke-banyak dengan Kursus
:
// Sisi dari Mahasiswa.
func (Student) Edges() []ent.Edge {
return []ent.Edge{
edge.To("courses", Course.Type), // Mendefinisikan asosiasi banyak-ke-banyak dari Mahasiswa ke Kursus
}
}
Demikian pula, dalam entitas Course
, kita menetapkan asosiasi balik ke Mahasiswa
untuk hubungan banyak-ke-banyak:
// Sisi dari Kursus.
func (Course) Edges() []ent.Edge {
return []ent.Edge{
edge.From("students", Student.Type). // Mendefinisikan asosiasi banyak-ke-banyak dari Kursus ke Mahasiswa
Ref("courses"), // Menentukan nama asosiasi balik dari Kursus ke Mahasiswa
}
}
Jenis-jenis asosiasi ini merupakan dasar dari membangun model data aplikasi kompleks, dan memahami cara mendefinisikan dan menggunakan mereka dalam ent
sangat penting untuk memperluas model data dan logika bisnis.
3. Operasi Dasar untuk Asosiasi Entitas
Bagian ini akan menunjukkan bagaimana melakukan operasi dasar menggunakan ent
dengan hubungan yang telah ditentukan, termasuk membuat, mengambil, dan menelusuri entitas yang terkait.
3.1 Membuat Entitas Terkait
Saat membuat entitas, Anda dapat secara bersamaan mengatur hubungan antara entitas. Untuk hubungan satu-ke-banyak (O2M) dan banyak-ke-banyak (M2M), Anda dapat menggunakan metode Add{Edge}
untuk menambahkan entitas yang terkait.
Sebagai contoh, jika kita memiliki entitas pengguna dan entitas hewan peliharaan dengan hubungan tertentu, di mana seorang pengguna dapat memiliki beberapa hewan peliharaan, berikut adalah contoh membuat pengguna baru dan menambahkan hewan peliharaan untuk mereka:
// Buat pengguna dan tambahkan hewan peliharaan
func CreateUserWithPets(ctx context.Context, client *ent.Client) (*ent.User, error) {
// Buat contoh hewan peliharaan
fido := client.Pet.
Create().
SetName("Fido").
SaveX(ctx)
// Buat contoh pengguna dan mengaitkannya dengan hewan peliharaan
user := client.User.
Create().
SetName("Alice").
AddPets(fido). // Gunakan metode AddPets untuk mengaitkan hewan peliharaan
SaveX(ctx)
return user, nil
}
Pada contoh ini, kita pertama-tama membuat contoh hewan peliharaan bernama Fido, kemudian membuat pengguna bernama Alice dan mengaitkan contoh hewan peliharaan dengan pengguna menggunakan metode AddPets
.
3.2 Mengambil Entitas Terkait
Mengambil entitas terkait adalah operasi umum dalam ent
. Sebagai contoh, Anda dapat menggunakan metode Query{Edge}
untuk mengambil entitas lain yang terkait dengan entitas tertentu.
Melanjutkan dengan contoh kita tentang pengguna dan hewan peliharaan, berikut cara untuk mengambil semua hewan peliharaan yang dimiliki oleh seorang pengguna:
// Mengambil semua hewan peliharaan dari seorang pengguna
func QueryUserPets(ctx context.Context, client *ent.Client, userID int) ([]*ent.Pet, error) {
pets, err := client.User.
Get(ctx, userID). // Dapatkan contoh pengguna berdasarkan ID pengguna
QueryPets(). // Query entitas hewan peliharaan yang terkait dengan pengguna
All(ctx) // Mengembalikan semua entitas hewan peliharaan yang diambil
if err != nil {
return nil, err
}
return pets, nil
}
Pada potongan kode di atas, kita pertama-tama mendapatkan contoh pengguna berdasarkan ID pengguna, lalu memanggil metode QueryPets
untuk mengambil semua entitas hewan peliharaan yang terkait dengan pengguna tersebut.
Catatan: Alat pembangkit kode
ent
secara otomatis menghasilkan API untuk kueri asosiasi berdasarkan hubungan entitas yang ditentukan. Disarankan untuk meninjau kode yang dihasilkan.
4. Pemuatan Awal (Eager Loading)
4.1 Prinsip Pemuatan Awal
Pemuatan awal adalah teknik yang digunakan dalam kueri basis data untuk mengambil dan memuat entitas-entitas terkait sebelumnya. Pendekatan ini umum digunakan untuk mendapatkan data yang terkait dengan beberapa entitas sekaligus, untuk menghindari operasi kueri basis data yang berulang di proses selanjutnya, sehingga signifikan meningkatkan kinerja aplikasi.
Dalam kerangka kerja ent, pemuatan awal terutama digunakan untuk menangani hubungan antara entitas, seperti satu-ke-banyak dan banyak-ke-banyak. Saat mengambil entitas dari basis data, entitas yang terkait diperlakukan tidak dipuat secara otomatis. Sebaliknya, mereka dimuat secara eksplisit saat diperlukan melalui pemuatan awal. Hal ini penting untuk mengurangi masalah kueri N+1 (yaitu, melakukan kueri terpisah untuk setiap entitas induk).
Dalam kerangka kerja ent, pemuatan awal dicapai dengan menggunakan metode With
dalam pembangun kueri. Metode ini menghasilkan fungsi With...
yang sesuai untuk setiap sisi (edge), seperti WithGroups
dan WithPets
. Fungsi-fungsi ini secara otomatis dihasilkan oleh kerangka kerja ent, dan programmer dapat menggunakannya untuk meminta pemuatan awal asosiasi tertentu.
Prinsip kerja pemuatan awal entitas adalah saat mengkueri entitas utama, ent melakukan kueri tambahan untuk mengambil semua entitas yang terkait. Selanjutnya, entitas-entitas ini dimasukkan ke dalam lapangan Edges
dari objek yang dikembalikan. Ini berarti bahwa ent mungkin mengeksekusi beberapa kueri basis data, setidaknya sekali untuk setiap sisi (edge) yang perlu dipuat sebelumnya. Meskipun metode ini mungkin kurang efisien daripada satu kueri JOIN
kompleks dalam beberapa skenario, metode ini menawarkan fleksibilitas yang lebih besar dan diharapkan menerima optimasi kinerja pada versi ent yang akan datang.
4.2 Implementasi Pemuatan Awal
Sekarang kami akan menunjukkan bagaimana melakukan operasi pemuatan awal dalam kerangka kerja ent melalui beberapa contoh kode, menggunakan model-model pengguna dan hewan peliharaan yang dijelaskan dalam gambaran umum.
Memuatkan Sebuah Asosiasi Tunggal
Misalkan kita ingin mengambil semua pengguna dari basis data dan memuatkan data hewan peliharaan. Hal ini dapat dicapai dengan menulis kode berikut:
users, err := client.User.
Query().
WithPets().
All(ctx)
if err != nil {
// Mengatasi kesalahan
return err
}
for _, u := range users {
for _, p := range u.Edges.Pets {
fmt.Printf("Pengguna (%v) memiliki hewan peliharaan (%v)\n", u.ID, p.ID)
}
}
Pada contoh ini, kita menggunakan metode WithPets
untuk meminta ent untuk memuatkan entitas hewan peliharaan yang terkait dengan pengguna. Data hewan peliharaan yang dimuatkan akan dipopulasikan ke dalam kolom Edges.Pets
, dari mana kita dapat mengakses data terkait ini.
Memuatkan Beberapa Asosiasi
ent memungkinkan kita untuk memuatkan beberapa asosiasi sekaligus, dan bahkan menentukan pemunuhan asosiasi bersarang, penyaringan, pengurutan, atau pembatasan jumlah hasil yang dimuat. Berikut adalah contoh memuatkan hewan peliharaan dari administrator dan tim yang mereka miliki, sambil juga memuatkan pengguna yang terkait dengan tim tersebut:
admins, err := client.User.
Query().
Where(user.Admin(true)).
WithPets().
WithGroups(func(q *ent.GroupQuery) {
q.Limit(5) // Batasi hingga 5 tim pertama
q.Order(ent.Asc(group.FieldName)) // Urutkan secara meningkat berdasarkan nama tim
q.WithUsers() // Memuatkan pengguna dalam tim
}).
All(ctx)
if err != nil {
// Mengatasi kesalahan
return err
}
for _, admin := range admins {
for _, p := range admin.Edges.Pets {
fmt.Printf("Admin (%v) memiliki hewan peliharaan (%v)\n", admin.ID, p.ID)
}
for _, g := range admin.Edges.Groups {
fmt.Printf("Admin (%v) tergabung dalam tim (%v)\n", admin.ID, g.ID)
for _, u := range g.Edges.Users {
fmt.Printf("Tim (%v) memiliki anggota (%v)\n", g.ID, u.ID)
}
}
}
Melalui contoh ini, Anda dapat melihat betapa kuat dan fleksibelnya ent. Dengan hanya beberapa panggilan metode sederhana, ia dapat memuatkan data terkait yang kaya dan mengatur mereka dengan cara yang terstruktur. Ini memberikan kemudahan besar dalam pengembangan aplikasi berbasis data.