Jumat, 21 September 2012

One to Many


List dengan order by

Kita memiliki beberapa kasus One To Many. Pertama, kita lihat dulu tipe collection List yang menggunakan sorting di sisi database melalui perintah order by.

Hubungan ini ada antara Author dan Article. Karena hubungannya dua arah (bidirectional), maka kita akan melihat mapping di kedua sisi class. Berikut gambar domain modelnya.



Di sisi Article, mapping Many-to-One Author sama dengan Category yang kita sudah buat sebelumnya.


public class Article {
@ManyToOne
@JoinColumn(name = "author_id", nullable = false)
private Author author;
}


Di sisi Author,

public class Author {
@OneToMany(mappedBy = "author")
@OrderBy("publishDate")
private List<Article> articles
= new ArrayList<Article>();
}


Pemetaan di atas akan menghasilkan struktur tabel database sebagai berikut.



Kita menggunakan anotasi @OrderBy untuk menentukan urutan munculnya Article. Jika anotasi tersebut kita hilangkan, JPA akan mengurutkannya berdasarkan id.

Pada mapping di atas, kita menggunakan keyword mappedBy. Keyword ini dikenal juga dengan atribut inverse=true pada mapping XML.

Keyword inverse atau mappedBy memberitahukan JPA bahwa isi List adalah Entity object, bukan Component. Dengan demikian, kalau object Author dihapus, JPA tidak akan otomatis menghapus juga Article.

Selain mengatur masalah ketergantungan, keyword inverse ini juga mengatur eksekusi SQL. Mari kita bahas internal JPA sedikit.

Asosiasi dua arah antara Author dan Article sebetulnya terdiri dari dua asosiasi satu arah, yaitu dari Author ke Article, dan sebaliknya. Di dalam database, asosiasi ini diimplementasikan dengan foreign key author_id yang ada dalam tabel T_ARTICLE. Dengan demikian, satu hubungan foreig  key dipetakan dua kali oleh JPA.

Konsekuensinya, dua mapping ini akan menghasilkan dua kali eksekusi SQL. Coba perhatikan skenario berikut.

Author endy = entityManager.createQuery(“select a from
Article a where a.id=:id”)
.setParameter(“id”,100).getSingleResult();
Article tutorial = new Article();
tutorial.setTitle(“Belajar JPA”);
tutorial.setAuthor(endy);
endy.getArticles().add(tutorial);
entityManager.persist(tutorial);
entityManager.persist(endy);


Rangkaian kode program di atas akan menghasilkan eksekusi SQL
sebagai berikut:

select * from T_AUTHOR where id=100;
insert into T_ARTICLE (title, author_id)
values ('Belajar JPA', 100);
update T_ARTICLE set author_id=100
where title='Belajar JPA';


Kita lihat di atas, JPA menjalankan SQL update, padahal sebelumnya author_id sudah diset menurut id authornya. Ini disebabkan karena foreign key author_id dipetakan dua kali.

JPA tidak bisa mendeteksi ini secara otomatis, karena tidak memungkinkan secara logika. Jadi, kita harus memberi tahu JPA bahwa author_id sudah dipetakan oleh class Article.

Setelah kita menggunakan keyword mappedBy, kode program di atas akan menghasilkan eksekusi SQLsebagai berikut.

select * from T_AUTHOR where id=100;
insert into T_ARTICLE (title, author_id)
values ('Belajar JPA', 100);


Keyword mappedBy menyebabkan JPA mengabaikan pemetaan di sisi Author. Yang dipantau oleh JPA hanyalah sisi Article.

Dengan demikian, bila kita save seperti ini:

Article tutorial = new Article();
endy.getArticles().add(tutorial);
entityManager.persist(endy);


Object tutorial tidak akan tersimpan ke database. Untuk memperbaikinya, kita lakukan seperti ini:

Article tutorial = new Article();
tutorial.setAuthor(endy)
entityManager.persist(tutorial);


Bila kita sudah mengaktifkan transitive persistence, maka kita bisa melakukannya seperti ini:

Article tutorial = new Article();
tutorial.setAuthor(endy)
entityManager.persist(endy);


Bagaimana jika kita ingin membalik sisi inverse? Misalnya, kita ingin agar JPA mengabaikan sisi Article.

Keyword mappedBy atau inverse tidak dapat digunakan di sisi @ManyToOne. Untuk melakukan hal ini, kita gunakan keyword updateble dan insertable. Di sisi Article, kita tambahkan updatable dan insertable.

public class Article {
@ManyToOne
@JoinColumn(
name = "author_id", nullable = false
updatable = false, insertable = false
)
private Author author;
}


Di sisi Author, kita hilangkan saja mappedBy.

public class Author {
@OneToMany
@OrderBy("publishDate")
private List<Article> articles
= new ArrayList<Article>();
}

2 komentar:

  1. JPA mampu membuat pemetaan satu arah dari Author ke Article, bahkan tanpa adanya property
    author di sisi Article. Pemetaan satu arah ini
    tetap dilakukan dengan adanya foreign key author_id di T_ARTICLE Tapi konsekuensinya, JPA tidak tahu apakah foreign key author_id tersebut sudah dipetakan atau belum.Ini sebabnya mengapa kita harus menggunakan keyword inverse.

    BalasHapus
  2. Inverse tidak berhubungan dengan cascade. Cascade hanya menyuruh JPA untuk memeriksa apakah entity
    lain yang terhubung sudah disave/update/delete atau belum

    BalasHapus