天天看点

Hibernate关联关系映射

在系统中、关系型数据库中 描述数据之间关系 共有三种 一对多、一对一、多对多

在软件的设计阶段,数据库建模阶段 ---- 绘制E-R图 实体关系图

* 企业中最流行数据库建模工具 PowerDesigner

* PD工具内部 提供三种常用图  概念图(E-R图) 、 面向对象图、 物理数据表图 , 三种图之间相互转换

E-R图一般用以下图形来表示

Hibernate关联关系映射

三种数据关系建表原则

Hibernate关联关系映射

表与表之间关系,通过外键描述

类对象之间关系,通过内存地址引用就可以了        

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

Hibernate关联关系映射

问题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 进行实现 和 扩展             

Hibernate关联关系映射

常用的属性值

         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" ---- 只要删除一条数据时,就会将所有数据都删除,因此在开发中很少使用多对多的级联