对象的关系
(1)依赖关系:如果A对象离开了B对象就不能编译,那么A对象依赖B对象;
(2)关联关系:如果A对象依赖B对象,并且把B对象作为A对象的一个属性,我们就说,A对象和B对象是关联关系;
-
关联关系从多重性来讲:
一对多:一个A包含多个B;多对一:多个A属于一个B;并且一个A只能属于一个B;
多对多:一个A属于多个B,一个B可以属于多个A;
一对一:一个A属于一个B,一个B属于一个A;
-
关联关系从导航性来讲(java代码):通过A的某一个属性能够访问到和这个属性关联的B对象,A能够在这个关系上面导航到B;
-
单向;如果针对一个关联关系,只能由一边通过属性导航到另一边,
-
双向;如果针对一个关联关系,A能够通过属性导航到B,并且B也能够通过属性导航到A;
关系的判定:
1,关系的判定是针对实体判断,不是针对类来判断;
2,关系的判定是针对一对特定的属性判断;
3,关系的判定要结合具体的需求;
(3)聚合关系:是一个关联关系,部分和整体的关系;部分和整体是可以单独存在的,在系统当中,一定是两个模块在分别管理整体和部分;
(4)组合关系:是一种强聚合关系,但是部分和整理是不能单独存在的,在系统当中,一定是在一个模块中管理的;
(5)泛化关系:继承;
1、关联关系
1.1、单向多对一
(1)表的设计和SQL
(2)关于单向多对一的使用方法如下:
配置文件
<hibernate-mapping package="com.it520.many2one">
<!--配置one方 -->
<class name="Department" >
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
</class>
<!--配置many方 -->
<class name ="Employee">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<!-- 配置和one方的关系,column熟悉是外键的类名 -->
<many-to-one name="dept" column="dept_id"></many-to-one>
</class>
</hibernate-mapping>
测试类代码:(后续关联关系的介绍中就不列出测试类代码了)
public class Many2oneTest {
@Before
public void save(){
Department d = new Department();
Department d1 = new Department();
d.setName("开发部");
d1.setName("运维部");
Employee e = new Employee();
e.setName("张三");
e.setDept(d);
Employee e1 = new Employee();
e1.setName("李四");
e1.setDept(d1);
Session session = HibernateUtil.getinstance().getSession();
Transaction tx = session.beginTransaction();
session.save(e);
session.save(e1);
session.save(d1);
session.save(d);
tx.commit();
session.close();
}
@Test
public void get() {
Session session = HibernateUtil.getinstance().getSession();
Employee e = (Employee)session.get(Employee.class, 1L);
Employee e1 = (Employee)session.get(Employee.class, 2L);
System.out.println(e);
System.out.println(e1);
}
}
执行结果和SQL:
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, dept_id) values (?, ?)
Hibernate: insert into Employee (name, dept_id) values (?, ?)
Hibernate: select employee0_.id as id1_1_0_, employee0_.name as name2_1_0_, employee0_.dept_id as dept3_1_0_ from Employee employee0_ where employee0_.id=?
Hibernate: select employee0_.id as id1_1_0_, employee0_.name as name2_1_0_, employee0_.dept_id as dept3_1_0_ from Employee employee0_ where employee0_.id=?
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
Employee [id=1, name=张三, dept=Department [id=2, name=开发部]]
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
Employee [id=2, name=李四, dept=Department [id=1, name=运维部]]
(3)进一步研究get方法
- 通过many方拿one方使用延迟加载,测试代码如下
public void get() {
Session session = HibernateUtil.getinstance().getSession();
Employee e = (Employee)session.get(Employee.class, 1L);
Department d = e.getDept();
System.out.println("==============");
System.out.println(d);
System.out.println(e);
}
结果:
Hibernate: select employee0_.id as id1_1_0_, employee0_.name as name2_1_0_, employee0_.dept_id as dept3_1_0_ from Employee employee0_ where employee0_.id=?
==============
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
//department的select语句在==============之后,使用延迟加载;
Department [id=2, name=开发部]
Employee [id=1, name=张三, dept=Department [id=2, name=开发部]]
- 通过ifnull来判断many方是否有one方关联;
@Test
public void get() {
Session session = HibernateUtil.getinstance().getSession();
Employee e = (Employee) session.get(Employee.class, 1L);
Employee e1 = (Employee) session.get(Employee.class, 2L);
if(e.getDept()!=null) {
System.out.println(e.getName()+"是属于"+e.getDept().getName());
}else {
System.out.println(e.getName()+"是没有部门的");
}
if(e1.getDept()!=null) {
System.out.println(e1.getName()+"是属于"+e1.getDept().getName());
}else {
System.out.println(e1.getName()+"是没有部门的");
}
}
- 拿到的one方需要在session关闭之前实例化,不然就会出现no Session的错误
@Test
public void get() {
Session session = HibernateUtil.getinstance().getSession();
Employee e1 = (Employee) session.get(Employee.class, 2L);
Department dept = e1.getDept();
session.close();
System.out.println(dept);//session关闭之后实例化的,会有no Session的错误
}
注意:所有使用延迟加载拿到的对象都要在session关闭之前实例化。
(4)进一步研究save 方法
先保存many方再保存one方,会产生额外的SQL,这些SQL是由于持久化对象(many)的脏数据造成的;
先保存one方发出的SQL:
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, dept_id) values (?, ?)
先保存many方发出的SQL:(会多出一条update的SQL)
Hibernate: insert into Employee (name, dept_id) values (?, ?)
Hibernate: insert into Department (name) values (?)
Hibernate: update Employee set name=?, dept_id=? where id=?
1.2、单向一对多
(1)表的设计和SQL
(2)配置文件
<!--配置one方 -->
<class name="Department">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="emps">
<key column = "DEPT_ID"/>
<one-to-many class = "Employee"/>
</set>
</class>
<!--配置many方 -->
<class name="Employee">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
</class>
(3)进一步研究get方法
- 通过one方的到many方使用延迟加载,在session关闭之前要使用many方的集合,不然会出现no Session的错误。
- 不能提供ifnull来判断one方是否有many方,要通过集合的size()方法判断。
- one方中的many方式一个集合,这时候只能使用集合接口。
1.3、双向多对一,一对多
双向many2one/one2many的问题:
(1)一定有额外的SQL发出;
在数据库里面,外键表示many2one或者one2many的关系,外键是没有方向的;但是在面向对象里面,关系有方向,所以两个方向都要去管理这个外键,造成的额外的SQL(这些多余的SQL都是ONE方想去管理many方造成的);
我们既想保留对象中的双向的关系;又想减少额外的SQL,就需要一方放弃对另一方的管理。
<class name="Department" >
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<!-- inverse属性告诉Hibernate是否放弃对集合中对象的管理权,默认值为false(管理集合对象) -->
<set name="emps" inverse="true">
<key column="dept_id"></key>
<one-to-many class="Employee"/>
</set>
</class>
<class name="Employee">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<many-to-one name="dept" column="DEPT_ID" />
</class>
save对象时没有设置inverse="true"时的SQL
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, DEPT_ID) values (?, ?)
Hibernate: insert into Employee (name, DEPT_ID) values (?, ?)
Hibernate: update Employee set dept_id=? where id=?
Hibernate: update Employee set dept_id=? where id=?
save对象设置inverse="true"后的SQL
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, DEPT_ID) values (?, ?)
Hibernate: insert into Employee (name, DEPT_ID) values (?, ?)
(2)对象的删除:
1、many方都可以直接删除;
2、删除one方:
- inverse=“false”:可以正常删除;
Hibernate: update Employee set DEPT_ID=null where DEPT_ID=?
Hibernate: delete from Department where id=?
- inverse=“true”:不能正常删除:
外键约束错误;
(3)单向多对一,单向一对多,双向多对一一对多的选择。
1、90%的情况,使用单向的many2one;(删除使用HQL;)
2、9%的情况使用双向的many2one/one2many:( 自连接(树状结构); ,组合关系;)
3、1%的情况使用单向的one2many;(使用两个one2many来实现many2many)
1.4、hibernate中的集合
(1)在hibernate中,集合只能使用接口
(2)集合的使用;
-
Set:代表集合中的对象不能重复;集合中的对象没有顺序;对于Set集合,只能使用元素来映射;
-
List:集合中的对象可以重复,集合中的对象是有顺序的;
1,要让one方知道many方的顺序,必须在many方的表中添加一列用来保存顺序; 2,这个顺序只有one方知道,所以,这个列应该由one方来维护;
3,在元素中添加来映射这个列;
4,不管list的inverse=true,总要发送额外的SQL去维护这个many方的顺序;
<list name="emps" inverse="true">
<key column="DEPT_ID" />
<list-index column="SEQ" />
<one-to-many class="Employee"/>
</list>
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, DEPT_ID) values (?, ?, ?)
Hibernate: update Employee set SEQ=? where id=?//额外SQL维护这个many方的顺序,many方数据库也会多出SEQ列
-
第二种映射List的方式:
1,如果只是想把List作为一个集合,而不让hibernate来帮我们维护顺序,就可以是用 2,bag元素不关心one中many方的顺序;
<bag name="emps" inverse="true" >
<key column="DEPT_ID" />
<one-to-many class="Employee"/>
</bag>
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, sequence, DEPT_ID) values (?, ?, ?)
//没有额外的SQL,many方数据库也不会多出SEQ列
(3)选择
-
确定集合中的对象是否能重复;
-
如果使用List,尽量使用bag来映射;
-
如果必须要让many方有顺序;在many方中添加一个表示顺序的属性
第一步:在many方添加一个顺序的属性
public class Employee { private Long id; private String name; private Integer sequence;//用来记录顺序的属性 private Department dept; }
第二步:在one方的集合上面添加order-by属性
<bag name="emps" inverse="true" order-by="sequence" > <key column="DEPT_ID" /> <one-to-many class="Employee"/> </bag>
order-by属性的意思是,在加载many方集合的时候,使用order by语句按照many方的指定属性来查询;
Hibernate: select * where emps0_.DEPT_ID=? order by emps0_.sequence
1.5、一对一
(1)表设计和SQL:实现一对一有两种方式
-
使用many2one来实现one2one
hibernate在这种方式映射one2one不是很好理解(不讲)
-
使用主键外键的方式来实现one2one
(2)映射文件
<!-- 配置主对象-->
<class name="QQNumber">
<id name="id">
<generator class="native"/>
</id>
<property name="number" />
<one-to-one name="zone" />
</class>
<!-- 配置从对象-->
<class name="QQZone">
<id name="id">
<!-- 配置从对象-->
<generator class="foreign">
<param name="property">number</param>
</generator>
</id>
<property name="title" />
<one-to-one name="number" constrained="true"/>
</class>
1、在从对象中,从对象的主键需要参照number属性的主键来生成,所以从对象的主键生成策略为foreign;
2、在foreign策略中,需要配置一个property(属性),告诉hibernate,从对象的主键是根据哪个属性对应的主键来生成;
3、默认情况下,one2one并没有生成从对象主键的外键约束,在从对象中的one-to-one元素中添加constrained属性来为从表的主键添加外键约束;
4、不管是先保存从对象还是先保存主对象,甚至直接保存从对象,hibernate都会先保存主对象,在保存从对象;
5、从主对象拿从对象,不存在延迟加载的问题,hibernate使用一条SQL就把主对象和对应的从对象一起查询出来了;
思考:为什么hibernate在这个地方不用延迟加载,他是不想用,还是不能用?为什么?
6,从从对象拿主对象,使用延迟加载;(必须在session关闭之前实例化主对象;一定存在主对象;)
思考:为什么从对象拿主对象又可以使用延迟加载?
(3)选择:
- 一般情况下,都不会使用one2one关系,我们一般都直接使用单向的many2one+业务逻辑来完成one2one;
- 如果非得使用one2one,使用主键外键的方式实现;
1.6、多对多
(1)表设计和SQL
(2)映射文件
<class name="Teacher">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="students" table="STU_TEA">
<key column="TEACHER_ID" />
<many-to-many class="Student" column="STUDENT_ID"/>
</set>
</class>
<class name="Student">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="teachers" table="STU_TEA" inverse="true">
<key column="STUDENT_ID" />
<many-to-many class="Teacher" column="TEACHER_ID"/>
</set>
</class>
(3)进一步分析
1、在映射文件里面,set需要table属性,来告知中间表;
2、set中的key子元素代表,在中间表里面那格列作为外键关联到我自己;
3、many-to-many元素:和集合中的对象是多对多的关系;
4、many-to-many的column属性代表:在中间表里面那个列作为外键关联到对方表的主键;
5、many-to-many双方的配置其实是相反的;
6、如果配置的集合是set,hibernate会为中间表创建一个复合主键,
7、不管集合是list还是set,都需要让一边放弃对关系的维护inverse=true;
8、get的时候,使用延迟加载;
- 必须在session关闭之前初始化;
- 不能使用ifnull来判断集合中是否有对象,只能使用size()来判断;
9、一般来说,我们使用单向的many2many;
10、在项目中,我们应该让哪边来维护关系?需要看具体的设计.页面里面;哪个模块在管理关系,我们就把关系配置在哪边.
两个老师同时是两位学生的老师,save方法的SQL
Hibernate: insert into Teacher (name) values (?)
Hibernate: insert into Teacher (name) values (?)
Hibernate: insert into Student (name) values (?)
Hibernate: insert into Student (name) values (?)
Hibernate: insert into STU_TEA (TEACHER_ID, STUDENT_ID) values (?, ?)
Hibernate: insert into STU_TEA (TEACHER_ID, STUDENT_ID) values (?, ?)
Hibernate: insert into STU_TEA (TEACHER_ID, STUDENT_ID) values (?, ?)
Hibernate: insert into STU_TEA (TEACHER_ID, STUDENT_ID) values (?, ?)
数据库的表格