HIbernate 的一对多和多对多

数据库中多表之间存在着三种关系,也就是系统设计中的三种实体关系,分别是一对一,一对多和多对多。这里我主要详解一对多和多对多。

Hibernate 的一对多关联映射

一对多在表中的表达

例如:
在这里插入图片描述
建表原则:在多的一方创建外键,指向一的一方的主键

一对多在实体中的表达

例如客户与联系人之间的关系(一个客户可以对应多个联系人)
在这里插入图片描述
在HIbernate 中,是采用 java 对象关系来描述数据表之间的关系

在了解了一对多在数据库表中和实体中的表达后,那么在映射文件中如何表达呢?下面通过一个例子,在例子中学习、理解在映射文件中表达一对多之间的关系。

一个 Hibernate 的一对多关联映射简单例子

1.创建表
在上一篇,我们创建了一个customer表,我们在演示一对多关系映射,还需要一个 联系人表
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) NOT 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=3 DEFAULT CHARSET=utf8;
2.创建实体
//Customer实体
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_linkman;
	private String cust_phone;
	private String cust_mobile;

	// 使用set集合,表达一对多关系
	private Set<LinkMan> linkMens = new HashSet<LinkMan>();
	
	setter/getter方法。。。。
	}
//LinkMan实体
public class LinkMan {
	
	private Long lkm_id;
	private Character lkm_gender;
	private String lkm_name;
	private String lkm_phone;
	private String lkm_email;
	private String lkm_qq;
	private String lkm_mobile;
	private String lkm_memo;
	private String lkm_position;
	
	//表达多对一关系
	private Customer customer ;
	
    setter/getter方法。。。。
	}
3.创建映射
//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 package="com.pngyul.domain" >
	<class name="Customer" table="cst_customer" >
		
		<id name="cust_id"  >
			<generator class="native"></generator>
		</id>
		
		<property name="cust_name" column="cust_name" ></property>
		<property name="cust_source" column="cust_source" ></property>
		<property name="cust_industry" column="cust_industry" ></property>
		<property name="cust_level" column="cust_level" ></property>
		<property name="cust_linkman" column="cust_linkman" ></property>
		<property name="cust_phone" column="cust_phone" ></property>
		<property name="cust_mobile" column="cust_mobile" ></property>
		<!-- 一对多-->
		<!-- 
			private Set<LinkMan> linkMens = new HashSet<LinkMan>();
			
			name:对应Customer定义的属性,多的一方的属性名字 linkMens
			column:数据表中多的一方的外键,指向一的一方的主键
			class:多的一方的完整类名
		-->
		<set name="linkMens"> 
		  		<key column="lkm_cust_id"></key>
		  		<!-- 一对多关系 -->
		  		<one-to-many class="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 package="com.pngyul.domain" >
	<class name="LinkMan" table="cst_linkman" >
		<id name="lkm_id"  >
			<generator class="native"></generator>
		</id>
		<property name="lkm_gender"  ></property>
		<property name="lkm_name"  ></property>
		<property name="lkm_phone"  ></property>
		<property name="lkm_email"  ></property>
		<property name="lkm_qq"  ></property>
		<property name="lkm_mobile"  ></property>
		<property name="lkm_memo"  ></property>
		<property name="lkm_position"  ></property>
		
		<!-- 多对一 -->
		<!-- 
			name属性:引用属性名
			column属性: 外键列名
			class属性: 与我关联的对象完整类名
		 -->
		<many-to-one name="customer" column="lkm_cust_id" class="Customer">
		</many-to-one>
	</class>
