Hibernate入门第十讲——Hibernate的一对多关联映射

环境搭建

创建一个普通的Java项目,导入相应jar包

创建一个普通的Java项目,例如hibernate_demo03,然后在项目下新建一个lib包,导入Hibernate开发相关依赖jar包。还记得要导入一个日志记录文件哟!之所以要它,是因为我们可以通过控制台看到Hibernate到底做了哪些操作,向数据库发送了什么样的SQL语句。
在这里插入图片描述

创建数据库和表

这里我们以客户(Customer)与联系人(linkMan)为例来讲解Hibernate关联映射中的一对多关联关系,因此我们要创建一个数据库,并在该数据库下新建两张表——客户表和联系人表,这里笔者使用的数据库是MySQL。

create database hibernate_demo03;
use hibernate_demo03;

CREATE TABLE `cst_customer` (
	`cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
	`cust_name` varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
	`cust_source` varchar(32) DEFAULT NULL COMMENT '客户信息来源',
	`cust_industry` varchar(32) DEFAULT NULL COMMENT '客户所属行业',
	`cust_level` varchar(32) DEFAULT NULL COMMENT '客户级别',
	`cust_phone` varchar(64) DEFAULT NULL COMMENT '固定电话',
	`cust_mobile` varchar(16) DEFAULT NULL COMMENT '移动电话',
	PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `cst_linkman` (
	`lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
	`lkm_name` varchar(16) DEFAULT NULL COMMENT '联系人姓名',
	`lkm_cust_id` bigint(32) DEFAULT NULL COMMENT '客户id',
	`lkm_gender` char(1) DEFAULT NULL COMMENT '联系人性别',
	`lkm_phone` varchar(16) DEFAULT NULL COMMENT '联系人办公电话',
	`lkm_mobile` varchar(16) DEFAULT NULL COMMENT '联系人手机',
	`lkm_email` varchar(64) DEFAULT NULL COMMENT '联系人邮箱',
	`lkm_qq` varchar(16) DEFAULT NULL COMMENT '联系人qq',
	`lkm_position` varchar(16) DEFAULT NULL COMMENT '联系人职位',
	`lkm_memo` varchar(512) DEFAULT NULL COMMENT '联系人备注',
	PRIMARY KEY (`lkm_id`),
	KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
	CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

创建实体

在src目录下创建一个com.meimeixia.hibernate.domain包,并在该包下创建两个实体类,如下:

  • 客户类

    package com.meimeixia.hibernate.domain;
    
    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * 客户的实体
     * @author liayun
     *
     */
    public class Customer {
    
    	private Long cust_id;
    	private String cust_name;
    	private String cust_source;
    	private String cust_industry;
    	private String cust_level;
    	private String cust_phone;
    	private String cust_mobile;
    
    	//通过ORM方式表示:一个客户对应多个联系人
    	//这儿放置的是多的一方的集合。Hibernate默认使用的是Set集合。
    	private Set<LinkMan> linkMans = new HashSet<LinkMan>();
    	
    	public Long getCust_id() {
    		return cust_id;
    	}
    	public void setCust_id(Long cust_id) {
    		this.cust_id = cust_id;
    	}
    	public String getCust_name() {
    		return cust_name;
    	}
    	public void setCust_name(String cust_name) {
    		this.cust_name = cust_name;
    	}
    	public String getCust_source() {
    		return cust_source;
    	}
    	public void setCust_source(String cust_source) {
    		this.cust_source = cust_source;
    	}
    	public String getCust_industry() {
    		return cust_industry;
    	}
    	public void setCust_industry(String cust_industry) {
    		this.cust_industry = cust_industry;
    	}
    	public String getCust_level() {
    		return cust_level;
    	}
    	public void setCust_level(String cust_level) {
    		this.cust_level = cust_level;
    	}
    	public String getCust_phone() {
    		return cust_phone;
    	}
    	public void setCust_phone(String cust_phone) {
    		this.cust_phone = cust_phone;
    	}
    	public String getCust_mobile() {
    		return cust_mobile;
    	}
    	public void setCust_mobile(String cust_mobile) {
    		this.cust_mobile = cust_mobile;
    	}
    	public Set<LinkMan> getLinkMans() {
    		return linkMans;
    	}
    	public void setLinkMans(Set<LinkMan> linkMans) {
    		this.linkMans = linkMans;
    	}
    	
    }
    
  • 联系人类

    package com.meimeixia.hibernate.domain;
    
    /**
     * 联系人的实体
     * @author liayun
     *
     */
    public class LinkMan {
    
    	private Long lkm_id;
    	private String lkm_name;
    	private String lkm_gender;
    	private String lkm_phone;
    	private String lkm_mobile;
    	private String lkm_email;
    	private String lkm_qq;
    	private String lkm_position;
    	private String lkm_memo;
    	
    	//通过ORM方式来表示:一个联系人只能属于某一个客户
    	//这儿放置的是一的一方的对象
    	private Customer customer;
    	
    	public Long getLkm_id() {
    		return lkm_id;
    	}
    	public void setLkm_id(Long lkm_id) {
    		this.lkm_id = lkm_id;
    	}
    	public String getLkm_name() {
    		return lkm_name;
    	}
    	public void setLkm_name(String lkm_name) {
    		this.lkm_name = lkm_name;
    	}
    	public String getLkm_gender() {
    		return lkm_gender;
    	}
    	public void setLkm_gender(String lkm_gender) {
    		this.lkm_gender = lkm_gender;
    	}
    	public String getLkm_phone() {
    		return lkm_phone;
    	}
    	public void setLkm_phone(String lkm_phone) {
    		this.lkm_phone = lkm_phone;
    	}
    	public String getLkm_mobile() {
    		return lkm_mobile;
    	}
    	public void setLkm_mobile(String lkm_mobile) {
    		this.lkm_mobile = lkm_mobile;
    	}
    	public String getLkm_email() {
    		return lkm_email;
    	}
    	public void setLkm_email(String lkm_email) {
    		this.lkm_email = lkm_email;
    	}
    	public String getLkm_qq() {
    		return lkm_qq;
    	}
    	public void setLkm_qq(String lkm_qq) {
    		this.lkm_qq = lkm_qq;
    	}
    	public String getLkm_position() {
    		return lkm_position;
    	}
    	public void setLkm_position(String lkm_position) {
    		this.lkm_position = lkm_position;
    	}
    	public String getLkm_memo() {
    		return lkm_memo;
    	}
    	public void setLkm_memo(String lkm_memo) {
    		this.lkm_memo = lkm_memo;
    	}
    	public Customer getCustomer() {
    		return customer;
    	}
    	public void setCustomer(Customer customer) {
    		this.customer = customer;
    	}
    	
    }
    

创建映射配置文件

实体类创建完成之后,再在com.meimeixia.hibernate.domain包下分别创建这两个类的映射配置文件。

  • 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="com.meimeixia.hibernate.domain.Customer" table="cst_customer">
    		<!-- 建立OID与主键的映射 -->
    		<id name="cust_id" column="cust_id">
    			<generator class="native"></generator>
    		</id>
    		
    		<!-- 建立类中的普通属性和数据库表中字段的映射 -->
    		<property name="cust_name" column="cust_name" />
    		<property name="cust_source" column="cust_source" />
    		<property name="cust_industry" column="cust_industry" />
    		<property name="cust_level" column="cust_level" />
    		<property name="cust_phone" column="cust_phone" />
    		<property name="cust_mobile" column="cust_mobile" />
    		
    		<!-- 配置一对多的映射:放置的是多的一方的对象的集合 -->
    		<!--  
    			set标签:
    				1. name:多的一方的对象集合的属性名称
    		-->
    		<set name="linkMans">
    			<!--  
    				key标签:
    					1. column:多的一方的外键的名称
    			-->
    			<key column="lkm_cust_id"></key>
    			<!--  
    				one-to-many标签:
    					1. class:多的一方的类的全路径
    			-->
    			<one-to-many class="com.meimeixia.hibernate.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="com.meimeixia.hibernate.domain.LinkMan" table="cst_linkman">
    		<!-- 建立OID与主键的映射 -->
    		<id name="lkm_id" column="lkm_id">
    			<generator class="native"></generator>
    		</id>
    	
    		<!-- 建立类中的普通属性和数据库表中字段的映射 -->
    		<property name="lkm_name" />
    		<property name="lkm_gender" />
    		<property name="lkm_phone" />
    		<property name="lkm_mobile" />
    		<property name="lkm_email" />
    		<property name="lkm_qq" />
    		<property name="lkm_position" />
    		<property name="lkm_memo" />
    		
    		<!-- 配置多对一的关系:放置的是一的一方的对象 -->
    		<!--  
    			many-to-one标签:
    				1. name		:	一的一方的对象的属性名称
    				2. class	:	一的一方的类的全路径
    				3. column	:	在多的一方的表的外键名称
    		-->
    		<many-to-one name="customer" class="com.meimeixia.hibernate.domain.Customer" column="lkm_cust_id"></many-to-one>
    	</class>
    </hibernate-mapping>
    

创建核心配置文件

在src目录下新建一个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>
	<session-factory>
		<!-- 下面是三个必须要有的配置 -->
		<!-- 配置连接MySQL数据库的基本参数 -->
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.url">jdbc:mysql:///hibernate_demo03</property>
		<property name="hibernate.connection.username">root</property>
		<property name="hibernate.connection.password">liayun</property>
		
		<!-- 配置Hibernate的方言 -->
		<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>
		<!-- 自动创建表 -->
		<property name="hibernate.hbm2ddl.auto">update</property>
		
		<!-- 配置C3P0连接池 -->
		<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
		<!-- 在连接池中可用的数据库连接的最少数目 -->
		<property name="c3p0.min_size">5</property>
		<!-- 在连接池中所有数据库连接的最大数目 -->
		<property name="c3p0.max_size">20</property>
		<!-- 
			设定数据库连接的过期时间,以秒为单位。
			如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除。
		-->
		<property name="c3p0.timeout">120</property>
		<!-- 每3000秒检查所有连接池中的空闲连接,以秒为单位 -->
		<property name="c3p0.idle_test_period">3000</property>
		
		<!-- 设置事务隔离级别 -->
		<property name="hibernate.connection.isolation">4</property>
		
		<!-- 配置当前线程来绑定的Session -->
		<property name="hibernate.current_session_context_class">thread</property>
		
		<!-- (引入映射文件)告诉Hibernate的核心配置文件加载哪个映射文件 -->
		<mapping resource="com/meimeixia/hibernate/domain/Customer.hbm.xml"/>
		<mapping resource="com/meimeixia/hibernate/domain/LinkMan.hbm.xml"/>
	</session-factory>
</hibernate-configuration>

温馨提示:在核心配置文件中,一定记住得引入映射配置文件,也就是告诉Hibernate的核心配置文件要加载哪几个映射配置文件。

引入工具类

在src目录下新建一个com.meimeixia.hibernate.utils包,并在该包下创建一个名称叫HibernateUtils的工具类,专门用于获取Session对象。

package com.meimeixia.hibernate.utils;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtils {

	public static final Configuration cfg;
	
	public static final SessionFactory sf;//一个项目只会创建一个SessionFactory对象
	
	static {
		cfg = new Configuration().configure();
		sf = cfg.buildSessionFactory();
	}
	
	public static Session openSession() {
		return sf.openSession();
	}
	
	public static Session getCurrentSession() {
		return sf.getCurrentSession();
	}
	
}

编写测试类测试双向关联保存

现在我们来测试保存的操作,在src目录下创建一个com.meimeixia.hibernate.demo01包,并在该包下编写一个OneToManyTest单元测试类,然后在该类中编写一个用于测试保存的操作,如下:

package com.meimeixia.hibernate.demo01;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import com.meimeixia.hibernate.domain.Customer;
import com.meimeixia.hibernate.domain.LinkMan;
import com.meimeixia.hibernate.utils.HibernateUtils;

/**
 * 一多对关系的测试类
 * 
 * @author liayun
 *
 */
public class OneToManyTest {

	// 现在想要保存2个客户,3个联系人,并且建立好关系
	@Test
	public void demo01() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();

		// 创建2个客户
		Customer customer1 = new Customer();
		customer1.setCust_name("张小敬");
		Customer customer2 = new Customer();
		customer2.setCust_name("李必");

		// 创建3个联系人
		LinkMan linkMan1 = new LinkMan();
		linkMan1.setLkm_name("凤姐");
		LinkMan linkMan2 = new LinkMan();
		linkMan2.setLkm_name("如花");
		LinkMan linkMan3 = new LinkMan();
		linkMan3.setLkm_name("来福");

		// 设置关系(双向关联关系)
		linkMan1.setCustomer(customer1);
		linkMan2.setCustomer(customer1);
		linkMan3.setCustomer(customer2);

		customer1.getLinkMans().add(linkMan1);
		customer1.getLinkMans().add(linkMan2);
		customer2.getLinkMans().add(linkMan3);

		// 保存数据
		session.save(linkMan1);
		session.save(linkMan2);
		session.save(linkMan3);

		session.save(customer1);
		session.save(customer2);

		tx.commit();
	}

}

运行以上demo01方法,就能向客户表中插入2条记录,向联系人表中插入3条记录。其实上面测试保存的操作就是一种双向关联关系,如果做的是双向的关联,而没有用cascade去做级联,那么就存在一个浪费的环节,后面会讲。

Hibernate的一对多相关操作

一对多的关系,只保存一边是否可以?

顺其自然地,我们就会想可不可以只保存客户或只保存联系人就能完成保存的操作呢?答案是不言而喻的不可以。下面我就来简单地讲讲。
还是在双向关联关系的情况下,我们只保存一边,也就是说你要么只保存客户,要么只保存联系人,可以预想到的是我们可能会这样写代码:

//一对多的关系,只保存一边是否可以?
@Test
public void demo02() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//创建1个客户
	Customer customer = new Customer();
	customer.setCust_name("崔器");
	
	//创建1个联系人
	LinkMan linkMan = new LinkMan();
	linkMan.setLkm_name("小强");
	
	//设置关系(双向关联关系)
	customer.getLinkMans().add(linkMan);
	linkMan.setCustomer(customer);
	
	//只保存一边(只保存客户)是否可以?不可以,这里报了一个瞬时态对象异常:持久态对象关联了一个瞬时态对象
	//session.save(customer);
	session.save(linkMan);
	
	tx.commit();
}

运行以上方法,会发现报了一个瞬时态对象异常,即持久态对象关联了一个瞬时态对象。
在这里插入图片描述
出现问题,就要着手解决,那又该怎么解决呢?这就引出了下面我要讲的一对多的级联操作。

一对多的级联操作

什么叫做级联呢?级联指的是操作某一个对象的时候,是否会同时操作其关联的对象。我们一定要注意:级联是有方向性的,也就是说操作一的一方的时候,是否操作到多的一方,或者操作多的一方的时候,是否操作到一的一方。

级联保存或更新

级联是有方向性的,现在我们要做的是保存客户,然后去级联保存联系人。由于现在操作的主体对象是客户对象,所以就需要在客户的映射文件中进行配置。
在这里插入图片描述
这时,运行以下测试方法,就能在保存客户时顺带自动保存联系人。

/**
 * 级联保存或更新操作
 * 		保存客户,然后去级联联系人(现在操作的主体对象是客户对象,那么就需要在客户的映射文件中进行配置)
 * 		<set name="linkMans" cascade="save-upadte">
 */
@Test
public void demo03() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//创建1个客户
	Customer customer = new Customer();
	customer.setCust_name("崔器");
	
	//创建1个联系人
	LinkMan linkMan = new LinkMan();
	linkMan.setLkm_name("小强");
	
	//设置关系(双向关联关系)
	customer.getLinkMans().add(linkMan);
	linkMan.setCustomer(customer);
	
	//只保存一边(只保存客户)是否可以?现在就可以
	session.save(customer);
	
	tx.commit();
}

我们又想要在保存联系人时,保存客户,那又该怎么做呢?答案是不言而喻的,用屁股想都能知道,现在操作的主体对象是联系人对象,所以就需要在联系人的映射文件中进行配置。
在这里插入图片描述
这时,运行以下测试方法,就能在保存联系人时顺带自动保存客户。

/**
 * 级联保存或更新操作
 * 		保存联系人,然后去级联客户(现在操作的主体对象是联系人对象,那么就需要在联系人的映射文件中进行配置)
 * 		<many-to-one cascade="save-update" name="customer" class="com.meimeixia.hibernate.domain.Customer" column="lkm_cust_id"></many-to-one>
 */
@Test
public void demo04() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//创建1个客户
	Customer customer = new Customer();
	customer.setCust_name("阿昀");
	
	//创建1个联系人
	LinkMan linkMan = new LinkMan();
	linkMan.setLkm_name("小小");
	
	//设置关系(双向关联关系,也即对象导航)
	customer.getLinkMans().add(linkMan);
	linkMan.setCustomer(customer);
	
	//只保存一边(只保存联系人)是否可以?现在就可以
	session.save(linkMan);
	
	tx.commit();
}

级联删除

啥是级联删除呢?级联删除指的是在删除一方的时候,同时会将另一方的数据也一并删除掉。现在我们有这样一个需求:当我们删除一个客户时,应该将客户关联的联系人也删除掉。由于删除的主体对象是客户对象,所以就需要在客户的映射文件中进行配置。
在这里插入图片描述
此时,运行以下测试方法,就能在删除客户时,然后去级联删除联系人。

/**
 * 级联删除
 * 		删除客户,然后去级联删除联系人,
 * 		同样,要看现在删除的主体对象是客户对象,那么就需要在客户的映射文件中进行配置:<set name="linkMans" cascade="delete">
 * 		
 */
@Test
public void demo06() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//删除客户,同时删除联系人
	Customer customer = session.get(Customer.class, 1l);
	session.delete(customer);
	
	tx.commit();
}

如果要是在客户的映射文件中没有设置级联删除呢?那会是什么情况呢?默认情况是修改了联系人的外键(即将联系人的外键置为null),然后删除了客户,看Eclipse控制台中Hibernate向数据库发送的SQL语句便知。
在这里插入图片描述
又有这样一个需求:当我们删除一个联系人时,应该将联系人关联的客户也删除掉,这种情况在实际开发中基本不会遇到。由于现在删除的主体对象是联系人对象,那么就需要在联系人的映射文件中进行配置。
在这里插入图片描述
此时,运行以下测试方法,就能在删除联系人时,然后去级联删除客户。

/**
 * 级联删除
 * 		删除联系人,然后去级联删除客户,
 * 		同样,要看现在删除的主体对象是联系人对象,那么就需要在联系人的映射文件中进行配置:
 * 		<many-to-one cascade="save-update,delete" name="customer" class="com.meimeixia.hibernate.domain.Customer" column="lkm_cust_id"></many-to-one>	
 */
@Test
public void demo07() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//记得一定要查询,再进行删除,删除客户,同时删除联系人
	LinkMan linkMan = session.get(LinkMan.class, 1l);
	session.delete(linkMan);
	
	tx.commit();
}

delete-orphan用法

delete-orphan:字面意思是删除孤儿,其实指的是删除与当前对象解除关系的对象。为了说明delete-orphan到底是个啥玩意,我们可以这样子做,先别在双方的映射配置文件中设置级联操作,然后运行以下测试方法。

//演示delete-orphan
@Test
public void tesDeleteOrphan() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	Customer customer = session.get(Customer.class, 2l);
	LinkMan linkMan = session.get(LinkMan.class, 3l);
	
	customer.getLinkMans().remove(linkMan);//从集合里面移除掉一个联系人,能实现这个效果吗?不能
	
	tx.commit();
}

发现归属于2号客户的3号联系人并没有被删除掉,只是将3号联系人的外键置为了null。
在这里插入图片描述
如果想起到我们预期的效果,可以在客户类的映射文件中进行配置。
在这里插入图片描述
此时,再次运行tesDeleteOrphan()测试方法,那可真就将归属于2号客户的3号联系人删除掉了。

cascade总结

使用cascade可以完成级联操作,它的常用可取值:

  • none:这是一个默认值;
  • save-update:当我们配置它后,底层使用save、update或saveOrUpdate完成操作时,会级联保存临时对象,如果是游离对象,会执行update;
  • delete:级联删除;
  • delete-orphan:删除与当前对象解除关系的对象;
  • all:它包含了save-update、delete操作;
  • all-delete-orphan:它包含了delete-orphan与all操作。

一对多关系的对象导航

下面来看看一对多关系的对象导航问题,如图:
在这里插入图片描述
接着,我们来编写代码进行演示,在OneToManyTest单元测试类编写如下测试方法:

/**
 * 测试对象的导航
 * 		前提:一对多的双方都设置了cascade="save-update"
 */
@Test
public void demo05() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//创建1个客户
	Customer customer = new Customer();
	customer.setCust_name("阿昀");
	
	//创建3个联系人
	LinkMan linkMan1 = new LinkMan();
	linkMan1.setLkm_name("常威");
	LinkMan linkMan2 = new LinkMan();
	linkMan2.setLkm_name("如花");
	LinkMan linkMan3 = new LinkMan();
	linkMan3.setLkm_name("来福");
	
	//现在不设置双向的关联了
	linkMan1.setCustomer(customer);
	customer.getLinkMans().add(linkMan2);
	customer.getLinkMans().add(linkMan3);
	
	
	//双方都设置了cascade,那么执行下面的操作,会发送几条insert语句?
	//会发送4条insert语句
	//session.save(linkMan1);
	
	//双方都设置了cascade,那么执行下面的操作,会发送几条insert语句?
	//会发送3条insert语句
	//session.save(customer);
	
	//双方都设置了cascade,那么执行下面的操作,会发送几条insert语句?
	//会发送1条insert语句
	session.save(linkMan2);
	
	tx.commit();
}

运行以上方法,你可以在Eclipse控制台查看Hibernate向数据库到底发送了几条insert语句。

一对多设置了双向关联会产生多余的SQL语句

我们在开发中有时要配置双向关联,这样就可以通过任意一方来操作对方,但在写操作代码时,应尽量要进行单向关联,因为这样可以尽量减少资源浪费。在前面编写测试类测试双向关联保存那一小节中,我讲到存在一个资源浪费的问题,在这一节中我就会细讲一下是怎么回事。
回到最初编写测试类测试双向关联保存的位置(双方都没配置级联),如果像那样运行demo01方法,那么Eclipse控制台会打印如下SQL语句:
在这里插入图片描述
发现有6条update语句,为什么会这样呢?原因非常简单,我们现在这个操作,是做了一个双向关联,那么客户和联系人都会去维护lkm_cust_id这个外键,也就是说当我们插入联系人的时候,lkm_cust_id是没值的,因为还没有Customer,所以它插入的是一个null值。当我们插入完联系人以后,再去插入客户,客户有了,我们就需要对联系人里面的lkm_cust_id去修改,所以就会出现这样一种情况。
在双向关联中,会产生多余的update语句,这个存在虽然不影响我们的程序运行,但是会影响性能,因为它在浪费资源。所以在写操作代码时,尽量要进行单向关联,但如果我们非得进行双向关联呢?那又该怎么减少资源浪费呢?这时我们可以使用inverse属性来设置,双向关联时由哪一方来维护表与表之间的关系。通常我们都会在多的一方维护关联关系,也就是说使一的一方放弃外键维护权,所以最好由联系人来维护双向关联关系,这样子的话,客户的映射配置文件应修改为:
在这里插入图片描述

  • inverse的值如果为true,代表由对方来维护外键;
  • inverse的值如果为false,代表由本方来维护外键。

关于inverse的取值有这样一个原则:外键在哪一个表中,我们就让哪一方来维护外键。就这样简简单单修改之后,再次运行demo01方法,Eclipse控制台会打印如下SQL语句:
在这里插入图片描述

区分cascade和inverse

很多人初次学Hibernate的时候,会很容易把cascade和inverse搞混,包括笔者也是,但它俩之间有本质的区别,压根就不是一个东西。

  • cascade:控制这个对象的关联对象;
  • inverse:控制一的一方能不能去维护这个外键,也就是说管不管这个外键?

咱也不说啥,用事实说话。我们可以这样子做,首先在客户的映射配置文件中设置级联操作,并且放弃外键维护权。
在这里插入图片描述
联系人的映射配置文件中啥也不做,内容如下:
在这里插入图片描述
然后,我们只设置单向关联关系,即客户关联联系人,联系人就不要关联客户了,运行以下测试方法,会有什么效果呢?

/**
 * 区分cascade和inverse的区别?
 * 		cascade:控制这个对象的关联对象
 * 		inverse:控制一的一方能不能去维护这个外键,管不管这个外键?
 */
@Test
public void demo09() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	Customer customer = new Customer();
	customer.setCust_name("阿水");
	
	LinkMan linkMan = new LinkMan();
	linkMan.setLkm_name("包租婆");
	
	//设置单向关联关系
	customer.getLinkMans().add(linkMan);
	
	session.save(customer);//此时,会有什么效果?客户会插入到数据库,联系人也会插入到数据库,但是外键为null。
	
	tx.commit();
}

此时,客户会插入到数据库表中,联系人也会插入到数据库表中,但是外键为null。为啥呢?因为现在一的一方(即客户)已经放弃外键的维护权了,而且你又只是设置单向关联关系,那当然联系人表中的外键为null。这也从另外一个角度说明,如果只设置了单向关联关系(一的一方关联多的一方),那么最好是不要放弃外键的维护权。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:白松林 返回首页