天天看点

Hibernate关系映射学习点滴1:一对一关系映射

 一对一关系映射

    一对一关系映射有两种实现方式,一种是基于主键的关系映射, 使用<one-to-one>来实现, 一种是基于外键的关系映射, 使用<many-to-one>来实现,具体可见前面收集的转载的文章。

   基于主键的映射示例:

<one-to-one name="dept" class="cn.javass.h4.dept.DeptModel"> 
//one-to-one元素没有column属性, 所以他的关联的表column只可能是主键列;而且,
//这个元素只是定义了一种关系, 不会定义一个新的主键列出来,不会引起主键列重复定义的问题
</one-to-one>
           

基于外键的映射示例:(通过unique="true", 表明一对一关联)

<many-to-one name="dept" column="deptId" class="cn.javass.h4.dept.DeptModel"
	lazy="false"
	unique="true"
	 > //因为有column元素, 所以<many-to-one>不仅定义了关系, 还定义了新的表列 (外键列)
</many-to-one>
           

注:

1. Hibernate中unique的真正意义是根据hbm生成DDL时,

如果设置了unique="true"则生成的表中对应的字段就有unique限制;

而如果是根据数据库的表生成的hbm,那么在hbm中设置的unique="true"是没有任何用处的。

Hibernate不会因为你给这个属性或字段设置了unique为true就给你检查。

2. <one-to-one>和<many-to-one>的区别在于, 因为one-to-one没有column属性,他的关联列就是主键列,所以他只是定义了一种关系,不会产生列重复定义的问题。

如果像下面这样通过<many-to-one>定义一对一关联,并且希望使用主键做关联列的话, 因为在class的定义里面已经定义了一个id主键列, many-to-one元素又定义了一个列"id", 所以id列就重复定义了,Hibernate执行时会报错。因此,如果需要让主键列作当前类的关联列,就必须要用到<one-to-one>。由此可见, 在双向的一对一关联中, 不管是主键一对一还是外键一对一的方式, 至少有一方需要使用<one-to-one>的形式。

<class name="cn.javass.h4.dept.DeptModel" table="tbl_dept">
		<id name="id">
			<generator class="native" />
		</id>
		<property name="name"></property>
		<many-to-one name="du" column="id" unique="true"></many-to-one> //这样做是不对的
	</class>
           

3. <one-to-one>元素中, 有一个constrained 元素 (注意:many-to-one没有), 这个选项影响 save() 和 delete() 在级联(cascade)执行时的先后顺序,默认为false。

constrained和cascade的关系如下例:

假定对象的关系映射配置如下:

<class name="DeptModel" table="tbl_dept">
		<id name="id">
			<generator class="native" />
		</id>
		<property name="name"></property>
		<one-to-one name="du" class="DeptUserModel" property-ref="dept" constrained="true" cascade="save-update"></one-to-one>
	</class>
	<class name="DeptUserModel" table="tbl_user"> 
		<id name="uuid">
			<generator class="assigned" />
		</id>

		<property name="userId"></property>
		<property name="name"></property>
		<property name="age"></property>
		<many-to-one name="dept" column="deptId" class="DeptModel"
				 lazy="false" cascade="save-update"
				 >
		</many-to-one>
	</class>
           

在上面的配置中, constrained有两个方面的作用:

1)constrained为true的时候, Hibernate要求声明constrained属性的对象,也就是 DeptModel对象的du属性(关联属性)不能为空, 也就是说程序中一定要对du属性赋值, 这个特性对保证程序的正确性有一定作用;constrained为false就没有这个限制。

2)constrained 值为 true,这样的话,  在执行 session.save(DeptModel instance)的时候, Hibernate会先保存与DeptModel关联的DeptUserModel对象, 然后再保存DeptModel自身;而当constrained为false的时候,执行session.save(DeptModel instance), Hibernate会先保存DeptModel自身, 然后再保存DeptUserModel。

      如果你 执行的是session.save(DeptUserModel) , 无论DeptModel的constrained为true还是false, Hibernate都会先保存DeptModel, 再保存DeptUserModel; 但是,constrained为true的时候, Hibernate仍然要求 DeptModel的du属性不能为空, 也就是说程序中一定要对du属性赋值。

下面的解释来自于网上, 经过验证是正确的:

constrained默认值为false

constrained只能在one-to-one的映射中使用,(一般在主表的映射中,有外键的那个表)。如果

constrained=true,则表明存在外键与关联表对应,并且关联表中肯定存在对应的键与其对应, 另外该选项最关键的是影响save和delete的先后顺序。例如增加的时候,如果constainted=true,则会先增加关联表,然后增加本表。删除的时候反之。

