在Hibernate中,一对一关联关系有两种情况:

基于外键映射的一对一

基于主键映射的一对一


基于外键映射的一对一

     这种映射其实是由多对一而来,这种映射情况下,外键可以放在任意的一端,在需要存放外键的一端(简称a端),添加<many-to-one>节点,为<many-to-one>节点增加unique="true"属性来约束多对一成为一对一,另一端(简称b端)使用<one-to-one>节点来映射,并增加property-ref属性指定使用在该端(b端)被关联的另一端(a端)的非主键的字段作为关联字段,这句话比较拗口,下面结合例子来看看并理解

     举例模型为部门(Department)和经理(Manager)

package com.demo.one2one.foreign;

public class Department {

	private Integer id;
	private String deptName;
	private Manager mgr;

	get/set

}
package com.demo.one2one.foreign;

public class Manager {

	private Integer id;
	private String mgrName;
	private Department dept;

	get/set

}
<hibernate-mapping package="com.demo.one2one.foreign">
    <class name="Department" table="DEPARTMENT">
    
        <id name="id" type="java.lang.Integer">
            <column name="DEPT_ID" />
            <generator class="native" />
        </id>
        
        <property name="deptName" type="java.lang.String">
            <column name="DEPT_NAME" />
        </property>
        
        <!-- 使用many-to-one方式来映射1-1 -->
        <!-- unique:值为true时添加唯一约束,即该外键唯一而不可重复,这样就保证一对一映射 -->
        <many-to-one name="mgr" class="Manager" column="MRG_ID" unique="true"></many-to-one>
        
    </class>
</hibernate-mapping>
<hibernate-mapping package="com.demo.one2one.foreign">
    <class name="Manager" table="MANAGER">
    
        <id name="id" type="java.lang.Integer">
            <column name="MGR_ID" />
            <generator class="native" />
        </id>
        
        <property name="mgrName" type="java.lang.String">
            <column name="MGR_NAME" />
        </property>
        
        <!-- 在没有外键的一端使用one-to-one进行映射 -->
        <!-- property-ref属性指定被关联实体(Department)对应的数据表的主键以外的字段作为关联字段 -->
        <one-to-one name="dept" class="Department" property-ref="mgr"></one-to-one>
        
    </class>
</hibernate-mapping>

      简单的执行空测试,可以看到两个表,并且在department表中有外键mgr_id参照manager表的主键mgr_id,另外,在索引那一栏可以看到department表除了有一个主键的唯一索引,还有一个外键列的唯一索引,这就是在Department的配置文件中的<many-to-one>节点中加了unique="true"的作用

      下面来说一下为什么要加property-ref,看下面的例子(数据插入部分省略),这里先将property-ref属性删掉

	@Test
	public void testGet() throws Exception{
		Department department = (Department) session.get(Department.class, 1);
		System.out.println(department.getDeptName());
		System.out.println(department.getMgr().getMgrName());
	}

      下面是控制台打印结果

Hibernate: 
    select
        department0_.DEPT_ID as DEPT_ID1_0_0_,
        department0_.DEPT_NAME as DEPT_NAM2_0_0_,
        department0_.MRG_ID as MRG_ID3_0_0_ 
    from
        DEPARTMENT department0_ 
    where
        department0_.DEPT_ID=?
d-aa
Hibernate: 
    select
        manager0_.MGR_ID as MGR_ID1_1_1_,
        manager0_.MGR_NAME as MGR_NAME2_1_1_,
        department1_.DEPT_ID as DEPT_ID1_0_0_,
        department1_.DEPT_NAME as DEPT_NAM2_0_0_,
        department1_.MRG_ID as MRG_ID3_0_0_ 
    from
        MANAGER manager0_ 
    left outer join
        DEPARTMENT department1_ 
            on manager0_.MGR_ID=department1_.DEPT_ID 
    where
        manager0_.MGR_ID=?
