Memahami JPA, Bagian 2: Hubungan dengan cara JPA

Aplikasi Java Anda bergantung pada web hubungan data, yang bisa menjadi berantakan jika tidak ditangani dengan benar. Di paruh kedua pengenalan Java Persistence API, Aditi Das menunjukkan kepada Anda bagaimana JPA menggunakan anotasi untuk membuat antarmuka yang lebih transparan antara kode berorientasi objek dan data relasional. Hubungan data yang dihasilkan lebih mudah untuk dikelola dan lebih kompatibel dengan paradigma pemrograman berorientasi objek.

Data merupakan bagian integral dari aplikasi apa pun; yang sama pentingnya adalah hubungan antara berbagai bagian data. Database relasional mendukung sejumlah jenis hubungan yang berbeda antar tabel, semuanya dirancang untuk menegakkan integritas referensial.

Di paruh kedua Memahami JPA ini, Anda akan belajar cara menggunakan Java Persistence API dan anotasi Java 5 untuk menangani hubungan data dengan cara berorientasi objek. Artikel ini ditujukan bagi pembaca yang memahami konsep JPA dasar dan masalah yang terlibat dalam pemrograman basis data relasional secara umum, dan yang ingin lebih jauh menjelajahi dunia hubungan JPA yang berorientasi objek. Untuk pengantar JPA, lihat "Memahami JPA, Bagian 1: Paradigma berorientasi objek dari persistensi data."

Skenario kehidupan nyata

Bayangkan sebuah perusahaan bernama XYZ yang menawarkan lima produk langganan kepada pelanggannya: A, B, C, D, dan E. Pelanggan bebas memesan produk dalam kombinasi (dengan harga diskon) atau mereka dapat memesan produk satu per satu. Seorang pelanggan tidak perlu membayar apapun pada saat memesan; pada akhir bulan, jika pelanggan puas dengan produk, faktur dibuat dan dikirim ke pelanggan untuk ditagih. Model data untuk perusahaan ini ditunjukkan pada Gambar 1. Seorang pelanggan dapat memiliki nol atau lebih pesanan, dan setiap pesanan dapat dikaitkan dengan satu atau lebih produk. Untuk setiap pesanan, faktur dibuat untuk penagihan.

Sekarang XYZ ingin mensurvei pelanggannya untuk melihat seberapa puas mereka dengan produknya, dan karenanya perlu mencari tahu berapa banyak produk yang dimiliki setiap pelanggan. Untuk mencari cara bagaimana meningkatkan kualitas produknya, perusahaan juga ingin melakukan survei khusus terhadap pelanggan yang membatalkan langganannya dalam satu bulan pertama.

Secara tradisional, Anda dapat mengatasi masalah ini dengan membuat lapisan objek akses data (DAO) tempat Anda akan menulis gabungan kompleks antara tabel CUSTOMER, ORDERS, ORDER_DETAIL, ORDER_INVOICE, dan PRODUCT. Desain seperti itu akan terlihat bagus di permukaan, tetapi mungkin sulit untuk mempertahankan dan men-debug karena aplikasi semakin kompleks.

JPA menawarkan cara lain yang lebih elegan untuk mengatasi masalah ini. Solusi yang saya sajikan dalam artikel ini mengambil pendekatan berorientasi objek dan, berkat JPA, tidak melibatkan pembuatan kueri SQL apa pun. Penyedia ketekunan dibiarkan dengan tanggung jawab melakukan pekerjaan secara transparan kepada pengembang.

Sebelum melanjutkan, Anda harus mengunduh paket kode sampel dari bagian Sumber Daya di bawah ini. Ini termasuk kode contoh untuk hubungan satu-ke-satu, banyak-ke-satu, satu-ke-banyak, dan banyak-ke-banyak yang dijelaskan dalam artikel ini, dalam konteks aplikasi contoh.

Hubungan satu-ke-satu

Pertama, aplikasi contoh perlu menangani hubungan pesanan-faktur. Untuk setiap pesanan, akan ada faktur; dan, demikian pula, setiap faktur dikaitkan dengan pesanan. Kedua tabel ini terkait dengan pemetaan satu-ke-satu seperti yang ditunjukkan pada Gambar 2, digabungkan dengan bantuan kunci asing ORDER_ID. JPA memfasilitasi pemetaan satu-ke-satu dengan bantuan @OneToOneanotasi.

Aplikasi sampel akan mengambil data pesanan untuk ID faktur tertentu. The Invoiceentitas ditunjukkan pada Listing 1 memetakan semua bidang meja FAKTUR sebagai atribut dan memiliki Orderobjek bergabung dengan ORDER_ID kunci asing.

Daftar 1. Sebuah entitas sampel yang menggambarkan hubungan satu-ke-satu

@Entity(name = "ORDER_INVOICE") public class Invoice { @Id @Column(name = "INVOICE_ID", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private long invoiceId; @Column(name = "ORDER_ID") private long orderId; @Column(name = "AMOUNT_DUE", precision = 2) private double amountDue; @Column(name = "DATE_RAISED") private Date orderRaisedDt; @Column(name = "DATE_SETTLED") private Date orderSettledDt; @Column(name = "DATE_CANCELLED") private Date orderCancelledDt; @Version @Column(name = "LAST_UPDATED_TIME") private Date updatedTime; @OneToOne(optional=false) @JoinColumn(name = "ORDER_ID") private Order order; ... //getters and setters goes here }

The @OneToOnedan @JoinCloumnpenjelasan pada Listing 1 diselesaikan secara internal oleh penyedia ketekunan, seperti yang diilustrasikan pada Listing 2.

Kode 2. Query SQL menyelesaikan hubungan satu-ke-satu

SELECT t0.LAST_UPDATED_TIME, t0.AMOUNT_PAID, t0.ORDER_ID, t0.DATE_RAISED ,t1.ORDER_ID, t1.LAST_UPDATED_TIME, t1.CUST_ID, t1.OREDER_DESC, t1.ORDER_DATE, t1.TOTAL_PRICE FROM ORDER_INVOICE t0 INNER JOIN ORDERS t1 ON t0.ORDER_ID = t1.ORDER_ID WHERE t0.INVOICE_ID = ?

Query dalam Listing 2 menunjukkan gabungan antara tabel ORDERS dan INVOICE. Tetapi apa yang terjadi jika Anda membutuhkan hubungan gabungan luar? Anda dapat mengontrol jenis gabungan dengan sangat mudah dengan menyetel optionalatribut @OneToOneke salah satu trueatau falseuntuk menunjukkan apakah pengaitan bersifat opsional atau tidak. Nilai defaultnya adalah true, yang menandakan bahwa objek terkait mungkin atau mungkin tidak ada dan gabungan tersebut akan menjadi gabungan luar dalam kasus itu. Karena setiap pesanan pasti memiliki faktur dan sebaliknya, dalam hal ini optionalatribut telah disetel ke false.

Kode 3 menunjukkan cara mengambil pesanan untuk faktur tertentu yang Anda tulis.

Kode 3. Mengambil objek yang terlibat dalam hubungan satu-ke-satu

.... EntityManager em = entityManagerFactory.createEntityManager(); Invoice invoice = em.find(Invoice.class, 1); System.out.println("Order for invoice 1 : " + invoice.getOrder()); em.close(); entityManagerFactory.close(); ....

Tetapi apa yang terjadi jika Anda ingin mengambil faktur untuk pesanan tertentu?

Hubungan dua arah satu-ke-satu

Setiap hubungan memiliki dua sisi:

  • Sisi pemilik bertanggung jawab untuk menyebarkan pembaruan hubungan ke database. Biasanya ini adalah sisi dengan kunci asing.
  • Sisi terbalik memetakan ke sisi pemilik.

Dalam pemetaan satu-ke-satu di aplikasi contoh, Invoiceobjek adalah sisi yang memiliki. Kode 4 menunjukkan seperti apa sisi terbalik - the Order- itu.

Daftar 4. Entitas dalam sampel hubungan satu-ke-satu dua arah

@Entity(name = "ORDERS") public class Order { @Id @Column(name = "ORDER_ID", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private long orderId; @Column(name = "CUST_ID") private long custId; @Column(name = "TOTAL_PRICE", precision = 2) private double totPrice; @Column(name = "OREDER_DESC") private String orderDesc; @Column(name = "ORDER_DATE") private Date orderDt; @OneToOne(optional=false,cascade=CascadeType.ALL, mappedBy="order",targetEntity=Invoice.class) private Invoice invoice; @Version @Column(name = "LAST_UPDATED_TIME") private Date updatedTime; .... //setters and getters goes here }

Mendaftar 4 peta ke bidang ( order) yang memiliki hubungan dengan mappedBy="order". targetEntitymenentukan nama kelas pemilik. Atribut lain yang diperkenalkan di sini adalah cascade. Jika Anda menjalankan operasi penyisipan, pembaruan, atau penghapusan pada Orderentitas dan Anda ingin menyebarkan operasi yang sama ke objek turunan ( Invoice, dalam hal ini), gunakan opsi bertingkat; Anda mungkin hanya ingin menyebarkan operasi PERSIST, REFRESH, REMOVE, atau MERGE, atau menyebarkan semuanya.

Kode 5 menunjukkan bagaimana mengambil rincian faktur untuk sesuatu yang OrderAnda tulis.

Kode 5. Mengambil objek yang terlibat dalam hubungan satu-ke-satu dua arah

.... EntityManager em = entityManagerFactory.createEntityManager(); Order order = em.find(Order.class, 111); System.out.println("Invoice details for order 111 : " + order.getInvoice()); em.close(); entityManagerFactory.close(); ....

Hubungan banyak-ke-satu

Di bagian sebelumnya, Anda melihat cara berhasil mengambil detail faktur untuk pesanan tertentu. Sekarang Anda akan mengubah fokus Anda untuk melihat bagaimana mendapatkan detail pesanan untuk pelanggan tertentu, dan sebaliknya. Seorang pelanggan dapat memiliki nol atau lebih pesanan, sedangkan pesanan dipetakan ke satu pelanggan. Jadi, a Customermenikmati hubungan satu-ke-banyak dengan an Order, sedangkan an Ordermemiliki hubungan banyak-ke-satu dengan Customer. Ini diilustrasikan pada Gambar 3.

Di sini, Orderentitas adalah sisi pemilik, yang dipetakan Customeroleh kunci asing CUST_ID. Kode 6 menggambarkan bagaimana hubungan banyak-ke-satu dapat ditentukan dalam Orderentitas.

Daftar 6. Contoh entitas yang menggambarkan hubungan banyak-ke-satu dua arah

@Entity(name = "ORDERS") public class Order { @Id //signifies the primary key @Column(name = "ORDER_ID", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private long orderId; @Column(name = "CUST_ID") private long custId; @OneToOne(optional=false,cascade=CascadeType.ALL, mappedBy="order",targetEntity=Invoice.class) private Invoice invoice; @ManyToOne(optional=false) @JoinColumn(name="CUST_ID",referencedColumnName="CUST_ID") private Customer customer; ............... The other attributes and getters and setters goes here } 

Dalam Daftar 6, Orderentitas digabungkan dengan Customerentitas dengan bantuan kolom kunci asing CUST_ID. Di sini juga kode menentukan optional=false, karena setiap pesanan harus memiliki pelanggan yang terkait dengannya. The Orderentitas sekarang memiliki hubungan satu-ke-satu dengan Invoicedan hubungan banyak-ke-satu dengan Customer.

Kode 7 mengilustrasikan bagaimana mengambil detail pelanggan untuk suatu tertentu Order.

Kode 7. Mengambil objek yang terlibat dalam hubungan banyak-ke-satu

........ EntityManager em = entityManagerFactory.createEntityManager(); Order order = em.find(Order.class, 111); System.out.println("Customer details for order 111 : " + order.getCustomer()); em.close(); entityManagerFactory.close(); ........

Tetapi apa yang terjadi jika Anda ingin mengetahui berapa banyak pesanan yang telah dilakukan oleh seorang pelanggan?

Hubungan satu-ke-banyak

Fetching order details for a customer is pretty easy once the owning side has been designed. In the previous section, you saw that the Order entity was designed as the owning side, with a many-to-one relationship. The inverse of many-to-one is a one-to-many relationship. The Customer entity in Listing 8 encapsulates the one-to-many relationship by being mapped to the owning side attribute customer.

Listing 8. A sample entity illustrating a one-to-many relationship

@Entity(name = "CUSTOMER") public class Customer { @Id //signifies the primary key @Column(name = "CUST_ID", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private long custId; @Column(name = "FIRST_NAME", length = 50) private String firstName; @Column(name = "LAST_NAME", nullable = false,length = 50) private String lastName; @Column(name = "STREET") private String street; @OneToMany(mappedBy="customer",targetEntity=Order.class, fetch=FetchType.EAGER) private Collection orders; ........................... // The other attributes and getters and setters goes here }

The @OneToMany annotation in Listing 8 introduces a new attribute: fetch. The default fetch type for one-to-many relationship is LAZY. FetchType.LAZY is a hint to the JPA runtime, indicating that you want to defer loading of the field until you access it. This is called lazy loading. Lazy loading is completely transparent; data is loaded from the database in objects silently when you attempt to read the field for the first time. The other possible fetch type is FetchType.EAGER. Whenever you retrieve an entity from a query or from the EntityManager, you are guaranteed that all of its eager fields are populated with data store data. In order to override the default fetch type, EAGER fetching has been specified with fetch=FetchType.EAGER. The code in Listing 9 fetches the order details for a particular Customer.

Kode 9. Mengambil objek yang terlibat dalam hubungan satu-ke-banyak

........ EntityManager em = entityManagerFactory.createEntityManager(); Customer customer = em.find(Customer.class, 100); System.out.println("Order details for customer 100 : " + customer.getOrders()); em.close(); entityManagerFactory.close(); .........

Hubungan banyak ke banyak

Ada satu bagian terakhir dari pemetaan hubungan yang perlu dipertimbangkan. Pesanan dapat terdiri dari satu atau lebih produk, sedangkan produk dapat dikaitkan dengan nol atau lebih pesanan. Ini adalah hubungan banyak-ke-banyak, seperti yang diilustrasikan pada Gambar 4.