Hibernate关联关系之一对多

本文介绍Hibernate框架中如何处理一对多及多对一关联关系,包括使用XML映射文件配置关联关系、通过Java代码建立和解除关联关系的方法,并讨论了级联操作及inverse属性的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

下面这个案例是一对多和多对一共存的一个案例:

Customer类:

public class Customer {
	private Integer id;
	private String fname;
	private String lname;
	private Integer age;
	private Date birthday;
	private boolean married;
	private byte[] photo;
	private String description;
	//建立从Customer到Order之间的一对多关联关系
	/*
	 * 为何选用Set:
	 * 1.List:元素可重复,但是我们的需求是订单集合不要重复
	 * 2.Map:是一个键值对,value是每个不同的订单,那么key呢?如果一个客户对应100张订单,这时候让这个客户作为key,那不是数据冗余了么?
	 * 3.Set:无序并且唯一。
	 */
	private Set<Order> orders = new HashSet<Order>();
}

Customer.hbm.xml:

<?xml version="1.0"?>
<!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.xxc.domain.Customer" table="Customers" lazy="false">
		<id name="id" column="id" type="integer">
			<generator class="native"></generator>
		</id>
		<property name="name" column="name" type="string" length="20" access="property"/>
		<property name="birthday" column="birthday" type="date"/>
		<property name="age" column="age" type="integer" length="20"/>
		<property name="description" column="description" type="text" />
		<property name="married">
			<column name="married" sql-type="int(20)"></column>
		</property>
		<property name="photo">
			 <!-- property表的column属性和子元素column是互斥的,只能用其一 -->
			<column name="photo" sql-type="longblob"></column>
		</property>
		<!-- 映射一对多关系  name是集合名   并设置级联添加操作-->
		<set name="orders" cascade="save-update">
			<!-- 这个写的是"多"那一端的外键在数据库中的字段名,因为客户表中的所有属性都是描述自身属性的,没有一个是具有关联关系的-->
			<key column="cid"></key>
			<!-- 集合中存的"多"那一端的类型 -->
			<one-to-many class="com.xxc.domain.Order"/>
		</set>
	</class>
</hibernate-mapping>

Order类:

public class Order {
	private Integer id;
	private String orderNumber;
	private float price;
	/* 建立从Order到Customer之间多对一关联关系 */
	private Customer customer ;
}


Order.hbm.xml:

<?xml version="1.0"?>
<!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.xxc.domain.Order" table="Orders" lazy="false">
		<id name="id" column="id" type="integer">
			<generator class="native"></generator>
		</id>
		<property name="orderNumber" column="orderNumber" type="string" length="20"/>
		<property name="price" column="price" type="float"/>
		<!-- 映射从Order到Customer之间多对一关联关系 -->
		<many-to-one name="customer" class="com.xxc.domain.Customer" column="cid" cascade="save-update" />
	</class>
</hibernate-mapping>

按照下图测试案例1,session.save(o1)


测试:

package com.xxc.app;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.BeforeClass;
import org.junit.Test;

import com.xxc.domain.Customer;
import com.xxc.domain.Order;

public class App {
	private static SessionFactory sf = null;
	
	@BeforeClass
	public static void initialize(){
		Configuration config = new Configuration();
		/*  也可以写成这样的形式
		 *	sf = config.addClass(Customer.class).addClass(Order.class).buildSessionFactory();
		 */
		
		config.addClass(Customer.class);
		config.addClass(Order.class);
		sf = config.buildSessionFactory();
		
	}
	
	@Test
	public void insertCustomer(){
		Customer c = new Customer();
		
		Order o1 = new Order();
		Order o2 = new Order();
		Order o3 = new Order();
		//设置订单和客户的关联关系
		c.getOrders().add(o2);
		c.getOrders().add(o3);
		
		o1.setCustomer(c);
		
		Session session = sf.openSession();
		Transaction t = session.beginTransaction();
		
		session.save(o1);
		t.commit();
		session.close();
	}
	
	/*
	 * 建立一对多关系的主要目的不是在于插入,而是在于查询
	 * 下面的例子中,可以发现当查询某个客户的时候,因为客户类设置了一对多关联关系
	 * hibernate会将此客户中所有的订单表查询出来,封装在客户类中的订单集合中
	 */
	@Test
	public void loadOrders(){
		Session session = sf.openSession();
		Transaction t= session.beginTransaction();
		Customer c = (Customer) session.load(Customer.class, 1);
		t.commit();
		for(Order o : c.getOrders()){
			System.out.println(o.getId());
		}
		session.close();
	}
}


Many方<set>元素的inverse属性:

Customer表:



Orders表:



需求:将Orders表中id为4的数据的cid改成1。利用Hibernate面向对象的方法来实现。说的书面化点就是:修改Order表中指定数据和Customer表中数据的所属关系。

思路:由于Customer类中有存放Order的集合,Order类中又要设置所属的Customer。所以我们要分别获取需要被修改的Order和它新所属的Customer对象。然后分别设置。

测试:

package com.xxc.app;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.BeforeClass;
import org.junit.Test;

import com.xxc.domain.Customer;
import com.xxc.domain.Order;

public class OneToMany {
	private static SessionFactory sf = null;
	@BeforeClass
	public static void initialize(){
		Configuration config = new Configuration();
		/*  也可以写成这样的形式
		 *	sf = config.addClass(Customer.class).addClass(Order.class).buildSessionFactory();
		 */
		
		config.addClass(Customer.class);
		config.addClass(Order.class);
		sf = config.buildSessionFactory();
	}
	
	@Test
	public void changeRelation(){//改变所属 关系
		Session session = sf.openSession();
		Transaction t = session.beginTransaction();
		
		//获取Order表中指定数据的新的所属对象
		Customer c = (Customer) session.load(Customer.class, 1);
		//获取需要被改变的Order对象
		Order o = (Order) session.load(Order.class, 4);
		c.getOrders().add(o);
		o.setCustomer(c);
		//这里是不需要session.save(),原因是从数据库里查询出来的数据状态为持久化状态,在持久化状态的对象上修改属性,只要当事务提交的时候,这个对象会自动会和session缓存里此对象的快照进行对比,如果属性和快照里的不同,那么会自动进行更新数据库操作。
		
		t.commit();
		session.close();
	}
	
}
经过以上代码测试,发现控制台打印以下两句SQL。其实两句SQL语句执行的操作是同一个意思,都是将Order表中id为4的数据的Cid字段改成1。书面化点:将id为4的订单数据的所属客户改成所属于id为1的客户。

出现这样的情况势必会导致程序性能下降,那么为什么会出现这样的情况呢?

是因为Session缓存监控到了两个对象的改变,而这两个对象的改变对应的sql操作是同一种结果。



解决办法:

在"one"方的<set>元素中增加inverse=true。Hibernate不会按照Customer对象的属性变化来同步更新数据库。

修改Customer.hbm.xml

<?xml version="1.0"?>
<!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.xxc.domain.Customer" table="Customers" lazy="false">
		<id name="id" column="id" type="integer">
			<generator class="native"></generator>
		</id>
		<property name="name" column="name" type="string" length="20" access="property"/>
		<property name="birthday" column="birthday" type="date"/>
		<property name="age" column="age" type="integer" length="20"/>
		<property name="description" column="description" type="text" />
		<property name="married">
			<column name="married" sql-type="int(20)"></column>
		</property>
		<property name="photo">
			<column name="photo" sql-type="longblob"></column>
		</property>
		<set name="orders" cascade="save-update" inverse="true"><!-- 让缓存忽略掉客户类中订单集合的变化 true|false -->
			<key column="cid"></key>
			<one-to-many class="com.xxc.domain.Order"/>
		</set>
	</class>
</hibernate-mapping>

注意:在建立两个对象的双向关联时,应该同时修改关联两端的对象的相应属性。这样才会使程序更加健壮,提高业务逻辑层的独立性,使业务逻辑层的程序代码不受Hibernate实现的影响。同理,当解除双向关联的关系时,也应该修改关联两端的对象的相应属性。

解除关联关系:解除id为5的order和id为3的Customer数据的关联关系(就是将多的一方的外键置空而不是删除)

public void removeRelation(){//改变所属 关系
	Session session = sf.openSession();
	Transaction t = session.beginTransaction();
	Customer c = (Customer) session.load(Customer.class, 3);
	Order o = (Order) session.load(Order.class, 5);
	//在Customer的Orders集合中删除要解除关系的order对象
	c.getOrders().remove(o);
	//在Order类中将所属的Customer类置空
	o.setCustomer(null);
	
	t.commit();
	session.close();
}

cascade属性:



all-delete-orphan:

1、当保存或更新customer对象时,级联保存或更新所有关联的order对象,相当于save-update。

2、当删除customer对象时,级联删除所有的order对象,相当于delete。

3、删除不再和customer对象关联的所有order对象。

当关联双方存在父子关系,就可以把父方的cascade属性设为all-delete-orphan。

所谓父子关系:是指由父方来控制子方的持久化生命周期,子方对象必须和一个父方对象关联,而不允许单独存在。如果删除父方对象,应该级联删除所有关联的子方对象;如果一个子方对象不再和一个父方对象关联,应该把这个子方对象删除。

例如:

1、订单和客户是一个父子关系,如a客户有1 2 3三个订单,如果将a客户信息删除,那么1 2 3订单也没有存在的必要了。

2、公司和职工不算父子关系,因为职工换工作后,可以和新的一个公司对象关联。


级联删除:

在one方的hbm.xml中修改如下

<set name="orders" cascade="all" inverse="true"><!-- all表示既可以save-update也可以delete级联删除 如果为默认值none,那么就不会进行级联操作了 -->
	<key column="cid"></key>
	<one-to-many class="com.xxc.domain.Order"/>
</set>


经过测试如果在many方的hbm.xml中的<many-to-one cascade="all">是不能进行级联删除的。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值