文档章节

hibernate系列(二)一对多的关联关系

乒乓狂魔
 乒乓狂魔
发布于 2015/02/07 10:24
字数 2812
阅读 78
收藏 2
点赞 1
评论 0
上一篇文章介绍了基本知识后,本篇该介绍下现实中的一对多的关联关系。如Customer和Order,一个Customer可以拥有多个Order,每个Order只属于一个Customer。这样就存在几种表示形式,可以分为单向关联和双向关联。
形式1:Order拥有一个Customer引用,这种属于单向关联
形式2:Customer拥有Order的集合,这种也属于单向关联
形式3:Order拥有一个Customer引用,同时Customer拥有Order集合,这种属于双向关联

先来说说形式1:Order拥有一个Customer引用
Customer还是上一篇文章的形式:

public class Customer {

	private Long id;
	private String name;
	private String email;
	private Timestamp registeredTime;
//省略get、set方法
}

customer表的信息是:
CREATE TABLE `customer` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) DEFAULT '',
  `email` varchar(45) DEFAULT '',
  `registeredTime` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

它的映射文件为Customer.hbm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
          "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
	<class name="com.ligang.domain.Customer" table="customer">
		<id name="id" column="id" type="long">
			<generator class="identity"/>
		</id>
		<property name="name" column="name" type="string"/>
		<property name="email" column="email" type="string"/>
		<property name="registeredTime" column="registeredTime" type="timestamp"/>
	</class>
</hibernate-mapping>

然后是Order类:
public class Order {

	private Long id;
	private String orderNumber;
	private Customer customer;
//省略get、set方法
}

order表的信息如下:
CREATE TABLE `order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `orderNumber` varchar(45) DEFAULT '',
  `customer_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

对应的映射文件为Order.hbm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
          "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
	<class name="com.ligang.domain.Order" table="order">
		<id name="id" column="id" type="long">
			<generator class="identity"/>
		</id>
		<property name="orderNumber" column="orderNumber" type="string"/>
		<many-to-one name="customer" column="customer_id" class="com.ligang.domain.Customer"/>
	</class>
</hibernate-mapping>

然后我们看下这种单向关联的形式的增添和查询。增添如下:
@Test
	public void testAddOrder(){
		Session session=hibernateDao.getSession();
		Transaction tx=session.beginTransaction();
		
		Customer customer=new Customer();
		customer.setName("校长");
		customer.setEmail("sdsd@qq.com");
		customer.setRegisteredTime(getCurrentTime());
		session.save(customer);
		System.out.println(customer.getId()+":"+customer.getName());
		
		Order order1=new Order();
		order1.setCustomer(customer);
		order1.setOrderNumber("第一个订单");
		
		Order order2=new Order();
		order2.setCustomer(customer);
		order2.setOrderNumber("第二个订单");
		session.save(order1);
		session.save(order2);
		
		tx.commit();
		session.close();
	}

这个过程先保存了Customer对象,然后保存了Order对象,此时一切正常,如下sql语句:
Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)
33:校长
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

然而当我们不保存Customer对象时,即不执行上述的session.save(customer),则会报如下错误:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.ligang.domain.Customer

说Order对象引用了未保存的Customer对象,如果希望customer_id可以为空,单独保存Order,则需要在Order.hbm.xml中指明customer_id字段是可以为空的,即not-null="false"(默认就是false,所以不用再设置)。同时,不能执行setCustomer(customer);这样的语句,要保留Customer为空,一旦不为空,并且属于没有保存的对象,则就会报上述错误。
如果希望,在保存Order的时候,同时级联的保存Customer对象,则需要在Order.hbm.xml中指明customer_id字段的cascade="save-update",此时就会保存或者更新Order的同时级联的保存
Customer对象,如下sql:

null:校长
Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

对于查询Order,如下:
@Test
	public void testGetOrder(){
		Session session=hibernateDao.getSession();
		Transaction tx=session.beginTransaction();
		
		Order order=(Order)session.get(Order.class,9L);
		System.out.println(order.getOrderNumber());
		System.out.println(order.getCustomer().getName());
		
		tx.commit();
		session.close();
	}

查询sql如下:
Hibernate: select order0_.id as id1_1_0_, order0_.orderNumber as orderNum2_1_0_, order0_.customer_id as customer3_1_0_ from hibernate.order order0_ where order0_.id=?
第二个订单
Hibernate: select customer0_.id as id1_0_0_, customer0_.name as name2_0_0_, customer0_.email as email3_0_0_, customer0_.registeredTime as register4_0_0_ from hibernate.customer customer0_ where customer0_.id=?
校长

会先去查询Order信息,然后当你使用到customer时,才会进一步去查询Customer信息,这就是延迟加载,此时的customer对象仅仅是一个代理对象。如果你想查询Order信息时同时把Customer信息查询出来,就需要在Order.hbm.xml的customer_id字段中指定lazy="false",默认是"proxy"。如下效果:
Hibernate: select order0_.id as id1_1_0_, order0_.orderNumber as orderNum2_1_0_, order0_.customer_id as customer3_1_0_ from hibernate.order order0_ where order0_.id=?
Hibernate: select customer0_.id as id1_0_0_, customer0_.name as name2_0_0_, customer0_.email as email3_0_0_, customer0_.registeredTime as register4_0_0_ from hibernate.customer customer0_ where customer0_.id=?
第二个订单
校长

先把所有信息都查出来,然后供使用。目前lazy的取值有三个,false、proxy、no-proxy。false代表:查询Order信息时,立马把Customer的信息查出来,此时order.getCustomer()就是一个Customer对象。proxy代表:查询Order信息时,并不会去查询Customer信息,只有当你用到order的Customer时才会去查询,此时order.getCustomer()返回的是一个代理对象。no-proxy:目前的效果和proxy一样,这一块我还需要继续研究。


然后来说说形式2:Customer拥有Order的集合
其中Order类为:

public class Order {

	private Long id;
	private String orderNumber;
//略get、set方法
}

Order对应的映射文件为:
<hibernate-mapping>
	<class name="com.ligang.domain.Order" table="order">
		<id name="id" column="id" type="long">
			<generator class="identity"/>
		</id>
		<property name="orderNumber" column="orderNumber" type="string"/>
	</class>
</hibernate-mapping>

Customer对应的类为:
public class Customer {

	private Long id;
	private String name;
	private String email;
	private Timestamp registeredTime;
	private Set<Order> orders;
//略get、set方法
}

Customer对应的映射文件为:
<hibernate-mapping>
	<class name="com.ligang.domain.Customer" table="customer">
		<id name="id" column="id" type="long">
			<generator class="identity"/>
		</id>
		<property name="name" column="name" type="string"/>
		<property name="email" column="email" type="string"/>
		<property name="registeredTime" column="registeredTime" type="timestamp"/>
		<set name="orders" cascade="save-update">
			<key column="customer_id"/>
			<one-to-many class="com.ligang.domain.Order"/>
		</set>
	</class>
</hibernate-mapping>

形式2的增添为:
@Test
	public void testAddCustomerInverse(){
		Session session=hibernateDao.getSession();
		Transaction tx=session.beginTransaction();
		
		Customer customer=new Customer();
		customer.setName("校长");
		customer.setEmail("sdsd@qq.com");
		customer.setRegisteredTime(getCurrentTime());
		Set<Order> orders=new HashSet<Order>();
		
		Order order1=new Order();
		order1.setOrderNumber("第一个订单");
		
		Order order2=new Order();
		order2.setOrderNumber("第二个订单");
		
		orders.add(order1);
		orders.add(order2);
		customer.setOrders(orders);
		
		session.save(customer);
		
		tx.commit();
		session.close();
	}

会产生如下sql语句:
Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)
Hibernate: insert into hibernate.order (orderNumber) values (?)
Hibernate: insert into hibernate.order (orderNumber) values (?)
Hibernate: update hibernate.order set customer_id=? where id=?
Hibernate: update hibernate.order set customer_id=? where id=?

当保存完Customer对象后,由于cascade="save-update"设置,会级联的保存Customer所包含的Order集合,但是此时的Order没有保存customer_id属性,在事务提交前,才会去检查和更新Order的customer_id属性,这个关系是有Customer维护的,当Customer的映射文件中<set>标签的inverse="true"时,即Customer放弃维护这个关系时,此时就只有三个insert语句了:
Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)
Hibernate: insert into hibernate.order (orderNumber) values (?)
Hibernate: insert into hibernate.order (orderNumber) values (?)

此时的数据就会不完整了,所以inverse必须为true,由Customer去维护关系,此时又会造成多出两个update语句,无法消除,所以一般不采用形式2,关联关系尽量由many的一方来维护。
对于形式2的查询,可以从Customer去查到它所包含的Order,但是从Order就查不到它所在的Customer,只能手写sql去查出customer_id的值,然后查出对应的Customer,所以更不会采用形式2了。

说完了形式2,来说说形式3:Order拥有一个Customer引用,同时Customer拥有Order集合
若仍想使用上述的数据库的表结构,即order表中含有一个customer_id信息。则映射文件的配置要做相应的修改。
对于Customer类:

public class Customer {

	private Long id;
	private String name;
	private String email;
	private Timestamp registeredTime;
	private List<Order> orders;
//略get、set方法
}

对于Customer的映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
          "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
	<class name="com.ligang.domain.Customer" table="customer">
		<id name="id" column="id" type="long">
			<generator class="identity"/>
		</id>
		<property name="name" column="name" type="string"/>
		<property name="email" column="email" type="string"/>
		<property name="registeredTime" column="registeredTime" type="timestamp"/>
		<list name="orders" cascade="save-update">
			<key column="customer_id"/>
			<list-index base="0" column="list_index"/>
			<one-to-many class="com.ligang.domain.Order"/>
		</list>
	</class>
</hibernate-mapping>

比之前多加了一个list,这里有一个无法理解的地方。对于List,是虽说是有序的,但是有时候我并不看重这个顺序,而它这里强制性加上了一个<list-index>标签或者<index>标签,不然就报错,这个标签的作用就是对于Customer的List<Order>集合从base开始编号,然后把这个编号强制到数据库order表中的column="list_index"指定的字段上,也就是说,数据库的Order表必须多余的添加一个list_index字段用来存储编号。我无法忍受这样的强制性,各种各样的需求在很多情况下都存在的,不要以为你认为有几种情况就只有几种情况。为了不需要这样的字段,我们只能使用Set,这就必须要求Customer中不是List<Order>而是Set<Order>。所以这一块,我无法理解,还希望知道的网友帮我解答这个疑问。
对于Order没有变化和上面的一样。
测试添加:

@Test
	public void testTwoRelation(){
		Session session=hibernateDao.getSession();
		Transaction tx=session.beginTransaction();
		
		Customer customer=new Customer();
		customer.setName("校长");
		customer.setEmail("sdsd@qq.com");
		customer.setRegisteredTime(getCurrentTime());
		List<Order> orders=new ArrayList<Order>();
		
		Order order1=new Order();
		order1.setOrderNumber("第一个订单");
		
		Order order2=new Order();
		order2.setOrderNumber("第二个订单");
		
		orders.add(order1);
		orders.add(order2);
		customer.setOrders(orders);
		
		session.save(customer);
		
		tx.commit();
		session.close();
	}

会看到如下的sql语句:
Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)
Hibernate: update hibernate.order set customer_id=?, list_index=? where id=?
Hibernate: update hibernate.order set customer_id=?, list_index=? where id=?

为什么会有两个update语句呢?
还是因为那个list-index标签要存储编号到order的list_index字段上。
这里我就更该List为Set,同时还要重写Order类的equals和hashcode方法,要满足orderNumber和customer_id都一致时视为重复,为什么这样做可以看我的之前的一篇专门讲述这个事情的文章(http://lgbolgger.iteye.com/blog/2115446)。
更该为set后又产生了一个新的问题,上述sql语句中insert into hibernate.order (orderNumber, customer_id) values (?, ?)其中customer_id是有值的,但是更该为Set后就变成null,导致后来又补上两个update语句把customer_id值补上去了。
如下sql:

Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)
Hibernate: update hibernate.order set customer_id=? where id=?
Hibernate: update hibernate.order set customer_id=? where id=?

越来越感觉挺简单的东西让hibernate搞的逻辑更加复杂了。这时候就需要这样做了,建立两者的双重关联,并且在配置文件中让one方放弃维护两者关系,在set标签中使用inverse="true"。即在Customer.hbm.xml中如下更该:
<hibernate-mapping>
	<class name="com.ligang.domain.Customer" table="customer">
		<id name="id" column="id" type="long">
			<generator class="identity"/>
		</id>
		<property name="name" column="name" type="string"/>
		<property name="email" column="email" type="string"/>
		<property name="registeredTime" column="registeredTime" type="timestamp"/>
		<set name="orders" cascade="save-update" inverse="true">
			<key column="customer_id"/>
			<one-to-many class="com.ligang.domain.Order"/>
		</set>
	</class>
</hibernate-mapping>

在代码中需要建立双重关系,如下:
@Test
	public void testTwoRelation(){
		Session session=hibernateDao.getSession();
		Transaction tx=session.beginTransaction();
		
		Customer customer=new Customer();
		customer.setName("校长");
		customer.setEmail("sdsd@qq.com");
		customer.setRegisteredTime(getCurrentTime());
		Set<Order> orders=new HashSet<Order>();
		
		Order order1=new Order();
		order1.setCustomer(customer);
		order1.setOrderNumber("第一个订单");
		
		Order order2=new Order();
		order2.setCustomer(customer);
		order2.setOrderNumber("第二个订单");
		
		orders.add(order1);
		orders.add(order2);
		customer.setOrders(orders);
		
		session.save(customer);
		
		tx.commit();
		session.close();
	}

此时,一切就正常了,只有三个insert语句,如下:
Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)
Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

对于形式3的查询,从Customer查Order:
@Test
	public void testGetTwoRelation(){
		Session session=hibernateDao.getSession();
		Transaction tx=session.beginTransaction();
		
		Customer customer=(Customer)session.get(Customer.class,48L);
		System.out.println(customer.getName());
		for(Order order:customer.getOrders()){
			System.out.println(order.getOrderNumber());
		}
		tx.commit();
		session.close();
	}

此时也可以通过设定Customer.hbm.xml中set标签的lazy="false",来提前查询Order,默认是用到Order时才会去查询。
从Order到Customer的查询也是类似的。

若想转载请注明出处
作者:乒乓狂魔

© 著作权归作者所有

共有 人打赏支持
乒乓狂魔
粉丝 971
博文 105
码字总数 271356
作品 0
长宁
程序员
Java程序员从笨鸟到菜鸟之(五十四)细谈Hibernate(五)Hibernate一对多关系映射

