一对多关联
首先举一例:阵型cancan的人找lulu阵型里的人做心灵pk,无奈cancan阵型里的人与lulu阵型实力相距甚远。。。于是提出cancan阵型里的人可以群K lulu阵型里的单个人,当然,接受单挑。
在hibernate的映射中,一对多关联分为单向一对多和双向一对多关联。
单向一对多
TLulu.java:
package learnHibernate.bean;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
public class TLulu implements Serializable {
private static final long serialVersionUID = -252962688967803016L;
private int id ;
private String name;
private String sixthSense;
private TOham oh;
private Set<TCancan> cs;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TOham getOh() {
return oh;
}
public void setOh(TOham oh) {
this.oh = oh;
}
public String getSixthSense() {
return sixthSense;
}
public void setSixthSense(String sixthSense) {
this.sixthSense = sixthSense;
}
public Set<TCancan> getCs() {
return cs;
}
public void setCs(Set<TCancan> cs) {
this.cs = cs;
}
}
TCancan.java
package learnHibernate.bean;
public class TCancan {
private int id;
private String name;
private String think;
private TOham oh;
private int rivalId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getThink() {
return think;
}
public void setThink(String think) {
this.think = think;
}
public TOham getOh() {
return oh;
}
public void setOh(TOham oh) {
this.oh = oh;
}
public int getRivalId() {
return rivalId;
}
public void setRivalId(int rivalId) {
this.rivalId = rivalId;
}
}
TLulu.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TLulu" table="t_lulu"> <id name="id" column="id" type="java.lang.Integer"> <generator class="foreign"> <param name="property">oh</param> </generator> </id> <one-to-one name="oh" class="TOham" constrained="true"/> <property name="name" column="name" type="java.lang.String" /> <property name="sixthSense" column="sixthsense" type="java.lang.String" /> <!-- 这里用set存储多个TCancan,因为是一对多的关联, set中必须指定“多”方的关联表以及关联字段 --> <set name="cs" table="t_cancan" cascade="all"> <key column="rivalid" /> <one-to-many class="TCancan" /> </set> </class> </hibernate-mapping>
TCancan.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TCancan" table="t_cancan"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native" /> </id> <one-to-one name="oh" class="TOham" property-ref="can" /> <property name="name" column="name" type="java.lang.String" /> <property name="think" column="think" type="java.lang.String" /> <property name="rivalId" column="rivalid" type="java.lang.Integer" /> </class> </hibernate-mapping>
注意,在做保存更新操作的时候,为了保持关联关系,只能通过主控方(此处为被外键引用方:TLulu)对被动方(持有关联外键方:TCancan)进行操作。所以这里就可能出现一个问题,当关联字段不允许为null时,hibernate在进行创建或更新关联关系的时候可能出现约束违例。
现在想对某个TLulu的人再分配一个TCancan对手:
TLulu lu = (TLulu)session.get(TLulu.class, new Integer(9));
TCancan can = new TCancan();
can.setName("Can3");
can.setThink("Inversion not exists");
lu.getCs().add(can);
session.save(lu);
tx.commit();
此时问题出现了:当执行到session.save(lu)时,hibernate执行如下sql:
Hibernate:
insert
into
t_cancan
(name, think, rivalid)
values
(?, ?, ?)
于是抛了一个异常:org.hibernate.exception.ConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`hibernate/t_cancan`, CONSTRAINT `FK_t_cancan_1` FOREIGN KEY (`rivalid`) REFERENCES `t_lulu` (`id`))。
这是因为此时关联是单向的关联关系是由TLulu对象维持,而TCancan并不知自己与哪个TLulu相关联,所以在save(lu)的时候去保存Tcancan,只能想先给rivalid插个空值,其实当执行tx.commit()的时候,hibernate会执行一条update语句,因为save的时候是saveTLulu对象的,所以hibernate会将TLulu的对象自身的id赋值给TLulu的对象中的TCancan,在事务提交的时候,hibernate会发现这一变化,于是就执行一条sql语句。
针对上述,现在将TCancan.hbm.xml中 的rivalid映射属性去掉,执行save的时候就不会试图将null保存给t_cancan了,此处可以对t_cancan表做些修改,给rivalid设置默认值。这样就避免了上述异常。
但问题是为了插一条记录执行两个sql语句,效率并不高,这里完全只需一条sql能搞定,只要想法使得执行save(lu)的时候让can对象知道如何获取lu对象的id并以其作为自身的rivalId的值。
于是,双向的一对多关联来了。
双向一对多
双向的一对多关联其实是“一对多”与“多对一”关联的组合。也就是说在主控方配置一对多映射的同时,也需要在被控方配置多对一的映射。
将TCancan.hbm.xml修改如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="learnHibernate.bean"> <class name="TCancan" table="t_cancan"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native" /> </id> <one-to-one name="oh" class="TOham" property-ref="can" /> <property name="name" column="name" type="java.lang.String" /> <property name="think" column="think" type="java.lang.String" /> <many-to-one name="lu" class="TLulu" cascade="none" outer-join="auto" update="true" insert="true" access="property" column="rivalid" not-null="true" /> </class> </hibernate-mapping>
TCancan.java修改如下:
package learnHibernate.bean;
public class TCancan {
private int id;
private String name;
private String think;
private TOham oh;
private TLulu lu;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getThink() {
return think;
}
public void setThink(String think) {
this.think = think;
}
public TOham getOh() {
return oh;
}
public void setOh(TOham oh) {
this.oh = oh;
}
public TLulu getLu() {
return lu;
}
public void setLu(TLulu lu) {
this.lu = lu;
}
}
执行代码修改如下,加了一行can.setLu(lu):
TLulu lu = (TLulu)session.get(TLulu.class, new Integer(10));
TCancan can = new TCancan();
can.setName("Can3");
can.setThink("Inversion not exists");
can.setLu(lu);
lu.getCs().add(can);
session.save(lu);
tx.commit();
结果输出:
Hibernate:
insert
into
t_cancan
(name, think, rivalid)
values
(?, ?, ?)
Hibernate:
update
t_cancan
set
rivalid=?
where
id=?
。。。结果不对,不应该输出两个sql,应该是一个insert的sql才对。。。
原因是这里的关联主控方还是TLulu,在hibernate的一对多的映射关联概念中,关联关系的维护室主控方负责的,虽然执行代码中已经can.setLu(lu)这样了,但由于TCancan不是主控方,所以,在映射的层面上,TCancan不会因为有了can.setLu(lu)而主动将lu的id赋值到自身的rivalid。
于是此时需要把TCancan作为关联的主控方,有它负责维护关联关系。
对TLulu.hbm.xml的set节点做下修改:
<set name="cs" table="t_cancan" cascade="all" inverse="true"> <key column="rivalid" /> <one-to-many class="TCancan" /> </set>
inverse设置为true,就行了。意思是我TLulu本来的关联主控方放弃主控角色的责任,交由TCancan被控方负责维护关联。
重新执行代码,结果:
Hibernate:
insert
into
t_cancan
(name, think, rivalid)
values
(?, ?, ?)
算是成事了。
注意的是inserve与cascade的区别,inverse是指关联关系的控制方向,而cascade是层级的连锁操作。在一对多的关联中将多的一方设为主控方去维护关联,有助性能优化。