首先需要明确一件事,在ORM的管理下,因为关联方式的不同,单从一个类的结构没法看出表的确切结构,也不能从一张表的结构看出对应的类的确切结构。
在双向1-1关联中的两个类,必会去组合对方的对象作为属性,这样在面向对象层面才是互相可寻的。
基于唯一外键的双向1-1关联
在用唯一外键建立双向1-1关联时,外键可以放在两张表的任意一边。需要为存放外键的一端配置<many-to-one unique="true" .../>
将单向N-1关联变成单向1-1关联,为另一端使用<one-to-one property-ref="从表外键属性名" .../>
配置单向1-1关联,即可实现双向1-1关联:
如将UserLogin对应的表作为主表,UserMsg对应的表作为从表,建立双向1-1关联。
主表类UserLogin的属性
private Integer id;
private String userName;
private String password;
private UserMsg msg;// 主表类组合一个对方的对象,但不会在数据库中加这个字段
UserLogin.hbm.xml
主表类的映射配置文件配置one-to-one
关联。
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="myPOJO">
<class name="UserLogin" table="USERLOGIN">
<!-- 基于唯一外键时两者主键不必相同,各自配好即可 -->
<!-- 当生成策略确定时,其实也可以不为主键指定type -->
<id name="id" column="ID">
<generator class="native"/>
</id>
<!-- name指出组合的属性(对方对象)名,表示从UserLogin能唯一找到其内组合的UserMsg对象 -->
<!-- 指出属性名,这个属性是属于哪个实现类的,指出是否要通过外键引用对主键进行约束,指出关联类引用自己为外键的那个属性名 -->
<one-to-one name="msg" class="UserMsg" constrained="true" property-ref="ul"/>
<property name="userName" column="USERNAME" type="string" not-null="true" />
<property name="password" column="PASSWORD" type="string" not-null="true" />
</class>
</hibernate-mapping>
从表类UserMsg的属性
private Integer id;
private Boolean sex;
private String address;
private UserLogin ul;// 从表类组合一个对方的对象,在数据库中将成为外键
UserMsg.hbm.xml
从表类的映射配置文件配置加唯一约束的many-to-one
关联。
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="myPOJO">
<class name="UserMsg" table="USERMSG">
<!-- 基于唯一外键时两者主键不必相同,各自配好即可 -->
<!-- 当生成策略确定时,其实也可以不为主键指定type -->
<id name="id" column="ID">
<generator class="native"/>
</id>
<property name="sex" column="SEX" type="boolean" not-null="true" />
<property name="address" column="ADDRESS" type="string" not-null="true" />
<!-- 外键的属性名,实现类名,外键的字段名,级联策略,唯一约束 -->
<many-to-one name="ul" class="UserLogin" column="USERLOGIN_ID" cascade="all" unique="true"/>
</class>
</hibernate-mapping>
Main.java
注意有主从关系的表,务必让其底层先生成主表中的行,再去生成引用了该行的从表中的行。体现在Hibernate中就是可以直接持久化从表类对象,它会先去将其所组合的主表类对象持久化,再来持久化这个从表类对象。
package myApplication;
import myPOJO.UserLogin;
import myPOJO.UserMsg;
import myTools.HibernateUtils;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class Main {
public static void main(String[] args) {
// 先分别建立这两个类的对象
UserMsg msg = new UserMsg(true, "上海大学", null);
UserLogin ul = new UserLogin("刘知昊", "123", null);
// 为它们相互设置关联
msg.setUl(ul);
ul.setMsg(msg);
// 获取Session对象
Session sssn = HibernateUtils.getSession();
// 开启事务
Transaction trnsctn = sssn.beginTransaction();
// 保存持久化对象,因为外键的关系,必须先主表再从表,即可以只做从表(会自动先去做其组合的主表)
// sssn.save(ul);// 主表
sssn.save(msg);// 从表
// 提交事务
trnsctn.commit();
// 关闭Session实例并且把ThreadLocal中的副本清除
HibernateUtils.closeSession();
}
}
运行结果
控制台输出:
Hibernate: insert into USERLOGIN (USERNAME, PASSWORD) values (?, ?)
Hibernate: insert into USERMSG (SEX, ADDRESS, USERLOGIN_ID) values (?, ?, ?)
数据库中生成:
基于共享主键的双向1-1关联
只要在基于共享主键的单向1-1关联的基础上,为另一方也打上<one-to-one .../>
关联,并且在POJO中各自组合对方的对象即可。
主键生成器不要改动,还是由新打上关联的这一方来生成或指定主键,而由另一方来共享之。
在共享外键上仍然可以通过<one-to-one .../>
的constrained="true"
设置主键上的外键关系,这时仍然可以存在主表和从表的关系。
如UserMsg对应的表为主表时,如下配置。
特别注意,在主表上不要设置通过外键引用对主键约束,即不可以互相设置constrained="true"
,否则会造成两个外键互相引用,谁也不能插入。
UserLogin.hbm.xml
在从表上配置主键引用主表主键,并同时设置为外键。
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="myPOJO">
<class name="UserLogin" table="USERLOGIN">
<id name="id" column="ID">
<!-- 生成器策略写foreign,表示将通过关联的持久化对象为主键赋值 -->
<generator class="foreign">
<!-- 将组合的属性(对方对象)名写进来,表示使用的是它的主键 -->
<param name="property">msg</param>
</generator>
</id>
<!-- 1:1映射,组合的对方对象属性名,实现类名,是否通过外键引用对主键约束 -->
<one-to-one name="msg" class="UserMsg" constrained="true"/>
<property name="userName" column="USERNAME" type="string"
not-null="true" />
<property name="password" column="PASSWORD" type="string"
not-null="true" />
</class>
</hibernate-mapping>
UserMsg.hbm.xml
因为从表主键使用了主表主键为外键,在主表上可以配置级联策略。千万不要再互为外键了,不然删的时候都要到DBMS里去先删个外键才能删表。
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="myPOJO">
<class name="UserMsg" table="USERMSG">
<!-- 主键在该表中自动生成,共享给UserLogin使用 -->
<id name="id" column="ID">
<generator class="native"/>
</id>
<!-- 1:1映射,组合的对方对象属性名,实现类名 -->
<!-- 注意因为另一表的外键使用了本主键,所以可以在这个1:1映射上配置级联策略 -->
<one-to-one name="ul" class="UserLogin" cascade="all"/>
<property name="sex" column="SEX" type="boolean" not-null="true" />
<property name="address" column="ADDRESS" type="string" not-null="true" />
</class>
</hibernate-mapping>
Main.java
互相设置关联后,只实例化任意一个PO就可以完成两表两行的实例化。
package myApplication;
import myPOJO.UserLogin;
import myPOJO.UserMsg;
import myTools.HibernateUtils;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class Main {
public static void main(String[] args) {
// 先分别建立这两个类的对象
UserMsg msg = new UserMsg(true, "上海大学", null);
UserLogin ul = new UserLogin("刘知昊", "123", null);
// 为它们相互设置关联
msg.setUl(ul);
ul.setMsg(msg);
// 获取Session对象
Session sssn = HibernateUtils.getSession();
// 开启事务
Transaction trnsctn = sssn.beginTransaction();
// sssn.save(ul);// 从表
sssn.save(msg);// 主表
// 提交事务
trnsctn.commit();
// 关闭Session实例并且把ThreadLocal中的副本清除
HibernateUtils.closeSession();
}
}
运行结果
这两种方式都是表达双向1-1关联,但对应现实世界的模型不太一样,当两张表有相同的标识时使用共享主键;当两张表各自有自己的唯一标识时应当使用唯一外键为某表添加一个外键字段作为从表。