目录
关于数据库中“一对多”关系的介绍
在数据库设计的时候我们经常会遇到这样的问题:一个表中的字段可以对应另一张表的很多个字段。这样子的关系我们就称之为“一对多”的关系,而如果站在“多”的一方来看的话,就是“多对一”的关系。它们实质上是指的同一种关系。例子:
以上部门表与员工表之间就存在着:一个部门可以对应多个员工,但是一个员工只能对应一个部门这种关系。这种关系就属于典型的也是经典的“一对多”的关系。而“一”的一方就是部门,“多”的一方就是员工。
此篇博客所涉及到的数据库
我们这篇博客使用的是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( )时先保存“一”再保存“多”。