在我們做數據庫設計的時候,最煩的就是各種表之間的關聯關系了,關聯關系有:一對多,多對一,一對一,其中還有單向和雙向的區別.
1.雙向一對多及多對一映射:既然是雙向,那么就是同一類的了:雙向一對多關系中,必須存在一個關系維護端,在 JPA 規范中,要求 many 的一方作為關系的維護端(owner side), one 的一方作為被維護端(inverse side)。 可以在 one 方指定 @OneToMany 注釋並設置 mappedBy 屬性,以指定它是這一關聯中的被維護端,many 為維護端。 在 many 方指定 @ManyToOne 注釋,並使用 @JoinColumn 指定外鍵名稱,講的比較難理解,下面是代碼:
User.java:
@OrderBy("id")//指的是取出數據的時候,按照哪一個字段來進行排列,我們這里是按照user表的id字段排序,默認是ASC,完整用法:@OrderBy(value= "group_name ASC, name DESC") @OneToMany(targetEntity=Phone.class,mappedBy="user")//mappedBy說的就是上面說的被維護端,多的那一端就是維護端,注意mappedBy="xxx" xxx表示的是在另外一個類中關聯這個類的那個屬性名,在這個例子中是user(需要注意,這里必須是相同的名稱,如果不同的話,是會報錯的)
public Set<Phone> getPhones() { return phones; }
Phone.java @JoinColumn(name="user_id")//指定在本實體所映射的那個表中關聯的外鍵 @ManyToOne(targetEntity=User.class)// public User getUser() { return user; }
順帶講一下,單向多對一的怎么做,對於上面的代碼來說,我們只要將User中將被維護的注解給去掉,當然Set<Phone>也需要去掉。
下面再講一下單向一對多:
JPA單向一對多只需要在多的一端使用如下注解:
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY)//級聯保存、更新、刪除、刷新;延遲加載36
@JoinColumn(name="author_id")//在book表增加一個外鍵列來實現一對多的單向關聯 private Set<Book> books = new HashSet<Book>();
而在Book.java里頭不需要任何有關author的信息。
但是經過測試發現,在Book表中,author_id需要設置允許為空,因為JPA是先往兩張表插入新數據,然后再更新Book表中的author_id字段的。
所以不可以在數據庫中設置該外鍵為空。
推薦使用雙向關系
2.雙向一對一映射:基於外鍵的 1-1 關聯關系:在雙向的一對一關聯中,需要在關系被維護端(inverse side)中的 @OneToOne 注釋中指定 mappedBy,以指定是這一關聯中的被維護端。同時需要在關系維護端(owner side)建立外鍵列指向關系被維護端的主鍵列。
User.java:
@OneToOne(mappedBy="user")//因為需要指定其中一方為被維護段,我們設置user為被維護段,和雙向一對多相同,這個名稱必須和FirstLover定義User屬性的名稱一樣 public FirstLover getFirstLover() { return firstLover; } FirstLover.java: //使用OneToOne進行一對一的映射,name表示的是關聯關系表的外鍵,注意的是,這個外鍵是被維護段的主鍵,所以是unqie的 @JoinColumn(unique=true,name="user_id") @OneToOne public User getUser() { return user; }
在關聯關系中,延遲加載經常是我們考慮的問題,可以使用在我們的這個例子中,可以使用:
@JoinColumn(unique=true,name="user_id") @OneToOne(fetch=FetchType.LAZY) public User getUser() { return user; }
但是,有這樣的情況:user可以不關聯firstLover(因為外鍵是定義在FirstLover表中的),如果有 FirsetLover 關聯就設置為代理對象而延遲加載, 如果不存在關聯的 FirsetLover 就設置 null, Hibernate 在不讀取 FirsetLover 表的情況是無法判斷是否有關聯有 FirsetLover , 因此無法判斷設置 null 還是代理對象, 而統一設置為代理對象,也無法滿足不關聯的情況, 所以無法使用延遲加載,只 有顯式讀取 FirsetLover .
單向一對一的話只需要在上面增加注解就可以了
@OneToOne(fetch=FetchType.EAGER) protected User updateUser;//這樣就是將user的id作為我們現在這個注解了的表的外鍵了。這樣子就單向的關聯上了
最后一種就是多對多的關聯關系的情況,在雙向多對多關系中,我們必須指定一個關系維護端(owner side),可以通過 @ManyToMany 注釋中指定 mappedBy 屬性來標識其為關系維護端。為了測試,我定義了兩個類:
Student.java: package com.hotusm.commom.entity; import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; @Entity public class Student { private Integer id; private String name; private String sex; private String school; private Set<Teacher> teachers=new HashSet<>(); @GeneratedValue @Id public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getSchool() { return school; } public void setSchool(String school) { this.school = school; } @JoinTable(name="teach_stu",//指定中間表的表名,如果沒有指定表名,那么默認的名字是:tab1_tab2 joinColumns={@JoinColumn(name="teacher_id",//指定本類的主鍵在中間表的外鍵的字段名稱, referencedColumnName="ID")},//指定本類的主鍵是什么,在這個例子中,teach_stu的teacher的id就是本類的主鍵值 inverseJoinColumns={@JoinColumn(name="student_id",//和上面是一樣的含義,指明另外一個類主鍵在中間表的名稱 referencedColumnName="ID" //指定另外一個類的主鍵是什么,這里都是ID )} ) @ManyToMany//加上多對多的標示 public Set<Teacher> getTeachers() { return teachers; } public void setTeachers(Set<Teacher> teachers) { this.teachers = teachers; } @Override public String toString() { return this.name; } } Teacher: package com.hotusm.commom.entity; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; @Entity public class Teacher { private Integer id; private String name; private String subjectName; private Set<Student> students=new HashSet<>(); @GeneratedValue @Id public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Column(name="SUBJECT_NAME") public String getSubjectName() { return subjectName; } public void setSubjectName(String subjectName) { this.subjectName = subjectName; } @ManyToMany(mappedBy="teachers")//指定維護的一方,注意這里的名稱是另外一個類的屬性名: private Set<Teacher> teachers=new HashSet<>(); public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; } @Override public String toString() { return this.name; } }
基本上,關聯關系也就幾種了。