Hibernate——关联关系
主键表和外键表的理解:
(1)以公共关键字作主键的表为主键表(父表,主表)
(2)以公共关键字作外键的表为外键表(从表,外表)
1.单向多对一关联:
我们采用学校和学生的关系来举例,多个学生能对应一个学校:此例中school是主表,student是外表,公共关键字是schoolID
首先先分别创建它们的持久化类:
School:
public class School {
private Integer schoolId;
private String schoolName;
public Integer getSchoolId() {
return schoolId;
}
public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
}
Student:
public class Student {
private Integer studentId;
private String studentName;
private School school;
public Integer getStudentId() {
return studentId;
}
public void setStudentId(Integer studentId) {
this.studentId = studentId;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
}
School.hbm.xml 映射文件:
<hibernate-mapping>
<class dynamic-update="true" name="com.chenx.hibernate.n21.School" table="tb_schools">
<id name="schoolId" type="java.lang.Integer">
<column name="SchoolID"></column>
<generator class="native"></generator>
</id>
<property name="schoolName" type="string">
<column name="SchoolName"></column>
</property>
</class>
</hibernate-mapping>
Studenr.hbm.xml 映射文件:
<hibernate-mapping>
<class name="com.chenx.hibernate.n21.Student" table="tb_students">
<id name="studentId" type="java.lang.Integer">
<column name="StudentID"></column>
<generator class="native"></generator>
</id>
<property name="studentName" type="string">
<column name="StudentName"></column>
</property>
<many-to-one name="school" class="com.chenx.hibernate.n21.School">
<column name="SchoolID"></column>
</many-to-one>
</class>
</hibernate-mapping>
首先看到学生的持久化类中包含一个School的字段,其映射当然不能简单的使用property来映射:
<many-to-one name="school" class="com.chenx.hibernate.n21.School">
<column name="SchoolID"></column>
</many-to-one>
many-to-one:
1.name : 表示自己持久化类中其属性名称
2.class :表示其中one 所对应的全类名
3.column:表示one那端在多那端外键的名称
测试代码save:
@Test
public void testSession(){
School school=new School();
school.setSchoolName("zwu");
Student student=new Student();
student.setStudentName("cxw");
student.setSchool(school);
Student student1=new Student();
student1.setStudentName("fyf");
student1.setSchool(school);
session.save(school);
session.save(student);
session.save(student1);
}
save的不同顺序也会影响:
1.如先插入school(one),再插入student(many) 只会有Insert语句
2.如先插入student(many),再插入school(one) 会有Insert语句和Update语句
因为在插入之前没有schoolid的信息,插入的是空,当把school插入后,会执行更新语句
测试get:
@Test
public void testSession(){
Student student=session.get(Student.class,1);
System.out.println(student.getSchool());
}
注意:如果只查询student,并没有使用school的信息,它是不会执行select语句获取关联的school信息的
这样就会存在发生懒加载异常——如果在获取之前就把session关了,实际其返回的是一个代理对象
测试update:
@Test
public void testSession(){
Student student=session.get(Student.class,1);
student.getSchool().setSchoolName("HBS");
}
测试delete:
@Test
public void testSession(){
School school=session.get(School.class,1);
session.delete(school);
}
在不设定级联关系的情况下,且one这端的对象被many对象所引用,则不能删除one这端
2.双向多对一,双向一对多
双向多对一和双向一对多关系是一样的
student的类和映射都不用改
School: 作为一对多的one:
public class School {
private Integer schoolId;
private String schoolName;
private Set<Student> students=new HashSet<>();//需要初始化集合,防止出现空指针异常
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
public Integer getSchoolId() {
return schoolId;
}
public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
}
School.hbm.xml:
<hibernate-mapping>
<class dynamic-update="true" name="com.chenx.hibernate.n21.School" table="tb_schools">
<id name="schoolId" type="java.lang.Integer">
<column name="SchoolID"></column>
<generator class="native"></generator>
</id>
<property name="schoolName" type="string">
<column name="SchoolName"></column>
</property>
<set name="students" table="tb_students">
<key column="SchoolID"></key>
<one-to-many class="com.chenx.hibernate.n21.Student"></one-to-many>
</set>
</class>
</hibernate-mapping>
set:
name:属性名
table:set数组在tb_schools实际是没有的,其数据存在tb_students中,所以table指定数据存在的表名
key:column:从表中存储主表的主键的外键名
class:many的全类名
save测试:
@Test
public void testSession(){
School school=new School();
school.setSchoolName("uuu");
Student student=new Student();
student.setStudentName("yyy");
student.setSchool(school);
Student student1=new Student();
student1.setStudentName("yyy1");
student1.setSchool(school);
school.getStudents().add(student);
school.getStudents().add(student1);
session.save(school);
session.save(student);
session.save(student1);
}
1.先存school ,再存student,3条insert,2条update
2.先存student,再存school,3条insert,4条update
其中3insert和2条update是可以理解的,但是多了二条,这二条是由于school和student相互存在关系,所以update维护关联关系,可以使用set中inverse属性,将其设为true,即可设为被动方,利于性能提升
get测试:
@Test
public void testSession(){
School school=session.get(School.class,3);
System.out.println(school.getSchoolName());
System.out.println(school.getStudents().getClass());
}
同样,也会发生延迟加载的异常,需要注意的是其返回的是class org.hibernate.collection.internal.PersistentSet,是Hibernate中的一个集合类型,具有延迟加载和存放代理对象的功能,因此我们在持久化类中创建时要用HashSet接口,如果使用set就会类型不匹配
set属性:
1.inverse 反转
一般设置为true,以由many一方维护关联关系
2.cascade 级联(不建议使用该属性,建议手工的方式)
设定cascade=“delete” 级联删除,可使删除school时,student中相关联的也删除
设定cascade=“delete-orphan” 删除孤儿,可使会话中解除关系的student被删除:
@Test
public void testSession(){
School school=session.get(School.class,6);
school.getStudents().clear();
}
设定cascade=“save-update” 级联保存 保存school的同时,保存student
3.order-by: 当Hibernate通过select语句时会利用此属性进行排序,其中还可加入SQL函数
其中属性填的是表中属性名,而不是持久化类的名字
基于外键的双向一对一关系:
一对一关系和多对一关系很类似,多对一举得例子是student和school的关系,多个学生可以对应一个学校,把学校的ID作为外键以关联,同样的如果把此外键设置成唯一岂不就把多对一关系中的多设置为了一,达到一对一的关系吗.
例子:一个部门对应一个部门经理,一个经理管理一个部门:
Department:
public class Department {
private Integer departmentID;
private String departmentName;
private Manager manager;
public Integer getDepartmentID() {
return departmentID;
}
public void setDepartmentID(Integer departmentID) {
this.departmentID = departmentID;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
public Manager getManager() {
return manager;
}
public void setManager(Manager manager) {
this.manager = manager;
}
}
Manager:
public class Manager {
private Integer managerID;
private Department department;
private String managerName;
public Integer getManagerID() {
return managerID;
}
public void setManagerID(Integer managerID) {
this.managerID = managerID;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String getManagerName() {
return managerName;
}
public void setManagerName(String managerName) {
this.managerName = managerName;
}
}
因为是一对一关系,所以每个持久化类中都有对方的一个属性
Department.hbm.xml:
<hibernate-mapping>
<class name="com.chenx.hibernate.one2one.Department" table="tb_departments">
<id name="departmentID" type="java.lang.Integer">
<column name="DepartmentID"></column>
<generator class="native"></generator>
</id>
<property name="departmentName" type="string">
<column name="DepartmentName"></column>
</property>
<!-- 使用many-one 来放外键在此表中-->
<many-to-one name="manager" class="com.chenx.hibernate.one2one.Manager" unique="true">
<column name="ManagerID"></column>
</many-to-one>
</class>
</hibernate-mapping>
Department中对应的ManagerID使用了多对一的关系,但又限制其为唯一的索引
Manager.hbm.xml:
<hibernate-mapping>
<class name="com.chenx.hibernate.one2one.Manager" table="tb_managers">
<id name="managerID" type="java.lang.Integer">
<column name="ManagerID"></column>
<generator class="native"></generator>
</id>
<property name="managerName" type="string">
<column name="ManagerName"></column>
</property>
<!--在对应的数据表中已经由外键了,当前持久化类使用one-to-one进行映射-->
<one-to-one name="department" class="com.chenx.hibernate.one2one.Department"></one-to-one>
</class>
</hibernate-mapping>
测试save,update,delete操作错误和问题和以上一样:
测试get:
@Test
public void testSession(){
Manager manager=session.get(Manager.class,1);
System.out.println(manager.getManagerName());
}
观察其sql语句发现其用了left outer join(其会自动使用left直接把department查询出来,因为manager中没有外键提供查询,所以会一次性查出所有的相关对象) 但是条件错误,managerID匹配的是department表中的主键(departementID),应该是匹配表中的外键managerID,因此我们需要在配置文件中明确指定其匹配属性:
<one-to-one name="department" class="com.chenx.hibernate.one2one.Department" property-ref="manager"></one-to-one>
不可以使用耳边都是外键的一对一,虽然每边都只能指对一个,但是指对的可以是不同的,这样就形成不了相互的一对一,换句话说就是四个人,每边二个A1,A2 ,B1,B2 A1喜欢B1,但是B1喜欢A2,A2喜欢B2,但是B2喜欢A1,这样也是一对一,但是不是相互的,所以不行。
基于主键的一对一关系:
同样采用以上的例子,持久化类变,主要是hbm.xml文件配置不同:(manager为主表,department为从表)
Department.hbm.xml:
<hibernate-mapping>
<class name="com.chenx.hibernate.one2one.Department" table="tb_departments">
<id name="departmentID" type="java.lang.Integer">
<column name="DepartmentID"></column>
<generator class="foreign">
<param name="property">manager</param>
</generator>
</id>
<property name="departmentName" type="string">
<column name="DepartmentName"></column>
</property>
<one-to-one name="manager" class="com.chenx.hibernate.one2one.Manager" constrained="true"></one-to-one>
</class>
</hibernate-mapping>
可以看到不同:
1.主键生成策略不同:采用foreign:根据主表的主键来生成
2.需要配置根据哪个主表来生成主键
<param name="property">manager</param>
3.配置一对一:并使用constrained=“true” 设置外键约束(https://blog.csdn.net/metheir/article/details/81877055)
<one-to-one name="manager" class="com.chenx.hibernate.one2one.Manager" constrained="true"></one-to-one>
Manager.hbm.xml:
<hibernate-mapping>
<class name="com.chenx.hibernate.one2one.Manager" table="tb_managers">
<id name="managerID" type="java.lang.Integer">
<column name="ManagerID"></column>
<generator class="native"></generator>
</id>
<property name="managerName" type="string">
<column name="ManagerName"></column>
</property>
<one-to-one name="department" class="com.chenx.hibernate.one2one.Department"></one-to-one>
</class>
</hibernate-mapping>
测试save:
测试发现无论插入哪一个都不会有多余的update语句,可以从上方的配置文件推测出,之前因为要update是因为插入之前需要一个外键的值,但是现在基于主键的保存没有了外键这一列,所以插入之前都是知道的值,但是仔细看insert语句,发现其插入顺序都是一样的,先插入manager再插入department,因为department的主键生成策略是根据manager来的,所以你不先插入manager就无法插入department,当然这个系统自动会帮我们的做好,我们无需关心顺序。
单向多对多:
例子:每个种类包含很多商品,每个商品又隶属于多种种类,在多对多的关系中只用二张表是不够的,需要加入一张中间表。以此创建以下文件:
Categories:
public class Categories {
private Integer categoriesID;
private String categoriesName;
private Set<Items> items=new HashSet<>();
public Integer getCategoriesID() {
return categoriesID;
}
public void setCategoriesID(Integer categorieID) {
this.categoriesID = categorieID;
}
public String getCategoriesName() {
return categoriesName;
}
public void setCategoriesName(String categorieName) {
this.categoriesName = categorieName;
}
public Set<Items> getItems() {
return items;
}
public void setItems(Set<Items> items) {
this.items = items;
}
}
Items:
public class Items {
private Integer itemID;
private String itemName;
public Integer getItemID() {
return itemID;
}
public void setItemID(Integer itemID) {
this.itemID = itemID;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
}
Categories.hbm.xml:
<hibernate-mapping>
<class name="com.chenx.hibernate.n2n.Categories" table="tb_categories">
<id name="categoriesID" type="java.lang.Integer">
<column name="CategoriesID"></column>
<generator class="native"></generator>
</id>
<property name="CategoriesName" type="string">
<column name="CategoriesName"></column>
</property>
<set name="items" table="tb_categories_items">
<key column="Categories_ID"></key>
<many-to-many class="com.chenx.hibernate.n2n.Items" >
<column name="Items_ID"></column>
</many-to-many>
</set>
</class>
</hibernate-mapping>
其中set属性中:
name:持久化类中属性名
table:中间表名
key:column:中间表中对应主键的外键名
many-to-many:class:后者many对应的类名 column:中间表中对应的主键的外键名
双向多对多:
单项多对多只能从一边获取到另一边的数据,反之则不行,实现双向多对多达到目的;
双向多对多和单项多对多的配置很类似,现在只需二边都那么设置即可。
Categories 类和配置不变,改变Items 的配置:
public class Items {
private Integer itemID;
private String itemName;
private Set<Categories> categories=new HashSet<>();
public Integer getItemID() {
return itemID;
}
public void setItemID(Integer itemID) {
this.itemID = itemID;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public Set<Categories> getCategories() {
return categories;
}
public void setCategories(Set<Categories> categories) {
this.categories = categories;
}
}
<hibernate-mapping>
<class name="com.chenx.hibernate.n2n.Items" table="tb_items">
<id name="itemID" type="java.lang.Integer">
<column name="ItemID"></column>
<generator class="native"></generator>
</id>
<property name="itemName" type="string">
<column name="ItemName"></column>
</property>
<set name="categories" table="tb_categories_items" >
<key column="Items_ID" ></key>
<many-to-many class="com.chenx.hibernate.n2n.Categories">
<column name="Categories_ID"></column>
</many-to-many>
</set>
</class>
</hibernate-mapping>
配置文件可以看出来,配置刚好交叉的
需要注意的是如果二边都没有加inverse=“true”,并且在测试的时候,你向二边都add了相互,那将会出现异常,因为插入的重复的记录,所以要么就二边都不加inverse,插入时只插入任意一边,或者哪边使用inverse 就插维护那边(或者全插)
映射继承关系:
1.采用subclass元素的继承映射:
->采用subclass的继承映射可以实现对继承关系中父类和子类使用同一张表
->父类和子类的实例全部保存在同一张表中,增加辨别者列来区分是父类还是子类
->使用subclass来映射子类,使用class和subclass的discriminator-value的属性值来赋辨别者的值
->注意:因为子类的个别属性父类是没有的,所以不能把这些属性置为非空
采用以下例子:
Person:
public class Person {
private Integer id;
private String name;
private int age;
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 int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Student:继承Person:
public class Student extends Person {
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
因为只有一张表只需要一个hbm.xml配置文件即可:
<hibernate-mapping>
<class name="com.chenx.hibernate.subclass.Person" table="tb_persons" discriminator-value="Person">
<id name="id" type="java.lang.Integer">
<column name="ID"></column>
<generator class="native"></generator>
</id>
<discriminator column="Type" type="string"> </discriminator>
<property name="name" type="string">
<column name="Name"></column>
</property>
<property name="age" type="int">
<column name="Age"></column>
</property>
<subclass name="com.chenx.hibernate.subclass.Student" discriminator-value="Student">
<property name="school" type="string">
<column name="School"></column>
</property>
</subclass>
</class>
使用subclass总结:
1.查询父类和子类记录,都只需要查询一张数据表
2.缺点:多使用了辨别者列,子类独有的字段不能添加非空约束,若继承层次深,数据表字段会很多
2.使用joined-subclass元素的继承映射:
使用joined-subclass:
父类一张表,子类一张表,子类表中只有子类独有的字段:
持久化类不变,只有hbm.xml配置改变:
<hibernate-mapping>
<class name="com.chenx.hibernate.subclass.Person" table="tb_persons" >
<id name="id" type="java.lang.Integer">
<column name="ID"></column>
<generator class="native"></generator>
</id>
<property name="name" type="string">
<column name="Name"></column>
</property>
<property name="age" type="int">
<column name="Age"></column>
</property>
<joined-subclass name="com.chenx.hibernate.subclass.Student" table="tb_students">
<key column="StudentID"></key>
<property name="school" type="string" column="School"></property>
</joined-subclass>
</class>
joined-subclass:
name: 子类的全类名
table:子类的表名
key:name:子类的主键名(外键)
property :子类持久化字段的属性信息
1.保存父类只需插入一张表,保存子类的数据需要插入二张表
2.查询父类用到了left outer join ,查询子类用到了 inner join
3.优点:无需使用辨别者列,可用对子类字段使用非空,数据不会冗余
3.采用union-subclass
父类一张表,子类一张表,id是连续的
<hibernate-mapping>
<class name="com.chenx.hibernate.subclass.Person" table="tb_persons">
<id name="id" type="java.lang.Integer">
<column name="ID"></column>
<generator class="increment"></generator>
</id>
<property name="name" type="string">
<column name="Name"></column>
</property>
<property name="age" type="int">
<column name="Age"></column>
</property>
<union-subclass name="com.chenx.hibernate.subclass.Student" table="tb_students">
<property name="school" type="string" column="School"></property>
</union-subclass>
</class>
1.保存父类子类都只需要插入一张表
2.查询父类需要查询子类合并父类再查询,查询子类只需要查询一张表
3.无需辨别者列,可使用非空字段
4.存在冗余的字段
5.使用update更新时,需要更新二张表,效率低