Hibernate4 关联关系的映射案例
一对多、多对多、一对一的建表原则
一对多
如:一个用户,生成多个订单,每一个订单只能属于一个用户。
建表原则:
在多的一方创建一个字段,作为外键,指向一的一方的主键.
多对多
如:一个学生可以选择多门课程,一个课程可以被多个学生选择。
建表原则:
创建第三张表,中间表至少有两个字段,分别作为外键指向多对多双方主键.
一对一(特殊,应用很少)
一个公司只能有一个注册地址,一个注册地址,只能被一个公司使用(否则将两个表建到一个表)
建表原则:
* 唯一外键方式:一对一的双方,假设一方是多的关系,需要在多的一方创建一个字段作为外键,指向一的一方的主键。但是在外键添加一个unique。
* 主键对应方式:一对一的双方,通过主键进行关联。
案例
1. 需求描述:
- 设计一个数据库表结构,可以存储满足记录班级学生选课的信息记录;
- 一个班级可以有多个学生,一个学生可以选择多门课程;
- 添加如下表数据,session.save方法只操作student对象即可完成相关联的其他表数据
学生姓名 | 所属班级 | 所选课程 |
---|---|---|
张三 | 三年二班 | 高等数学、物理 |
李四 | 三年二班 | 物理、英语 |
小丽 | 三年四班 | 高等数学、物理、英语 |
James | 三年四班 | 高等数学 |
David | 三年二班 | 高等数学、英语 |
2. 需求分析
设计三张表格,分别存储学生、课程、班级信息,其中:
学生表,主键sid,关联外键班级表-classid
课程表,主键cid,关联外键学生表-sid
班级表,主键classid。
学生表与课程表是多对多关系;
班级表与学生表是一对多关系;
班级表与课程表没有关系。
学生表与课程表的关联:生成一张中间表,分别使用学生表sid和课程表cid作为外键关联这两张表。
学生表与班级表的关联,在学生表中(一对多的多方)维护外键,添加外键关联到班级表的classid。
表结构及对象结构如下图所示。
3. 持久化对象类的定义
student.java
public class Student {
private Integer sid;
private String sname;
private Classnum sclass;
private Set<Course> courses = new HashSet<Course>();
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Classnum getSclass() {
return sclass;
}
public void setSclass(Classnum sclass) {
this.sclass = sclass;
}
public Set<Course> getCourses() {
return courses;
}
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
}
Course.java
public class Course {
private Integer cid;
private String cname;
private Set<Student> students = new HashSet<Student>();
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
}
classnum.java
public class Classnum {
private Integer classid;
private String classname;
private Set<Student> students = new HashSet<Student>();
public Integer getClassid() {
return classid;
}
public void setClassid(Integer classid) {
this.classid = classid;
}
public String getClassname() {
return classname;
}
public void setClassname(String classname) {
this.classname = classname;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
}
4. 映射配置文件
student.hbm.xml
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.lim.user.domain">
<class name="Student" table="student">
<!-- 配置唯一标识 -->
<id name="sid" column="sid">
<!-- 主键生成策略 -->
<!-- native: 本地策略,根据底层数据库不同,自动选择identity或sequence -->
<generator class="native" />
</id>
<!-- 配置属性映射 -->
<property name="sname" column="sname" type="java.lang.String" length="30" />
<!-- 配置关联关系 -->
<!-- 学生表和课程表是多对多的关系,生成的中间表名称是choose_course -->
<set name="courses" table="choose_course" cascade="save-update">
<!-- 当前表在中间表的外键列 -->
<key column="sid" />
<!-- class:另一方类的全路径. column:另一方在中间表中外键名称 -->
<many-to-many class="cn.lim.user.domain.Course" column="cid" />
</set>
<!-- 班级表和学生表是一对多的关系,使用多方学生表维护外键 -->
<!-- name:关联对象的java属性的名称.column:表中的外键名称.class:关联对象类的全路径-->
<many-to-one name="sclass" column="classid" class="cn.lim.user.domain.Classnum" cascade="save-update"/>
</class>
</hibernate-mapping>
course.hbm.xml
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.lim.user.domain">
<class name="Course" table="courses">
<!-- 配置唯一标识 -->
<id name="cid" column="cid">
<!-- 主键生成策略 -->
<!-- native: 本地策略,根据底层数据库不同,自动选择identity或sequence -->
<generator class="native" />
</id>
<!-- 配置属性映射 -->
<property name="cname" column="cname" type="java.lang.String" length="30" />
<!-- 配置关联关系 -->
<!-- 学生表和课程表是多对多的关系 -->
<!-- name是本java对象保存对方表对象的属性,生成的中间表名称是choose_course,inverse:课程是被动方,放弃外键的维护权以避免产生多余sql -->
<set name="students" table="choose_course" cascade="save-update" inverse="true">
<!-- 当前表在中间表的外键列 -->
<key column="cid" />
<!-- class:另一方类的全路径. column:另一方在中间表中外键名称 -->
<many-to-many class="cn.lim.user.domain.Student" column="sid" />
</set>
</class>
</hibernate-mapping>
classnum.hbm.xml
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.lim.user.domain">
<class name="Classnum" table="class">
<!-- 配置唯一标识 -->
<id name="classid" column="classid">
<!-- 主键生成策略 -->
<!-- native: 本地策略,根据底层数据库不同,自动选择identity或sequence -->
<generator class="native" />
</id>
<!-- 配置属性映射 -->
<property name="classname" column="classname" type="java.lang.String" length="30" />
<!-- 配置多表级联 -->
<!-- 班级表和学生表是一对多的关系 -->
<!-- name是本java对象保存对方表对象的属性 -->
<set name="students" cascade="save-update" inverse="true">
<!-- 多方的外键的名称(学生表中的classid) -->
<key column="classid" />
<!-- 另一方类的全路径 -->
<one-to-many class="cn.lim.user.domain.Student" />
</set>
</class>
</hibernate-mapping>
5. 几个属性选项
级联属性cascade的取值:
- none:不使用级联
- dave-update:保存或更新的时候级联
- delete:删除的时候级联
- all:除了孤儿删除以外的所有级联.
delete-orphan:孤儿删除(孤子删除).
* 仅限于一对多.只有一对多时候,才有父子存在.认为一的一方是父亲,多的一方是子方.
* 当一个客户与某个订单解除了关系.将外键置为null.订单没有了所属客户,相当于一个孩子没有了父亲.将这种记录就删除了。
- all-delete-orphan:包含了孤儿删除的所有的级联.
具体在哪里配置级联属性?
如,只想session.save(Object1)时,Object2就被自动关联保存,那么在Object1的级联配置下使用cascate属性。
外键弃权inverse的取值
- true:在哪一端配置,那么哪一端放弃了外键的维护权。
多对多必须有一方放弃外键的维护权,否则会报错。一般情况下,被动的一方去放弃,如学生表和课程表,学生选课,因此学生主动,课程被动。
一对多最好有一方放弃外键的维护权,否则会产生多余的sql语句,影响性能。一般情况下一的一方放弃维护权。
lazy属性和fetch属性的取值
在一的一方关联多的一方:在<set>标签下配置。
fetch属性控制sql语句的类型,其取值有:
* join:发送迫切左外连接的SQL查询关联对象。如果fetch=“join”,那么lazy被忽略。
* select :默认值,发送多条SQL查询关联对象。
* subselect:发送子查询查询关联对象。(需要使用Query接口测试)
lazy属性控制关联对象的检索是否采用延迟,其取值有:
* true:默认值,查询关联对象的时候使用延迟检索
* false:查询关联对象的时候不使用延迟检索
* extra:及其懒惰
在多的一方关联一的一方:在<many-to-one>标签下配置。
fetch属性控制sql语句的类型,其取值有:
* join:发送迫切左外连接的SQL查询关联对象。如果fetch=“join”,那么lazy被忽略。
* select :发送多条SQL查询关联对象。
lazy属性控制关联对象的检索是否采用延迟,其取值有:
* false:不延迟
* proxy:使用代理.检索订单额时候,是否马上检索客户,由Customer对象的映射文件中class标签上的lazy属性来决定。
* no-proxy:不使用代理