</hibernate-mapping>
4.创建主配置文件,将映射文件添加到配置文件中
//这里我们在上一篇例子的主配置文件的基础上添加两行代码即可
<mapping resource="com/pngyul/domain/Customer.hbm.xml" />
<mapping resource="com/pngyul/domain/LinkMan.hbm.xml" />
5.编写代码测试
// 保存客户 以及客户 下的联系人
	@Test
	public void fun1() {

		Session session = HibernateUtils.openSession();
		Transaction transaction = session.beginTransaction();

		// 操作
		Customer c = new Customer();
		c.setCust_name("张三2");
		LinkMan lkm1 = new LinkMan();
		lkm1.setLkm_name("刘强东2");
		LinkMan lkm2 = new LinkMan();
		lkm2.setLkm_name("张一鸣2");

		// 表达一对多,客户下有多个联系人
		c.getLinkMens().add(lkm1);
		c.getLinkMens().add(lkm2);

		// 表达对对对,联系人属于哪个客户
		lkm1.setCustomer(c);
		lkm2.setCustomer(c);

		session.save(c);
		session.save(lkm1);
		session.save(lkm2);

		transaction.commit();
		session.close();

	}

这样这三条记录就保存在数据库中了,且客户与联系人之间存在一对多的关系

然而我们发现,如果一个客户对应很多个联系人,在持久化对象(调用 save 方法)时,每一个联系人都要写一行代码转换成持久态,未免有点累赘。所以我们试着注释这两行代码,试着运行是否可行

		session.save(c);
		//session.save(lkm1);
		//session.save(lkm2);

运行发现,这样操作显然不行,无论从哪一方保存都会出现 瞬时对象异常。一个持久态对象关联了一个瞬时态对象,那就说明我们不能只保存一方。那么如果我们就只想保存一方应该如何进行操作呢?Hibernate的级联级别。

级联操作

级联操作是指当主控方执行保存、更新或者删除操作时,其关联对象(被控方)也执行相同的操作。

级联保存或更新

级联是有方向性的,所谓的方向性是指,在保存一的一方级联多的一方和在保存多的一方级联一的一方

一个 [在保存一的一方级联多的一方] 的例子

//在Customer映射文件中的<set>标签添加cascade属性
		<set name="linkMens" cascade="save-update"> 
		  		<key column="lkm_cust_id"></key>
		  		<!-- 一对多关系 -->
		  		<one-to-many class="LinkMan" />
		</set>

添加了级联更新属性之后,在运行之前注释了两行代码的程序,发现没毛病。

相反也一样,你在 LinkMan 映射文件中添加级联更新,再注释保存 Customer 的代码,同样该 Customer 也会保存到数据库中。

级联删除

级联删除也是有方向性的,删除客户同时级联删除联系人,也可以删除联系人同时级联删除客户。由于级联删除操作风险很大,在实际开发中一般很少用。

	// 级联删除客户 以及客户 下的联系人
	@Test
	public void fun2() {

		Session session = HibernateUtils.openSession();
		Transaction transaction = session.beginTransaction();

		// 操作
		Customer c = session.get(Customer.class, 2l);
		session.delete(c);
		transaction.commit();
		session.close();

	}
至于 Customer 映射文件的修改,把 cascade 的属性只改成 delete 即可

PS:cascade 还有一个属性值all,该属性值的意思 [save-update+delete] ,就是同时具备级联更新和级联删除

关系维护

双向关联产生多余的 sql 语句

例如下面这个例子:

		@Test
		public void fun3() {

			Session session = HibernateUtils.getCurrentSession();
			Transaction transaction = session.beginTransaction();
			// 操作
			Customer customer = session.get(Customer.class, 1l);
			LinkMan linkMan = session.get(LinkMan.class, 3l);//该linkMan还不是该顾客的联系人
			//该该客户添加该联系人
			linkMan.setCustomer(customer);
			customer.getLinkMens().add(linkMan);
			transaction.commit();
		}

控制态最后输出了两个update

在这里插入图片描述
其实这两条 update 语句都是修改外键的操作,那为什么发两次呢?

因为映射文件中双向维护了关系,产生了多余的 sql,那么该如何解决呢?其实解决方法也很简单,只需要将一方放弃外键维护关系即可,通常我们会交给多的一方去维护。

例如,想让一的一方放弃维护,只需要进行如下配置,

       <!--  inverse属性:
							true:放弃维护
							false:默认值,不放弃维护
		 -->
		<set name="linkMens" cascade="save-update" inverse="true"> 
		  		<key column="lkm_cust_id"></key>
		  		<one-to-many class="LinkMan" />
		</set>

