`
oham_一1一
  • 浏览: 49962 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Hibernate使用——一对多关联示例

阅读更多

一对多关联

 

首先举一例:阵型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是层级的连锁操作。在一对多的关联中将多的一方设为主控方去维护关联,有助性能优化。

 

 

 

 

 

 

 

 

 

 

  • 大小: 7 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics