Ketekunan Java dengan JPA dan Hibernate, Bagian 1: Entitas dan hubungan

Java Persistence API (JPA) adalah spesifikasi Java yang menjembatani kesenjangan antara database relasional dan pemrograman berorientasi objek. Tutorial dua bagian ini memperkenalkan JPA dan menjelaskan bagaimana objek Java dimodelkan sebagai entitas JPA, bagaimana relasi entitas didefinisikan, dan bagaimana menggunakan JPA EntityManagerdengan pola Repositori dalam aplikasi Java Anda.

Perhatikan bahwa tutorial ini menggunakan Hibernate sebagai penyedia JPA. Sebagian besar konsep dapat diperluas ke kerangka kerja ketekunan Java lainnya.

Apa itu JPA?

Lihat "Apa itu JPA? Pengantar Java Persistence API" untuk mempelajari tentang evolusi JPA dan kerangka kerja terkait, termasuk EJB 3.0. dan JDBC.

Relasi objek di JPA

Database relasional telah ada sebagai sarana untuk menyimpan data program sejak tahun 1970-an. Sementara pengembang saat ini memiliki banyak alternatif untuk database relasional, jenis database ini dapat diskalakan dan dipahami dengan baik, dan masih banyak digunakan dalam pengembangan perangkat lunak berskala kecil dan besar.

Objek Java dalam konteks database relasional didefinisikan sebagai entitas . Entitas ditempatkan dalam tabel yang menempati kolom dan baris. Pemrogram menggunakan kunci asing dan tabel gabungan untuk menentukan hubungan antar entitas - yaitu hubungan satu-ke-satu, satu-ke-banyak, dan banyak-ke-banyak. Kami juga dapat menggunakan SQL (Structured Query Language) untuk mengambil dan berinteraksi dengan data dalam tabel individual dan di beberapa tabel, menggunakan batasan kunci asing. Model relasionalnya datar, tetapi developer dapat menulis kueri untuk mengambil data dan membuat objek dari data tersebut.

Ketidakcocokan impedansi hubungan objek

Anda mungkin sudah familiar dengan istilah ketidakcocokan impedansi hubungan objek , yang merujuk pada tantangan memetakan objek data ke database relasional. Ketidakcocokan ini terjadi karena desain berorientasi objek tidak terbatas pada hubungan satu-ke-satu, satu-ke-banyak, dan banyak-ke-banyak. Sebaliknya, dalam desain berorientasi objek, kita memikirkan objek, atribut dan perilakunya, dan bagaimana objek berhubungan. Dua contoh adalah enkapsulasi dan pewarisan:

  • Jika sebuah objek berisi objek lain, kita mendefinisikannya melalui enkapsulasi --a memiliki hubungan.
  • Jika sebuah objek adalah spesialisasi dari objek lain, kita mendefinisikannya melalui pewarisan --an hubungan is-a .

Asosiasi, agregasi, komposisi, abstraksi, generalisasi, realisasi, dan dependensi adalah semua konsep pemrograman berorientasi objek yang dapat menjadi tantangan untuk dipetakan ke model relasional.

ORM: Pemetaan relasional objek

Ketidaksesuaian antara desain berorientasi objek dan pemodelan basis data relasional telah menyebabkan kelas alat yang dikembangkan secara khusus untuk pemetaan relasional objek (ORM). Alat ORM seperti Hibernate, EclipseLink, dan iBatis menerjemahkan model database relasional, termasuk entitas dan hubungannya, ke dalam model berorientasi objek. Banyak dari alat ini ada sebelum spesifikasi JPA, tetapi tanpa standar fitur mereka bergantung pada vendor.

Pertama kali dirilis sebagai bagian dari EJB 3.0 pada tahun 2006, Java Persistence API (JPA) menawarkan cara standar untuk membuat anotasi objek sehingga dapat dipetakan dan disimpan dalam database relasional. Spesifikasi juga mendefinisikan konstruksi umum untuk berinteraksi dengan database. Memiliki standar ORM untuk Java membawa konsistensi pada implementasi vendor, sementara juga memungkinkan fleksibilitas dan add-on. Sebagai contoh, meskipun spesifikasi JPA asli berlaku untuk database relasional, beberapa implementasi vendor telah memperluas JPA untuk digunakan dengan database NoSQL.

Evolusi JPA

Rilis pertama JPA, versi 1.0, diterbitkan pada tahun 2006 melalui Java Community Process (JCP) sebagai Java Specification Request (JSR) 220. Versi 2.0 (JSR 317) diterbitkan pada tahun 2009, versi 2.1 (JSR 338) pada tahun 2013, dan versi 2.2 (rilis pemeliharaan JSR 338) diterbitkan pada tahun 2017. JPA 2.2 telah dipilih untuk dimasukkan dan dikembangkan di Jakarta EE.

Memulai JPA

Java Persistence API adalah spesifikasi, bukan implementasi: API ini mendefinisikan abstraksi umum yang dapat Anda gunakan dalam kode untuk berinteraksi dengan produk ORM. Bagian ini mengulas beberapa bagian penting dari spesifikasi JPA.

Anda akan mempelajari cara:

  • Tentukan entitas, bidang, dan kunci utama dalam database.
  • Buat hubungan antar entitas dalam database.
  • Bekerja dengan EntityManagerdan metodenya.

Mendefinisikan entitas

Untuk menentukan entitas, Anda harus membuat kelas yang dianotasi dengan @Entityanotasi. The @Entitypenjelasan adalah penjelasan penanda , yang digunakan untuk menemukan entitas gigih. Misalnya, jika Anda ingin membuat entitas buku, Anda harus menambahkan catatan sebagai berikut:

 @Entity public class Book { ... } 

Secara default, entitas ini akan dipetakan ke Booktabel, seperti yang ditentukan oleh nama kelas yang diberikan. Jika Anda ingin memetakan entitas ini ke tabel lain (dan, secara opsional, skema tertentu) Anda dapat menggunakan @Tableanotasi untuk melakukannya. Inilah cara Anda memetakan Bookkelas ke tabel BUKU:

 @Entity @Table(name="BOOKS") public class Book { ... } 

Jika tabel BUKU berada dalam skema PUBLISHING, Anda dapat menambahkan skema ke @Tableanotasi:

 @Table(name="BOOKS", schema="PUBLISHING") 

Memetakan bidang ke kolom

Dengan entitas yang dipetakan ke tabel, tugas Anda selanjutnya adalah menentukan bidangnya. Bidang didefinisikan sebagai variabel anggota di kelas, dengan nama setiap bidang yang dipetakan ke nama kolom di tabel. Anda dapat mengganti pemetaan default ini dengan menggunakan @Columnanotasi, seperti yang ditunjukkan di sini:

 @Entity @Table(name="BOOKS") public class Book { private String name; @Column(name="ISBN_NUMBER") private String isbn; ... } 

Dalam contoh ini, kami telah menerima pemetaan default untuk nameatribut tetapi menetapkan pemetaan kustom untuk isbnatribut tersebut. The nameatribut akan dipetakan ke nama kolom, tapi isbnatribut akan dipetakan ke kolom ISBN_NUMBER.

The @Columnpenjelasan memungkinkan kita untuk mendefinisikan properti tambahan bidang / kolom, termasuk panjang, apakah itu nullable, apakah itu harus unik, presisi dan skala (jika itu nilai desimal), apakah itu insertable dan diupdate, dan lain sebagainya .

Menentukan kunci utama

Salah satu persyaratan untuk tabel database relasional adalah harus berisi kunci utama , atau kunci yang secara unik mengidentifikasi baris tertentu dalam database. Di JPA, kami menggunakan @Idanotasi untuk menetapkan bidang menjadi kunci utama tabel. Kunci utama harus berupa tipe primitif Java, pembungkus primitif, seperti Integeratau Long, a String, a Date, a BigInteger, atau a BigDecimal.

Dalam contoh ini, kami memetakan idatribut, yang merupakan Integer, ke kolom ID di tabel BUKU:

 @Entity @Table(name="BOOKS") public class Book { @Id private Integer id; private String name; @Column(name="ISBN_NUMBER") private String isbn; ... } 

@IdAnotasi juga dapat digabungkan dengan @Columnanotasi untuk menimpa pemetaan nama kolom dari kunci utama.

Hubungan antar entitas

Sekarang setelah Anda mengetahui cara mendefinisikan entitas, mari kita lihat cara membuat hubungan antar entitas. JPA mendefinisikan empat anotasi untuk mendefinisikan entitas:

  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany

Hubungan satu-ke-satu

The @OneToOnepenjelasan digunakan untuk mendefinisikan hubungan satu-ke-satu antara dua entitas. Misalnya, Anda mungkin memiliki Userentitas yang berisi nama pengguna, email, dan kata sandi, tetapi Anda mungkin ingin menyimpan informasi tambahan tentang pengguna (seperti usia, jenis kelamin, dan warna favorit) di UserProfileentitas terpisah . The @OneToOnepenjelasan memfasilitasi mogok data dan entitas cara ini.