one-to-one关联中(不管是单向还是双向),如果constrained=false,则会在查询时就全部取出来,用left outer join的方式;如果constrained=true,也会在查询时就全部取出来,使用select的抓取方式。 

one-to-one的双向关联中 (应该是指基于主键的关系映射),必须设置constrained=true,要不然会有重复数据读,如2个表user,car;在位false时sql如下:select * from user a left outer join car b on a.id=b.id left outer join on user c on a.id=c.id where a.id=? 删除的时候最好删除从表,删除主表会先查询下主表,在联合查询下。

Constrained和 lazy属性的关系:

lazy

(可选 — 默认为 

proxy

):默认情况下,单点关联是经过代理的。

lazy="no-proxy"

指定此属性应该在实例变量第一次被访问时应该延迟抓取(fetched lazily)(需要运行时字节码的增强)。

lazy="false"

指定此关联总是被预先抓取。

注意,如果

constrained="false"

, 不可能使用代理,Hibernate会采取预先抓取:也就是说, 如果constrained="false", 那hibernate会对所关联的对象始终使用left-outer-join方式来抓取数据,即使该对象的lazy属性值为 lazy="proxy"。

如果

constrained="true"

,  Hibernate也是预先抓取数据,hibernate会对所关联的对象使用 select方式来抓取数据,即使该对象的lazy属性值为 lazy="proxy"

4. cascade属性

     cascade属性为save-update的意思是:在对设置了该属性的对象进行save或者update的时候, 如果要级联保存的对象数据在数据库中不存在,那么对级联对象也进行save;如果数据库中存在相应的数据, 那就对级联对象执行update。(注:我以前的认识是错误的,以为它的意思是, 主对象save, 级联对象也save, 主对象update, 级联对象也update,这是不正确的)。

    上面所说的级联对象数据在数据库中是否存在的判断依据该对象主键<generator>机制的不同而有所差异:

1) 如果 级联对象的 主键生成策略是 <generator class="native" />, 在进行级联的时候, hibernate会根据程序中级联对象的主键是否赋值来决定是进行save还是update, 如果该对象主键未赋值,就执行save, 如果该对象主键赋了值,就执行update;如果对一个赋了主键值的对象执行update的时候,数据库中不存在相应的主键值的记录,那么Hibernate会报错。

       2) 如果 级联对象的 主键生成策略是 <generator class="assigned" />, 那么Hibernate在级联操作的时候会先利用级联对象的主键ID来查询数据库, 如果数据库中不存在该数据,就执行save;如果数据库中存在该数据,就执行update。

       3)<generator>主键生成策略对save或者update的判断应该具有一般性,在Hibernate中, 所有涉及到saveOrUpdate的操作,主键生成策略都会在进行save 或者update 判断的时候起作用。

只要你在关联上面定义了cascade, 那么无论你的关联对象有没有修改, 关联对象都会进行级联操作, 这在很多时候造成了浪费。

比如下面的关系:(DeptModel里面有一个类型为SubDeptModel的集合属性subDepts, 他们是一对多的关系, DeptModel是“一”方)

<class name="cn.javass.h4.dept.DeptModel" table="tbl_dept">
		<id name="id">
			<generator class="native" />
		</id>
		<property name="name"></property>
		<set name="subDepts" inverse="true" cascade="save-update,delete">
			<key column="deptId"/>
			<one-to-many class="cn.javass.h4.dept.SubDeptModel"></one-to-many>
		</set>
	</class>
           

假定deptId=62的subDepts集合中有两个对象,对于DeptModel, 当执行如下代码时,Hibernate都会执行3条update语句, 一条是更新DeptMode, 另两条是更新subDepts中的每一个SubDeptModel对象, 无论这两个对象是否有更新。

DeptModel dm = ds.queryDeptById(62);
			
		ds.update(dm);
		
           

因此 在性能要求比较高的场合, 不建议使用cascade="save-update" 或者 cascade="all",因为这个很多情况下会引起多余的sql语句。

对于cascade="delete|all-delete-orphan|delete-orphan", 这个比较有用, 因为在一个一对多关联里面, 删除1方往往要求把所有的多方也删除掉(数据库外键关联), 所以

cascade="delete|all-delete-orphan|delete-orphan" 经常使用在one-to-many的一方 或者 one-to-one的主键方 (注意:many-to-one关联上一般不用)。

5. inverse="true"

inverse的具体作用可以见网上的转载。

inverse和cascade的关系: inverse和cascade没有必然的联系, 没有设置cascade属性, inverse设置依然其作用。但是要注意, 如果没有设置cascade, 那么在对象的保存/更新和删除时, 就要注意按照一定的顺序, 以保证inverse能正确维护关联双方的关系。

继续阅读