hibernate中维护“一对多”关系的两种方式

目录

 

关于数据库中“一对多”关系的介绍

此篇博客所涉及到的数据库

交给“一”的一方来维护“一对多”关系:

交给“多”的一方来维护“一对多”关系

“一对多”关系究竟交给谁维护?


关于数据库中“一对多”关系的介绍

在数据库设计的时候我们经常会遇到这样的问题:一个表中的字段可以对应另一张表的很多个字段。这样子的关系我们就称之为“一对多”的关系,而如果站在“多”的一方来看的话,就是“多对一”的关系。它们实质上是指的同一种关系。例子:

 以上部门表与员工表之间就存在着:一个部门可以对应多个员工,但是一个员工只能对应一个部门这种关系。这种关系就属于典型的也是经典的“一对多”的关系。而“一”的一方就是部门,“多”的一方就是员工。

此篇博客所涉及到的数据库

我们这篇博客使用的是mysql数据库,而其数据库中的表结构如下:

表dep(部门表):

    depid : 表示部门的id即编号  (主键,非空)

    depname : 表示部门的名字(非空)

表emp(员工表):

    eid : 表示员工的id即编号(主键,非空)

    ename : 表示员工的姓名(非空)

    did : 表示员工所在部门的编号(外键于dep表中的depid,非空)

其创建表时的sql语句如下(也可以使用Navicat这种可视化工具创建,效率相对较高):

create table dep(
    -> depid int(11) not null,
    -> depname varchar(255) not null,
    -> primary key(depid)
    -> );
 create table emp(
    -> eid int(11) not null,
    -> ename varchar(255) not null,
    -> did int(11),
    -> foreign key(did) references dep(depid)
    -> );

hibernate中用两种维护“一对多”关系的方式,一种是交给“一”的一方来维护,另一种是交给“多”的一方来维护。不管这种关系是交给“一”的一方来维护还是交给“多”的一方来维护,其前提都是有以上说明的数据库做支撑,也就是必须严格按照上面的数据库进行设计主键和外键。

交给“一”的一方来维护“一对多”关系:

交给“一”的一方来维护“一对多”的关系的方式就是在“一”的一方的数据库中的表所对应的pojo类中新增一个集合属性用于存储“多”的一方的数据库中的表所对应的pojo类的对象。

首先,在dep表所对应的Dep类中定义了一个Set集合用于存储Emp类的对象:

public class Dep {

	private int depid;
	private String depname;
	private Set<Emp> emps = new HashSet<Emp>();//“多”的一方的集合

	public Set<Emp> getEmps() {
		return emps;
	}
	public void setEmps(Set<Emp> emps) {
		this.emps = emps;
	}
	public int getDepid() {
		return depid;
	}
	public void setDepid(int depid) {
		this.depid = depid;
	}
	public String getDepname() {
		return depname;
	}
	public void setDepname(String depname) {
		this.depname = depname;
	}
}

其次,在Dep类所对应的dep.hbm.xml配置文件中使用<set>对其关系进行声明:

<hibernate-mapping>
	<!-- 一张表对应一个类 orm  -->
	<class name="com.pojo.Dep" table="dep">
		<id column="depid" name="depid">
			<generator class="assigned"></generator>		
		</id>
		<property column="depname" name="depname"></property>
		<!-- 关系 -->
		<set name="emps">
			<key column="did"></key>
			<one-to-many class="com.pojo.Emp"/>
		</set>
	</class>
</hibernate-mapping>

关于<set>中的属性和子标签:

    name属性:对应Dep类中set集合类型的属性的属性名;

    <key>子标签:对应set集合中存储的类对象所对应的类所对应的表(即emp表)中与dep表中depid所联系的外键列名;

    <one-to-many>子标签中class属性:set集合中存储的类的位置。

以上就是我们在“一对多”关系出现后,由“一“的一方去维护此关系时所需要的配置。关于此关系”一“的一方如何来维护,或者说如何使用此配置,下面我们写一个测试类来进行说明:

public class Test {
	public static void main(String[] args) {
		// 1.读取总的配置文件
		Configuration configuration = new Configuration().configure();
		// 2.创建session工厂
		SessionFactory factory = configuration.buildSessionFactory();
		// 3.得到session
		Session session = factory.openSession();
		// 4.开启事务
		Transaction transaction = session.beginTransaction();

		Dep dep = new Dep();
		dep.setDepid(7);
		dep.setDepname("中卖部");

		Emp emp = new Emp();
		emp.setEmpid(6);
		emp.setEmpname("鸟上飞");

		dep.getEmps().add(emp);
	
		session.save(dep);
		session.save(emp);
		transaction.commit();
		session.close();		
	}
}

在上边的main方法执行后,数据库中dep表中就会加入一条数据:

emp表中也会加上一条数据:

 接下来我们考虑一个问题:session.save( )的执行顺序是否会影响到最后的结果?在上面我们的测试方法中是下面这个顺序:

session.save(dep);
session.save(emp);

即先保存dep对象,再保存emp对象。而控制台通过log4j打印出来的sql语句如下:

    Hibernate: insert into dep (depname, depid) values (?, ?)
    Hibernate: insert into emp (ename, did, eid) values (?, ?, ?)
    Hibernate: update emp set did=? where eid=?

通过这个可以看出,hibernate框架对其的执行过程是:先加入dep对象所对应的属性值进入数据库,再加入emp对象所对应的属性值进入数据库而此时did字段的值是多少呢?我们暂时还不知道,但是不妨猜想一下是null,最后再根据eid找到emp表中刚刚加入的数据对did进行修改。

上面设计数据库的时候我们知道,emp表中的did字段(即外键字段)并没有设置not null 。那我们现在把它设置成not null,看一下main方法的执行结果是什么。 将刚刚添加在数据库中的数据删除并且把did字段修改成not null之后再去执行一下main方法我们发现控制台输出结果如下:

并且数据库中两张表中都没有相关数据的加入。也就是说在执行到“insert into emp(ename,did,eid) values(?,?,?)”这条sql语句的时候,出错了,它说did字段不可以为空值。这就可以证实了我们之前的猜想,那就是关于数据库中数据的变化过程是:先加入dep对象所对应的属性值进入数据库,再加入emp对象所对应的属性值进入数据库而此时did字段的值为null,最后再根据eid找到emp表中刚刚加入的数据对did进行修改。

但是是在执行到“insert into emp(ename,did,eid) values(?,?,?)”的时候报的错,按说“insert into dep(depname,depid) values (?,?)”已经执行完了,dep表中应该有相关数据的加入呀,事实并没有。是因为我们此次操作开启了事务,一旦全部流程中任何一处报了错,就会进行回滚。之前执行的sql语句所产生的结果都会被打回原形。

现在我们把session.save( )的顺序进行颠倒:

session.save(emp);
session.save(dep);

此次 运行控制台打印输出的结果如下:

可以看出还是在执行到“insert into emp(ename,did,eid) values (?,?,?)”的时候出了did字段为null的错误。但是这次与上次不同的是这次并没有打印输出“insert into dep(depname,depid) values (?,?)”这句话。可见是因为还没有来得及执行这句话就已经出现了错误。而不难推断:出现这个错误的原因与上边所提到的sql语句的执行有关。那session.save( )的顺序不变,我们把数据库中emp表所对应的did字段的not null取消掉,之后再执行一边main方法。

控制台完美输出且数据库中有相关数据加入:

Hibernate: insert into dep (depname, depid) values (?, ?)
Hibernate: insert into emp (ename, did, eid) values (?, ?, ?)
Hibernate: update emp set did=? where eid=?

由以上分析可见:

    1. session.save( )的顺序只会影响到insert语句的执行顺序,而不会对最终结果造成影响。因为每次加入数据的时候,两个表都是独立的加入的,并不是dep不加入emp就不可以加入,也不是emp不加入dep就不可以加入。也就是:虽然在emp表中did字段依赖于dep表中的depid字段,但是在emp表中加入数据的原理是did一开始加入null,然后再去修改did。这也就是session.save( )的书写顺序不会对结果造成影响的原因;

    2. emp表即“多”的一方的表中的外键字段,不能设置为非空。否则这种方式是不可用的。

交给“多”的一方来维护“一对多”关系

上边我们演示的是如何使用“一”的一方来维护数据库中“一对多”的关系。下面我们演示一下如何使用“多”的一方来维护此关系。

首先,在“多”的一方的pojo类中加入“一”的一方所对应的Pojo类所对应的类型的属性。例如:

public class Emp {
	private int empid;
	private String empname;
	private Dep dep;//“一”的一方

	public int getEmpid() {
		return empid;
	}
	public Dep getDep() {
		return dep;
	}
	public void setDep(Dep dep) {
		this.dep = dep;
	}
	public void setEmpid(int empid) {
		this.empid = empid;
	}
	public String getEmpname() {
		return empname;
	}
	public void setEmpname(String empname) {
		this.empname = empname;
	}
}

其次,在emp.hbm.xml文件中配置<many-to-one>标签:

<hibernate-mapping>
	<!-- 一张表对应一个类 orm -->
	<class name="com.pojo.Emp" table="emp">
		<id column="eid" name="empid">
			<generator class="assigned"></generator>
		</id>
		<property column="ename" name="empname"></property>
	
		<!-- 多对一  -->
		<many-to-one name="dep" class="com.pojo.Dep" column="did"></many-to-one>
	</class>
</hibernate-mapping>

关于<many-to-one>标签:

    name属性:Emp类中对应emp表中的外键did字段的属性的名字;

    class属性:指明“一”所对应的类;

    column属性:emp表中外键名字。

以上就是使用“多”的一方去维护“一对多”关系时的准备工作。关于此关系”多“的一方如何来维护,或者说如何使用此配置,下面我们写一个测试类来进行说明:

public class Test3 {
	public static void main(String[] args) {
		Configuration configuration = new Configuration().configure();
		SessionFactory factory = configuration.buildSessionFactory();
		Session session = factory.openSession();
		Transaction transaction = session.beginTransaction();

		Dep dep = new Dep();
		dep.setDepid(8);
		dep.setDepname("信笺部");

		Emp emp = new Emp();
		emp.setEmpid(11);
		emp.setEmpname("王九");

		emp.setDep(dep);

		session.save(dep);
		session.save(emp);

		transaction.commit();

		session.close();
	}
}

其运行结束后数据库中存在相关数据:

 

并且控制台打印输出两条sql语句:

    Hibernate: insert into dep (depname, depid) values (?, ?)
    Hibernate: insert into emp (ename, did, eid) values (?, ?, ?)

可见,其执行过程是:加入相对应的dep表中的数据,然后再加入emp表中的数据。(由其打印输出的sql语句可知,这时候如果将emp表中的did字段设置成not null之后不会再像由“一”的一方维护关系时那样再去报错)

那么我们把数据库中刚刚加入的数据删除并将session.save( )换一下顺序:

session.save(emp);
session.save(dep);

之后执行main方法,控制台打印输出结果如下并且数据库中存在相关数据: 

    Hibernate: select dep_.depid, dep_.depname as depname0_ from dep dep_ where dep_.depid=?
    Hibernate: insert into emp (ename, did, eid) values (?, ?, ?)
    Hibernate: insert into dep (depname, depid) values (?, ?)
    Hibernate: update emp set ename=?, did=? where eid=? 

可以看出有update语句,所以可以猜测出,如果再把emp表中的did字段设置成not null,之后它还会报错。我们测试了一下,果不其然,结果如下:

 由上边的sql语句可以看出,"insert into emp(ename,did,eid) values(?,?,?)"语句执行的时候报了错,由此可知这条语句执行的时候did为null。故其执行过程也就可以看的出了。

但是依照“人之常情”session.save( )的执行过程就应该是:

session.save(dep);
session.save(emp);

因为如果没有部门,加个卵子的员工。肯定是现有部门后有员工啊,并且此时的“一对多”关系是由“多”的一方来维护的,是在Emp类中使用了一个Dep类型的属性。用脚趾头想想,没有dep之前,就先有emp也不符合思维习惯不是?! 

“一对多”关系究竟交给谁维护?

上边我们知道了:“一对多”关系可以交给“一”来维护,也可以交给“多”来维护。那么究竟交给谁来维护更加好些呢?下面我们根据它们的执行过程进行分析。

由”一“的一方来维护此关系的话。在session.save( )中不管是先保存”一“还是先保存”多“,其sql语句都是相同的顺序:insert...+insert...+update...也就是都会执行三条sql语句。并且在”多“的一方的表中的外键字段不能设置成not null。

由”多“的一方来维护此关系的话。在session.save( )中先保存”一“和先保存”多"其区别较大:先保存“一”的话,执行两条insert...语句,并没有执行update语句,所以此时在"多"的一方的表中的外键字段可以设置成not null ;如果先保存“多”的一方的话,执行的sql语句较多,共有四条:select...+insert..+insert...+update...并且"多"的一方的表中的外键字段不可以设置成not null(只要是有update...语句执行那么外键字段都不能设置成not null,因为在前面的insert...语句中会先将外键字段赋值为null)。

总结:由“一”的一方来维护的话,需要三条sql语句,其效率会变低。如果由“多”的一方来维护的话,一定要记得先保存“一”的一方,这样只会执行两条sql语句,效率较高,并且不需要考虑外键字段是否为not null;不建议先保存“多”的一方,第一是不符合人之常情,第二就是所执行的sql语句较多,效率较低,并且还需要考虑外键字段是否为not null这个问题。

所以:建议使用“多”的一方来维护“一对多”的关系,并且在使用session.save( )时先保存“一”再保存“多”。

猜你喜欢

转载自blog.csdn.net/My_name_is_ZwZ/article/details/83003864