1. inverse="true"
例如 one-to-many 关系User (1) - Order(*)
Java代码:
public class User {
private Set<Order> orders = new HashSet<Order>();
// ...
}
public class Order {
private User user;
// ..
}
对应的 mapping files:
Order.hbm.xml
<hibernate-mapping package="org.pprun.hjpetstore.domain">
<class name="Order" table="Orders">
...
<!-- owner side of this bidirectional one-to-many association (user to order)-->
<many-to-one column="userId" foreign-key="fk_order_userId" name="user" not-null="true" />
</class>
</hibernate-mapping>
User.hbm.xm
<hibernate-mapping package="org.pprun.hjpetstore.domain">
<class name="User" table="User">
...
<set cascade="all" inverse="true" name="orders">
<key column="userId" not-null="true"/>
<one-to-many class="Order"/>
</set>
</class>
</hibernate-mapping>
好了,以上都是作为开发者必须了解的内容,也是日常工作之一。
inverse 的由来
在 Java 的世界里,我们将利用如下代码建立起 object instance 关系:
user.getOrders().add(newOrder);
order.setUser(user);
这个建立关系的代码一般会反映在 domain 对象中的一个方法中,这个方法有一个恐怖的名字 scaffolding method:
/** scaffolding code for collection field */
public final void addOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Can't add a null Order.");
}
order.setUser(this);
this.getOrders().add(order);
}
那么,如果在 User.hbm.xm 对于关系的映射没有指定 inverse="true" 会发生什么?
Hibernate 将发出两条不同的 SQL 做同一件事情,建立起关系,但反映在数据库中(见ERD),只是对同一个字段 orders.userId 这个外键进行操作。
通过指定 inverse="true", 我们告诉 hibernate 哪端是 owner 端,并将维护实体关系。而另一端是 inverse 端,不需要维护关系。
规则1,双向many-to-one 关系,one 端永远是 inverse 端,而many 端永远是 owner 端。
这下明白了 inverse 的含义了,它是相对于实体关系的 owner 来讲的。
Owner 怎么来的?
在这个例子中,User 是 one, Order 是 many, 因 Order 是 owner, 所以在 Orders 表中包含外键关系 userId. 这是 Owner 的来历-- 谁拥有外键字段,谁就是 Owner.
规则2, 双向 one-to-one 关系,决定取决于 ERD,而非 JAVA, 同样是哪个实体拥有外键,哪边就是 Owner, 相反,另一端(other side)就是inverse.
规则3,双向 many-to-many,因为实现这一关系的有效办法是利用独立关联表(join table), 所以我们不得不任意指定任一端为 owner(另外一端就是 inverse),但外键关系(复合外键)实际上是在独立的关联表中。
inverse 该放在哪边?
规则1,双向many-to-one 关系,one 端永远是 inverse 端,而many 端永远是 owner 端。
有这个规则,在映射关系时,one-to-many 是指定 one 端,即 inverse 端,因些应该指定 inverse 属性。
<set cascade="all" inverse="true" name="orders">
<key column="userId" not-null="true"/>
<one-to-many class="Order"/>
</set>
因此,many-to-one 是 owner 端,不应该指定 inverse 属性:
<!-- owner side of this bidirectional one-to-many association (user to order)-->
<many-to-one column="userId" foreign-key="fk_order_userId" name="user" not-null="true" />
因此完整的映射如下(fixed 错误的注释,请好好理解一下注释)
<hibernate-mapping package="org.pprun.hjpetstore.domain">
<class name="Order" table="Orders">
...
<!-- owner side of this bidirectional one-to-many association (user to order)-->
<many-to-one column="userId" foreign-key="fk_order_userId" name="user" not-null="true" />
</class>
</hibernate-mapping>
User.hbm.xm
<hibernate-mapping package="org.pprun.hjpetstore.domain">
<class name="User" table="User">
...
<!--
inverse="true" means Order is the owner side (User is the mirror side - inverse),
Order will take charge of relationship 'userId', which is the foreign-key in Order.
If only call user.getOrders().add(newOrder), no changes are made persistent.
But only call order.setUser(user) will make the relationship set - means the foreign-key will be persisted.
-->
<set cascade="all" inverse="true" name="orders">
<key column="userId" not-null="true"/>
<one-to-many class="Order"/>
</set>
</class>
</hibernate-mapping>
2. Inverse和Cascade的比较
Inverse:负责控制关系,默认为false,也就是关系的两端都能控制,但这样会造成一些问题,更新的时候会因为两端都控制关系,于是重复更新。一般来说有一端要设为true。
Cascade:负责控制关联对象的级联操作,包括更新、删除等,也就是说对一个对象进行更新、删除时,其它对象也受影响,比如我删除一个对象,那么跟它是多对一关系的对象也全部被删除。
举例说明区别:删除“一”那一端一个对象O的时候,如果“多”的那一端的Inverse设为true,则把“多”的那一端所有与O相关联的对象外键清空;如果“多”的那一端的Cascade设为Delete,则把“多”的那一端所有与O相关联的对象全部删除。
3. 各个配置属性含义
<
many-to-one
name
="parent"
class
="com.client.model.ClientChildType"
update
="false"
insert
="false"
cascade
="none"
>
<
column
name
="parent_id"
/>
</
many-to-one
>
update:进行update操作时是否包含此字段
insert:进行insert操作时是否包含此字段
name:映射类属性的名字
column:关联的字段
class:关联类的名字
cascade:设置操作中的级联策略 可选值为 all所有操作情况均进行级联、none所有操作情况均不进行级联、save-update执行save和update操作时级联、delete执行删除操作时级联
fetch:设置抓取数据的策略 默认值为select序列选择抓取 可选值为join外连接抓取
property-ref:指定关联类的一个属性,这个属性将会和本类的外键相对应(当外键参照一键时需要指定改属性)
fetch属性
hibernate抓取策略(单端代理的批量抓取fetch=select(默认)/join)
测试用例:
Student student = (Student)session.get(Student.class, 1);
System.out.println(student.getName());
System.out.println(student.getClasses().getName());
1)保持默认,同fetch="select",如:
<many-to-one name="classes" column="classesid" fetch="select"/>
fetch="select",另外发送一条select语句抓取当前对象关联实体或集合
执行结果:2条语句
Hibernate: select student0_.id as id1_0_, student0_.name as name1_0_, student0_.class_id as class3_1_0_ from student_join student0_ where student0_.id=?
学生1
Hibernate: select classes0_.id as id0_0_, classes0_.name as name0_0_ from classes_join classes0_ where classes0_.id=?
高一(1)班
======================================
2)设置fetch="join",如:
<many-to-one name="classes" column="classesid" fetch="join"/>
fetch="join",hibernate会通过select语句使用外连接来加载其关联实体或集合
此时lazy会失效
执行结果:一条join语句
Hibernate: select student0_.id as id1_1_, student0_.name as name1_1_, student0_.class_id as class3_1_1_, classes1_.id as id0_0_, classes1_.name as name0_0_ from student_join student0_ left outer join classes_join classes1_ on student0_.class_id=classes1_.id where student0_.id=?
学生1
高一(1)班