今日内容:
通过案例学习hibernate的多表操作
1. 案例1:使用hibernate完成1对多的关联关系映射并操作
* 部门和员工。1对多的关系,一个部门有多名员工;一名员工,只会归属某一个部门。
2. 案例2:使用hibernate完成多对多的关联关系映射并操作
* 学生选课。学生表和课程表之间,就是多对多关系。一个学生,可以选择多门课程。一门课程,可以被多个学生选择。
java表达表与表的关系 – 表达1对多关系(部门和员工)
1)数据库的表达:利用外简约束表示表与表之间的关系
create database day39_hibernate;
use database day39_hibernate;
CREATE TABLE department(
did INT PRIMARY KEY AUTO_INCREMENT,
dname CHAR(20));
CREATE TABLE employee(
eid INT PRIMARY KEY AUTO_INCREMENT,
ename CHAR(20),
e_did INT,
FOREIGN KEY (e_did) REFERENCES department(did)
);
2)java的表达–javaBean中表达部门和员工对象之间的关系:
在javaBean中创建私有字段中创建相应的对象表达与某种对象之间的关系
// 每个部门,有多个员工
class Department{
Integer id;
String name;
// 这个部门对应的所有员工
Set<Employee> employees=new HashSet<Employee>();
}
// 每个员工归属于一个部门
class Employee{
Integer id;
String name;
//Integer department_id; // 部门的id,不够面向对象
Department department; // 员工所在的部门
}
//上述方式双向1对多
**为什么当部门对象中含有多个员工的时候,选择在部门对象中设定一个set集合用来表示该部门的员工:**
1) 第一个知识点:set,list, map的区别(必须记住的)
* list: 有序(记住了插入的顺序),可以重复的
* set:无序(不记住插入的顺序),不可以重复
* map: 保存的键值对(key,value), 其中key,不能重复。
2)hibernate使用set的原因:(hibernate推荐使用set)
set特点是,无序(符合数据库的特性),不重复(可以去掉重复的数据)。
set的主要方法就使用:add(), remove()
Java表达存在方向性:
1对多为例。存在三种情况: 单向1对多,单向多对1, 双向1对多
数据库表达方面,都是一样的,都是多的一方使用外键指向一的一方的主键
但是java的表达存在不同:
a)单向1对多
可以从1的一方(部门)找到多的一方(员工),反过来不可以。
// 每个部门,有多个员工
class Department{
Integer id;
String name;
Set<Employee> employees=new HashSet<Employee>();
}
class Employee{
Integer id;
String name;
}
b) 单向多对1
可以从多的一方(员工)找到的1的一方(部门),反过来不可以。
class Department{
Integer id;
String name;
}
// 每个员工归属于一个部门
class Employee{
Integer id;
String name;
Department department;
}
c)双向1对多:
即可以从多找到1,又可以从1找到多。
// 每个部门,有多个员工
class Department{
Integer id;
String name;
Set<Employee> employees=new HashSet<Employee>();
}
// 每个员工归属于一个部门
class Employee{
Integer id;
String name;
Department department;
}
方向性有什么价值啊?
* 考虑场景:实际开发的时候。解决现实中存在的问题。
* 部门和员工。
* 单向多对1,更加实用。
3)映射配置文件的编写
1的一方 —- 部门
<set name="employees" cascade="save-update" inverse="true">
<key column="e_did"></key>
<one-to-many class="cn.itcast.domain.Employee"/>
</set>
配置标签讲解:
1的一方hbm.xml映射–set标签的理解(重点)
* set标签:javaBean中是set集合
* name: 多的一方,在1的一方对应类中属性的名字
* key标签:查询的关键。知道了部门信息,查询员工的语句:
参考:select * from employee where fk_did =?
* column: 多的一方对应的表中,指向1的一方外键的名称 ,这个是在数据库表中的列名
* one-to-many标签: 1对多
* class: 查询到的结果,要封装的java类型。即多的一方对应类的全路径名称
双向1对多的配置:
多的一方:
<many-to-one name="department" column="e_did"
class="com.itheima.domain.Department" cascade="save-update"></many-to-one>
多的一方映射文件–manytoone标签的理解
many-to-one标签:从多的一方查询1的一方
* name: 一的一方,在多的一方的持久化类中,属性的名字
* class: 查询到的结果,要封装的java类型。即 一的一方对应的类的全路径名称
* column: 多的一方,对应的表中,外键所在字段的名称。思考查询: 已知员的信息,去查询部门的信息。
hibernate的级联:
引申:经典错误:object references an unsaved transient instance
分析:事务提交时,持久化态的对象关联了瞬时态的对象,就会报上述错误。(没配置级联时)
解决方案:配置级联保存
* 级联重要的是理解:方向性
级联的定义:
a. 当主控方执行保存、更新或者删除操作时,其关联对象(被控方)也执行相同的操作。
b. 在映射文件中通过对cascade属性的设定来控制是否对关联对象,采用级联操作。
c. 举例:在部门和员工的关系中,在部门对应的映射文件上,配置了级联保存,保存部门的同时,自动把部门关联的员工,也保存了。
配置:
a. 映射文件中,和都可以配置级联属性:cascade
b. 级联的常见取值
*
* none: 默认值。所有情况下均不进行关联操作
* save-update: 在执行save/update/saveOrUpdate时进行关联操作。
* delete: 在执行delete时进行关联操作
* all: 所有情况下均进行关联操作。
c. 使用举例:`cascade="save-update"`
重点:理解方向性(级联操作和关系的方向性)
案例:
@Test
// 小游戏
public void test3(){
Session session =HibernateUtils.openSession();
Transaction transaction=session.beginTransaction();
// 参考代码--保存操作:1个部门3个员工
Department department = new Department();
department.setName("研发部");
Employee employee1 = new Employee();
employee1.setName("研发1号");
Employee employee2 = new Employee();
employee2.setName("研发2号");
Employee employee3 = new Employee();
employee3.setName("研发3号");
// 建立双向关系
department.getEmployees().add(employee1);
employee1.setDepartment(department);
employee2.setDepartment(department);
employee3.setDepartment(department);
// 双方配置级联保存
// 执行保存操作,单独执行语句1,2,3,4,问分别数据库分别保存了几条数据,那些数据
// 语句1
//session.save(department);// 部门,1号员工
// 语句2
//session.save(employee1); // 1, 部门,
// 语句3
//session.save(employee2); //2, 部门,1号
// 语句4
session.save(employee3); //3, 部门,1号
// 释放资源
transaction.commit();
session.close();
关于级联删除:
a. 级联删除的配置
* 绝对不会使用 ,配置方法就是在映射配置文件中写上:cascade="delete"
b. 演示(同样存在方向性)
(1) 不配置级联删,默认情况下的删除操作
(2) 只删除部门
(3) 只删除员工
c. 恐怖的双向级联删除
* 如双方都配置了级联删除。
* 删除一个员工,先删除这个员工,然后通过“员工的级联删除”配置,把对应的部门删除,再次通过“部门的级联删除”配置,把这个部门其他的员工全部删除。
d. 级联删除的鸡肋性
* 回顾商品的上下架问题。与其直接删除商品,不如为商品设置一个属性:上下架属性,将其下架即可,这样仍然可以查询到商品的相关信息,也不会引起数据库的问题
对象导航查询:
对象导航查询
* 体会hibernate之美,自动查询了关联对象
// 对象导航查询
@Test
public void test02() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Department department = session.get(Department.class, 3);
System.out.println(department.toString());
System.out.println(department.getEmployees().toString());
// 释放资源
transaction.commit();
session.close();
}
放弃外键维护权:
原因:双向关系的时候,更新数据的时候,会导致重复sql语句查询和修改数据库,增加数据库的访问量
a. 演示双向关系导致的重复sql语句
(1) 模拟:员工更换部门
(2) 原因分析:hibernate默认部门和员工双方,都维护外键,从而导致多余的sql语句。
(3) 解决方案:其中一方放弃外键维护权
b. 放弃外键维护权
* 重点:所谓放弃外键维护权:指不负责维护双方的关系。
配置方式:
在set或者many-to-one标签上,配置inverse属性。解释:逆转。默认值是:false,不放弃;设置成true,表示放弃外键维护权。
Hibernate推荐:
1对多的关系:应该由多的这方维护。1的一方,放弃外键维护,所以在1 的这方的set属性里面配置放弃外键
重点理解:cascade和inverse的区别
a. 侧重点不同
(1) cascade:级联,强调操作一个对象的时候,是否操作其关联对象
(2) inverse:强调的是外键的维护权
案例:
* 保存操作,一个部门,2个员工。部门配置了级联保存,并按照规范,放弃了外键维护权。维护了部门到员工的单向关系,只保存部门。
public void test04() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 节省时间的代码
Department department =new Department();
department.setName("供应链部");
Employee employee1 =new Employee();
employee1.setName("张三1");
Employee employee2 =new Employee();
employee2.setName("张三2");
// 建立关系
department.getEmployees().add(employee1);
department.getEmployees().add(employee2);
// 保存
session.save(department);
// 释放资源
transaction.commit();
session.close();
}
c. 分析(重点)
(1) 前提:部门上配置了级联保存和放弃外键维护权,部门单向关联员工
(2) 保存部门,出现的现象是:部门的数据保存了,员工的数据保存了,但是员工记录中的外键是null.
(3) 放弃外键维护权本质:放弃对双方关系的表达。
* 在一对多中,通过外键字段体现关系,放弃外键维护权,即不负责维护外键字段。
hibernate:多对多 —- 学生选课
1)数据库准备:
数据库表表示两张表的多对多关系方式:
create database day39_hibernate;
user database day39_hibernate;
CREATE TABLE student(
sid INT PRIMARY KEY AUTO_INCREMENT,
sname CHAR(20)
);
CREATE TABLE course(
cid INT PRIMARY KEY AUTO_INCREMENT,
cname CHAR(20)
);
CREATE TABLE student_course(
id INT PRIMARY KEY AUTO_INCREMENT,
fk_sid INT,
fk_cid INT,
FOREIGN KEY (fk_sid) REFERENCES student(sid),
FOREIGN KEY (fk_cid) REFERENCES course(cid)
);
2)java表达:
student类:
public class Student {
private Integer id;
private String name;
private Set<Course> courses = new HashSet<Course>();
}
Course类:
// 持久化的类
public class Course {
private Integer id; //oid
private String name;
private Set<Student> students=new HashSet<Student>();
}
3)映射配置文件的写法:
course .hbm.xml
<set name="students" table="student_course" cascade="save-update">
<key column="fk_cid"></key>
<many-to-many class="com.itheima.domain.Student" column="fk_sid"></many-to-many>
</set>
student.hbm.xml
<set name="courses" table="student_course" cascade="save-update">
<key column="fk_sid"></key>
<many-to-many class="com.itheima.domain.Course" column="fk_cid"></many-to-many>
</set>
* set标签:javaBean是set集合
* name: 关联的多的一方,在持久化类中属性的名字
* table: 中间表的名字
* key标签:查询的关键。知道了课程的信息,查询选修了这门课程的所有学生
参考: select * from student_course where sc_cid =?
* column: 自身在中间表中对应的外键的字段名称
* many-to-many标签: 多对多
* class: 多对多的查询,查询到的结果,封装的类型的全路径名称
* column :关联的多的一方,在中间表中,外键字段的名称
多对多案例:
@Test
public void test01() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Student student1 = new Student();
student1.setName("刘德华");
Student student2 = new Student();
student2.setName("郭富城");
Course course1 = new Course();
course1.setName("java");
Course course2 = new Course();
course2.setName("python");
Course course3 = new Course();
course3.setName("c++");
// 2. 建立关系(学生选课)
// 学生到课程
// 在多对多的关系中,每个关系,对应中间表的一条记录
// 放弃外键维护权:不负责处理关系。每个关系,对应中间表的一条记录
// 在1对多的关系中,每个关系,只是对应一个外键字段。
student1.getCourses().add(course1);
student1.getCourses().add(course2);
student2.getCourses().add(course1);
student2.getCourses().add(course3);
// 3. 保存操作
session.save(student1);
session.save(student2);
// 释放资源
transaction.commit();
session.close();
}
多对多的放弃外键维护权
a. hibernate默认,学生和课程双方,都维护外键
b. 上面提到的,维护外键的本质是:表达双方的关系。
c. 多对多中,表达双方的关系,不是通过某个外键字段,而是通过中间表的一条完整的记录。即:这里的外键,其实指中间表的一条记录。
d. 不同于1对多,多对多中,增加一个关系,中间表增加一条记录;减少一个关系,中间表,删除一个记录来实现。
放弃外键维护权
a. 在many-to-many标签上,配置inverse属性。设置成:true,放弃外键维护权
b. 多对多的关系:主动方维护外键,被动方,放弃外键维护权。
* 理解主动与被动:学生选课。学生是主动方。
案例:操作中间表
@Test
public void test04() { //新增一门课程
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Student student = session.get(Student.class, 10);
Course course = session.get(Course.class, 14);
student.getCourses().add(course);
// 释放资源
transaction.commit();
session.close();
}
@Test
// 学生新增一门课程
// 郭富城,觉得移动互联网是未来的趋势,python更加适合自己。c++暂时先不学,学习python
public void test3(){
Session session =HibernateUtils.openSession();
Transaction transaction=session.beginTransaction();
Student student1=session.get(Student.class, 4);// 郭富城
Course course1 =session.get(Course.class, 6);// c++
Course course2 =session.get(Course.class, 4);// python
student1.getCourses().remove(course1);
student1.getCourses().add(course2);
// 释放资源
transaction.commit();
session.close();
}