深入Hibernate映射
@GeneratedValue
如果希望 Hibernate 为逻辑主键自动生成主键值,则应该使用 @GeneratedValue 来修饰实体的标识属性:
@JoinColumn
@JoinColumn 注解专门用于定义外键列:
@Column
在默认情况下,被 @Entity 修饰的持久化类的所有属性都会被映射到底层数据表。为了指定某个属
性所映射的数据列的详细信息,如列名、列宇段长度等,可以在实体类中使用 @Column 修饰该属性:
@Entity
@Entity : 被该注解修饰的POJO 就是一个实体。使用该注解时可指定一个name 属性, name 属性指定该实体类的名称,但大部分时候无须指定该属性,因为系统默认以该类的类名作为实体
类的名称。
@Table
该注解指定持久化类所映射的表:
持续补充。。。。
Hibernate的关联映射
Hibernate 的关联映射将可以大大简化持久层数据的访问,关联关系大致有如下两个分类:
单向关系:只需单向访问关联端。例如,只能通过老师访问学生,或者只能通过学生访问老师。
双向关系:关联的两端可以互相访问。例如,老师和学生之间可以互相访问。
单向关联可分为:
单向 1 → 1
单向 1 → N
单向 N → 1
单向 N → N
双向关联又可分为:
双向 1 - 1
双向 1 - N
双向 N - N
双向关系里没有 N - 1 , 因为双向关系 1 - N 和 N - 1 是完全相同的。下面讲解几种常用关联的映射方法。
单向 N → 1 关联
单向 N → 1 关系,比如多个学生对应同一门课程,只需从学生这一端可以找到对应的地址实体,无须关心某个课程的学生。 即单向的 N → 1 关联只需从N 的一端可以访问1的一端。
为了让两个持久化类支持这种关联映射,程序应该在N 的一端的持久化类中增加一个属性,该属
性引用 1 的一端的关联实体。
如下为 N → 1 (@ManyToOne)支持的属性
注意,对于大部分的关联关系, Hibernate 都可以通过 反射 来确定关联实体的类型,因此可
以无须指定 targetEntity 属性,但因此会给系统增加负担,所以个人建议应习惯性指定 targetEntity 属性;一些特殊情况下,例如使用@ OneToMany 、@ManyToMany 修饰的1 - N、N - N 关联,如果用于表示关联实体的Set 集合不带泛型信息,那就必须指定targetEntity 属性。
可使用 @JoinColumn 来修饰代表关联实体的属性, @JoinColumn 用于映射底层的外键列。
Student.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "student_table")
public class Student {
@Id
@Column(name = "student_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String studentNo;
private String name;
@ManyToOne(targetEntity = Course.class, cascade = CascadeType.ALL)
@JoinColumn(name = "course_id")
private Course course;
public void setId(Integer id) {
this.id = id;
}
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
public void setName(String name) {
this.name = name;
}
public void setCourse(Course course) {
this.course = course;
}
public Integer getId() {
return id;
}
public String getStudentNo() {
return studentNo;
}
public String getName() {
return name;
}
public Course getCourse() {
return course;
}
public Student(){}
}
上面是 Student 类的代码,每个 Student 单向地持有一个 Course ,因此 Student 类里增加了一个Course 类型的属性,并使用 @ManyToOne 修饰代表关联实体的 Course 属性,而且使用@JoinColumn 映射了底层外键列。
除此之外,上面程序还为 @ManyToOne 注解指定了 cascade 属性,该属性用于指定级联操作策略,此处指定的级联策略是 CascadeType. ALL:这表明对 Student 实体的所有持久化操作都会级联到它关联的 Course 实体。
程序无须从 Course 访问 Student , 所以 Course 无须增加 Student 属性。如下面代码所示:
Course.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "course_table")
public class Course {
@Id
@Column(name = "course_no")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer courseNo;
private String courseName;
public Integer getCourseNo() {
return courseNo;
}
public void setCourseNo(Integer courseNo) {
this.courseNo = courseNo;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getCourseName() {
return courseName;
}
public Course(){ }
public Course(String courseName){
this.courseName = courseName;
}
}
执行下面的方法:
public void OneWayManyToOne(){
Session session = HibernateUtil.currentSession();
Transaction transaction = session.beginTransaction();
Student student = new Student();
Course course = new Course("Java开发框架");
student.setName("lyq");
student.setStudentNo("1901030086");
//设置student和course的关联关系
student.setCourse(course);
//持续化student对象
session.persist(student);
Course new_course = new Course("项目实战");
//修改持久化状态的student对象
student.setCourse(new_course);
transaction.commit();
HibernateUtil.closeSession();
}
结果如下:多个学生和一个课程的对应关系
上面的方法创建了三个持久化实体, 即一个 Student 对象、两个 Course 对象,该方法只保存了一次 Student 对象,从来不曾保存过 Course 对象。
程序在 Course course = new Course("Java开发框架"); 处创建了一个瞬态的 Course 对象,当程序执行到 session.persist(student); 处时,系统准备保存 Student 对象,系统将要向student_table 数据表中插入一条记录,由于在 Student 类里的@ManyToOne 指定了cascade=CascadeType.ALL 属性,这意味着系统将先自动级联插入主表记录,也就是先持久化 Course 对象,再持久化 Student 对象。也就是说, Hibernate 在 session.persist(student); 处先执行一条insert into Course ..语句,再执行一条insert into Student ..语句。如果 Student 类中缺少了cascade=CascadeType . ALL 属性,则程序运行到 session.persist(student); 时将抛出TransientPropertyValueException 异常。
程序在 Course new_course = new Course("项目实战"); 处再次创建了一个瞬态的 Course 对象,但当程序执行到 transaction.commit(); 处时,程序将瞬态的 Course 对象关联到持久化状态下的Student 对象。类似地, 系统也会自动持久化 Course 对象,再建立 Course 对象和 Student 对象之间的关联。也就是说, Hibernate 在 transaction.commit(); 处先执行一条insert into
Course ...语句插入记录,再执行update Student ...语句修改该 student_table表的记录的外键值。
双向 1 - N 关联
对于1-N 关联, Hibernate 推荐使用双向关联,而且不要让1的一端控制关联关系,而使用N 的一
端控制关联关系。
N 的一端需要增加 @ManyToOne 注解来修饰代表关联实体的属性,在使用@ManyToOne 注解的同时,使用@JoinColumn 来映射外键列。而 1 的一端则需要使用@OneToMany 注解来修饰代表关联实体的属性,在使用@OneToMany 注解时指定mappedBy 属性,则表明当前实体不能控制关联关系,放弃控制关联关系之后, Hibernate 就不允许使用@JoinColumn 或@JoinTable 修饰代表关联实体的属性了。
注意:对于指定了mappedBy 属性的@OneToMany 、@ManyToMany 、@OneToOne 注解,都
不能与@JoinColumn 或@JoinTable 同时修饰代表关联实体的属性。
使用@OneToMany 注解可以指定如的属性如下图所示:
Student.java
package domain;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "student_table")
public class Student {
@Id
@Column(name = "student_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String studentNo;
private String name;
@OneToMany(targetEntity = Course.class, mappedBy = "student")
private Set<Course> courses = new HashSet<>();
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
public Set<Course> getCourses() {
return courses;
}
public void setId(Integer id) { this.id = id; }
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public String getStudentNo() {
return studentNo;
}
public String getName() {
return name;
}
public Student(){}
}
Course.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "course_table")
public class Course {
@Id
@Column(name = "course_no")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer courseNo;
private String courseName;
@ManyToOne(targetEntity = Student.class)
@JoinColumn(name = "student_id", referencedColumnName = "student_id", nullable = true)
private Student student;
public void setStudent(Student student) {
this.student = student;
}
public Student getStudent() {
return student;
}
public Integer getCourseNo() {
return courseNo;
}
public void setCourseNo(Integer courseNo) {
this.courseNo = courseNo;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getCourseName() {
return courseName;
}
public Course(){ }
public Course(String courseName){
this.courseName = courseName;
}
}
运行如下方法:
public void TwoWayOneToMany(){
Session session = HibernateUtil.currentSession();
Transaction transaction = session.beginTransaction();
Student student = new Student();
student.setName("lyq");
student.setStudentNo("1901030086");
session.save(student);
Course course1 = new Course("java开发框架");
course1.setStudent(student);
session.persist(course1);
Course course2 = new Course("项目实战");
course2.setStudent(student);
session.persist(course2);
transaction.commit();
HibernateUtil.closeSession();
}
程序需要注意如下几点:
1.最好先持久化 Student 对象(或该 Student 对象本身己处于持久化状态) 。因为程序希望持久化
Course 对象时, Hibernate 可为 Course 的外键属性分配值一一也就是说,向 course_table 数据表插入记录时, 该记录的外键列己指定了值这表明它参照的主表记录己存在,也就是Person
对象必须已被持久化。
2.先设置 Student 和 Course 的关联关系,再保存持久化 Course 对象。如果顺序反过来,程序持久化 Course 对象时, 该 Course 对象还没有关联实体,所以Hibernate 不可能为对应记录的外
键列指定值;等到设置关联关系时, Hibernate 只能再次使用 update 语句来修改关联关系。
3.不要通过 Student 对象来设置关联关系,因为己经在 Student 映射注解@OneToMany 中指定了
mappedBy 属性, 该属性表明 Student 对象不能控制关联关系。
运行结果如下:一个学生和对个课程的对应关系
双向 N - N 关联
双向 N - N 关联需要两端都使用Set 集合属性,两端都增加对集合属性的访问。双向 N - N 关联没
有太多选择,只能采用连接表来建立两个实体之间的关联关系。
双向 N - N 关联需要在两端分别使用@ManyToMany 修饰Set 集合属性,并在两端都使用 @JoinTable显式映射连接表。在两端映射连接表时,两端指定的连接表的表名应该相同,而且两端使用@JoinTable时指定的外键列的列名也是相互对应的。
如果程序希望某一端放弃控制关联关系,则可在这一端的@ManyToMany 注解中指定mappedBy 属性,这一端就无须、也不能使用@JoinTable 映射连接表了。
使用 @ManyToMany 时可指定的属性如下所示:
@JoinTable 专门用于映射底层 连接表 的信息,使用@JoinTable 时可指定的属性如下所示:
Student.java
package domain;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "student_table")
public class Student {
@Id
@Column(name = "student_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String studentNo;
private String name;
@ManyToMany(targetEntity = Course.class)
@JoinTable(name = "student_course",
joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_no", referencedColumnName = "course_no"))
private Set<Course> courses = new HashSet<>();
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
public Set<Course> getCourses() {
return courses;
}
public void setId(Integer id) { this.id = id; }
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public String getStudentNo() {
return studentNo;
}
public String getName() {
return name;
}
public Student(){}
}
代码映射了底层连接表的表名为 student_course ,并指定该连接表中包含两列: student_id 和course_no , 其中 student_id 参照 student_table 表的 student_id 主键列,course_no 参照
course_table 表的 course_no 主键列。由于此处管理的是 N - N 关联, 因此不能为任何@JoinColumn 注解增加 unique = true。
Course.java
package domain;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "course_table")
public class Course {
@Id
@Column(name = "course_no")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer courseNo;
private String courseName;
@ManyToMany(targetEntity = Student.class)
@JoinTable(name = "student_course",
joinColumns = @JoinColumn(name = "course_no", referencedColumnName = "course_no"),
inverseJoinColumns = @JoinColumn(name = "student_id", referencedColumnName = "student_id"))
private Set<Student> students= new HashSet<>();
public void setStudents(Set<Student> students) {
this.students = students;
}
public Set<Student> getStudents() {
return students;
}
public Integer getCourseNo() {
return courseNo;
}
public void setCourseNo(Integer courseNo) {
this.courseNo = courseNo;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getCourseName() {
return courseName;
}
public Course(){ }
public Course(String courseName){
this.courseName = courseName;
}
}
执行如下方法:
public void TwoWayManyToMany() {
Session session = HibernateUtil.currentSession();
Transaction transaction = session.beginTransaction();
Student student = new Student();
student.setName("lyq");
student.setStudentNo("1901030086");
session.save(student);
Course course1 = new Course("C语言");
course1.getStudents().add(student);
session.persist(course1);
Course course2 = new Course("数据库");
course2.getStudents().add(student);
session.persist(course2);
Student student2 = new Student();
student2.setName("msr");
student2.setStudentNo("00000001");
student2.getCourses().add(course2);
session.save(student2);
transaction.commit();
HibernateUtil.closeSession();
}
运行结果如下: