其实配置one-to-many的映射很简单,就是在one所代表的这个类中添加一个集合对象,一般用set,来保存对应的many所代表的那个类的对象,比如,一个person对应多个order,(一个人对应多个订单),那么在person类中添加private Set<Order> order ;这个属性,然后提供get,set方法
然后在Person.hbm.xml中配置:
<set name="order" inverse="true" cascade="all">
<key column="userid" />
<one-to-many class="Order" />
</set>
这样,一个单向的一对多关系就配置完成了,在主程序中新建order对象,保存进set中,再把person.setOrder(set),然后保存order对象,保存person对象就行了。但是这样配置有很大的问题,在数据库中的处理是先是往order表和person表中插入数据,但是order表的外键列插入的是null,然后执行更新语句,更新order表的外键列,把它设置成对应的personid
如下控制台输出的信息:(里面有张表是userinfo而不是personinfo)
Hibernate: insert into USERINFO (username, password, userid) values (?, ?, ?)
Hibernate: insert into ORDERINFO (product, oid) values (?, ?)
Hibernate: insert into ORDERINFO (product, oid) values (?, ?)
Hibernate: insert into ORDERINFO (product, oid) values (?, ?)
Hibernate: update ORDERINFO set USERID=? where oid=?
Hibernate: update ORDERINFO set USERID=? where oid=?
Hibernate: update ORDERINFO set USERID=? where oid=?
这样操作的话性能就很不好了,
如果设置双向一对多关联,那么就很好了,就是在order类中加一个属性private Person person,然后提供get,set方法,然后在Order.hbm.xml中配置
<many-to-one name="person" class="Person" column="personid" />,然后在主程序中,order.set(person),要设置值(控制关联关系),其他的和上面的一样,这样
控制台输出信息如下:
Hibernate: insert into USERINFO (username, password, userid) values (?, ?, ?)
Hibernate: insert into ORDERINFO (product, USERID, oid) values (?, ?, ?)
Hibernate: insert into ORDERINFO (product, USERID, oid) values (?, ?, ?)
Hibernate: insert into ORDERINFO (product, USERID, oid) values (?, ?, ?)
就只有4条插入语句在操作数据库了。
关于双向一对多的操作注意一下几点:(引用自李刚的JAVAEEssh整合开发那本书)
1.先持久化one的对象
2.先设置one和many的关联关系,在保存many对象,否则还是会出现先插入,然后再更新的重复操作数据库的现象
3.不要通过one的那一端来控制关联关系,可以设置set的inverse=“true”来控制,或者在主程序中不通过设置set来关联两个类
(inverse的意思是反转,用来设置关系哪一方是拥有者owner,由他来维护这个关系。
作用是在一对多双向关联关系中使一方放弃维护到对方的关系(抽象)。其实就是减少重复执行多余的SQL语句 )
inverse="false" 是默认情况,此时没有反转,则 Set/Collection 关系由包含它的“一”这一方来维;
inverse="true" 时,表示 Set/Collection 关系由另一方来维护,由不包含这个关系的一方来维护这个关系,所以才称为“反转”了
实例分析:
以Student(学生)和Class(班级)为例,它们之间的关系为一对多的关系,即一个学生只能属于一个班级,一个班级可以包含多个学生。 Class班级:
Class Class{
private int id;
private String name;
private Set students = new HashSet();
//省略getter()和setter()方法
}
Student学生:
Class Student{
private int id;
private String name;
private Class class;
//省略getter()和setter()方法
}
Class类的映射文件:
<class name="Class" table="CLASS"> <id name="id" type="int" column="ID"> <generator class="native" /> </id> <property name="name" type="string" column="NAME" /> <set name="students" table="STUDENT" cascade="save-update" inverse="false"> <key column="CLASS_ID" /> <one-to-many class="Student" /> </set> </class>
Student类的映射文件:
<class name="Student" table="STUDENT"> <id name="id" type="int" column="ID"> <generator class="native" /> </id> <property name="name" type="string" column="NAME" /> <many-to-one name="class" column="CLASS_ID" class="Class" cascade="save-update" /> </class>
STUDENT表中存在一个名为CLASS_ID的字段,它和CLASS表中的ID字段是主外键关系。那个inverse属性就是用来规定是由谁(Student或Class)来维护这个主外键关系的。 inverse的默认值为false。 在处理逻辑代码中,如下:
Class c1 = new Class();
c1.setName("一班");
Student s1 = new Student();
Student s2 = new Student();
s1.setName("Jason");
s2.setName("Tom");
c1.getStudents().add(s1);
c2.getStudents().add(s2);
s1.setClass(c1);
s2.setClass(c1); //注释1
session.save(c1);
上面的代码会使Hibernate执行五条SQL语句,其中前三条是insert插入语句,后两条是update更新语句。插入就不用说了,那么为什么还要有更新语句呢?这是因为Class类映射文件的<set>元素中指定了inverse="false",这就告之Hibernate:STUDENT表与CLASS表的主外键关系是由Class类来维护的。当执行save后,执行了三条insert语句,这三条语句中的后两条是插入到STUDENT表的,它们的CLASS_ID字段是通过s1.getClass().getID()取出的,假如我将上面“注释1”处修改为s2.setClass(c2);(c2是另一个Class对象,可能是持久化对象),这样,主外键关系不就乱了吗。为了保证主外键关系,Hibernate在这种情况下会再执行两条update语句来更改STUDENT表中两个新插入记录的CLASS_ID字段,当然,这时CLASS_ID字段的取值是从c1对象中直接取得,而不再是s1.getClass().getID()方式了。
如果我们将Class类映射文件的<set>元素中的inverse属性修改为true,这就是告诉Hibernate:Class类不维护主外键关系了,这个任务就交给了Student类。于是,我们再执行上面的代码,Hibernate就会只执行三条insert语句,而不会执行任何update语句。因为Hibernate会通过Student类的s1.getClass().getID()和s2.getClass().getID()来确定CLASS_ID字段的值。
故,为了节省数据库资源,省却不必要的update语句,我们一般建议在一对多双向关联关系中,将一方的inverse属性设置为true,即将主外键的关系交由多方来维护。
打个比方:在一个公司中,是老板认识所有的员工容易,还是所有员工认识老板容易?