The Userkelas bawah memiliki satu UserProfilecontoh. The UserProfilememetakan ke satu Usercontoh.

 @Entity public class User { @Id private Integer id; private String email; private String name; private String password; @OneToOne(mappedBy="user") private UserProfile profile; ... } 
 @Entity public class UserProfile { @Id private Integer id; private int age; private String gender; private String favoriteColor; @OneToOne private User user; ... } 

Penyedia JPA menggunakan UserProfile's userlapangan untuk memetakan UserProfileke User. Pemetaan ditentukan dalam mappedByatribut di @OneToOneanotasi.

Hubungan satu-ke-banyak dan banyak-ke-satu

The @OneToManydan @ManyToOnepenjelasan memfasilitasi kedua sisi hubungan yang sama. Perhatikan contoh di mana seorang Bookdapat memiliki hanya satu Author, tetapi seorang Authordapat memiliki banyak buku. The Bookentitas akan menentukan @ManyToOnehubungan dengan Authordan Authorentitas akan menentukan @OneToManyhubungan dengan Book.

 @Entity public class Book { @Id private Integer id; private String name; @ManyToOne @JoinColumn(name="AUTHOR_ID") private Author author; ... } 
 @Entity public class Author { @Id @GeneratedValue private Integer id; private String name; @OneToMany(mappedBy = "author") private List books = new ArrayList(); ... } 

Dalam hal ini, Authorkelas menyimpan daftar semua buku yang ditulis oleh penulis tersebut dan Bookkelas mempertahankan referensi ke penulis tunggalnya. Selain itu, @JoinColumnmenentukan nama kolom dalam Booktabel untuk menyimpan ID dari Author.

Hubungan banyak ke banyak

Finally, the @ManyToMany annotation facilitates a many-to-many relationship between entities. Here's a case where a Book entity has multiple Authors:

 @Entity public class Book { @Id private Integer id; private String name; @ManyToMany @JoinTable(name="BOOK_AUTHORS", [email protected](name="BOOK_ID"), [email protected](name="AUTHOR_ID")) private Set authors = new HashSet(); ... } 
 @Entity public class Author { @Id @GeneratedValue private Integer id; private String name; @ManyToMany(mappedBy = "author") private Set books = new HashSet(); ... } 

In this example, we create a new table, BOOK_AUTHORS, with two columns: BOOK_ID and AUTHOR_ID. Using the joinColumns and inverseJoinColumns attributes tells your JPA framework how to map these classes in a many-to-many relationship. The @ManyToMany annotation in the Author class references the field in the Book class that manages the relationship; namely the authors property.

That's a quick demo for a fairly complex topic. We'll dive further into the @JoinTable and @JoinColumn annotations in the next article.

Working with the EntityManager

EntityManager is the class that performs database interactions in JPA. It is initialized through a configuration file named persistence.xml. This file is found in the META-INF folder in your CLASSPATH, which is typically packaged in your JAR or WAR file. The persistence.xml file contains:

  • The named "persistence unit," which specifies the persistence framework you're using, such as Hibernate or EclipseLink.
  • A collection of properties specifying how to connect to your database, as well as any customizations in the persistence framework.
  • A list of entity classes in your project.

Let's look at an example.

Configuring the EntityManager

First, we create an EntityManager using the EntityManagerFactory retrieved from the Persistence class:

 EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("Books"); EntityManager entityManager = entityManagerFactory.createEntityManager(); 

In this case we've created an EntityManager that is connected to the "Books" persistence unit, which we've configured in the persistence.xml file.

The EntityManager class defines how our software will interact with the database through JPA entities. Here are some of the methods used by EntityManager:

  • find retrieves an entity by its primary key.
  • createQuery creates a Query instance that can be used to retrieve entities from the database.
  • createNamedQuery loads a Query that has been defined in a @NamedQuery annotation inside one of the persistence entities. Named queries provide a clean mechanism for centralizing JPA queries in the definition of the persistence class on which the query will execute.
  • getTransaction defines an EntityTransaction to use in your database interactions. Just like database transactions, you will typically begin the transaction, perform your operations, and then either commit or rollback your transaction. The getTransaction() method lets you access this behavior at the level of the EntityManager, rather than the database.
  • merge() adds an entity to the persistence context, so that when the transaction is committed, the entity will be persisted to the database. When using merge(), objects are not managed.
  • persist adds an entity to the persistence context, so that when the transaction is committed, the entity will be persisted to the database. When using persist(), objects are managed.
  • refresh refreshes the state of the current entity from the database.
  • flush synchronizes the state of the persistence context with the database.

Jangan khawatir tentang mengintegrasikan semua metode ini sekaligus. Anda akan mengenal mereka dengan bekerja secara langsung dengan EntityManager, yang akan kita lakukan lebih banyak di bagian selanjutnya.