十、多表映射

0、内容补充:数据完整性

作用:防止用户的误操作。

实体完整性:主键。用于确定表中唯一的一条记录。

域完整性:表中的字段。

数据类型约束:

非空约束:

唯一约束:

参照完整性:

多表设计:表之间的关系

一对多(用的最多的)

多对多(比较重要)

一对一(实际开发中,根本不用)

1、一对多关系映射(非常重要)

1.1、单向多对一映射

 /**
* 客户的数据模型
* @author zhy
*
* 一个客户可以有多个订单
* 多个订单属于一个客户。
*
* 客户和订单之间的关系是一对多
*/
public class Customer implements Serializable { private Integer id;
private String name;
private Integer age; //一对多关系映射:一个客户可以有多个订单
private Set<Order> orders = new HashSet<Order>(0); public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
 /**
* 订单的数据模型
* @author zhy
*
* 一个客户可以有多个订单
* 多个订单属于一个客户。
*
* 客户和订单之间的关系是一对多
*/
public class Order implements Serializable { private Integer id;
private String ordernum;
private Float money; //多对一关系映射:多个订单属于一个客户。
private Customer customer; public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrdernum() {
return ordernum;
}
public void setOrdernum(String ordernum) {
this.ordernum = ordernum;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "Order [id=" + id + ", ordernum=" + ordernum + ", money=" + money + "]";
}
}
 <hibernate-mapping package="cn.itcast.domain">
<class name="Customer" table="T_CUSTOMERS">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="NAME"></property>
<property name="age" column="AGE"></property>
<!-- 一对多关系映射:
set元素:
作用:映射集合元素
属性:
name:映射实体类中的集合属性
table:指定对应的表
key元素:它是set的子元素
作用:就是用于指定外键的
属性:
column:指定外键字段的名称
one-to-many元素:它是set的子元素
作用:描述当前实体映射文件和set中指定属性之间的关系。
属性:
class:指定是从表的实体类名称
-->
<set name="orders" table="T_ORDERS" cascade="save-update,delete" inverse="true">
<key column="CUSTOMER_ID"></key>
<one-to-many class="Order"/>
</set>
</class>
</hibernate-mapping>
 <hibernate-mapping package="cn.itcast.domain">
<class name="Order" table="T_ORDERS">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="ordernum" column="ORDERNUM"></property>
<property name="money" column="MONEY"></property> <!-- 多对一关系映射
使用的元素:many-to-one
属性:
name:指定的是在实体类中要映射的属性
class:指定该属性所对应的类
column:指定外键字段。
-->
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID" cascade="save-update"></many-to-one>
</class>
</hibernate-mapping>

a、保存操作

 /*
* 保存操作
* 需求:
* 保存两个订单,同时保存一个客户
* 一定是先保存订单,再保存客户
* 问题:
* 当我们先保存订单,再保存客户时,会执行5条SQL语句
* Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?)
Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?)
Hibernate: insert into T_CUSTOMERS (NAME, AGE) values (?, ?)
Hibernate: update T_ORDERS set ORDERNUM=?, MONEY=?, CUSTOMER_ID=? where id=?
Hibernate: update T_ORDERS set ORDERNUM=?, MONEY=?, CUSTOMER_ID=? where id=?
解决办法:
实际上我们只需要三条insert语句就够了
在保存时,先保存主表数据,再保存从表数据
*/
@Test
public void test1(){
//数据准备
Customer c1 = new Customer();
c1.setName("test");
c1.setAge(18); Order o1 = new Order();
o1.setOrdernum("A001");
o1.setMoney(100f); Order o2 = new Order();
o2.setOrdernum("A002");
o2.setMoney(200f);
//建立单向多对一关联关系
o1.setCustomer(c1);
o2.setCustomer(c1); Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
//保存操作
s.save(c1);
s.save(o1);
s.save(o2); tx.commit();
s.close();
}

b、查询操作

 @Test
public void test2(){
//数据准备
Customer c1 = new Customer();//临时态
c1.setName("test2");
c1.setAge(28); Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
//查询id为1的订单
Order o1 = s.get(Order.class, 1);//持久态
tx.commit();
s.close();
}

c、持久态引用临时态报错

     /*
* 更新操作
* 需求:
* 先创建一个订单,然后查询出来一个客户。
* 建立客户和新订单的关联关系。
* 更新客户
* 问题:
* 一个持久态对象,关联了一个临时态的对象。
* 解决办法:
* 配置级联保存更新
* <set name="orders" table="T_ORDERS" cascade="save-update">
*/
@Test
public void test2(){ //创建一个新的订单
Order o1 = new Order();//临时态
o1.setOrdernum("A003");
o1.setMoney(100f); Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
//查询一个客户
Customer c1 = s.get(Customer.class, 1);//持久态
//建立双向关联关系
c1.getOrders().add(o1);
o1.setCustomer(c1);
//更新操作
s.update(c1); tx.commit();
s.close();
}

d、级联保存和更新

 /*
*
* 更新操作
* 需求:
* 创建一个新的客户,查询出来一个订单。把新客户和查询的订单建立关联关系。
* 然后更新订单
* 问题:
* 一个持久态对象,关联了一个临时态对象。会报错。
* 解决办法:
* 思路:在更新之前,先把临时态对象,转成持久态。(先执行保存,再执行更新)
* 执行级联保存更新。
* 在配置文件中配置:要想级联谁,就在对应的映射属性上配置
* cascade属性:就是用于配置级联操作的
* <many-to-one name="customer" class="Customer" column="CUSTOMER_ID" cascade="save-update">
*/
@Test
public void test2(){
//数据准备
Customer c1 = new Customer();//临时态
c1.setName("test2");
c1.setAge(28); Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
//查询id为1的订单
Order o1 = s.get(Order.class, 1);//持久态
//建立订单和客户的单向多对一关联关系
o1.setCustomer(c1);
//更新订单
s.update(o1);
tx.commit();
s.close();
}

1.2、双向关联映射

注意事项:

Hibernate要求在持久化类中定义集合属性时,必须把属性声明为接口类型,如Set、Map、List.声明接口类型可提高持久化类的透明性。(与延迟加载有关)

通常在定义集合属性时,直接初始化为一个实现类的实例。可避免空指针异常。

a、双向关联关系保存操作

 /*
* 保存操作
* 需求:
* 先保存客户,再保存订单
问题:
当我们建立了双向关联关系之后,就算是先保存主表,再保存从表,也是会产生5条SQL语句
Hibernate: insert into T_CUSTOMERS (NAME, AGE) values (?, ?)
Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?)
Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?)
Hibernate: update T_ORDERS set CUSTOMER_ID=? where id=?
Hibernate: update T_ORDERS set CUSTOMER_ID=? where id=?
解决办法:
思路:让主表的集合放弃维护关联关系的权利。
操作方式:注释上
c1.getOrders().add(o1);
c1.getOrders().add(o2);
*/
@Test
public void test1(){
//数据准备
Customer c1 = new Customer();
c1.setName("testC");
c1.setAge(18); Order o1 = new Order();
o1.setOrdernum("C001");
o1.setMoney(100f); Order o2 = new Order();
o2.setOrdernum("C002");
o2.setMoney(200f); //建立双向一对多关联关系
o1.setCustomer(c1);
o2.setCustomer(c1); //c1.getOrders().add(o1);
//c1.getOrders().add(o2); Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
//保存操作
s.save(c1);
s.save(o1);
s.save(o2); tx.commit();
s.close();
}

b、双向关联关系,持久态关联临时态的问题及解决

 /*
* 更新操作
* 需求:
* 先创建一个订单,然后查询出来一个客户。
* 建立客户和新订单的关联关系。
* 更新客户
* 问题:
* 一个持久态对象,关联了一个临时态的对象。
* 解决办法:
* 配置级联保存更新
* <set name="orders" table="T_ORDERS" cascade="save-update">
*/
@Test
public void test2(){ //创建一个新的订单
Order o1 = new Order();//临时态
o1.setOrdernum("A003");
o1.setMoney(100f); Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
//查询一个客户
Customer c1 = s.get(Customer.class, 1);//持久态
//建立双向关联关系
c1.getOrders().add(o1);
o1.setCustomer(c1);
//更新操作
s.update(c1); tx.commit();
s.close();
}

c、变更关系(关于双向关联的处理办法)

 /*
* 需求:变更关系
* 把id为1的订单,从属于2号客户,改为属于1号客户
* 解决办法:
* 使用配置的方式,来实现让有集合的一方,放弃维护的权利
* inverse:是否放弃维护的权利
* 取值:true放弃 和 false 不放弃(默认值)。
* inverse用于应该只出现在set元素上
* <set name="orders" table="T_ORDERS" cascade="save-update" inverse="true" >
*/
@Test
public void test3(){
Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
Customer c1 = s.get(Customer.class, 1);//查询出来1号客户
Order o1 = s.get(Order.class, 1);//查询出啦1号订单
//建立双向关联关系
c1.getOrders().add(o1);
o1.setCustomer(c1);
//更新操作
s.update(c1); tx.commit();
s.close(); //System.out.println(c1.getOrders());
}

为了保持程序的健壮性,建议是双向关联,就建立双向关联的关系。

弊端:当我们使用了双向关联时,会有冗余的SQL语句执行,造成程序的效率下降。

解决办法:不要双向维护关联关系,让少的一方放弃维护权利。(但不要在代码中修改,而是写在配置文件中)

d、解除关系

 /*
* 解除关系
* 需求:
* 把1订单和1号客户之间的关系解除
*/
@Test
public void test6(){
Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
Customer c1 = s.get(Customer.class,1);
Order o1 = s.get(Order.class, 1); //解除1号订单和1号客户之间的关系
c1.getOrders().remove(o1);
o1.setCustomer(null); tx.commit();
s.close();
}

e、删除操作

/*
* 需求:
* 删除一个客户
*
* 在直接删除客户的时候,如果客户的集合属性(set元素)上没有配置inverse=true,
* 会直接把客户删除掉,同时把订单中关联改该客户的id置为null
*
* 在直接删除客户的时候,如果客户的集合属性(set元素)上配置了inverse=true,
* 如果有订单引用该客户的话,则不能删除成功。
*
* 原因:
* 因为客户的集合属性已经放弃维护和订单之间的关联关系,也就是说,它将不能把
* 订单的CUSTOMER_ID列置为null。
*/
@Test
public void test5(){
Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
Customer c1 = s.get(Customer.class,3);
s.delete(c1);
tx.commit();
s.close();
} /*
* 需求:
* 删除一个订单
*
* 可以删除成功
*/
@Test
public void test4(){
Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
Order o1 = s.get(Order.class, 1);
s.delete(o1);
tx.commit();
s.close();
}
/*
* 级联删除:
*
* <set name="orders" table="T_ORDERS" cascade="save-update,delete" inverse="true">
*/
@Test
public void test8(){
Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
Customer c1 = s.get(Customer.class,2);
s.delete(c1);
tx.commit();
s.close();
} /*
* 孤儿数据
* 在一对多对象关系映射中,数据具有父子关系,当子数据和父数据之间失去了关联关系。子数据被称之为孤儿数据
* 孤儿删除
* 在hibernate中认为孤儿数据,是没有存在的意义,理应删除。
* 需要在配置文件中配置
* 孤儿删除的配置:cascade="delete-orphan"
* <set name="orders" table="T_ORDERS" cascade="save-update,delete-orphan" inverse="true">
*/
@Test
public void test7(){
Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
Customer c1 = s.get(Customer.class,1);
Order o1 = s.get(Order.class, 1); //解除1号订单和1号客户之间的关系
c1.getOrders().remove(o1);
o1.setCustomer(null); //s.update(c1);
tx.commit();//有快照机制的存在
s.close();
}

2、映射多对多

2.1、多对多单项映射

a、保存操作

