การคงอยู่ของ 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เป็นหรือBigIntegerBigDecimal

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 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.

ไม่ต้องกังวลกับการผสานรวมวิธีการเหล่านี้ทั้งหมดในครั้งเดียว คุณจะได้รู้จักพวกเขาโดยการทำงานโดยตรงกับสิ่งเหล่าEntityManagerนี้ซึ่งเราจะดำเนินการเพิ่มเติมในหัวข้อถัดไป