【Hibernate步步为营】--双向关联一对一映射详解(一)

时间:2021-02-11 04:39:51

         一对一的映射在对象模型中是经常见到的,为了将对象模型转换为关系模型就必须在映射文件中进行配置,上篇文章讨论了一对一映射的单向关联的情况,重点是<one-to-one>标签的使用,需要在映射的主对象中添加该标签,并将该对象的主键设置为foreign这样就实现了主键关联映射。讨论完了单向接下来讨论双向映射。


一、双向主键关联


        双向的主键关联其实是单向一对一主键关联的一种特殊情况,只不过要在关联对象的两端的映射文件中都要进行<one-to-one>的配置,另外还要在主映射的主键一端采用foreign外键关联属性。

        这里同样使用Person和IdCard来讨论,一个人对应着一个唯一的身份证,而且一个身份证也唯一映射着一个人,所以这就产生了双向的关联关系,Person的主键同样也是IdCard的主键,分别是主键的同时也是外键,这种关联关系成为双向一对一映射,表现到关系模型中可如下图:

【Hibernate步步为营】--双向关联一对一映射详解(一)

        图中的两个表采用了主键关联,person的主键是idCard的主键,所以它们之间构成了朱外键的约束关系,并且保证唯一性,映射到对象模型中,转变为person类和idCard类的一对一关系,如下图:

【Hibernate步步为营】--双向关联一对一映射详解(一)

        这种一对一的关系上篇文章中也有讲到用的是<one-to-one>标签,另外这种一对一映射又是双向的,所以要在两个对象之间同时配置<one-to-one>,首先来看idCard对应的类代码和映射文件代码。


   1、IdCard对应的信息

        IdCard.java类,IdCard类和Person类之间有一对一的关联关系所以要在IdCard类中添加对应的Person属性,这是为了能在映射文件中的外键中添加对应的属性,设置对应的外键关联类。

package com.src.hibernate;

public class IdCard {
	
	//id属性
	private int id;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	
	//卡号属性
	private String cardNo;
	public String getCardNo() {
		return cardNo;
	}
	public void setCardNo(String cardNo) {
		this.cardNo = cardNo;
	}
	
	//卡号对应的人
	private Person person;
	public Person getPerson(){
		return person;
	}
	public void setPerson(Person person){
		this.person=person;
	}
}

      IdCard.hbm.xml映射文件,在映射文件中添加外键属性person,并添加对应的<one-to-one>标签,目的是强制约束person类来实现一对一的映射关系,最后在映射中将constrained属性设为true,保证强制约束关系。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2014-5-15 23:47:00 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
    <class name="com.src.hibernate.IdCard" table="IDCARD">
        <id name="id" type="int" column="personId">
        	<generator class="foreign">
        		<param name="property">person</param>
        	</generator>
        </id>
        
        <property name="cardNo" type="string" column="cardno"></property>
        <one-to-one name="person" constrained="true"></one-to-one>
    </class>
</hibernate-mapping>

  2、Person对应的信息

     Person.java类,在该类中除了添加基本的属性外还要添加对应的IdCard类作为属性,因为它们之间是一对一的双向关联关系,所以在Person类中同样要添加IdCard类,相同的道理IdCard类中同样添加了Person类属性。

package com.src.hibernate;

public class Person {
	
	//id号
	private int id;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	
	//姓名
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	//idCard
	private IdCard idcard;
	public IdCard getIdcard() {
		return idcard;
	}
	public void setIdcard(IdCard idcard) {
		this.idcard = idcard;
	}
}

        Person.hbm.xml映射文件,该文件中主键生成策略没有特殊的要求,因为它和IdCard类相互制约的关系,它的主键和外键都是IdCard的主键,另外因为是一对一关系所以要在映射文件中添加<one-to-one>标签来标示。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2014-5-15 23:47:00 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
    <class name="com.src.hibernate.Person" table="PERSON">
        <id name="id" type="int" column="personId">
            <generator class="native"></generator>
        </id>
        
        <property name="name" type="string" column="personName"></property>
	<!-- 
	one-to-one标签指示Hibernate如何加载其关联对象,默认根据主键加载,也就是拿到关系字段值,根据对端的主键来加载关联对象
	 -->
	<one-to-one name="idcard"></one-to-one>
    </class>