 /**
* 学生的实体模型
* @author zhy
*
* 一个教师可以教授多个学生
* 一个学生可以被多个教师教授
*
* 教师和学生的关系是多对多
*/
public class Student implements Serializable { private Integer id;
private String name;
private String gender; //多对多关系映射
private Set<Teacher> teachers = new HashSet<Teacher>(0); public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Set<Teacher> getTeachers() {
return teachers;
}
public void setTeachers(Set<Teacher> teachers) {
this.teachers = teachers;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", gender=" + gender + "]";
}
}
 <hibernate-mapping package="cn.itcast.domain">
<class name="Student" table="T_STUDENTS">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="NAME"></property>
<property name="gender" column="GENDER"></property>
<!-- 多对多关系映射
set元素:
作用:就是用于映射集合属性
属性:
name:指定集合属性名称
table:指定的是关联关系表
key元素:
作用:就是用于指定外键的
属性:
column:指定当前实体类在关联关系表中的外键
many-to-many元素:
作用:指定和对方之间的关系是多对多
属性:
class:指定对方的实体类名称
column:指定对方在关联关系表中的外键-->
<set name="teachers" table="t_teacher_student_ref" cascade="save-update,delete" >
<key column="STUDENT_ID"/>
<many-to-many class="Teacher" column="TEACHER_ID"/>
</set>
</class>
</hibernate-mapping>
 /**
* 教师的实体模型
* @author zhy
*
* 一个教师可以教授多个学生
* 一个学生可以被多个教师教授
*
* 教师和学生的关系是多对多
*
*/
public class Teacher implements Serializable { private Integer id;
private String name;
private Float salary; //多对多关系映射
private Set<Student> students = new HashSet<Student>(0); public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getSalary() {
return salary;
}
public void setSalary(Float salary) {
this.salary = salary;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Teacher [id=" + id + ", name=" + name + ", salary=" + salary + "]";
}
}
 <hibernate-mapping package="cn.itcast.domain">
<class name="Teacher" table="T_TEACHERS">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="NAME"></property>
<property name="salary" column="SALARY"></property>
<!-- 多对多关系映射 -->
<set name="students" table="t_teacher_student_ref" cascade="save-update,delete" inverse="true">
<key column="TEACHER_ID"/>
<many-to-many class="Student" column="STUDENT_ID"/>
</set>
</class>
</hibernate-mapping>
 /*
* 保存操作
* 需求:
* 创建:1号学生 2号学生 3号学生
* 1号教师 2号教师
* 建立关联关系
* 1号教师教过 1号学生和2号学生
* 2号教师教过 2号学生和3号学生
* 执行保存
*
* 多对多关系映射,在执行保存操作时,需要有一方放弃维护的权利。
* 任意一方
*/
@Test
public void test1(){
//准备数据
Teacher t1 = new Teacher();
t1.setName("hqy");
t1.setSalary(500f); Teacher t2 = new Teacher();
t2.setName("lx");
t2.setSalary(1000f); Student s1 = new Student();
s1.setName("王占青");
s1.setGender("male"); Student s2 = new Student();
s2.setName("秦鹏飞");
s2.setGender("male"); Student s3 = new Student();
s3.setName("郑恒明");
s3.setGender("male"); //建立关联关系
t1.getStudents().add(s1);
t1.getStudents().add(s2);
s1.getTeachers().add(t1);
s2.getTeachers().add(t1); t2.getStudents().add(s2);
t2.getStudents().add(s3);
s2.getTeachers().add(t2);
s3.getTeachers().add(t2); Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction(); //保存操作
s.save(t1); tx.commit();
s.close();
}

b、删除操作

未配置级联:

 /*
* 删除操作:
* 删除id为1的教师
* 注意事项:
* 在多对多的删除操作时,不要配置级联删除。
*/
@Test
public void test2(){
Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
Teacher t1 = s.get(Teacher.class, 1);
s.delete(t1);
tx.commit();
s.close();
}

如果在Teacher.hbm.xml中配置了级联,那么删除时会先删除teacher1这个老师,然后去删关联表中的数据。由于有级联,还会去学生表中删除对应的学生数据,但是由于student2这个学生被teacher2老师引用着所以删不掉。会报错。

进一步,如果我们配置了双向级联,那么结果将是所有数据全没了。

结论:

 1、多对多映射,不要配置级联删除。(可以配置级联保存)

2、双向多对多映射时,不要双向维护关系。或让任意一方放弃权利,注意在配置文件中配置

3、一对一映射

3.1、按照外键关联

3.2、按照主键关联

Java实战之02Hibernate-04多表映射的更多相关文章

  1. 「小程序JAVA实战」小程序的表单组件(25)

    转自:https://idig8.com/2018/08/18/xiaochengxujavashizhanxiaochengxudebiaodanzujian25/ 来说下 ,小程序的基础组件.源码 ...

  2. Java实战:教你如何进行数据库分库分表

    摘要:本文通过实际案例,说明如何按日期来对订单数据进行水平分库和分表,实现数据的分布式查询和操作. 本文分享自华为云社区<数据库分库分表Java实战经验总结 丨[绽放吧!数据库]>,作者: ...

