六、Hibernate映射之一对多处理

本文深入讲解Hibernate框架下实体之间的关联关系处理,包括一对一、一对多的双向关联保存、级联操作,以及如何通过配置实现数据的自动保存和更新,特别强调了级联删除的实现方式。

环境准备

1.创建数据库
create database hibernate_day03;
2.创建web工程,导入相关jar包,具体jar包有哪些看这里
3.创建实体类
以客户和联系人关系为例,客户(Customer.java)为一方,联系人(Linkman.java)为多方.

Customer.java

/**
 * 客户实体 (一方)
 * @author mChenys
 *
 */
public class Customer {
	private Long cust_id;
	private String cust_name;
	
	// Hibernate框架默认的保存多方的集合是set集合,集合必须要自己手动的初始化
	// 注意,一方可以不配置多方也是可行的,这个看需求,如果不需要延迟查询联系人的话,可以不用配置多方也行.
	private Set<Linkman> linkmans = new HashSet<>();

	//get set方法...
}

Linkman.java

/**
 * 联系人实体(多方)
 * @author mChenys
 *
 */
public class Linkman {
	private Long lkm_id;
	private String lkm_name;
	
	//持有一方的引用(对应sql中的外键),该对象不需要手动初始化
	private Customer customer;

	//get set方法...
}

4.编写客户和联系人的映射配置文件

Customer.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

	<class name="blog.youkuaiyun.com.mchenys.domain.Customer" table="cst_customer">
		
		<id name="cust_id" column="cust_id">
			<generator class="native" />
		</id>
		
		<property name="cust_name" column="cust_name" />
		
		<!-- 在一方配置多方 -->
		<!-- set标签name属性:表示集合的名称 -->
		<set name="linkmans">
			<!-- 外键的字段名 -->
			<key column="lkm_cust_id" />
			<!-- 多方的全路径 -->
			<one-to-many class="blog.youkuaiyun.com.mchenys.domain.Linkman" />
		</set>
	</class>

</hibernate-mapping>

Linkman.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

	<class name="blog.youkuaiyun.com.mchenys.domain.Linkman" table="cst_linkman">

		<id name="lkm_id" column="lkm_id">
			<generator class="native" />
		</id>

		<property name="lkm_name" column="lkm_name" />

		<!-- 在多方配置一方 -->
		<!-- name: 多方javabean中持有一方引用的属性名 
			class:一方全路径名 
			colum:外键名,需要和一方的映射文件中配置的保持一致 -->
		<many-to-one name="customer" class="blog.youkuaiyun.com.mchenys.domain.Customer"
			column="lkm_cust_id" />
	</class>

</hibernate-mapping>

5.编写核心配置文件
hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
	"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
	"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">


<hibernate-configuration>

	<!-- 记住:先配置SessionFactory标签,一个数据库对应一个SessionFactory标签 -->
	<session-factory>

		<!-- 必须要配置的参数有5个,4大参数,数据库的方言 -->
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.url">jdbc:mysql:///hibernate_day03</property>
		<property name="hibernate.connection.username">root</property>
		<property name="hibernate.connection.password">1234</property>

		<!-- 数据库的方言 -->
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>


		<!-- 可选配置 -->
		<!-- 显示SQL语句,在控制台显示 -->
		<property name="hibernate.show_sql">true</property>
		<!-- 格式化SQL语句 -->
		<property name="hibernate.format_sql">true</property>
		<!-- 生成数据库的表结构 
			update:如果没有表结构,创建表结构。如果存在,不会创建,可动态添加字段(新增属性和映射即可),不能删除字段
		-->
		<property name="hibernate.hbm2ddl.auto">update</property>

		<!-- 开启绑定本地的session -->
		<property name="hibernate.current_session_context_class">thread</property>
		

		<!-- 映射配置文件,需要引入映射的配置文件 -->
		<mapping resource="blog/youkuaiyun.com/mchenys/domain/Customer.hbm.xml" />
		<mapping resource="blog/youkuaiyun.com/mchenys/domain/Linkman.hbm.xml" />

	</session-factory>

</hibernate-configuration>	

6.Hibernate的工具类

/**
 * Hibernate框架的工具类
 * @author mChenys
 *
 */
public class HibernateUtils {
	private static final SessionFactory FACTORY;
	private static final Configuration CONFIG;
	
	//保证仅初始化一次
	static {
		// 加载XML的配置文件
		CONFIG = new Configuration().configure();
		// 构造工厂
		FACTORY = CONFIG.buildSessionFactory();
	}
	
	/**
	 * 从工厂中获取Session对象
	 * @return
	 */
	public static Session getSession() {
		return FACTORY.openSession();
	}
	/**
	 * 获取当前线程的session
	 * @return
	 */
	public static Session getCurrentSession() {
		return FACTORY.getCurrentSession();
	}
	
	
	public static void main(String[] args) {
		getSession();
	}
}

数据保存处理

默认情况下,如果想只保存其中一方的数据,程序是会抛异常的,如果想完成只保存一方的数据,并且把相关联的数据都保存到数据库中,那么需要配置级联,否则只能进行双向关联保存(代码相对繁琐)

1.双向关联保存

以客户和联系人关系为例,客户和联系人的javabean创建完后,需要彼此关联,也就说要拥有彼此的对象引用,通过set方法赋值,同时调用hibernate的save方法保存的时候,所有创建的对象都需要通过save来进行保存,因此这种方式其实是最繁琐的(默认的处理方式)。
下面贴出示例代码:

/**
	 * 双向关联(最麻烦的保存数据方式)
	 */
	@Test
	public void test1() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tr = session.beginTransaction();
		
		//创建客户和联系人
		Customer cc = new Customer();
		cc.setCust_name("小陈");
		
		Linkman tom  = new Linkman();
		tom.setLkm_name("tom");
		Linkman jack = new Linkman();
		jack.setLkm_name("jack");
		
		//双向关联
		cc.getLinkmans().add(tom);
		cc.getLinkmans().add(jack);
		
		tom.setCustomer(cc);
		jack.setCustomer(cc);
		
		//保存数据
		session.save(cc);
		session.save(tom);
		session.save(jack);
		
		//提交
		tr.commit();
		
	}

2.级联保存

级联保存是有方向性,可以根据操作对象的频繁性来设置哪一方开启级联操作,这样保存其中一方的同时可以把关联的对象也自动保存到数据库中。当然也可以同时双方都开启级联操作。
开启级联保存操作,需要修改映射文件,添加cascade="save-update****。

例1 保存客户,级联保存联系人

1.修改Customer.hbm.xml文件,在set节点上添加cascade属性

	...

	<!--  set标签cascade属性:设置级联操作,save-update表示允许级联保存-->
	<set name="linkmans" cascade="save-update">
		...
	</set>
	...

2.Hibernate保存数据的操作

/**
	 * 级联保存之:保存客户,级联保存联系人
	 */
	@Test
	public void test2() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tr = session.beginTransaction();

		// 创建客户和联系人
		Customer cc = new Customer();
		cc.setCust_name("小陈");

		Linkman tom = new Linkman();
		tom.setLkm_name("tom");
		Linkman jack = new Linkman();
		jack.setLkm_name("jack");
		
		//单项关联
		cc.getLinkmans().add(tom);
		cc.getLinkmans().add(jack);
		
		//只保存客户数据就会自动保存联系人数据
		session.save(cc);

		tr.commit();
	}

例2 保存联系人,级联保存客户

1.修改Linkman.bhm.xml,在<many-to-one/>标签上添加cascade属性.

	...
	<many-to-one name="customer" class="blog.youkuaiyun.com.mchenys.domain.Customer"
			column="lkm_cust_id" cascade="save-update"/>
	...		

2.Hibernate保存数据的操作

/**
	 * 级联保存之:保存联系人,级联保存客户
	 */
	@Test
	public void test3() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tr = session.beginTransaction();

		// 创建客户和联系人
		Customer cc = new Customer();
		cc.setCust_name("小陈");

		Linkman tom = new Linkman();
		tom.setLkm_name("tom");
		Linkman jack = new Linkman();
		jack.setLkm_name("jack");
		
		//单项关联
		tom.setCustomer(cc);
		jack.setCustomer(cc);
		
		//保存联系人数据
		session.save(tom);
		//由于保存tom的时候已经级联保存了客户cc,因此保存jack的时候不会在执行cc的insert操作
		session.save(jack);

		tr.commit();
	}

例3 保存联系人1,级联保存客户,然后再级联保存联系人2

这个例子需要双方表都开启级联操作
修改Customer.hbm.xml和Linkman.hbm.xml文件,都添加cascade属性,参考上面的例1和例2.
下面是代码实现部分.

/**
	 * 级联保存之:保存联系人1级联保存客户然后再级联保存联系人2
	 */
	@Test
	public void test4() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tr = session.beginTransaction();

		// 创建客户和联系人
		Customer cc = new Customer();
		cc.setCust_name("小陈");

		Linkman tom = new Linkman();
		tom.setLkm_name("tom");
		Linkman jack = new Linkman();
		jack.setLkm_name("jack");
		
		//串联
		tom.setCustomer(cc);
		cc.getLinkmans().add(jack);
		
		//保存tom即可级联保存cc, 保存cc又可以级联保存jack
		session.save(tom);

		tr.commit();
	}

级联操作的分类

cascade的取值一共有6种:

  • none:不使用级联
  • save-update:级联保存或更新
  • delete :级联删除
  • delete-orphan:孤儿删除.(注意:只能应用在一对多关系)
  • all:除了delete-orphan的所有情况.(包含save-update delete)
  • all-delete-orphan:包含了delete-orphan的所有情况.(包含save-update delete delete-orphan)

什么是孤儿删除?
孤儿删除也叫孤子删除,它只能应用在一对多的关系中,以一的一方认为是父方,以多的一方认为是子方,当删除子方的时候除了会解除父子关系(即将外键置为null),还会将子方的该条记录直接删除。

数据删处理

如果直接使用sql语句操作删除数据的话,在存在外键约束的条件下,主表即一方表中的数据是删除不成功,因为其主键被其他表的外键所依赖,会看到类似的错误。
在这里插入图片描述

那么Hibernate是怎么来解决这个问题的呢?
hibernate操作delete方法的时候会先将该条数据解除从表和主表的依赖关系,通过修改从表的外键值为null来实现,然后再来删除主表中的数据。
删除代码示例:

/**
	 * 删除客户,客户下有2个联系人
	 */
	@Test
	public void test5() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tr = session.beginTransaction();
	
		//删除客户,先通过主键查询
		Customer cc = session.get(Customer.class, 10L);
		if(null !=cc) {
			//删除
			session.delete(cc);
		}
		tr.commit();
	}

从控制台输出的sql语句也可以看出他的操作步骤是先修改外键为null,然后再删除Customer数据
在这里插入图片描述

级联删除

同样需要修改hibernate的映射文件,添加cascade的属性,其值设置为delete或者all,通常会在一方的映射文件中添加,因为一方一旦删除,多方中的外键也就没啥意义了;而如果仅仅是删除多方中的某条数据的话,一方在多方表中任然还有其他数据与之关联,所以此时删除一方有点不妥。

下面演示级联删除操作,删除客户级联删除与之关联的联系人
修改Customer.hbm.xml文件,在set节点上添加cascade属性,其值取all

	...

	<!--  set标签cascade属性:设置级联操作,all表示允许级联保存,更新和删除-->
	<set name="linkmans" cascade="all">
		...
	</set>
	...

代码实现:

/**
	 * 级联删除:删除客户,同时删除与之关联的 联系人
	 */
	@Test
	public void test6() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tr = session.beginTransaction();
	
		//删除客户,先通过主键查询
		Customer cc = session.get(Customer.class, 11L);
		if(null !=cc) {
			//删除
			session.delete(cc);
		}
		tr.commit();
	}

从控制台输入的sql语句,可以知道hibernate是先将外键设置为null,然后再删除从表和主表中的数据,一共执行了3条delete语句,即一个Customer和2个Linkman的数据.
在这里插入图片描述

注意:通常情况下,不建议直接删除数据,而是会通过一个标记来标识该数据是否生效。

手动解除关系

解除关系并不是删除数据,而是将从表的某条数据的外键设置为null。
以Customer解除与tom联系人的关系为例:

/**
	 * 解除关系:从集合中删除联系人
	 */
	@Test
	public void test7(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tr = session.beginTransaction();
		// 获取到客户
		Customer cc = session.get(Customer.class, 13L);
		// 获取tom联系人
		Linkman tom = session.get(Linkman.class, 22L);
		// 解除客户与tom联系人的关系,对应到数据库就是操作外键,将tom的外键设置为null
		cc.getLinkmans().remove(tom);
		
		//提交
		tr.commit();
	}

查看控制台的log也能看出,并没有执行delete语句,而是执行update语句
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值