前几篇系列博客: 细谈Hibernate(一)hibernate基本概念和体系结构 细谈Hibernate(二)开发第一个hibernate基本详解 细谈Hibernate(三)Hibernate常用API详解及源码分析 细谈Hibernate(四...

长平狐 ⋅ 2012/11/12 ⋅ 0

Java程序员从笨鸟到菜鸟之(五十八)细谈Hibernate(九)hibernate一对一关系映射

一对一关系映射即为关系双方都含有对方一个引用,其实在生活中一对一关系也很常见,比如人和身份证,学生和学号等,都是一对一的关系映射,一对一映射分为单向的和双向的,没种关系映射又可以...

长平狐 ⋅ 2012/11/12 ⋅ 0

Hibernate框架学习之注解配置关系映射

上篇文章我们通过注解对映射了单个实体类,但是具体项目中往往实体类之间又是相互关联的,本篇文章就是从实体类之间存在的不同关联角度,具体学习下如何映射他们之间的关联,主要涉及内容如下...

Single_YAM ⋅ 2017/11/15 ⋅ 0

Hibernate中的cascade和inverse

这两个属性都用于一多对或者多对多的关系中。而inverse特别是用于双向关系,在单向关系中我们并不需要。 Cascade代表是否执行级联操作,Inverse代表是否由己方维护关系。 Cascade: Cascade属...

roockee ⋅ 2012/12/20 ⋅ 0

Hibernate映射——一对多关联映射(七)

一对多关联映射 映射原理 一对多关联映射和多对一的关联映射的映射原理是一致的,都是在多的一端加入一个外键,指向一的一端。关联关系都是由多端维护,只是在写映射时发生了变化。 多对一和...

architect刘源源 ⋅ 01/11 ⋅ 0

Hibernate(三)

一对多的双向 Class.hbm.xml <?xml version="1.0" encoding="utf-8"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net......

Hu_Captain ⋅ 2015/09/08 ⋅ 0

Hibernate(二)

对象的状态 代码 public class StateTest extends HiberanteUtils{/** session.save方法把一个临时状态的对象转化成持久化状态的对象 */@Testpublic void testSavePerson(){Session session ...

Hu_Captain ⋅ 2015/09/07 ⋅ 0

Hibernate利用关联关系操纵对象

Hibernate利用关联关系操纵对象 数据对象之间关联关系有一对一、一对多及多对多关联关系。在数据库操作中,数据对象之间的关联关系使用JDBC处理很困难。本节讲解如何在Hibernate中处理这些对...

微笑的江豚 ⋅ 2014/10/07 ⋅ 0

Hibernate 级联和关系维护

1、Hibernate中的级联操作: 在Hibernate中,针对持久化实体的配置文件中有Cascade这样一个属性,就是级联,也就是说在操作当前实体时,针对当前实体的操作会影响到相应配置的关联实体,比如...

Winnie007 ⋅ 2015/08/28 ⋅ 0

hibernate 基础

一、Hibernate工作原理: 读取并解析配置文件 读取并解析映射信息,创建SessionFactory 打开Sesssion 创建事务Transation 持久化操作 提交事务 关闭Session 关闭SesstionFactory 二、Hiberna...

scofi06 ⋅ 2010/09/01 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Day 17 vim简介与一般模式介绍

vim简介 vi和Vim的最大区别就是编辑一个文件时vi不会显示颜色,而Vim会显示颜色。显示颜色更便于用户编辑,凄然功能没有太大的区别 使用 yum install -y vim-enhanced 安装 vim的三种常用模式...

杉下 ⋅ 58分钟前 ⋅ 0

【每天一个JQuery特效】根据可见状态确定是否显示或隐藏元素(3)

效果图示: 主要代码: <!DOCTYPE html><html><head><meta charset="UTF-8"><title>根据可见状态确定 是否显示或隐藏元素</title><script src="js/jquery-3.3.1.min.js" ty......

Rhymo-Wu ⋅ 今天 ⋅ 0

OSChina 周四乱弹 —— 初中我身体就已经垮了,不知道为什么

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @加油东溪少年 :下完这场雨 后弦 《下完这场雨》- 后弦 手机党少年们想听歌,请使劲儿戳(这里) @马丁的代码 :买了日本 日本果然赢了 翻了...

小小编辑 ⋅ 今天 ⋅ 12

浅谈springboot Web模式下的线程安全问题

我们在@RestController下,一般都是@AutoWired一些Service,由于这些Service都是单例,所以并不存在线程安全问题。 由于Controller本身是单例模式 (非线程安全的), 这意味着每个request过来,...

算法之名 ⋅ 今天 ⋅ 0

知乎Java数据结构

作者:匿名用户 链接:https://www.zhihu.com/question/35947829/answer/66113038 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 感觉知乎上嘲讽题主简...

颖伙虫 ⋅ 今天 ⋅ 0

Confluence 6 恢复一个站点有关使用站点导出为备份的说明

推荐使用生产备份策略。我们推荐你针对你的生产环境中使用的 Confluence 参考 Production Backup Strategy 页面中的内容进行备份和恢复(这个需要你备份你的数据库和 home 目录)。XML 导出备...

honeymose ⋅ 今天 ⋅ 0

JavaScript零基础入门——(九)JavaScript的函数

JavaScript零基础入门——(九)JavaScript的函数 欢迎回到我们的JavaScript零基础入门,上一节课我们了解了有关JS中数组的相关知识点,不知道大家有没有自己去敲一敲,消化一下?这一节课,...

JandenMa ⋅ 今天 ⋅ 0

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 今天 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部