  3. java基础复习-自定义注解4(结合JDBC技术,打造类表映射微框架)

    写在前面: 1.该框架为自己所写的第一个框架类产品,可能有着许多不足的地方,读者可以到评论区指出.同时,该微框架的源码也会开源至博客中,够后来的学习者借鉴.由于该框架逻辑结构稍些复杂,不可能花大量篇幅 ...

  4. Hibernate 表映射 主键生成策略与复合主键

    主要分析三点: 一.数据表和Java类的映射 : 二.单一主键映射和主键的生成策略 : 三.复合主键的表映射 : 一.数据表和Java类的映射  Hibernate封装了数据库DDL语句,只需要将数据 ...

  5. 详解Java的MyBatis框架中SQL语句映射部分的编写

    这篇文章主要介绍了Java的MyBatis框架中SQL语句映射部分的编写,文中分为resultMap和增删查改实现两个部分来讲解,需要的朋友可以参考下 1.resultMap SQL 映射XML 文件 ...

  6. hibernate Java 时间和日期类型的 Hibernate 映射

    基础知识: 在 Java 中, 代表时间和日期的类型包含: java.util.Date 和 java.util.Calendar. 此外, 在 JDBC API 中还提供了 3 个扩展了 java. ...

  7. MyBatis快速入门(1):搭建环境和单表映射

    一.MyBatis简介    一说起对象关系映射框架,大家第一时间想到的肯定是Hibernate.Hibernate作为一个著名的框架,功能十分强大.我们只需要配置好实体类和数据表之间的关系,Hibe ...

  8. EJB_开发单表映射的实体bean

    开发单表映射的实体bean 实体bean 它属于java持久化规范(JPA)里的技术,实体bean通过元数据在Javabean和数据库表之间建立起映射关系,然后Java程序员就可以随心所欲的使用面向对 ...

  9. Java 时间和日期类型的 Hibernate 映射

    以下情况下必须显式指定 Hibernate 映射类型 一个 Java 类型可能对应多个 Hibernate 映射类型. 例如: 如果持久化类的属性为 java.util.Date 类型, 对应的 Hi ...

随机推荐

  1. 【STL源码学习】STL算法学习之二

    第一章:前言 学习笔记,记录学习STL算法的一些个人所得,在以后想用的时候可以快速拾起. 第二章:明细 copy 函数原型: template <class InputIterator, cla ...

  2. 在Entity Framework中重用现有的数据库连接字符串

    本文转载:http://www.cnblogs.com/dudu/archive/2011/01/29/entity_framework_connection_string.html 如果EF在使用实 ...

  3. 关于Mysql Can't connect to mysql server on localhost(10061)的问题解决

    这个问题很烦,试了网上很多朋友的方法,还是不行,大家都知道卸载mysql再想装就不那么容易了(虽然我卸载安装无数次都成功了),好了,不废话了, 如果出现这种问题,不要急,找到mysql的安装包  例如 ...

  4. BZOJ 1927: [Sdoi2010]星际竞速 费用流

    1927: [Sdoi2010]星际竞速 Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/pr ...

  5. Android 如何添加一种锁屏方式

    前言          欢迎大家我分享和推荐好用的代码段~~ 声明          欢迎转载,但请保留文章原始出处:          CSDN:http://www.csdn.net        ...

  6. 【转贴】gdb中的信号(signal)相关调试技巧

    一篇不错的帖子,讲的是gdb中的信号(signal)相关调试技巧 转自Magic C++论坛  http://www.magicunix.com/index_ch.html  http://www.m ...

  7. Linux性能及调优指南(翻译)之Linux内存架构

    http://blog.csdn.net/ljianhui/article/details/46734115

  8. WordPress搭建Personal Blog

    早就想搭建一个专属于自己的博客了,用来记录自己生活.学习的点点滴滴.之所以选WordPress,主要是因为它可以支持Latex,而且特别喜欢其简约的风格. WordPress有个the famous ...

  9. Java 之 调用.Net的 WebService 整理

    最近做一个 java 调用 .net 服务的项目,其中 .net做了一个WebService,需要java来调用. 最开始.net的Service代码如下: using System; using S ...

  10. 解析搜狗词库(python)

    #!/usr/bin/python # -*- coding: utf-8 -*- import struct import sys import binascii import pdb #搜狗的sc ...