Hibernate的一对一关联关系

  Hibernate的一对一关联关系,分为基于外键的一对一关联关系和基于主键的一对一关联关系。在这篇文章中,我们以部门和部门经理的例子来说明,一个部门对应唯一一个部门经理,一个部门经理也对应唯一一个部门。
  在基于外键的一对一关联关系中,一端通过一个主键以外的字段关联另一端的主键,如下图所示:
  E3CF6A0B-55EB-43AE-BDBC-D2DB80C116EF.jpg-8.3kB
   
  在基于主键的一对一关联关系中,一端直接通过主键关联另一端的主键,并通过另一端的主键生成自己的主键,如下图所示:
  image_1b36lr2j11khlidodn5i5e1net12.png-22.9kB
   
  下面进行详细说明。

基于外键的一对一关联关系

  对于基于外键的1-1关联关系,外键可以存放在任意一端,例如在本例中,外键既可以存放在department一端,也可以存放在manager一端,我们假设存放在department一端。在需要存放外键的一端,增加many-to-one节点,并且为many-to-one节点添加unique=”true”属性,来表示为1-1关联,添加unique=”true”属性了以后,不同的department就不能关联同一个manager了。在不存放外键的一端,需要使用one-to-one节点,并且在该节点中添加property-ref属性来指定存放外键一端的除主键以外的字段来作为关联字段。代码如下,首先创建两个类:

public class Department {

    private Integer deptId;
    private String deptName;

    private Manager mgr;

    //getters and setters
}

public class Manager {

    private Integer mgrId;
    private String mgrName;

    private Department dept;

    //getters and setters
}

映射文件:
 
Department.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>

    <class name="com.atguigu.hibernate.one2one.foreign.Department" table="DEPARTMENTS">

        <id name="deptId" 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"属性,来表示为1-1关联 -->
        <many-to-one name="mgr" class="com.atguigu.hibernate.one2one.foreign.Manager" 
            column="MGR_ID" unique="true"></many-to-one>            

    </class>
</hibernate-mapping>

Manager.hbm.xml
 

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

    <class name="com.atguigu.hibernate.one2one.foreign.Manager" table="MANAGERS">

        <id name="mgrId" type="java.lang.Integer">
            <column name="MGR_ID" />
            <generator class="native" />
        </id>

        <property name="mgrName" type="java.lang.String">
            <column name="MGR_NAME" />
        </property>

        <!-- 映射 1-1 的关联关系: 在对应的数据表中已经有外键了, 当前持久化类使用 one-to-one 进行映射 -->
        <!-- 
            没有外键的一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段
         -->
        <one-to-one name="dept" 
            class="com.atguigu.hibernate.one2one.foreign.Department"
            property-ref="mgr"></one-to-one>

    </class>

</hibernate-mapping>

运行一个空的test程序,可以生成数据库表managers和departments:
 
image_1b36mlbre1tev98d1f9rusbiu91f.png-19kB
 
image_1b36mmdvq19lu1o681ehs1sgc1gmh1s.png-19.8kB
image_1b36mmu0n1qp61tpek735sbdfr29.png-18.9kB

下面测试基于外键的1-1关联关系的save和get操作:

基于外键的1-1关联关系的save操作:

@Test
    public void testSave(){

        Department department = new Department();
        department.setDeptName("DEPT-BB");

        Manager manager = new Manager();
        manager.setMgrName("MGR-BB");

        //设定关联关系
        department.setMgr(manager);
        manager.setDept(department);

        //保存操作
        //建议先保存没有外键列的那个对象. 这样会减少 UPDATE 语句
            session.save(manager);
            session.save(department);
    }

  这段代码可以正常插入记录。和n-1关联关系中一样,如果先保存了存放外键一端的对象,后保存被外键关联的一端的对象,即如果先执行session.save(department);,后执行session.save(manager);,虽然同样可以正确保存,但是会多出一条update语句用于维护关联关系,所以通常建议先插入没有外键一端的对象,后插入有外键一端的对象。
  
基于外键的1-1关联关系的get操作:

@Test
    public void testGet(){
        //1. 默认情况下对关联属性使用懒加载
        Department dept = (Department) session.get(Department.class, 1);
        System.out.println(dept.getDeptName()); 

        //2. 所以会出现懒加载异常的问题. 
//      session.close();
//      Manager mgr = dept.getMgr();
//      System.out.println(mgr.getClass()); 
//      System.out.println(mgr.getMgrName()); 

        //3. 查询 Manager 对象的连接条件应该是 dept.manager_id = mgr.manager_id
        //而不应该是 dept.dept_id = mgr.manager_id
        Manager mgr = dept.getMgr();
        System.out.println(mgr.getMgrName()); 
    }

  同n-1关联关系一样,当查询存放外键的一端的对象department的时候,使用懒加载机制,即不会立即加载它关联的另一端的对象manager,而只有等到要使用manager的时候,才会发送select语句加载。那么同样也有可能发生懒加载异常。运行结果如下图所示:
  image_1b36o9f1j1a1mss9i951atf1cqh2m.png-65.3kB
  值得注意的是,当要使用到manager对象时,是通过左外连接查询到manager对象的,连接条件是dept.manager_id = mgr.manager_id,这是正确的,因为我们在Manager.hbm.xml中的one-to-one节点中配置了property-ref=”mgr”,指定关联字段为department的mgr字段。如果没有设置property-ref属性,那么默认关联的字段为department的id字段,例如,我们去掉property-ref=”mgr”的设置,运行testGet()方法,则会打印如下的sql语句(连接条件是dept.dept_id = mgr.manager_id),这显然是不符合需求的。
  image_1b36vjp9k2tp1jum6srtu7bs833.png-35.3kB
  
  还有一个注意点,就是当首先查询不存放外键的一端的对象时,即manager,由于其中没有设置外键关联到department,所以会使用左外连接查询,一并查询出另一端的对象,即department,而且已经完成了初始化。如下所示:

@Test
    public void testGet2(){
        //在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
        //并已经进行初始化. 
        Manager mgr = (Manager) session.get(Manager.class, 1);

        //在执行下面的代码之前已经完成了对department对象的初始化
        System.out.println(mgr.getMgrName()); 
        System.out.println(mgr.getDept().getDeptName()); 
    }

运行结果:
image_1b37027r68l01dvj1j2u1dnu14ua3t.png-33.9kB
  
  关于基于外键的一对一关系,还有一点值得注意,不能在两端都使用外键映射为1-1,例如下面这种情况,表department表和manager都分别设置了外键manager_id和department_id,那么当一条manager记录单向关联了一条department记录,而这条department记录却关联向另一条manager记录,就会出现问题,如下图所示:
  image_1b372rtct32gnaf1uu216bkcs94a.png-92kB
   
   

基于主键的一对一关联关系

  基于主键的1-1映射策略,是指一端的主键生成器使用foreign策略,表明根据“对方”的主键来生成自己的主键,自己并不能独立生成主键。<param>子节点指定使用当前持久化类的哪一个属性来作为“对方”。例如:

<generator class="foreign">
                <!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 -->
                <param name="property">mgr</param>
            </generator>

  采用foreign主键生成器策略的一端使用one-to-one元素映射关联属性,需要在one-to-one节点中设置constrained=”true”,以指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(即“对方”)所对应的数据库表的主键。另一端同样使用one-to-one节点映射关联关系。
  下面我们仍以department和manager的例子进行测试,首先新建两个类:

public class Department {

    private Integer deptId;
    private String deptName;

    private Manager mgr;

    //getters and setters
}

public class Manager {

    private Integer mgrId;
    private String mgrName;

    private Department dept;

    //getters and setters
}

映射文件:
 
Department.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.one2one.primary">

    <class name="Department" table="DEPARTMENTS">

        <id name="deptId" 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 元素映射关联属性,
        其 one-to-one 节点还应增加 constrained=true 属性, 以使当前的主键上添加外键约束
        -->
        <one-to-one name="mgr" class="Manager" constrained="true"></one-to-one>

    </class>
</hibernate-mapping>

Manager.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

    <class name="com.atguigu.hibernate.one2one.primary.Manager" table="MANAGERS">

        <id name="mgrId" 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="com.atguigu.hibernate.one2one.primary.Department"></one-to-one>

    </class>

</hibernate-mapping>

生成的数据库表如下:
 
managers
image_1b373jjnq7vj1dck1j6f4qkvjg4n.png-16.2kB

departments
image_1b373km8b96o52engkc4s197a54.png-16.9kB
image_1b373l1mp1cm2161gs14amffm5h.png-17.9kB
可以看到,表departments是根据主键DEPT_ID来关联表managers的。
 
下面测试save和get方法:

基于主键的1-1关联关系的save操作:

@Test
    public void testSave(){

        Department department = new Department();
        department.setDeptName("DEPT-AA");

        Manager manager = new Manager();
        manager.setMgrName("MGR-AA");

        //设定关联关系
        manager.setDept(department);
        department.setMgr(manager);

        //保存操作
        //先插入哪一个都不会有多余的 UPDATE
        session.save(department);
        session.save(manager);

    }

  和之前不同的是,不论是先执行session.save(department);,还是先执行session.save(manager);,效果都是一样的,都只有两条insert语句,不会有update语句,而且都会先执行insert into managers,后执行insert into departments,如下图:
  image_1b374o1mk1nr6pit19rc154h1ais5u.png-22.1kB
  
  这是因为,现在department是根据主键关联manager,主键是不能像外键那样先被置为null然后进行update修改的,所以不论哪一个语句放在前面,都会先等到manager记录插入后,再插入department记录。
  
基于主键的1-1关联关系的get操作:

@Test
    public void testGet(){
        //1. 默认情况下对关联属性使用懒加载
        Department dept = (Department) session.get(Department.class, 1);
        System.out.println(dept.getDeptName()); 

        //2. 所以会出现懒加载异常的问题. 
        Manager mgr = dept.getMgr();
        System.out.println(mgr.getMgrName()); 
    }

    @Test
    public void testGet2(){
        //在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
        //并已经进行初始化. 
        Manager mgr = (Manager) session.get(Manager.class, 1);
        /*System.out.println(mgr.getMgrName()); 
        System.out.println(mgr.getDept().getDeptName()); */
    }

  基于主键的1-1和基于外键的1-1十分相似,在查询department时都使用懒加载机制,可能会抛出懒加载异常,在查询manager时都会使用左外连接,但不同的是,我们在Manager.hbm.xml文件的one-to-one节点中没有设置property-ref属性,即默认department中关联manager的字段是department的id,这正是我们在基于主键的1-1关系中希望的,所以可以看到,左外连接的连接条件是dept.dept_id = mgr.manager_id:
  image_1b376sjn8145n11rm1844dfshjg6r.png-39.6kB

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值