การคงอยู่ของ Java กับ JPA และ Hibernate ตอนที่ 1: เอนทิตีและความสัมพันธ์
Java Persistence API (JPA) เป็นข้อกำหนดของ Java ที่เชื่อมช่องว่างระหว่างฐานข้อมูลเชิงสัมพันธ์และการเขียนโปรแกรมเชิงวัตถุ บทช่วยสอนสองส่วนนี้แนะนำ JPA และอธิบายถึงวิธีการจำลองวัตถุ Java เป็นเอนทิตี JPA วิธีกำหนดความสัมพันธ์ของเอนทิตีและวิธีใช้ JPA EntityManager
กับรูปแบบพื้นที่เก็บข้อมูลในแอปพลิเคชัน Java
โปรดทราบว่าบทช่วยสอนนี้ใช้ Hibernate เป็นผู้ให้บริการ JPA แนวคิดส่วนใหญ่สามารถขยายไปยังเฟรมเวิร์กการคงอยู่ของ Java อื่น ๆ ได้
JPA คืออะไร?
ดู "JPA คืออะไรรู้เบื้องต้นเกี่ยวกับ Java Persistence API" เพื่อเรียนรู้เกี่ยวกับวิวัฒนาการของ JPA และเฟรมเวิร์กที่เกี่ยวข้องรวมถึง EJB 3.0 และ JDBC
ความสัมพันธ์ของวัตถุใน JPA
ฐานข้อมูลเชิงสัมพันธ์มีอยู่เพื่อใช้ในการจัดเก็บข้อมูลโปรแกรมตั้งแต่ปี 1970 ในขณะที่นักพัฒนาในปัจจุบันมีทางเลือกมากมายสำหรับฐานข้อมูลเชิงสัมพันธ์ฐานข้อมูลประเภทนี้สามารถปรับขนาดได้และเข้าใจได้ดีและยังคงใช้กันอย่างแพร่หลายในการพัฒนาซอฟต์แวร์ขนาดเล็กและขนาดใหญ่
วัตถุ Java ในบริบทฐานข้อมูลเชิงสัมพันธ์จะถูกกำหนดเป็นหน่วยงาน เอนทิตีจะถูกวางไว้ในตารางที่พวกเขาครอบครองคอลัมน์และแถว โปรแกรมเมอร์ใช้คีย์ต่างประเทศและเข้าร่วมตารางเพื่อกำหนดความสัมพันธ์ระหว่างเอนทิตี ได้แก่ ความสัมพันธ์แบบหนึ่งต่อหนึ่งแบบหนึ่งต่อกลุ่มและความสัมพันธ์แบบกลุ่มต่อกลุ่ม นอกจากนี้เรายังสามารถใช้ SQL (Structured Query Language) เพื่อดึงข้อมูลและโต้ตอบกับข้อมูลในแต่ละตารางและในหลาย ๆ ตารางโดยใช้ข้อ จำกัด ของ Foreign Key โมเดลเชิงสัมพันธ์เป็นแบบเรียบ แต่นักพัฒนาสามารถเขียนคิวรีเพื่อดึงข้อมูลและสร้างอ็อบเจกต์จากข้อมูลนั้นได้
ความต้านทานของวัตถุสัมพันธ์ไม่ตรงกัน
คุณอาจคุ้นเคยกับคำว่าความต้านทานความสัมพันธ์ของวัตถุไม่ตรงกันซึ่งหมายถึงความท้าทายในการแมปวัตถุข้อมูลกับฐานข้อมูลเชิงสัมพันธ์ ความไม่ตรงกันนี้เกิดขึ้นเนื่องจากการออกแบบเชิงวัตถุไม่ได้ จำกัด อยู่ที่ความสัมพันธ์แบบหนึ่งต่อหนึ่งแบบหนึ่งต่อกลุ่มและแบบกลุ่มต่อกลุ่ม ในการออกแบบเชิงวัตถุเราจะนึกถึงวัตถุคุณลักษณะและพฤติกรรมของวัตถุและความสัมพันธ์ของวัตถุ สองตัวอย่างคือการห่อหุ้มและการสืบทอด:
- หากวัตถุมีวัตถุอื่นเรากำหนดสิ่งนี้ผ่านการห่อหุ้ม - มีความสัมพันธ์
- ถ้าวัตถุเป็นความเชี่ยวชาญของวัตถุอื่นที่เรากำหนดนี้ผ่านมรดก --an เป็นแบบความสัมพันธ์
การเชื่อมโยงการรวมองค์ประกอบการเป็นนามธรรมการวางนัยทั่วไปการทำให้เป็นจริงและการอ้างอิงล้วนเป็นแนวคิดการเขียนโปรแกรมเชิงวัตถุที่อาจเป็นเรื่องท้าทายในการแมปกับโมเดลเชิงสัมพันธ์
ORM: การทำแผนที่เชิงสัมพันธ์กับวัตถุ
ความไม่ตรงกันระหว่างการออกแบบเชิงวัตถุและการสร้างแบบจำลองฐานข้อมูลเชิงสัมพันธ์ได้นำไปสู่คลาสของเครื่องมือที่พัฒนาขึ้นโดยเฉพาะสำหรับการทำแผนที่เชิงสัมพันธ์เชิงวัตถุ (ORM) เครื่องมือ ORM เช่น Hibernate, EclipseLink และ iBatis จะแปลโมเดลฐานข้อมูลเชิงสัมพันธ์รวมทั้งเอนทิตีและความสัมพันธ์ให้เป็นโมเดลเชิงวัตถุ เครื่องมือเหล่านี้จำนวนมากมีอยู่ก่อนข้อกำหนดของ JPA แต่หากไม่มีมาตรฐานคุณสมบัติเหล่านี้ขึ้นอยู่กับผู้ขาย
เปิดตัวครั้งแรกโดยเป็นส่วนหนึ่งของ EJB 3.0 ในปี 2549 Java Persistence API (JPA) นำเสนอวิธีมาตรฐานในการใส่คำอธิบายประกอบอ็อบเจ็กต์เพื่อให้สามารถแมปและจัดเก็บในฐานข้อมูลเชิงสัมพันธ์ได้ ข้อกำหนดนี้ยังกำหนดโครงสร้างทั่วไปสำหรับการโต้ตอบกับฐานข้อมูล การมีมาตรฐาน ORM สำหรับ Java ทำให้เกิดความสอดคล้องกับการใช้งานของผู้ขายในขณะเดียวกันก็ช่วยให้มีความยืดหยุ่นและส่วนเสริม ตัวอย่างเช่นในขณะที่ข้อกำหนด JPA ดั้งเดิมสามารถใช้ได้กับฐานข้อมูลเชิงสัมพันธ์การใช้งานของผู้จำหน่ายบางรายได้ขยาย JPA เพื่อใช้กับฐานข้อมูล NoSQL
วิวัฒนาการของ JPA
รุ่นแรกของ JPA เวอร์ชัน 1.0 เผยแพร่ในปี 2549 ผ่าน Java Community Process (JCP) ในชื่อ Java Specification Request (JSR) 220 เวอร์ชัน 2.0 (JSR 317) เผยแพร่ในปี 2009 เวอร์ชัน 2.1 (JSR 338) ในปี 2013 และเวอร์ชัน 2.2 (รุ่นบำรุงรักษาของ JSR 338) ได้รับการเผยแพร่ในปี 2560 JPA 2.2 ได้รับการคัดเลือกให้รวมเข้าไว้ด้วยกันและพัฒนาอย่างต่อเนื่องใน Jakarta EE
เริ่มต้นกับ JPA
Java Persistence API เป็นข้อมูลจำเพาะไม่ใช่การนำไปใช้งาน: กำหนดนามธรรมทั่วไปที่คุณสามารถใช้ในโค้ดของคุณเพื่อโต้ตอบกับผลิตภัณฑ์ ORM ส่วนนี้จะทบทวนบางส่วนที่สำคัญของข้อกำหนด JPA
คุณจะได้เรียนรู้วิธีการ:
- กำหนดเอนทิตีฟิลด์และคีย์หลักในฐานข้อมูล
- สร้างความสัมพันธ์ระหว่างเอนทิตีในฐานข้อมูล
- ทำงานกับ
EntityManager
และวิธีการ
การกำหนดเอนทิตี
ในการกำหนดเอนทิตีคุณต้องสร้างคลาสที่มีคำอธิบาย@Entity
ประกอบ @Entity
คำอธิบายประกอบเป็นคำอธิบายประกอบเครื่องหมายที่ใช้ในการค้นพบหน่วยงานถาวร ตัวอย่างเช่นหากคุณต้องการสร้างเอนทิตีหนังสือคุณต้องใส่คำอธิบายประกอบดังนี้:
@Entity public class Book { ... }
โดยค่าเริ่มต้นเอนทิตีนี้จะถูกแมปกับBook
ตารางตามที่กำหนดโดยชื่อคลาสที่กำหนด หากคุณต้องการแมปเอนทิตีนี้กับตารางอื่น (และสคีมาเฉพาะ) คุณสามารถใช้@Table
คำอธิบายประกอบเพื่อทำสิ่งนั้นได้ นี่คือวิธีที่คุณจะจับคู่Book
ชั้นเรียนกับตารางหนังสือ:
@Entity @Table(name="BOOKS") public class Book { ... }
หากตาราง BOOKS อยู่ในสคีมา PUBLISHING คุณสามารถเพิ่มสคีมาใน@Table
คำอธิบายประกอบ:
@Table(name="BOOKS", schema="PUBLISHING")
การแม็พฟิลด์กับคอลัมน์
เมื่อแมปเอนทิตีกับตารางแล้วภารกิจต่อไปของคุณคือการกำหนดเขตข้อมูล เขตข้อมูลถูกกำหนดให้เป็นตัวแปรสมาชิกในคลาสโดยชื่อของแต่ละเขตข้อมูลจะถูกแมปกับชื่อคอลัมน์ในตาราง คุณสามารถแทนที่การแม็ปเริ่มต้นนี้ได้โดยใช้@Column
คำอธิบายประกอบดังที่แสดงไว้ที่นี่:
@Entity @Table(name="BOOKS") public class Book { private String name; @Column(name="ISBN_NUMBER") private String isbn; ... }
ในตัวอย่างนี้เรายอมรับการแมปเริ่มต้นสำหรับname
แอตทริบิวต์ แต่ระบุการแมปที่กำหนดเองสำหรับisbn
แอตทริบิวต์ name
แอตทริบิวต์จะถูกแมปกับชื่อคอลัมน์ แต่isbn
แอตทริบิวต์จะถูกแมปไปยังคอลัมน์ ISBN_NUMBER
@Column
บันทึกย่อช่วยให้เราสามารถกำหนดคุณสมบัติเพิ่มเติมของสนาม / คอลัมน์รวมทั้งความยาวไม่ว่าจะเป็น nullable ไม่ว่าจะต้องไม่ซ้ำกัน, ความแม่นยำและขนาด (ถ้ามันเป็นค่าทศนิยม) ไม่ว่าจะเป็น insertable และสามารถปรับปรุงและอื่น ๆ .
การระบุคีย์หลัก
ข้อกำหนดประการหนึ่งสำหรับตารางฐานข้อมูลเชิงสัมพันธ์คือต้องมีคีย์หลักหรือคีย์ที่ระบุแถวเฉพาะในฐานข้อมูลโดยไม่ซ้ำกัน ใน JPA เราใช้@Id
คำอธิบายประกอบเพื่อกำหนดเขตข้อมูลให้เป็นคีย์หลักของตาราง คีย์หลักจะต้องเป็น Java ชนิดดั้งเดิมเสื้อคลุมดั้งเดิมเช่นInteger
หรือLong
เป็นString
ที่Date
เป็นหรือBigInteger
BigDecimal
In this example, we map the id
attribute, which is an Integer
, to the ID column in the BOOKS table:
@Entity @Table(name="BOOKS") public class Book { @Id private Integer id; private String name; @Column(name="ISBN_NUMBER") private String isbn; ... }
It is also possible to combine the @Id
annotation with the @Column
annotation to overwrite the primary key's column-name mapping.
Relationships between entities
Now that you know how to define an entity, let's look at how to create relationships between entities. JPA defines four annotations for defining entities:
@OneToOne
@OneToMany
@ManyToOne
@ManyToMany
One-to-one relationships
The @OneToOne
annotation is used to define a one-to-one relationship between two entities. For example, you may have a User
entity that contains a user's name, email, and password, but you may want to maintain additional information about a user (such as age, gender, and favorite color) in a separate UserProfile
entity. The @OneToOne
annotation facilitates breaking down your data and entities this way.
The User
class below has a single UserProfile
instance. The UserProfile
maps to a single User
instance.
@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; ... }
The JPA provider uses UserProfile
's user
field to map UserProfile
to User
. The mapping is specified in the mappedBy
attribute in the @OneToOne
annotation.
One-to-many and many-to-one relationships
The @OneToMany
and @ManyToOne
annotations facilitate both sides of the same relationship. Consider an example where a Book
can have only one Author
, but an Author
may have many books. The Book
entity would define a @ManyToOne
relationship with Author
and the Author
entity would define a @OneToMany
relationship with 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(); ... }
In this case, the Author
class maintains a list of all of the books written by that author and the Book
class maintains a reference to its single author. Additionally, the @JoinColumn
specifies the name of the column in the Book
table to store the ID of the Author
.
Many-to-many relationships
Finally, the @ManyToMany
annotation facilitates a many-to-many relationship between entities. Here's a case where a Book
entity has multiple Author
s:
@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 aQuery
instance that can be used to retrieve entities from the database.createNamedQuery
loads aQuery
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 anEntityTransaction
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. ThegetTransaction()
method lets you access this behavior at the level of theEntityManager
, 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 usingmerge()
, 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 usingpersist()
, 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.
ไม่ต้องกังวลกับการผสานรวมวิธีการเหล่านี้ทั้งหมดในครั้งเดียว คุณจะได้รู้จักพวกเขาโดยการทำงานโดยตรงกับสิ่งเหล่าEntityManager
นี้ซึ่งเราจะดำเนินการเพิ่มเติมในหัวข้อถัดไป