</hibernate-mapping>

   3、Hibernate映射文件

      上面的类和映射文件配置好后接下来要在Hibernate.cfg.xml中配置与数据库映射的信息,需要将两个配置文件添加到Hibernate配置文件中,这样在生成对应的数据库时才能找到对应的生成项。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_one2one_pk1</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">1234</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        
        <mapping resource="com/src/hibernate/Person.hbm.xml"/>
        <mapping resource="com/src/hibernate/IdCard.hbm.xml" ></mapping>
    </session-factory>
</hibernate-configuration>

   4、生成结果


        配置完成后就可以将上面的内容生成对应的数据库了,在数据库中它会按照配置的内容生成相应的表结构,在表中有相应的外键和主键字段。生成表结构时Hibernate会在控制台输出相应的SQL语句,如下:

alter table IDCARD drop foreign key FK806F76ABAC038CD8
drop table if exists IDCARD
drop table if exists PERSON
create table IDCARD (personId integer not null, cardno varchar(255), primary key (personId))
create table PERSON (personId integer not null auto_increment, personName varchar(255), primary key (personId))
alter table IDCARD add index FK806F76ABAC038CD8 (personId), add constraint FK806F76ABAC038CD8 foreign key (personId) references PERSON (personId)

       生成的表结构如下图:

【Hibernate步步为营】--双向关联一对一映射详解(一)

        在两张表中同时生成了personId主键,并且也是相应的外键,它同时限制约束了两张表的主键相同且唯一。


  5、写入加载测试


     生成表后测试下对表的写入和从表中读取数据,编写相应的测试类,测试采用的是单元测试,编写对应的测试方法。

   5.1 写入测试

    在写入到数据库时一定要注意写入的两个对象都要转化到对应的Trainent状态,否则会出现状态转化的错误,测试代码如下: 
public void testSave1(){
	Session session=null;
	try{
		//创建一个会话对象
		session=HibernateUtils.getSession();
		//开启会话事务
		session.beginTransaction();
		
		//创建person对象,并保存
		Person person=new Person();
		person.setName("zhangsan");
		session.save(person);
		
		//创建idCard对象,并保存
		IdCard idcard=new IdCard();
		idcard.setCardNo("1111111111111");
		idcard.setPerson(person);
		session.save(idcard);
		
		//提交事务,修改数据库
		session.getTransaction().commit();
		
	}catch(Exception e){
		//打印错误信息
		e.printStackTrace();
		//业务回滚
		session.getTransaction().rollback();
	}finally{
		//关闭会话
		HibernateUtils.closeSession(session);
	}
}

      插入的数据如下图:
【Hibernate步步为营】--双向关联一对一映射详解(一)

   5.2 加载测试

      编写加载方法,因为关联关系是双向的,所以相应的加载操作应该是通过一端加载另一端,也就是获取对应的Person类,并通过Person类来获取对应的IdCard信息,相反的也要成立,代码如下:
public void testLoad1(){
	Session session=null;
	try{
		//创建一个会话对象
		session=HibernateUtils.getSession();
		//开启会话事务
		session.beginTransaction();
		
		//获取person对象,并保存
		Person person=(Person)session.load(Person.class,5);
		System.out.println("IdCard.Id: "+person.getIdcard().getId());
		System.out.println("IdCard.cardno: "+person.getIdcard().getCardNo());
		
		//创建idCard对象,并保存
		IdCard idcard=(IdCard)session.load(IdCard.class, 5);
		System.out.println("Person.Id: "+idcard.getPerson().getId());
		System.out.println("Person.name: "+idcard.getPerson().getName());
		
		//提交事务,修改数据库
		session.getTransaction().commit();
		
	}catch(Exception e){
		//打印错误信息
		e.printStackTrace();
		//业务回滚
		session.getTransaction().rollback();
	}finally{
		//关闭会话
		HibernateUtils.closeSession(session);
	}
}

     运行上面的测试方法,在控制台打印的相关内容如下:
【Hibernate步步为营】--双向关联一对一映射详解(一)

结语


   文章对双向关联关系做了详细的讨论,重点在于分析这种双向一对一关系的建立及应用,双向的一对一关系分为主键和外键两种,本文主要讨论了主键的约束关系,这种约束关系同时采用了<one-to-one>和<many-to-one>,下文将会详细讨论外键关联。