Hibernate学习笔记(四)关系映射之一对一关联映射

一、 一对一关联映射

²        两个对象之间是一对一的关系,如Person-IdCard(人—身份证号)

²        有两种策略可以实现一对一的关联映射

Ø        主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联。

Ø        唯一外键关联:外键关联,本来是用于多对一的配置,但是如果加上唯一的限制之后,也可以用来表示一对一关联关系。

 

对象模型

实体类:

/** 人-实体类 */

public class Person {

    private int id;

    private String name;

    public int getId() {return id;  }

    public void setId(int id) {this.id = id;}

    public String getName() {return name;}

    public void setName(Stringname) {this.name = name;}

}

 

/**身份证-实体类*/

public class IdCard {

    private int id;

    private String cardNo;

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getCardNo(){ return cardNo;}

    public void setCardNo(StringcardNo) {this.cardNo = cardNo;}

}

(一) 唯一外键关联-单向(unilateralism)

1、 说明:

人—-> 身份证号(PersonàIdCard),从IdCard看不到Person对象

2、 对象模型

需要在Person类中持有IdCard的一个引用idCard,则IdCard中没有Person的引用

3、 关系模型

关系模型目的:是实体类映射到关系模型(数据库中),是要求persion中添加一个外键指向idcard

4、 实体类:

      注:IdCard是被引用对象,没有变化。

         /** 人-实体类 */

public class Person {

    private int id;

    private String name;

private IdCard idCard;//引用IdCard对象

    public int getId() {return id;  }

    public void setId(int id) {this.id = id;}

    public String getName() {return name;}

    public void setName(String name){this.name = name;}

public IdCard getIdCard() { return idCard;}

    public void setIdCard(IdCardidCard) {this.idCard = idCard;}

}

 

5、 xml映射

IdCard实体类的映射文件:

     因为IdCard是被引用的,所以没有什么特殊的映射

<hibernate-mapping>

  <class name="com.wjt276.hibernate.IdCard" table="t_idcard">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="cardNo"/>

  </class>

</hibernate-mapping>

Person实体类的映射文件

         在映射时需要添加一个外键的映射,就是指定IdCard的引用的映射。这样映射到数据库时,就会自动添加一个字段并作用外键指向被引用的表

<hibernate-mapping>

  <class name="com.wjt276.hibernate.Person" table="t_person">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="name"/> 

<!-- <many-to-one>:在多的一端(当前Person一端),加入一个外键(当前为idCard)指向一的一端(当前IdCard),但多对一 关联映射字段是可以重复的,所以需要加入一个唯一条件unique="true",这样就可以此字段唯一了。-->

      <many-to-one name="idCard" unique="true"/>

  </class>

</hibernate-mapping>

注意:这里的<many-to-one>标签中的name属性值并不是数据库中的字段名,而是Person实体类中引用IdCard对象成员属性的getxxx方法后面的xxx(此处是getIdCard,所以是idCard),要求第一个字段小写。如果不指定column属性,则数据库中的字段名同name值

6、 annotateon注解映射

注意IdCard是被引用对象,除正常注解,无需要其它注解

/**身份证*/

@Entity

public class IdCard {

  private int id;

  private String cardNo;

  @Id

  @GeneratedValue

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getCardNo(){return cardNo;}

  public void setCardNo(StringcardNo) {this.cardNo = cardNo;}

}

 

而引用对象的实体类需要使用@OneToOne进行注解,来表面是一对一的关系

再使用@JoinColumn注解来为数据库表中这个外键指定个字段名称就可以了。如果省略@JoinColumn注解,则hibernate会自动为其生成一个字段名(好像是:被引用对象名称_被引用对象的主键ID)

/** 人-实体类 */

@Entity

public class Person {

  private int id;

  private IdCard idCard;//引用IdCard对象  

  private String name;   

  @Id

  @GeneratedValue

  public int getId() {return id;}

  @OneToOne//表示一对一的关系

  @JoinColumn(name="idCard")//为数据中的外键指定个名称

  public IdCard getIdCard(){ return idCard;}

  public String getName() {return name;}

  public void setId(int id) {this.id = id;}

  public void setIdCard(IdCardidCard) {this.idCard = idCard;}

  public void setName(Stringname) {this.name = name;}   

}

7、 生成的SQL语句:

    create tableIdCard (

        id integernot null auto_increment,

        cardNo varchar(255),

        primary key(id)

    )

    create tablePerson (

        id integernot null auto_increment,

        namevarchar(255),

        idCardinteger,//新添加的外键

        primary key(id)

    )

    alter tablePerson

        add indexFK8E488775BE010483 (idCard),

        addconstraint FK8E488775BE010483

        foreign key(idCard) //外键

        referencesIdCard (id)//引用IdCard的id字段

 