m-aa

      乍一看结果并没有什么不对,但是在这里,sql语句出现了问题:我们可以看到Hibernate使用了表的左连接sql来查询数据,但是在限定条件出现了问题,应该是manager的mgr_id等于department的mgr_id而不是department的dept_id,这是因为在Manager的配置文件中不加property-ref时,左连接的条件默认为department的主键字段,这样显然是我们不想看到的,而加入property-ref="mgr"后,结果成了如下所示:

Hibernate: 
    select
        department0_.DEPT_ID as DEPT_ID1_0_0_,
        department0_.DEPT_NAME as DEPT_NAM2_0_0_,
        department0_.MRG_ID as MRG_ID3_0_0_ 
    from
        DEPARTMENT department0_ 
    where
        department0_.DEPT_ID=?
d-aa
Hibernate: 
    select
        manager0_.MGR_ID as MGR_ID1_1_1_,
        manager0_.MGR_NAME as MGR_NAME2_1_1_,
        department1_.DEPT_ID as DEPT_ID1_0_0_,
        department1_.DEPT_NAME as DEPT_NAM2_0_0_,
        department1_.MRG_ID as MRG_ID3_0_0_ 
    from
        MANAGER manager0_ 
    left outer join
        DEPARTMENT department1_ 
            on manager0_.MGR_ID=department1_.MRG_ID 
    where
        manager0_.MGR_ID=?
Hibernate: 
    select
        department0_.DEPT_ID as DEPT_ID1_0_0_,
        department0_.DEPT_NAME as DEPT_NAM2_0_0_,
        department0_.MRG_ID as MRG_ID3_0_0_ 
    from
        DEPARTMENT department0_ 
    where
        department0_.MRG_ID=?
m-aa

      如此便成了我们想要的左连接,其余测试与前文出入不大,可自行测试观察


基于主键的一对一映射关系


      基于主键的一对一映射,其原理就是让一方的主键不再以自动生成或者其他数据库底层的方式产生,而是根据另一方的主键来生成自己的主键

      根据这个原理,在Hibernate中的做法是:

      1. 主键需要依赖另一方的主键而生成的一端(a端),其主键生成方式为foreign策略,表明根据“对方”的主键来生成自己的主键,还需要加入<param>子节点指定使用当前持久化类的哪个属性对应的类的数据表(b端)作为“对方”

      2. 采用foreign主键生成器策略的一端(a端)需要加入one-to-one节点关联映射属性,在one-to-one节点中还应该添加constrained="true"属性;另一端(b端)增加one-to-one元素映射关联属性

      由此,将Department的配置文件做一下修改:

<hibernate-mapping package="com.demo.model.one2one.primary">
    <class name="Department" table="DEPARTMENT">
    
        <id name="id" type="java.lang.Integer">
            <column name="DEPT_ID" />
            <!-- 使用外键的方式来生成当前的主键 -->
            <generator class="foreign">
            	<!-- property属性指定使用当前持久化类的哪个属性所对应的持久化类的对应数据表的主键作为外键 -->
            	<param name="property">mgr</param>
            </generator>
        </id>
        
        <property name="deptName" type="java.lang.String">
            <column name="DEPT_NAME" />
        </property>
        
        <!-- 采用foreign主键生成器策略的一端增加one-to-one节点关联映射属性 -->
        <!-- 并增加constrained="true"属性,使得当前持久化类对应数据表的主键添加外键约束 -->
        <one-to-one name="mgr" class="Manager" constrained="true"></one-to-one>
        
    </class>
</hibernate-mapping>

      而Manager的配置文件就是一个普通pojo类的配置文件:

<hibernate-mapping package="com.demo.model.one2one.primary">
    <class name="Manager" table="MANAGER">
    
        <id name="id" type="java.lang.Integer">
            <column name="MGR_ID" />
            <generator class="native" />
        </id>
        
        <property name="mgrName" type="java.lang.String">
            <column name="MGR_NAME" />
        </property>
        
        <one-to-one name="dept" class="Department"></one-to-one>
        
    </class>
</hibernate-mapping>

     执行空测试,可以看到Department表的主键dept_id同时也是外键,参照了Manager表的mgr_id,索引栏中有主键的索引但索引类型为Normal,而不是Unique,这就证明了Department表的主键是依据Manager表的主键而生成,不是自主生成了