หน่วยงาน JPA && ความสัมพันธ์ DB
ขอให้เป็นวันที่ดีเพื่อนร่วมงาน!เอนทิตีคืออะไร?
เอนทิตีคือวัตถุจากชีวิตจริง (เช่น รถยนต์) ที่มีคุณสมบัติ (ประตูล้อเครื่องยนต์) เอนทิตี DB: ในกรณีนี้ เอนทิตีของเราถูกจัดเก็บไว้ใน DB ทุกอย่างเรียบง่าย ทำไมเราถึงใส่รถลงในฐานข้อมูลและอย่างไร - เราจะดูในภายหลังความสัมพันธ์ DB คืออะไร?
นานมาแล้ว ในอาณาจักรอันห่างไกล ฐานข้อมูลเชิงสัมพันธ์ได้ถูกสร้างขึ้น ในฐานข้อมูลนี้ข้อมูลจะถูกนำเสนอในรูปแบบตาราง แต่แม้แต่ลาจากเชร็คก็เข้าใจว่าจำเป็นต้องสร้างกลไกในการเชื่อมต่อโต๊ะเหล่านี้เข้าด้วยกัน เป็นผล ให้เกิด ความสัมพันธ์ DB 4 รายการ : หากคุณเห็นทั้งหมดนี้เป็นครั้งแรก ฉันขอเตือนคุณอีกครั้ง - มันจะแย่ลง: คิดเกี่ยวกับการไปเดินเล่น เราจะวิเคราะห์ความสัมพันธ์ทั้งหมดเหล่านี้โดยใช้ตัวอย่างและเข้าใจความแตกต่างระหว่างกันตัวอย่างสยองขวัญ
เราจะมีหนึ่งโปรเจ็กต์ที่จะมี 5 สาขา ได้แก่ master ซึ่งจะมีคำอธิบายของโปรเจ็กต์ และ 1 สาขาสำหรับแต่ละความสัมพันธ์ของ DB แต่ละสาขาจะมีสคริปต์ SQL สำหรับสร้างฐานข้อมูลและกรอกข้อมูลทดสอบ รวมถึงคลาสเอนทิตีที่มีการแมปคำอธิบายประกอบ จะมีไฟล์กำหนดค่า Hibernate สำหรับแต่ละสาขาด้วย ฉันจะใช้ ฐานข้อมูลแบบฝัง H2สำหรับโปรเจ็กต์ เพื่อไม่ให้ถูกรบกวนจากแต่ละแง่มุมของฐานข้อมูลคลาวด์หรือฐานข้อมูลภายนอก ตามลิงค์เพื่อติดตั้ง H2 DB บนเครื่องดูดฝุ่นของคุณ ผมจะอธิบายแต่ละขั้นตอนใน 1 สาขา ที่เหลือเป็นเพียงประเด็นสำคัญเท่านั้น ในตอนท้ายเราจะสรุป ไป. นี่คือลิงก์ไปยังสาขาหลักของโครงการของฉันความสัมพันธ์แบบหนึ่งต่อหนึ่ง
ลิงค์สาขาที่นี่ครับ-
เราจำเป็นต้องเชื่อมต่อ H2 DB กับโครงการของเรา ที่นี่เราต้องเน้นย้ำว่าเราจำเป็นต้องมี Ultimate IDEA เพื่อทำงานกับ DB และงานอื่นๆ ได้อย่างสะดวกสบาย หากคุณมีอยู่แล้ว ให้ไปที่การเชื่อมต่อ DB โดยตรง ไปที่แท็บฐานข้อมูลแล้วทำตามในภาพหน้าจอ:
ต่อไปเราจะไปที่การตั้งค่าฐานข้อมูล คุณสามารถป้อนข้อมูลและแม้แต่ DBMS ของคุณได้ ขอย้ำอีกครั้งว่าฉันใช้ H2 DB เพื่อความเรียบง่าย
ต่อไปเรามาตั้งค่าวงจรกัน ขั้นตอนนี้เป็นทางเลือก แต่แนะนำหากคุณมีหลายสคีมาในฐานข้อมูล
ใช้การตั้งค่าและในที่สุดเราก็จะได้สิ่งนี้:
-
เราสร้างฐานข้อมูลและกำหนดค่าการเข้าถึงจาก IDEA ตอนนี้คุณต้องสร้างตารางในนั้นและกรอกข้อมูลบางส่วน ตัวอย่างเช่น ฉันจะใช้สองเอนทิตี: ผู้แต่งและหนังสือ หนังสืออาจมีผู้แต่ง อาจมีผู้แต่งหลายคน หรือไม่มีเลยก็ได้ ในตัวอย่างนี้ เราจะสร้างการเชื่อมต่อทุกประเภท แต่ ณ จุดนี้ - ความสัมพันธ์แบบหนึ่งต่อหนึ่ง มาสร้างสคริปต์ที่เกี่ยวข้องซึ่งสร้าง DB Tables :
DROP TABLE IF EXISTS PUBLIC.BOOK; CREATE TABLE PUBLIC.BOOK ( ID INTEGER NOT NULL AUTO_INCREMENT, NAME VARCHAR(255) NOT NULL, PRINT_YEAR INTEGER(4) NOT NULL, CONSTRAINT BOOK_PRIMARY_KEY PRIMARY KEY (ID) ); DROP TABLE IF EXISTS PUBLIC.AUTHOR; CREATE TABLE PUBLIC.AUTHOR ( ID INTEGER NOT NULL AUTO_INCREMENT, FIRST_NAME VARCHAR(255) NOT NULL, SECOND_NAME VARCHAR(255) NOT NULL, BOOK_ID INTEGER NOT NULL UNIQUE, CONSTRAINT AUTHOR_PRIMARY_KEY PRIMARY KEY (ID), CONSTRAINT BOOK_FOREIGN_KEY FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID) );
และมาดำเนินการกัน:
ผลลัพธ์การดำเนินการในคอนโซล:
ผลลัพธ์ในฐานข้อมูล:
-
ลองดูแผนภาพของตารางของเรา หากต้องการทำสิ่งนี้ ให้ใช้ RMB บนฐานข้อมูลของเรา:
ผลลัพธ์:
ใน แผนภาพ UMLเราจะเห็นคีย์หลักและคีย์นอกทั้งหมด เรายังเห็นการเชื่อมต่อระหว่างตารางของเราด้วย
-
มาเขียนสคริปต์ที่เติม DB ของเราด้วยข้อมูลทดสอบ:
INSERT INTO PUBLIC.BOOK (NAME, PRINT_YEAR) VALUES ('First book', 2010), ('Second book', 2011), ('Third book', 2012); INSERT INTO PUBLIC.AUTHOR (FIRST_NAME, SECOND_NAME, BOOK_ID) VALUES ('Pablo', 'Lambado', 1), ('Pazo', 'Zopa', 2), ('Lika', 'Vika', 3);
ฉันหมายถึงจะเกิดอะไรขึ้น? จำเป็นต้องมีความสัมพันธ์แบบหนึ่งต่อหนึ่งเมื่อเอนทิตีของตารางหนึ่งเกี่ยวข้องกับเอนทิตีหนึ่งของอีกเอนทิตี (หรือไม่เกี่ยวข้องเลยหาก NOT NULL ถูกลบออกจาก BOOK_ID) ในตัวอย่างของเรา หนังสือเล่มหนึ่งต้องมีผู้แต่งหนึ่งคน ไม่มีทางอื่น.
-
ตอนนี้สิ่งที่น่าสนใจที่สุดคือวิธีเชื่อมต่อคลาส Java กับเอนทิตี DB ได้อย่างไร ง่ายมาก. มาสร้างหนังสือและผู้แต่งสองคลาสกัน จากตัวอย่าง ฉันจะวิเคราะห์คลาส 1 และสาขาการสื่อสารที่สำคัญ ลองใช้คลาส Authorเป็นตัวอย่าง:
@Data @Entity @DynamicInsert @DynamicUpdate @Table(name = "AUTHOR") public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ID", nullable = false) private Long id; @Column(name = "FIRST_NAME", nullable = false) private String firstName; @Column(name = "SECOND_NAME", nullable = false) private String secondName; @OneToOne @JoinColumn(name = "BOOK_ID", unique = true, nullable = false) private Book book; }
- ฟิลด์ทั้งหมดในคลาสจะทำซ้ำแอตทริบิวต์ของเอนทิตี DB
- @Data (จากLombok ) บอกว่าสำหรับแต่ละฟิลด์ getter และ setter จะถูกสร้างขึ้น เท่ากับ hashcode จะถูกแทนที่ และเมธอด toString จะถูกสร้างขึ้น
- @Entityบอกว่าคลาสที่กำหนดนั้นเป็นเอนทิตีและเชื่อมโยงกับเอนทิตี DB
- @DynamicInsertและ@DynamicUpdateบอกว่าการแทรกและการอัพเดตแบบไดนามิกจะดำเนินการในฐานข้อมูล การตั้งค่าเหล่านี้เป็นการตั้งค่าไฮเบอร์เนตในระดับลึกซึ่งจะเป็นประโยชน์กับคุณเพื่อให้คุณมีชุดงานที่ถูกต้อง
- @Table (name = "AUTHOR") ผูกคลาส Book กับตาราง DB AUTHOR
- @Idบอกว่าฟิลด์นี้เป็นคีย์หลัก
- @GeneratedValue (กลยุทธ์ = GenerationType.IDENTITY) – กลยุทธ์การสร้างคีย์หลัก
- @Column (name = "ID", nullable = false) เชื่อมโยงฟิลด์กับแอตทริบิวต์ DB และยังบอกด้วยว่าฟิลด์ DB ที่กำหนดไม่สามารถเป็นค่าว่างได้ นอกจากนี้ยังมีประโยชน์เมื่อสร้างตารางจากเอนทิตีอีกด้วย กระบวนการย้อนกลับไปยังวิธีที่เราสร้างโครงการของเรา ซึ่งจำเป็นในฐานข้อมูลทดสอบสำหรับการทดสอบหน่วย
- @OneToOneบอกว่าฟิลด์ที่กำหนดเป็นฟิลด์ความสัมพันธ์แบบหนึ่งต่อหนึ่ง
- @JoinColumn (name = "BOOK_ID", Unique = true, nullable = false) - คอลัมน์ BOOK_ID จะถูกสร้างขึ้น ซึ่งไม่ซ้ำกันและไม่ใช่ null
-
ตอนนี้เรามากำหนดค่า Hibernate กันดีกว่า เมื่อต้องการทำเช่นนี้ ให้สร้าง ไฟล์ hibernate.cfg.xml :
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property> <property name="hibernate.connection.driver_class">org.h2.Driver</property> <property name="hibernate.connection.url">jdbc:h2:~/db/onetoone</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password"/> <property name="hibernate.hbm2ddl.auto">update</property> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> <property name="hibernate.use_sql_comments">true</property> <property name="hibernate.generate_statistics">true</property> <property name="hibernate.jdbc.batch_size">50</property> <property name="hibernate.jdbc.fetch_size">50</property> <property name="hibernate.order_inserts">true</property> <property name="hibernate.order_updates">true</property> <property name="hibernate.jdbc.batch_versioned_data">true</property> <mapping class="com.qthegamep.forjavarushpublication2.entity.Book"/> <mapping class="com.qthegamep.forjavarushpublication2.entity.Author"/> </session-factory> </hibernate-configuration>
- hibernate.dialect คือภาษาถิ่นของ DBMS ที่เราเลือก
- hibernate.connection.driver_class - คลาสไดรเวอร์ของ DB ของเรา
- hibernate.connection.url - utl ของฐานข้อมูลของเรา คุณสามารถรับได้จากจุดแรกที่เรากำหนดค่าฐานข้อมูล
- hibernate.connection.username - ชื่อผู้ใช้ DB
- hibernate.connection.password — รหัสผ่านผู้ใช้ DB
- hibernate.hbm2ddl.auto - การตั้งค่าการสร้างตาราง หากอัปเดต ระบบจะไม่สร้างหากสร้างไว้แล้ว แต่จะอัปเดตเฉพาะข้อมูลเท่านั้น
- hibernate.show_sql - จะแสดงคำสั่ง DB หรือไม่
- hibernate.format_sql - ไม่ว่าจะจัดรูปแบบแบบสอบถามฐานข้อมูลหรือไม่ ถ้าไม่เช่นนั้นทั้งหมดก็จะอยู่ในบรรทัดเดียว ฉันแนะนำให้เปิดเครื่อง
- hibernate.use_sql_comments - แบบสอบถามฐานข้อมูลความคิดเห็น หากนี่คือส่วนแทรก ความคิดเห็นจะถูกเขียนไว้เหนือคำขอว่าคำขอนั้นเป็นประเภทส่วนแทรก
- hibernate.generate_statistics - สร้างบันทึก ฉันแนะนำและแนะนำให้ตั้งค่าการบันทึกให้สูงสุด การอ่านบันทึกจะเพิ่มโอกาสในการทำงานอย่างถูกต้องกับ ORM
- hibernate.jdbc.batch_size — ขนาดแบตช์สูงสุด
- hibernate.jdbc.fetch_size — ขนาดการดึงข้อมูลสูงสุด
- hibernate.order_inserts - อนุญาตให้แทรกแบบไดนามิก
- hibernate.order_updates - อนุญาตการอัปเดตแบบไดนามิก
- hibernate.jdbc.batch_versioned_data - อนุญาตการแบตช์ ดู DBMS ของคุณ: ไม่ใช่ทุกคนที่รองรับสิ่งนี้
- คลาสการทำแผนที่ - คลาสที่เป็นเอนทิตีของเรา ทุกอย่างจะต้องมีการระบุไว้
-
ตอนนี้สาระสำคัญของเราจะต้องถูกกำหนด เราสามารถตรวจสอบสิ่งนี้ได้ในแท็บการคงอยู่:
ผลลัพธ์:
-
เรายังจำเป็นต้องกำหนดค่าข้อมูลการมอบหมาย:
ผลลัพธ์: เราได้ทำแผนที่แบบหนึ่งต่อหนึ่งแล้ว เนื้อหานี้มีวัตถุประสงค์เพื่อให้ข้อมูลเท่านั้น รายละเอียดอยู่ในข้อมูลอ้างอิง
ความสัมพันธ์แบบหนึ่งต่อหลาย
ลิงค์สาขาที่นี่ครับ ฉันจะไม่โพสต์โค้ดในบทความอีกต่อไปเนื่องจากมันยาวเกินไปแล้ว เราดูโค้ดทั้งหมดบน GitHub-
จากการรันสคริปต์การเริ่มต้นเราได้รับสิ่งต่อไปนี้:
รู้สึกแตกต่างกับตารางที่แล้วมั้ย?
-
แผนภาพ:
ความสัมพันธ์แบบหนึ่งต่อหลายคน - ผู้เขียนคนหนึ่งสามารถมีหนังสือได้หลายเล่ม เอนทิตีด้านซ้ายสอดคล้องกับเอนทิตีที่ถูกต้องหนึ่งรายการขึ้นไป
-
ความแตกต่างในการแมปจะอยู่ในคำอธิบายประกอบและฟิลด์:
ฟิลด์ปรากฏ ใน คลาส Author :
@OneToMany(fetch = FetchType.LAZY, mappedBy = "author") private Set<Book> books;
เป็นชุดอยู่แล้วเนื่องจากเราสามารถมีหนังสือได้หลายเล่ม @OneToManyกำลังพูดถึงประเภททัศนคติ FetchType.Lazy บอกว่าเราไม่จำเป็นต้องโหลดรายการหนังสือทั้งหมดหากไม่มีการระบุไว้ในคำขอ ควรกล่าวด้วยว่าไม่สามารถเพิ่มฟิลด์นี้ใน toString ได้ ไม่เช่นนั้นเราจะเริ่มสูบบุหรี่ StackOverflowError ลอมบอกที่รักของฉันดูแลเรื่องนี้:
@ToString(exclude = "books")
ใน ชั้น เรียนหนังสือเรามีข้อเสนอแนะแบบหลายต่อหนึ่ง:
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "AUTHOR_ID", nullable = false) private Author author;
ที่นี่เราสรุปได้ว่า One-to-Many เป็นภาพสะท้อนของ Many-to-One และในทางกลับกัน ควรเน้นว่า Hibernate ไม่รู้อะไรเกี่ยวกับการสื่อสารแบบสองทิศทาง สำหรับเขา นี่คือความเชื่อมโยงที่แตกต่างกันสองประการ: สายหนึ่งไปในทิศทางเดียว และอีกสายหนึ่งไปในทิศทางตรงกันข้าม
-
hibernate.cfg.xml ไม่มี อะไรเปลี่ยนแปลงไปมากนัก
-
วิริยะ:
ความสัมพันธ์แบบหลายต่อหนึ่ง
เนื่องจากหลายต่อหนึ่งเป็นภาพสะท้อนของหนึ่งต่อหลาย จึงมีความแตกต่างเล็กน้อย ลิงค์สาขาที่นี่ครับ-
จากการรันสคริปต์การเริ่มต้นเราได้ผลลัพธ์ดังต่อไปนี้:
-
แผนภาพ:
-
ความแตกต่างในการแมปจะอยู่ในคำอธิบายประกอบและฟิลด์:
ไม่มีชุด ใน คลาส Author อีกต่อไป เนื่องจากได้ย้ายไปยังคลาสBook แล้ว
-
วิริยะ:
ความสัมพันธ์แบบกลุ่มต่อกลุ่ม
เรามาดูความสัมพันธ์ที่น่าสนใจที่สุดกันดีกว่า ความสัมพันธ์นี้ตามกฎของความเหมาะสมและอนาจารทั้งหมดถูกสร้างขึ้นผ่านตารางเพิ่มเติม แต่ตารางนี้ไม่ใช่เอนทิตี น่าสนใจใช่ไหม? ลองมาดูเรื่องอึนี้กัน ลิงค์สาขาที่นี่ครับ-
ดูที่สคริปต์การเริ่มต้นตาราง HAS เพิ่มเติมจะปรากฏที่นี่ เราได้รับบางอย่างเช่น author-has-book
จากการรันสคริปต์เราจะได้ตารางดังต่อไปนี้:
-
แผนภาพ:
ในตัวอย่างของเรา ปรากฎว่าหนังสือสามารถมีผู้แต่งได้หลายคน และผู้แต่งสามารถมีหนังสือได้หลายเล่ม อาจทับซ้อนกัน
-
คลาสการทำแผนที่จะมีเซ็ตภายในคลาส แต่อย่างที่บอกไป ตาราง HAS ไม่ใช่เอนทิตี
คลาสผู้แต่ง :
@ManyToMany @JoinTable(name = "HAS", joinColumns = @JoinColumn(name = "AUTHOR_ID", referencedColumnName = "ID"), inverseJoinColumns = @JoinColumn(name = "BOOK_ID", referencedColumnName = "ID") ) private Set<Book> books;
@ManyToManyเป็นความสัมพันธ์ประเภทหนึ่ง
@JoinTable - นี่คือสิ่งที่จะเชื่อมโยงแอตทริบิวต์กับตาราง HAS เพิ่มเติม ในนั้นเราระบุแอตทริบิวต์สองรายการที่จะชี้ไปที่คีย์หลักของทั้งสองเอนทิตี
ชั้นเรียนหนังสือ :
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "books") private Set<Author> authors;
ที่นี่เราจะระบุ FetchType และฟิลด์ที่เราจะใช้ในการแมป
-
hibernate.cfg.xmlของเรายังคงไม่เปลี่ยนแปลงอีกครั้ง (ฉันไม่ได้คำนึงถึงข้อเท็จจริงที่ว่าเราสร้างฐานข้อมูลใหม่สำหรับแต่ละสาขา)
-
วิริยะ:
การซักถาม
ดังนั้นเราจึงได้ตรวจสอบประเภทของความสัมพันธ์ DB อย่างเผินๆ และค้นหาวิธีการนำไปใช้ในแบบจำลอง ORM เราเขียนโปรเจ็กต์ทดสอบที่สาธิตการเชื่อมต่อทั้งหมด และหาวิธีกำหนดค่า hibernate / jpa วุ้ย.ลิงค์ที่เป็นประโยชน์
- จริงๆแล้วโครงการ นั้นเอง
- สาขาตัวต่อตัว
- สาขาหนึ่งต่อหลาย
- สาขาหลายต่อหนึ่ง
- สาขาหลายต่อหลาย
- อ่านมัน
- และอ่านมัน
GO TO FULL VERSION