8、 存储测试

Session session = sf.getCurrentSession();      

IdCard idCard = new IdCard();

idCard.setCardNo("88888888888888888888888");       

session.beginTransaction();

// 如果先不保存idCard,则出抛出Transient异常,因为idCard不是持久化状态。

session.save(idCard);      

Person person = new Person();

person.setName("菜10");

person.setIdCard(idCard);

session.save(person);

session.getTransaction().commit();

 

(二) 唯一外键关联-双向

1、 说明:

人<—-> 身份证号(Person<->IdCard)双向:互相持有对方的引用

2、 对象模型:


3、 关系模型:

关系模型没有任务变化,同单向

4、 实体类:

实体类,只是相互持有对象的引用,并且要求getter和setter方法

5、 xml映射

Person实体类映射文件:同单向的没有变化

IdCard实体类映射文件:如果使用同样的方法映射,这样就会在表中也添加一个外键指向对象,但对象已经有一个外键指向自己了,这样就造成了庸字段,因为不需要在表另外添加字段,而是让hibernate在加载这个对象时,会根据对象的ID到对方的表中查询外键等于这个ID的记录,这样就把对象加载上来了。也同样需要使用<one-to-one>标签来映射,但是需要使用property-ref属性来指定对象持有你自己的引用的成员属性名称(是gettxxxx后面的名称),这样在生成数据库表时,就不会再添加一个多于的字段了。数据加载时hibernate会根据这些配置自己加载数据

    <class name="com.wjt276.hibernate.IdCard" table="idcard">

        <id name="id" column="id">

            <generator class="native"/></id>

        <property name="cardNo"/>

        <!--<one-to-one>标签:告诉hibernate如何加载其关联对象

            property-ref属性:是根据哪个字段进行比较加载数据 -->

        <one-to-one name="person" property-ref="idCard"/>

    </class>

一对一 唯一外键 关联映射 双向 需要在另一端(当前IdCard),添加<one-to-one>标签,指示hibernate如何加载其关联对象(或引用对象),默认根据主键加载(加载person),外键关联映射中,因为两个实体采用的是person的外键来维护的关系,所以不能指定主键加载person,而要根据person的外键加载,所以采用如下映射方式:

    <!--<one-to-one>标签:告诉hibernate如何加载其关联对象

            property-ref属性:是根据哪个字段进行比较加载数据 -->

        <one-to-one name="person" property-ref="idCard"/>

6、 annotateon注解映射

Person注解映射同单向一样

IdCard注解映射如下:使用@OneToOne注解来一对一,但这样会在表中多加一个字段,因为需要使用对象的外键来加载数据,所以使用属性mappedBy属性在实现这个功能

@Entity

public class IdCard {

  private int id;

  private String cardNo;

  private Person person; 

  //mappedBy:在指定当前对象在被Person对象的idCard做了映射了

  //此值:当前对象持有引用对象中引用当前对象的成员属性名称(getXXX后的名称)

  //因为Person对象的持有IdCard对象的方法是getIdCard()因为需要小写,所以为idCard

  @OneToOne(mappedBy="idCard")

  public Person getPerson(){return person;}

  public void setPerson(Person person){this.person = person;}

  @Id

  @GeneratedValue

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getCardNo(){return cardNo;}

  public void setCardNo(StringcardNo) {this.cardNo = cardNo;}

}

7、 生成SQL语句

因为关系模型没有变化,也就是数据库的结构没有变化,只是在数据加载时需要相互加载对方,这由hibernate来完成。因为生成的sql语句同单向一样

8、 存储测试

存储同单向一样

9、 总结:

规律:凡是双向关联,必设mappedBy

 

(三) 主键关联-单向(不重要)

主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联。

 

1、 说明:

人—-> 身份证号(PersonàIdCard),从IdCard看不到Person对象

2、 对象模型

站在人的角度来看,对象模型与唯一外键关联一个,只是关系模型不同

3、 关系模型

因为是person引用idcard,所以idcard要求先有值。而person的主键值不是自己生成的。而是参考idcard的值,person表中即是主键,同时也是外键

4、 实体类:

实体类同 一对一 唯一外键关联的实体类一个,在person对象中持有idcard对象的引用(代码见唯一外键关系)

5、 xml映射

