在系统中、关系型数据库中 描述数据之间关系 共有三种 一对多、一对一、多对多
在软件的设计阶段,数据库建模阶段 ---- 绘制E-R图 实体关系图
* 企业中最流行数据库建模工具 PowerDesigner
* PD工具内部 提供三种常用图 概念图(E-R图) 、 面向对象图、 物理数据表图 , 三种图之间相互转换
E-R图一般用以下图形来表示
三种数据关系建表原则
表与表之间关系,通过外键描述
类对象之间关系,通过内存地址引用就可以了
1、 一对多
class A {
B b; // 一个A对应一个B
}
class B {
A[] 、List<A>、Set<A> // 一个B 对应很多A
}
2、 一对一
class A {
B b;
}
class B {
A a;
}
3、 多对多
class A {
B[] 、List<B> 、 Set<B>
}
class B {
A[] 、List<A> 、Set<A>
}
一对多关系多表关联映射常见操作
客户和订单的案例 , 一个客户产生很多订单,一个订单 是对应一个下单客户
1、 编写类结构,进行关系映射
// 客户
public class Customer {
private Integer id;
private String name;
private Integer age;
private String city;
// 一个客户 对应 多个订单,在Customer类中维护一个set集合,为了方便操作就初始化了
private Set<Order>orders = new HashSet<Order>();
......set、get方法
}
// 订单
public class Order {
private Integer id;
private Double money;
private String addr;
// 一个订单 对应 一个客户
private Customer customer;
......set、get方法
}
配置 Order.hbm.xml
<hibernate-mapping>
<!-- order desc 是SQL关键字,不能作为表名或者列名 -->
<classname="cn.itcast.domain.onetomany.Order" table="orders"catalog="hibernate3day2">
<idname="id">
<generatorclass="native"></generator>
</id>
<propertyname="money"></property>
<propertyname="addr"></property>
<!-- 多对一的关联配置 -->
<!-- name Order类中关联Customer类属性名称
class 关联属性类型
column 在多方表添加外键,这个外键的列名-->
注:这个是Order的hbm文件,多个订单对应一个客户,所以这里要配置many-to-one
<many-to-onename="customer" class="cn.itcast.domain.onetomany.Customer"column="customer_id"> </many-to-one>
</class>
</hibernate-mapping>
配置Customer.hbm.xml
<hibernate-mapping>
<classname="cn.itcast.domain.onetomany.Customer" table="customer"catalog="hibernate3day2">
<idname="id">
<generatorclass="native"></generator>
</id>
<propertyname="name"></property>
<propertyname="age"></property>
<propertyname="city"></property>
<!-- 一对多 -->
注:一对多中的一里存在一个set集合,来维护所对应的多,这里要用到<set>标签
<setname="orders">
<!-- 通过key元素,配置当前表,在对方表产生外键列名 -->
<keycolumn="customer_id"></key>
<!-- 当前属性 对应实体类型 -->
<one-to-many class="cn.itcast.domain.onetomany.Order"/>
</set>
</class>
</hibernate-mapping>
总结:到底是用one-to-many还是many-to-one ,class属性到底写谁?
第一:看你配置的是谁的hbm.xml文件,若果是“多”的,那么就要用many-to-one,如果是“一”的, 那么就要用one-to-many。
第二:如果是用的many-to-one,那么就要写“one”的class,如果是用的one-to-many,那么就要写 “many”的class。
*** 在hibernate.cfg.xml 加载 hbm映射文件
<mappingresource="cn/itcast/domain/onetomany/Customer.hbm.xml"/>
<mappingresource="cn/itcast/domain/onetomany/Order.hbm.xml"/>
案例一: 数据保存
// 定义一个客户
Customer customer = newCustomer();
customer.setName("张三");
// 定义一个订单
Order order = new Order();
order.setMoney(2000d);
order.setAddr("西三旗 金燕龙");
// 建立对象之间关系
customer.getOrders().add(order);// 客户对象关联订单
order.setCustomer(customer);// 订单对象关联客户
//将瞬时对象变成持久对象
session.save(customer);
session.save(order);
问题: 代码中如果只保存customer或者 order(只执行一次save操作)
就会抛出异常
org.hibernate.TransientObjectException:object references an unsaved transient instance - save the transient instancebefore flushing: cn.itcast.domain.onetomany.Order
持久态对象关联了一个瞬时对象,因为客户关联了订单,订单又关联了客户,但是只将其中一个对象变为持久态了。
案例二 : 保存级联操作 (解决只save一次的操作)
有一个瞬时Customer 和 一个瞬时的 Order ,当save(customer) 后,Customer变为持久对象,通过关联关系,将Order对象也变为持久对象,为了达到这样的目的,需要设置cascade级联属性 cascade="save-update"
save-update作用:如果持久对象关联瞬时对象就执行save操作保存瞬时对象,如果持久对象关联离线对象就执行update 更新操作。
哪方发起级联就在哪方配置
用持久Customer 关联 瞬时Order(只用save customer对象),需要配置Customer.hbm.xml
* <setname="orders" cascade="save-update"> </set>
用持久Order 关联 瞬时Customer (只用save order对象),需要配置Order.hbm.xml
* <many-to-onecascade="save-update" ></many-to-one>
练习:对象导航
在Customer与Order的配置文件中都配置了级联保存,关系如下,order1关联了customer,customer关联了order2与order3
问题1:session.save(order1) 插入几条记录?----4,order1需要一条、customer需要一条、order2与order3 分别需要一条,因为order1中存在customer,customer中存在order2与order3
问题2: session.save(customer) 插入几条记录?----3,customer需要一条、order2与order3 分别需要一 条,因为customer中存在order2与order3
问题3: session.save(order2)插入几条记录?-----1,order2需要一条
案例三 : 级联删除
问题:如果customer 关联一个order, 删除customer ,order是否也会删除
答:能,删除customer时,先解除外键关联,再删除customer
问题:如果customer 关联一个order, 删除order ,customer是否会删除
答:能,删除order 时,直接删除,因为customer 不依赖于 order
问题: 如果删除customer用户,也想将customer关联的 Order全部删除叫做级联删除,怎么实现
需要配置Customer.hbm.xml <setname="orders" cascade="delete" >
注意:删除脱管对象没有级联效果,因为在托管的对象中没有关联对象,删除持久对象才可以!
如果Order.hbm.xml 配置中设置了外键不为空
<many-to-one ... not-null="true"></many-to-one>
注意:必须先存Customer,再存放Order,因为有了Customer才有外键的ID
注意:如果Customer关联 Order,无法删除脱管态 Customer
案例四 :孤儿删除
在一对多关系中,存在父子表关系 ,多方要依赖一方, 一方是父方,多方是子方, Customer是父方,Order是子方
如果一个customer 产生一个 Order, customer和order 解除了关系 , order信息不完整,成为孤儿, 将其删除
例:在Customer.hbm.xml(父方)文件中配置<setname="orders" cascade=" delete-orphan "> </set>
// 获得customer信息
Customer customer =(Customer) session.get(Customer.class, 1);
// 获得order 信息
Order order = (Order)session.get(Order.class, 2);
// 从集合解除关系
customer.getOrders().remove(order);
当接触关系后,order自动删除
* cascade取值 ,来源JPA , hibernate对JPA 进行实现 和 扩展
常用的属性值
save-update :持久对象记录保存或者更新 瞬时或脱管对象 (级联保存)
delete:删除持久对象,删除关联数据 (级联删除)
delete-orphan :当父方对象与 子方对象 解除关系,删除子方数据 (孤儿删除)
all : 代表除了delete-orphan之外 所有级联关系
all-delete-orphan : 所有记录关系(所以级联)
案例五 : inverse(反转)属性(外键列的操作)
外键列值默认为双向维护 (外键影响两个对象)
问题: 系统有张三、李四两个 customer,将1号订单的下单用户由张三改为李四(改变外键值)
order.setCustomer(customer); // 订单关联客户
customer.getOrders().add(order); // 客户关联订单
默认会产生两条update语句,会有一条多余的sql语句,原因为有两个关联语句,默认外键是双向维护的,两个sql都会设置外键的值,而实际开发中,并不需要双方都有外键的维护能力,在一对多关系中,通常在多方持有外键维护能力,因为多方只存在一个一方对象,切换外键比较简单。这时就要设置inverse属性了
设置inverse属性,使外键关系由一端维护
inverse 属性 默认值是 false ,由自己进行维护
实际开发中,在一的一方设置inverse="true" , 意味着一的一方放弃维护外键列权利,由多的一方来 维护
面试题: inverse和cascade的区别 ?
cascade 级联操作 (级联保存、更新、删除), 操作A时候,级联操作B
inverse 外键列维护权,如果设置inverse="true"放弃外键维护权
例: 设置了cascade级联保存,也设置了inverse为true后
// 定义一个客户
Customer customer = new Customer();
customer.setName("张三");
// 定义一个订单
Order order = new Order();
order.setMoney(2000d);
order.setAddr("西三旗 金燕龙");
// 建立对象之间关系
customer.getOrders().add(order); // 客户对象关联订单
session.save(customer);
保存customer 因为 cascade 关系,order也会保存 ,但是customer 放弃外键维护权,保存的order 外 键列就为null 。所以应该用订单关联用户。
一对多小结:
1、在一对多关系中,在一方配置cascade
级联保存 保存customer 保存order
级联删除 删除customer 删除order
孤儿删除 解除customer 和 order 关系,删除order
2、在一对多关系中,在一方配置inverse="true" , 将外键维护权交给多方
如果设置外键 not-null 同时在Customer.hbm.xml inverse="true"
1) 删除脱管的Customer但是Customer 被 Order 依赖就会报这个错误。
Causedby: java.sql.BatchUpdateException: Cannot delete or update a parent row: aforeign key constraint fails (`hibernate3day2`.`orders`, CONSTRAINT `FKC3DF62E55493DE91`FOREIGN KEY (`customer_id`) REFERENCES `customer` (`id`))
2) 如果删除持久态对象就可以。
一对一关联关系映射(少用)
公司和地址案例 ,一个公司对应一个地址,一个地址对应一个公司
两种建表方式 : 外键关联(在任意一方加上外键)、主键关联 (用任意一方的主键作为外键)
1、 外键关联
// 公司
public class Company {
private Integer id;
private String name;
// 对应一个地址
private Address address;
}
// 地址
public class Address {
private Integer id;
private String info;
// 对应一个公司
private Company company;
}
配置 Company.hbm.xml
<hibernate-mapping>
<classname="cn.itcast.domain.onetoonefk.Company"table="company">
<idname="id">
<generatorclass="native"></generator>
</id>
<propertyname="name"></property>
<!—如果在公司引入一个外键列 -->
<多对一元素,只要添加unique="true"就变成一对一,在公司对应的地址都唯一,就意味着是一对一了>
在Company的外键为address的id(主键),因此name为address,class也为address的class,column 为设置外键列的列名。
<many-to-onename="address" class="cn.itcast.domain.onetoonefk.Address"column="aid" unique="true"> </many-to-one>
</class>
</hibernate-mapping>
配置 Address.hbm.xml
<hibernate-mapping>
<class name="cn.itcast.domain.onetoonefk.Address"table="address">
<idname="id">
<generatorclass="native"></generator>
</id>
<propertyname="info"></property>
<!-- property-ref 属性指定使用被关联实体主键以外的字段作为关联字段(简单说就是放置外 键的的类中维护的关联类名)-->
<one-to-one name="company"class="cn.itcast.domain.onetoonefk.Company"property-ref="address"> </one-to-one>
</class>
</hibernate-mapping>
测试保存,保存要保证唯一性。不能保存两个id相同的对象。
2) 主键关联
一个表的主键,引用了另一个表主键(主键本身也是外键 ),类结构和外键关联是一样的
配置Company.hbm.xml
<hibernate-mapping>
<classname="cn.itcast.domain.onetoonepk.Company"table="company">
<idname="id">
<generatorclass="native"></generator>
</id>
<propertyname="name"></property>
主键关联
<one-to-onename="address"class="cn.itcast.domain.onetoonepk.Address"></one-to-one>
</class>
</hibernate-mapping>
配置Address.hbm.xml
<hibernate-mapping>
<classname="cn.itcast.domain.onetoonepk.Address"table="address">
<idname="id">
<!—foreign表示主键由外键生成 -->
(基于主键的映射策略:指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键. <param> 子元素指定使用当前持久化类的那个属性作为“对方”)
<generatorclass="foreign">
<!—property表示由哪个属性生成-->
<paramname="property">company</param>主键由company生成
</generator>
</id>
<propertyname="info"></property>
<!-- address 类 生成表 主键 使用 company 表主键 -->
<!-- address 表主键有外键约束, 引用company 表主键 -->
(采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其one-to-one 属性还应增加 constrained=“true”属性;另一端(company)增加one-to-one元素映射关联属性。
constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键)
<one-to-one name="company"class="cn.itcast.domain.onetoonepk.Company"constrained="true"> </one-to-one>
</class>
</hibernate-mapping>
多对多关系映射
以学生选课为例 ,一个学生可以选多门课程,一门课程可以被多个学生选修
多对多和一对多的区别:
一对多存在父子表关系,可以在一方配置级联 ,而多对多不存在父子表关系 ,多对多很少使用级联。
// 学生
public class Student {
private Integer id;
private String sname;
// 一个学生可以选多门课
private Set<Course>courses = new HashSet<Course>();
}
// 课程
public class Course {
private Integer id;
private String cname;
// 一门课 有多个学生选修
private Set<Student>students = new HashSet<Student>();
}
配置Student.hbm.xml
<hibernate-mapping>
<classname="cn.itcast.domain.manytomany.Student"table="student">
<idname="id">
<generatorclass="native"></generator>
</id>
<propertyname="sname"></property>
<!-- 通过table 设置中间关系表的表名 -->
<setname="courses" table="student_course">
<!-- 当前对象在关系表外键列名 -->
<keycolumn="student_id"></key>
<!-- manytomany 中 column代表集合对象在关系表外键列名 -->
<many-to-manyclass="cn.itcast.domain.manytomany.Course"column="course_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
配置Course.hbm.xml
<hibernate-mapping>
<classname="cn.itcast.domain.manytomany.Course"table="course">
<idname="id">
<generatorclass="native"></generator>
</id>
<propertyname="cname"></property>
<!—课程与学生相反-->
<setname="students" table="student_course">
<keycolumn="course_id"></key>
<many-to-manyclass="cn.itcast.domain.manytomany.Student"column="student_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
在hibernate.cfg.xml 进行映射
<mappingresource="cn/itcast/domain/manytomany/Student.hbm.xml"/>
<mappingresource="cn/itcast/domain/manytomany/Course.hbm.xml"/>
案例一 : 保存(建立关系向中间表insert )
student1.getCourses().add(course1);
course1.getStudents().add(student1);
* 建立数据双向关联 ,在多对多中默认每方都有外键维护权,每次建立关系,产生一条insert语句 ,双向关系默认产生两条insert语句
* 多对多发生一次关联,就会向中间表插入一条数据 ,所以不能写两次关联,以上保存会出错,改正方式是只留student1.getCourses().add(course1);或者course1.getStudents().add(student1);其中的一句关联就可以,或者在其中一方的配置中加入inverse="true",解除外键维护权。就不会报错了。
案例二: 解除关系 (直接删除中间表数据 )
让一号学生不选一号课程
Student student = (Student)session.get(Student.class, 1);
Course course = (Course)session.get(Course.class, 1);
// 只要一方解除关系,中间表数据就会 delete
student.getCourses().remove(course);
案例三 : 改变选课 ,将2号学生选修1号课程 变为2号学生选修2号课程
先解除,再建立
student.getCourses().remove(course1);
student.getCourses().add(course2);
案例四: 删除2号学生 ,2号学生有选课??
默认将中间表相关数据全部删除后,再删除学生
删除课程,也是先删除选课记录,后删除课程
案例五: 删除课程(级联),在课程端设置cascade="delete" ,能否删除学生 ??
如果多对多两端都配置cascade="delete" ---- 只要删除一条数据时,就会将所有数据都删除,因此在开发中很少使用多对多的级联