關系映射開發(一):一對一映射
- 1.認識實體間關系映射
- 1.1 映射方向
- 1.2 ORM 映射類型
- 2.實現 “一對一” 映射
- 2.1 編寫實體
- 2.1.1 新建 Student 實體
- 2.1.2 新建 Card 實體
- 2.2 編寫 Repository 層
- 2.2.1 編寫 Student 實體的 Repository
- 2.2.2 編寫 Card 實體的 Repository
- 2.3 編寫 Service 層
- 2.3.1 編寫 Student 的 Service 層
- 2.3.2 編寫 Card 的 Service 層
- 2.4 編寫 Service 實現
- 2.4.1 編寫 Student 實體的 Service 實現
- 2.4.2 編寫 Card 實體的 Service 實現
- 2.5 編寫測試
- 3.單向 / 雙向 OneToOne
- 3.1 單向一對一關系的擁有端
- 3.2 單向一對一關系的接收端
- 3.3 雙向一對一關系的接收端
1.認識實體間關系映射
對象關系映射(object relational mapping
)是指通過將對象狀態映射到數據庫列,來開發和維護對象和關系數據庫之間的關系。它能夠輕松處理(執行)各種數據庫操作,如插入、更新、 刪除等。
1.1 映射方向
ORM 的映射方向是表與表的關聯(join
),可分為兩種。
- 單向關系:代表一個實體可以將屬性引用到另一個實體。即只能從 A 表向 B 表進行聯表查詢。
- 雙向關系:代表每個實體都有一個關系字段(屬性)引用了其他實體。
1.2 ORM 映射類型
- 一對一(
@OneToOne
):實體的每個實例與另一個實體的單個實例相關聯。 - 一對多(
@OneToMany
):一個實體的實例可以與另一個實體的多個實例相關聯。 - 多對一(
@ManyToOne
): —個實體的多個實例可以與另一個實體的單個實例相關聯。 - 多對多(
@ManyToMany
):—個實體的多個實例可能與另一個實體的多個實例有關。在這個映射中,任何一方都可以成為所有者方。
2.實現 “一對一” 映射
一對一 映射首先要確定實體間的關系,并考慮表結構,還要考慮實體關系的方向性。
若為 雙向關聯,則在保存實體關系的實體中要配合注解 @JoinColumn;在沒有保存實體關系的實體中,要用 mappedBy
屬性明確所關聯的實體。
下面通過實例來介紹如何構建一對一的關系映射。
2.1 編寫實體
2.1.1 新建 Student 實體
package com.example.demo.entity;import lombok.Data;
import javax.persistence.*;@Entity
@Data
@Table(name = "stdu")
public class Student {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;private String name;@Column(columnDefinition = "enum('male','female')")private String sex;/*** Description:* 建立集合,指定關系是一對一,并且申明它在cart類中的名稱* 關聯的表為card表,其主鍵是id* 指定外鍵名為card_id*/@OneToOne(cascade = CascadeType.ALL)@JoinColumn(name = "card_id")private Card card;
}
2.1.2 新建 Card 實體
package com.example.demo.entity;import lombok.Data;
import javax.persistence.*;@Entity
@Table(name = "card")
@Data
public class Card {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;private Integer num;
}
2.2 編寫 Repository 層
2.2.1 編寫 Student 實體的 Repository
package com.example.demo.repository;import com.example.demo.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;import javax.transaction.Transactional;public interface StudentRepository extends JpaRepository<Student, Long> {Student findById(long id);Student deleteById(long id);
}
2.2.2 編寫 Card 實體的 Repository
package com.example.demo.repository;import com.example.demo.entity.Card;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;public interface CardRepository extends JpaRepository<Card, Long>, JpaSpecificationExecutor<Card> {Card findById(long id);
}
2.3 編寫 Service 層
2.3.1 編寫 Student 的 Service 層
package com.example.demo.service;import com.example.demo.entity.Student;
import java.util.List;public interface StudentService {public List<Student> getStudentlist();public Student findStudentById(long id);
}
2.3.2 編寫 Card 的 Service 層
package com.example.demo.service;import com.example.demo.entity.Card;
import java.util.List;public interface CardService {public List<Card> getCardList();public Card findCardById(long id);
}
2.4 編寫 Service 實現
2.4.1 編寫 Student 實體的 Service 實現
package com.example.demo.service.impl;import com.example.demo.entity.Student;
import com.example.demo.repository.StudentRepository;
import com.example.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class StudentServiceImpl implements StudentService {@Autowiredprivate StudentRepository studentRepository;@Overridepublic List<Student> getStudentlist() {return studentRepository.findAll();}@Overridepublic Student findStudentById(long id) {return studentRepository.findById(id);}
}
2.4.2 編寫 Card 實體的 Service 實現
package com.example.demo.service.impl;import com.example.demo.entity.Card;
import com.example.demo.repository.CardRepository;
import com.example.demo.service.CardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class CardServiceImpl implements CardService {@Autowiredprivate CardRepository cardRepository;@Overridepublic List<Card> getCardList() {return cardRepository.findAll();}@Overridepublic Card findCardById(long id) {return cardRepository.findById(id);}
}
2.5 編寫測試
完成了上面的工作后,就可以測試它們的關聯關系了。
package com.example.demo.entity;import com.example.demo.repository.CardRepository;
import com.example.demo.repository.StudentRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import static org.junit.Assert.*;@SpringBootTest
@RunWith(SpringRunner.class)
public class oneToOneTest {@Autowiredprivate StudentRepository studentRepository;@Autowiredprivate CardRepository cardRepository;@Testpublic void testOneToOne() {Student student1 = new Student();student1.setName("趙大偉");student1.setSex("male");Student student2 = new Student();student2.setName("趙大寶");student2.setSex("male");Card card1 = new Card();card1.setNum(422802);student1.setCard(card1);studentRepository.save(student1);studentRepository.save(student2);Card card2 = new Card();card2.setNum(422803);cardRepository.save(card2);/*** Description: 獲取添加之后的id*/Long id = student1.getId();/*** Description: 刪除剛剛添加的student1*/studentRepository.deleteById(id);}
}
首先,注釋掉 studentRepository.deleteById(id);
,看一下數據庫。因為級聯的原因,在運行 studentRepository.save(student1);
時,就保存了 card1
,所以不需要像 cardRepository.save(card2);
一樣運行 cardRepository.save(card1);
。
運行代碼,在控制臺輸出如下測試結果:
可以看到,同時在兩個表 stdu
和 card
中添加了內容,然后刪除了剛添加的有關聯的 stdu
和 card
表中的值。如果沒有關聯,則不刪除。
🚀 在雙向關系中,有一方為關系的 發出端,另一方是關系的 反端,即
Inverse
端(接收端)。對于雙向的一對一關系映射,發出端和接收端都要使用注解 @OneToOne,同時定義一個接收端類型的字段屬性和 @OneToOne 注解中的mappedBy
屬性。這個在雙向關系的接收端是必需的。
3.單向 / 雙向 OneToOne
單向一對一 是關聯關系映射中最簡單的一種,簡單地說就是可以從關聯的一方去查詢另一方,卻不能反向查詢。我們用下面的例子來舉例說明,清單 1 中的 Person 實體類和清單 2 中的 Address 類就是這種單向的一對一關系,我們可以查詢一個 Person 的對應的 Address 的內容,但是我們卻不能由一個 Address 的值去查詢這個值對應的 Person。
3.1 單向一對一關系的擁有端
@Entity
public class Person implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private int age; @OneToOne private Address address; // Getters & Setters
}
3.2 單向一對一關系的接收端
@Entity
public class Address implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String street; private String city; private String country;// Gettes & Setters
}
從上面的 ER 圖可以看出,這種單向的一對一關系在數據庫中是以外鍵的形式被映射的。其中關系的 發出端 存儲一個指向關系的接收端的一個外鍵。在這個例子中 person
表中的 ADDRESS_ID 就是指向 address
表的一個外鍵,缺省情況下這個外鍵的字段名稱,是以它指向的表的名稱加下劃線 _
加 ID
組成的。當然我們也可以根據我們的喜好來修改這個字段,修改的辦法就是使用 @JoinColumn 這個注解。在這個例子中我們可以將這個注解注釋在 Person 類中的 Address 屬性上去。
3.3 雙向一對一關系的接收端
@Entity
public class Address implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String street; private String city; private String country; @OneToOne(mappedBy = "address") private Person person; // Gettes & Setters
}
雙向關系有一方為關系的擁有端,另一方是關系的接收端(反端),也就是 Inverse 端。在這個例子中 Person 擁有這個關系,而 Address 就是關系的 Inverse 端。Address 中我們定義了一個 person
屬性,在這個屬性上我們使用了 @OneToOne 注解并且定義了它的 mappedBy
屬性,這個在雙向關系的 Inverse 端是必需的。