IdCard映射文件,先生成ID

    <class name="com.wjt276.hibernate.IdCard" table="t_idcard">

        <id name="id"column="id">

            <generator class="native"/>

        </id>

        <property name="cardNo"/>

    </class>

 

Person实体类映射文件,ID是根据IdCard主键值   

<class name="com.wjt276.hibernate.Person"table="t_person">

        <id name="id"column="id">

<!--因为主键不是自己生成的,而是作为一个外键(来源于其它值),所以使用foreign生成策略

foreign:使用另外一个相关联的对象的标识符,通常和<one-to-one>联合起来使用。再使用元素<param>的属性值指定相关联对象(这里Person相关联的对象为idCard,则标识符为idCard的id)为了能够在加载person数据同时加载IdCard数据,所以需要使用一个标签<one-to-one>来设置这个功能。  -->

            <generator class="foreign">

                <!-- 元素<param>属性name的值是固定为property -->

                <param name="property">idCard</param>

            </generator>

        </id>

        <property name="name"/>

        <!-- <one-to-one>标签

        表示如何加载它的引用对象(这里引用对象就指idCard这里的name值是idCard),同时也说是一对一的关系。 默认方式是根据主键加载(把person中的主键取出再到IdCard中来取相关IdCard数据。) 我们也说过此主键也作为一个外键引用 了IdCard,所以需要加一个数据库限制(外键约束)constrained="true"     -->

        <one-to-one name="idCard"constrained="true"/> 

6、 annotateon注解映射

Person实体类注解

方法:只需要使用@OneToOne注解一对一关系,再使用@PrimaryKeyJoinColumn来注解主键关系映射。

@Entity

public class Person {

    private int id;

    private IdCard idCard;//引用IdCard对象  

    private String name;   

    @Id

    public int getId() {return id;}

    @OneToOne//表示一对一的关系

    @PrimaryKeyJoinColumn//注解主键关联映射

    public IdCard getIdCard(){ return idCard;}

    public String getName() {return name;}

    public void setId(int id) {this.id = id;}

    public void setIdCard(IdCard idCard){this.idCard = idCard;}

    public void setName(Stringname) {this.name = name;}   

}

 

IdCard实体类,不需要持有对象的引用,正常注解就可以了。

7、 生成SQL语句

生成的两个表并没有多余的字段,因为是通过主键在关键的

    create tableIdCard (

        id integernot null auto_increment,

        cardNovarchar(255),

        primary key (id)

    )

    create tablePerson (

        id integernot null,

        namevarchar(255),

        primary key(id)

  )

alter table person

add index FK785BED805248EF3 (id),

add constraint FK785BED805248EF3

foreign key (id) references idcard (id)

    注意:annotation注解后,并没有映射出外键关键的关联,而xml可以映射,是主键关联不重要

8、 存储测试

session = HibernateUtils.getSession();

tx = session.beginTransaction();

IdCard idCard = new IdCard();

idCard.setCardNo("88888888888888888888888");

           

Person person = new Person();

person.setName("菜10");

person.setIdCard(idCard);

           

//不会出现TransientObjectException异常

//因为一对一主键关键映射中,默认了cascade属性。

session.save(person);

tx.commit();

 

9、 总结

让两个实体对象的ID保持相同,这样可以避免多余的字段被创建

<id name="id"column="id">

      <!—person的主键来源idcard,也就是共享idCard的主键-->

          <generator class="foreign">

              <param name="property">idCard</param>

          </generator>

      </id>

      <property name="name"/>

<!—one-to-one标签的含义:指示hibernate怎么加载它的关联对象,默认根据主键加载

  constrained="true",表面当前主键上存在一个约束:person的主键作为外键参照了idCard-->

      <one-to-one name="idCard" constrained="true"/>

(四) 主键关联-双向(不重要)

主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联。

主键关联映射,实际是数据库的存储结构并没有变化,只是要求双方都可以持有对象引用,也就是说实体模型变化,实体类都相互持有对方引用。

另外映射文件也变化了。

1、 xml映射

Person实体类映射文件不变,

IdCard如下:

<class name="com.wjt276.hibernate.IdCard" table="t_idcard">

        <id name="id" column="id">

            <generator class="native"/> </id>

        <property name="cardNo"/>

<!—one-to-one标签的含义:指示hibernate怎么加载它的关联对象(这里的关联对象为person),默认根据主键加载-->

        <one-to-one name="person"/>

    </class>

2、 annotateon注解映射:

Person的注解不变,同主键单向注解

 

IdCard注解,只需要在持有对象引用的getXXX前加上

        @OneToOne(mappedBy="idCard") 如下:

@Entity

public class IdCard {

    private int id;

