大纲:
-
对象关系映射
- 关联关系映射
- 单向多对一
- 单向一对多
- 双向多对一
- 双向多对多
- 集合映射
- 组件关系映射
- 继承关系映射
- 关联关系映射
-
数据查询
- Query查询
- NamedQuery查询
- NativeQuery查询
-
事务并发访问
- 隔离级别
- 悲观锁
- 乐观锁
-
一级缓存
-
二级缓存
- 依赖关系
如果A对象离开了B对象,A对象就不能正常编译,则A对象依赖B对象.
在A类使用到了B(调用了B的方法,调用了B的字段). - 关联关系
A对象依赖B对象,并且把B对象作为A对象的一个字段,则A和B是关联关系.
按照多重性分:
1).一对一:一个A对象属于一个B对象,一个B对象属于一个A对象.
比如:QQ号码对应一个QQ空间.
2).一对多:一个A对象包含多个B对象.
比如:一个部门包含多个员工对象,此时我们使用集合来封装B对象..
3).多对一:多个A对象同属于一个B对象,并且每个A对象只能属于一个B对象.
比如:多个员工对象属于同一个部门.
设计表的时候:外键在many这一方/在开发设计中:添加一个many方对象的时候,往往通过下拉列表去选择one方.
4).多对多:一个A对象属于多个B对象,一个B对象属于多个A对象.
比如:一个老师可以有多个学生,一个学生可以有多个老师.
通过中间表来表示关系.
按照导航性分:如果通过A对象中的某一个属性可以访问该属性对应的B对象,则说A可以导航到B.
1).单向:只能从A通过属性导航到B,B不能导航到A.
2).双向:A可以通过属性导航到B,B也可以通过属性导航到A.
判断方法:
1,判断都是从对象的实例上面来看的;
2,判断关系必须确定一对属性;
3,判断关系必须确定具体需求;
-
聚合关系
表示整体和部分的关系,整体和部分之间可以相互独立存在,一定是有两个模块来分别管理整体和部分.整体和部分可以分开. -
组合关系
强聚合关系,但是整体和部分不能独立存在,一定是在一个模块中同时管理整体和部分,生命周期必须相同.如:单据和单据明细/购物车信息 -
泛化关系
其实就是继承关系.
在实际开发中,我们需要去维护对象之间的关系,意思是说,在保存A的时候,需要考虑到关联的B,在查询到A之后,如何将关联的B对象查询到
- 单向多对一
在实际开发中,绝大部分的关联关系都是单向多对一,所以我们需要重点掌握
以员工和部门的关系为例
-
表结构的设计
通常,多对一的关系,可以在多方的表中添加一个外键列来维护双方的关系,如: -
对象的设计
@Getter@Setter public class Employee { private Long id; private String name; //封装当前员工所在的部门信息 private Department dept; }
@Getter@Setter public class Department { private Long id; private String name; }
可以看到,在表结构方面,我们应该是在employee表中添加外键列来维护员工和部门的关系
在对象设计方面,在Employee对象中关联Department对象,来封装当前员工所在的部门信息 -
SQL分析
保存(两个员工一个部门)
因为是在多方(employee)关联one方(department)的数据,所以,在保存employee的时候需要考虑关系数据的维护(外键列的信息)
先保存部门,再保存员工
保存部门信息,将数据库自动生成的主键信息作为保存员工时的dept_id的信息
INSERT INTO department(name) VALUES(?);
这里的dept_id是刚刚保存的部门信息的id
INSERT INTO employee(name,dept_id) VALUES(?,?);
INSERT INTO employee(name,dept_id) VALUES(?,?);
先保存员工,再保存部门
此时dept_id为null
Employee emp = new Employee();
emp.setName(“Neld”);
emp.setDept(null);
INSERT INTO employee(name,dept_id) VALUES(?,null);
INSERT INTO employee(name,dept_id) VALUES(?,null);
INSERT INTO department(name) VALUES(?,?);
这三条sql执行之后,员工表的外键列为null,此时,需要发送两条update语句将dept_id更新进去
UPDATE employee SET dept_id = ?,name=? WHERE id = ?
UPDATE employee SET dept_id = ?,name=? WHERE id = ?
此时需要多发送两条sql才能将数据保存完整,所以,这种情况,建议选择先保存one方,再保存many方,尽量减少sql的发送
查询id为1的员工,并获取到当前员工所在的部门信息
SELECT id,name,dept_id FROM employee WHERE id = ?;
查询部门是根据当前员工所在的部门的编号查询
SELECT id,name FROM department WHERE id = ?;
使用JPA实现many2one的映射关系非常的简单,只需要在关联的对象上贴上@Many2One的注解即可,如:
@Getter
@Setter
@Entity
public class Employee {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
private Department dept;
}
测试代码
public void testSave() throws Exception {
Employee e1 = new Employee();
e1.setName("Neld");
Employee e2 = new Employee();
e2.setName("Lucy");
Department dept = new Department();
dept.setName("研发部");
//设置关联关系
e1.setDept(dept);
e2.setDept(dept);
EntityManager em = JPAUtil.getEntityManager();
em.getTransaction().begin();
//先保存one方,再保存many方
em.persist(dept);
em.persist(e1);
em.persist(e2);
em.getTransaction().commit();
em.close();
}
执行的SQL:
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (dept_id, name) values (?, ?)
Hibernate: insert into Employee (dept_id, name) values (?, ?)
和预期的sql一致
如果先保存many方,再保存one方呢? 如:
em.persist(e1);
em.persist(e2);
em.persist(dept);
执行的SQL:
Hibernate: insert into Employee (dept_id, name) values (?, ?)
Hibernate: insert into Employee (dept_id, name) values (?, ?)
Hibernate: insert into Department (name) values (?)
Hibernate: update Employee set dept_id=?, name=? where id=?
Hibernate: update Employee set dept_id=?, name=? where id=?
可以看出,多执行了两条update语句来更新外键列的信息,而且更新语句中更新了除dept_id外键列以外的其他列的信息,说明这两条update语句必定是many方发出的,因为在one方是不知道有这些信息的
分析update语句的执行原理:
- 执行insert语句将Employee保存到数据库中,此时Employee对象由瞬时状态转换成持久状态,此时Employee所依赖的Department的id为null
- 保存Employee对象所依赖的Department对象,获取到数据库中自动生成的主键,此时一级缓存中的Employee对象发生改变(依赖的Department对象的id属性发生改变)
- 提交事务,检查缓存中的Employee对象和快照区域的Employee对象是否一致,发现两者所依赖的Department对象的主键信息是不一致的
- 一级缓存和快照区数据不一致,在提交事务的时候会执行对应的update语句将缓存中的脏数据持久化到数据库中
数据保存的细节分析:
现在创建表结构的sql是自动生成的,我们来做一个简单的分析
Hibernate: create table Employee (id bigint not null auto_increment, name varchar(255), dept_id bigint, primary key (id))
从建表语句中可以看出,为我们自动生成了关联Department的外键信息(dept_id)
外键列是有属性名_id组成的,如果需要修改,可以使用@JoinColumn注解修改外键列的信息
@ManyToOne
//修改外键列的相关信息(列名/约束/类型等)
@JoinColumn(name = "department_id")
private Department dept;
查询分析
测试代码
public void testGet() throws Exception{
EntityManager em = JPAUtil.getEntityManager();
Employee e = em.find(Employee.class, 1L);
System.out.println(e.getName());
System.out.println(e.getDept().getName());
em.close();
}
执行结果:
Neld
研发部
查询到了many方和many方关联的one的数据