本章主要是描述几种经典映射关系,顺带比较Hibernate4.x和Hibernate5.x之间的区别。
一、建立测试工程目录
有关实体类之间的相互映射关系,Hibernate官方文档其实描述的非常详细,这里只提供几种常见映射。(推荐4.3.11版本的 hibernate-release-4.3.11.Final\documentation\manual)
二、编写映射关系
(1)one2one单表内嵌映射:
package model.family; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Husband { private int id; private String husbandName; private Wife wife; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getHusbandName() { return husbandName; } public void setHusbandName(String husbandName) { this.husbandName = husbandName; } // 两个实体对象共用一张数据表,提高查询速度 @Embedded public Wife getWife() { return wife; } public void setWife(Wife wife) { this.wife = wife; } }
package model.family; //不用添加任何注解,持久化过程通过主表完成 public class Wife { private String wifeName; public String getWifeName() { return wifeName; } public void setWifeName(String wifeName) { this.wifeName = wifeName; } }
(2)one2one外键映射:
package model.userinfo; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Transient; @Entity public class User { private int id; private String username; private String password; private String confirm; private Information info; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } /* * 延迟加载,级联操作。 * 删除开启了级联的一方,被级联的一方也会被删除 * 注意:如果session的操作是通过hibernate控制,延迟加载不会出问题。如果是通过手工开启实物,操作不当延迟加载可能抛出懒加载异常 */ @OneToOne(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL) public Information getInfo() { return info; } public void setInfo(Information info) { this.info = info; } // 本字段不参与持久化过程 @Transient public String getConfirm() { return confirm; } public void setConfirm(String confirm) { this.confirm = confirm; } }
package model.userinfo; import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Temporal; import javax.persistence.TemporalType; //注解也可以直接配置在字段上,但是不推荐。据说原因是可能破坏oop封装。但是我觉得有时这样配置可以让代码显得更加整洁,特别是在Spring中。 @Entity public class Information { @Id @GeneratedValue private int id; @OneToOne private User user; @Temporal(TemporalType.DATE) private Date resgisterDate; private String address; public int getId() { return id; } public void setId(int id) { this.id = id; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public Date getResgisterDate() { return resgisterDate; } public void setResgisterDate(Date resgisterDate) { this.resgisterDate = resgisterDate; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
(3)many2many多表映射:
场景描述:学校里有多个老师,每个老师教授多个学生,每个学生每一门课程会有一个得分。
package model.school; import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; @Entity public class Teacher { private int id; private String tchName; private Set<Student> students = new HashSet<Student>(); @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTchName() { return tchName; } public void setTchName(String tchName) { this.tchName = tchName; } //老师和学生的对应表由学生一方负责维护 @ManyToMany(mappedBy = "teachers") public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; } }
package model.school; 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; import javax.persistence.OneToMany; @Entity public class Student { private int id; private String stuName; private Set<Teacher> teachers = new HashSet<Teacher>(); private Set<Score> scores = new HashSet<Score>(); @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } /* * many2many必须使用中间表,配置中间表的表明和列名 */ @ManyToMany @JoinTable(name = "student_teacher", joinColumns = { @JoinColumn(name = "studentId") }, inverseJoinColumns = { @JoinColumn(name = "teacherId") }) public Set<Teacher> getTeachers() { return teachers; } public void setTeachers(Set<Teacher> teachers) { this.teachers = teachers; } // 学生同分数之间的关系同样交给多的一方负责维护 @OneToMany(mappedBy = "student") public Set<Score> getScores() { return scores; } public void setScores(Set<Score> scores) { this.scores = scores; } }
package model.school; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; @Entity public class Score { private int id; private int courseScore; private Teacher teacher; private Student student; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public int getCourseScore() { return courseScore; } public void setCourseScore(int courseScore) { this.courseScore = courseScore; } @ManyToOne public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } @ManyToOne public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } }
按照以上的映射关系生成数据表以后会注意到,其实老师和学生之间的关系表纯粹多余,分数表已经维护了双方的关系。重新优化他们之间的映射关系:
package model.school; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Teacher { private int id; private String tchName; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTchName() { return tchName; } public void setTchName(String tchName) { this.tchName = tchName; } }
package model.school; import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; @Entity public class Student { private int id; private String stuName; private Set<Score> scores = new HashSet<Score>(); @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } @OneToMany(mappedBy = "student") public Set<Score> getScores() { return scores; } public void setScores(Set<Score> scores) { this.scores = scores; } }
package model.school; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; @Entity public class Score { private int id; private int courseScore; private Teacher teacher; private Student student; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public int getCourseScore() { return courseScore; } public void setCourseScore(int courseScore) { this.courseScore = courseScore; } @ManyToOne public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } @ManyToOne public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } }
由此可见,即使是一个相对复杂的映射关系也可以通过优化得到一个相对简单的数据模型。
(4)many2one和one2many单表树形映射:
场景描述:地图,一个国家包含多个省份,每个省份又包含多个城市...
package model.tree; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name = "_tree") public class Tree { private int id; private String name; private Tree parent; private Set<Tree> children = new HashSet<Tree>(); @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } @Column(name = "t_name", unique = true) public String getName() { return name; } public void setName(String name) { this.name = name; } @ManyToOne public Tree getParent() { return parent; } public void setParent(Tree parent) { this.parent = parent; } //删除根节点,与它相关的所有子节点全部删除 @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) public Set<Tree> getChildren() { return children; } public void setChildren(Set<Tree> children) { this.children = children; } }
注意:以上4种映射关系在4.3.11版本中正常。但在5.0.6版本中id字段被系统强制指定为了@GeneratedValue(strategy=GenerationType.TABLE)的方式。我曾经尝试手工指定生成策略为auto或者identity均无效。如果是通过xml的方式配置是正常的,目前我还不清楚是什么原因导致的上述异常。这个问题造成了下面的映射关系目前只能在4.x版本中正常使用:
(5)one2one主键映射
package model.personaddr; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.PrimaryKeyJoinColumn; @Entity public class Person { private int id; private String name; private Address address; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } // 两张表通过主键关联 @OneToOne(optional = true) @PrimaryKeyJoinColumn public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
package model.personaddr; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToOne; @Entity public class Address { private int id; private String local; private Person person; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getLocal() { return local; } public void setLocal(String local) { this.local = local; } @OneToOne(mappedBy = "address") public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } }
ps:好在主键映射在实际使用中并不常见。
最后按照惯例,提供整个项目的完整目录结构和IDE版本信息