    private String cardNo;

    private Person person; 

    @OneToOne(mappedBy="idCard")

    public Person getPerson(){

        return person;

    }}

(五) 联合主键关联(Annotation方式)

实现上联合主键的原理同唯一外键关联-单向一样,只是使用的是@JoinColumns,而不是@JoinColumn,实体类注解如下:

    @OneToOne

    @JoinColumns(

        {

            @JoinColumn(name="wifeId", referencedColumnName="id"),

            @JoinColumn(name="wifeName", referencedColumnName="name")

        }

    )

    public WifegetWife() {

        return wife;

    }

        注意:@oinColumns注解联合主键一对一联系,然后再使用@JoinColumn来注解当前表中的外键字段名,并指定关联哪个字段,使用referencedColumnName指定哪个字段的名称

 

二、 component(组件)关联映射

(一) Component关联映射:

目前有两个类如下:

         大家发现用户与员工存在很多相同的字段,但是两者有不可以是同一个类中,这样在实体类中每次都要输入很多信息,现在把联系信息抽取出来成为一个类,然后在用户、员工对象中引用就可以,如下:

值对象没有标识,而实体对象具有标识,值对象属于某一个实体,使用它重复使用率提升,而且更清析。

 

以上关系的映射称为component(组件)关联映射

在hibernate中,component是某个实体的逻辑组成部分,它与实体的根本区别是没有oid,component可以成为是值对象(DDD)。

采用component映射的好处:它实现了对象模型的细粒度划分,层次会更加分明,复用率会更高。

(二) User实体类:

public class User {

private int id;

  private String name;   

  private Contact contact;//值对象的引用   

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getName() {   return name;}

  public void setName(Stringname) {  this.name = name;}

  public ContactgetContact() {   return contact;}

  public void setContact(Contactcontact) {   this.contact = contact;}   

}

(三) Contact值对象:

public class Contact {

  private String email;  

  private String address;

  private String zipCode;

  private String contactTel;

  public String getEmail(){  return email;}

  public void setEmail(Stringemail) {    this.email = email; }

  public StringgetAddress() {return address;}

  public void setAddress(Stringaddress) {this.address = address;}

  public StringgetZipCode() {return zipCode;}

  public void setZipCode(StringzipCode) {this.zipCode = zipCode;}

  public StringgetContactTel() { return contactTel;}

  public voidsetContactTel(String contactTel){this.contactTel = contactTel;}

}

 

(四) xml--User映射文件(组件映射):

<hibernate-mapping>

  <class name="com.wjt276.hibernate.User" table="t_user">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="name" column="name"/>

      <!-- <component>标签用于映射Component(组件)关系

          其内部属性正常映射。

       -->

      <component name="contact">

          <property name="email"/>

          <property name="address"/>

          <property name="zipCode"/>

          <property name="contactTel"/>          

      </component>

  </class>

</hibernate-mapping>

(五) annotateon注解

使用@Embedded用于注解组件映射,表示嵌入对象的映射

@Entity

public class User {

    private int id;

    private String name;   

    private Contact contact;//值对象的引用   

    @Id

    @GeneratedValue

    public int getId() {    return id;}

 

    @Embedded//用于注解组件映射,表示嵌入对象的映射

    public ContactgetContact() {return contact;}

    public void setContact(Contactcontact) {this.contact = contact;}  

Contact类是值对象,不是实体对象,是属于实体类的某一部分,因此没有映射文件

(六) 导出数据库输出SQL语句:

    create table User (

        id integer not null auto_increment,

        address varchar(255),

        contactTel varchar(255),

        email varchar(255),

        zipCode varchar(255),

        name varchar(255),

        primary key (id)

)

(七) 数据表结构:


注:虽然实体类没有基本联系信息,只是有一个引用,但在映射数据库时全部都映射进来了。以后值对象可以重复使用,只要在相应的实体类中加入一个引用即可。

(八) 组件映射数据保存:

           session =HibernateUtils.getSession();

           tx =session.beginTransaction();

 

           User user= new User();

           user.setName("10");

          

           Contactcontact = new Contact();

           contact.setEmail("wjt276");

           contact.setAddress("aksdfj");

           contact.setZipCode("230051");

           contact.setContactTel("3464661");

          

           user.setContact(contact);

           session.save(user);

          

           tx.commit();

实体类中引用值对象时,不用先保存值对象,因为它不是实体类,它只是一个附属类,而session.save()中保存的对象是实体类。

posted @ 2013-11-25 19:39  塞北鸿雁飞  阅读(726)  评论(1编辑  收藏  举报