Hibernate 的多对多关联映射

Hibernate 的多对多关联映射与一对多有很多相似之处,相信学习完一对多关联再学多对多,学起来应该还是挺轻松的。

多对多在表中的表达

在这里插入图片描述
建表原则:创建一个中间表,中间表中至少两个字段作为外键分别指向多对多双方的主键

多对多在实体中的表达

例如员工与实体之间的关系
在这里插入图片描述
同样,下面也是通过一个例子来学习,多对多在映射文件中式如何表达的。说实话,和一对一差不多。。。

一个 Hibernate 的多对多关联映射简单例子

1.创建三个新表
//角色表
CREATE TABLE `sys_role` (
  `role_id` bigint(32) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(32) NOT NULL COMMENT '角色名称',
  `role_memo` varchar(128) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
//员工表
CREATE TABLE `sys_user` (
  `user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `user_code` varchar(32) NOT NULL COMMENT '用户账号',
  `user_name` varchar(64) NOT NULL COMMENT '用户名称',
  `user_password` varchar(32) NOT NULL COMMENT '用户密码',
  `user_state` char(1) NOT NULL COMMENT '1:正常,0:暂停',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
//员工说角色中间表
CREATE TABLE `sys_user_role` (
  `role_id` bigint(32) NOT NULL COMMENT '角色id',
  `user_id` bigint(32) NOT NULL COMMENT '用户id',
  PRIMARY KEY (`role_id`,`user_id`),
  KEY `FK_user_role_user_id` (`user_id`),
  CONSTRAINT `FK_user_role_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `FK_user_role_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.创建对应的实体
public class User {

	private Long user_id;
	private String user_code;
	private String user_name;
	private String user_password;
	private Character user_state;
	//表达多对多
	private Set<Role> roles = new HashSet<Role>();
	setter/getter方法。。。。。
	}
//角色对象
public class Role {
	
	private Long role_id;
	private String role_name;
	private String role_memo;
	//表达多对多
	private Set<User> users = new HashSet<User>();
	一堆setter/getter方法。。。
	}
3.编写对应的映射文件
//user映射文件
<?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">
   <!-- 配置表与实体对象的关系 --> 
   <!-- package属性:填写一个包名.在元素内部凡是需要书写完整类名的属性,可以直接写简答类名了. -->
<hibernate-mapping package="com.pngyul.domain" >

	<class name="User" table="sys_user" >
	
		<id name="user_id"  >
			<generator class="native"></generator>
		</id>
		
		<property name="user_code" ></property>
		<property name="user_name"></property>
		<property name="user_password"></property>
		<property name="user_state" ></property>
		
		<!-- 多对多关系表达 -->
		<!-- 
			name: 集合属性名
			table: 配置中间表名
			key
			 |-column:外键,别人引用"我"的外键列名
			  class: 我与哪个类是多对多关系
			  column:外键.我引用比人的外键列名
		 -->
		 <!-- cascade级联操作:
		 			save-update: 级联保存更新
		 			delete:级联删除
		 			all:级联保存更新+级联删除
		 	结论: cascade简化代码书写.该属性使不使用无所谓. 建议要用只用save-update.
		 		 如果使用delete操作太过危险.尤其在多对多中.不建议使用.
		 			 -->
		  <set name="roles" table="sys_user_role" cascade="save-update"> 
		  		<key column="user_id" ></key>
		  		<many-to-many class="Role" column="role_id" ></many-to-many>
		  </set>
	</class>
</hibernate-mapping>
//role映射文件
<?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 package="com.pngyul.domain" >

	<class name="Role" table="sys_role" >
	
		<id name="role_id"  >
			<generator class="native"></generator>
		</id>
		
		<property name="role_name" ></property>
		<property name="role_memo"></property>
		  <!--  inverse="true" 角色放弃关系维护 -->
		<set name="users" table="sys_user_role" inverse="true"> 
		  		<key column="role_id" ></key>
		  		<many-to-many class="User" column="user_id" ></many-to-many>
		  </set>
	</class>
</hibernate-mapping>
4.映射文件添加到主配置文件中
		<mapping resource="com/pngyul/domain/User.hbm.xml" />
		<mapping resource="com/pngyul/domain/Role.hbm.xml" />
5.测试
	@Test
	public void fun1() {

		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();

		// 操作
		User u1 = new User();
		u1.setUser_name("张三");
		User u2 = new User();
		u2.setUser_name("李四");

		Role r1 = new Role();
		r1.setRole_name("运维");
		Role r2 = new Role();
		r2.setRole_name("测试");
        //如果多对多建立了双向关联,一定要有一方放弃维护
		// 用户表达关系
		u1.getRoles().add(r1);
		u1.getRoles().add(r2);
		u2.getRoles().add(r1);
		u2.getRoles().add(r2);

		// 角色表达关系
		r1.getUsers().add(u1);
		r1.getUsers().add(u2);
		r2.getUsers().add(u1);
		r2.getUsers().add(u2);

		// 调用Save方法一次保存
		session.save(u1);
		session.save(u2);
		session.save(r1);
		session.save(r2);

		transaction.commit();
	}

测试发现数据都保存到了数据库当中,并且员工与角色存在着多对多的关系

ps:在多对多的保存操作中,如果进行了双向维护关系,就必须有一方放弃外键维护权。一般由被动方放弃,员工主动选择角色,角色是被选择的,所以一般角色要放弃外键维护权,但如果只进行单向维护关系,那么就不需要放弃外键维护权了。

级联操作

多对多的级联保存跟一对多的一样,如果只想保存单独的一方是不可以的,还是需要保存双方的。如果就想保存一方就需要设置级联操作了。同样要看保存的主控方是哪一端,就需要在那一端纪进行配置。

	// 级联更新--保存用户 以及担任的任务
	@Test
	public void fun2() {

		Session session = HibernateUtils.openSession();
		Transaction transaction = session.beginTransaction();

		// 操作
		User u1 = new User();
		u1.setUser_name("张三1");
		User u2 = new User();
		u2.setUser_name("李四1");

		Role r1 = new Role();
		r1.setRole_name("运维1");
		Role r2 = new Role();
		r2.setRole_name("测试1");

		// 用户表达关系
		u1.getRoles().add(r1);
		u1.getRoles().add(r2);
		u2.getRoles().add(r1);
		u2.getRoles().add(r2);

		// 角色表达关系
		// r1.getUsers().add(u1);
		// r1.getUsers().add(u2);
		// r2.getUsers().add(u1);
		// r2.getUsers().add(u2);

		// 调用Save方法一次保存
		session.save(u1);
		session.save(u2);
		// session.save(r1);
		// session.save(r2);

		transaction.commit();
		session.close();

	}
// 级联更新--为张三新增一个角色
	@Test
	public void fun3() {

		Session session = HibernateUtils.openSession();
		Transaction transaction = session.beginTransaction();

		// 操作
		User u = session.get(User.class, 1l);
		Role r = new Role();
		r.setRole_name("软件开发");

		u.getRoles().add(r);
		session.save(u);
		// session.save(r);

		transaction.commit();
		session.close();

	}
	@Test
	//为張三解除一个角色
	public void fun5(){
		
		Session session = HibernateUtils.openSession();
		
		Transaction tx = session.beginTransaction();
	
		User u = session.get(User.class, 2l);
		Role r = session.get(Role.class, 2l);
		u.getRoles().remove(r);
		
		
		tx.commit();
		session.close();
	}

级联保存说完了,那么我们来看下级联删除的操作,但在多对多中是不会使用的,因为我们不会有这类需求,比如删除员工,将员工关联的角色一起删除,或者删除角色的时候将员工删除。这是不可理的。但是级联删除的功能 Hibernate 已经提供了。所以我们只需要了解一下即可

	// 級聯刪除
	@Test
	public void fun4() {
		Session session = HibernateUtils.openSession();
		Transaction transaction = session.beginTransaction();
		// 操作
		User u = session.get(User.class, 1l);
		session.delete(u);
		transaction.commit();
		session.close();
	}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值