Java的Hibernate框架中的双向主键关联与双向外键关联

一、双向主键关联
双向的主键关联其实是单向一对一主键关联的一种特殊情况,只不过要在关联对象的两端的映射文件中都要进行<one-to-one>的配置,另外还要在主映射的主键一端采用foreign外键关联属性。
这里同样使用Person和IdCard来讨论,一个人对应着一个唯一的身份证,而且一个身份证也唯一映射着一个人,所以这就产生了双向的关联关系,Person的主键同样也是IdCard的主键,分别是主键的同时也是外键,这种关联关系成为双向一对一映射,表现到关系模型中可如下图:

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

这种一对一的关系上篇文章中也有讲到用的是<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)

生成的表结构如下图:

在两张表中同时生成了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);
  }
}

插入的数据如下图:

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);
  }
}

运行上面的测试方法,在控制台打印的相关内容如下:

二、双向外键关联
双向的外键关联可以理解为外键关联的一种特殊情况,这种特殊主要是由于它是一种双向的对应关系,在前篇文章中提到如果想要在一张表中添加一个外键字段的话可以使用<many-to-one>标签,它会关系模型中生成对应的外键列。这里想要实现双向的外键关联就必须使用该标签。
1、对象模型
先来看对象模型,人和身份证属于一对一的关系,一个人对应着一个身份,所以它们之间的多重性是一对一的,并且这种对应关系是双向的。所以它的对象模型同双向主键一对一是相同的,如下图:

2、关系模型
对应的关系模型会发生很大的变化,一对一的外键关联关系会在一张表中生成对应的外键,拿到人和身份证上来说也就是人的关系模型中会有一个身份证号的主键列,它们之间形成了双向的一对一的情况,如下图:

它们之间的对应关系就是上图中看到的,person表中有idCard表的主键,形成了一对一的外键关联关系,而且是双向的,也就是说通过person能够获取到idCard,另外通过idCard也能获取到person。
Person对象和IdCard对象内的代码同上篇文章中的对象代码一致,不在做代码罗列,唯一不同的是映射文件中的配置问题。
3、映射文件
idCard.hbm.xml映射文件,idCard表不是映射的主表,所以在做一对一的映射时需要使用的是<one-to-one>标签来配置,并且需要制定person关系模型中的外键属性,具体代码如下:

<?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-18 22:27:43 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
  <class name="com.src.hibernate.IdCard" table="IDCARD">
    <id name="id" type="int">
      <generator class="native" />
    </id>
    <property name="cardNo" type="java.lang.String">
      <column name="CARDNO" />
    </property> 

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

Person.hbm.xml映射文件,person表是映射的主表,需要在该表中添加一个外键属性列来标示idCard表,所以这里需要使用<many-to-one>标签,在person对象中生成相应的外键,并且还要使用unique标明属性唯一。

<?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-18 22:27:43 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" />
    </id>
    <property name="name" type="java.lang.String">
      <column name="NAME" />
    </property> 

    <many-to-one name="idCard" column="idCardNo" unique="true" not-null="true"></many-to-one>
  </class>
</hibernate-mapping>

对象的映射文件配置完成,接下来生成关系模型,SQL语句如下:

alter table PERSON drop foreign key FK8C768F55794A52CA
drop table if exists IDCARD
drop table if exists PERSON
create table IDCARD (id integer not null auto_increment, CARDNO varchar(255), primary key (id))
create table PERSON (personId integer not null auto_increment, NAME varchar(255), idCardNo integer not null unique, primary key (personId))
alter table PERSON add index FK8C768F55794A52CA (idCardNo), add constraint FK8C768F55794A52CA foreign key (idCardNo) references IDCARD (id)

生成的SQL语句首先是创建的表,在建表时指定了主键列,创建完成后修改了两个表指定外键属性,形成一对一的关系。

编写测试方法,采用单元测试,加载两个类的对象,并分别从对象的一端获取另一个对象

//加载对象,使用IdCard对象装载person对象
public void testLoad1(){
  Session session=null; 

  try{
    session=HibernateUtils.getSession();
    session.beginTransaction(); 

    //获取IdCard对象,在IdCard中获取与该对象唯一关联的person对象
    IdCard idcard=(IdCard)session.load(IdCard.class,1);
    System.out.println("person.Id= "+idcard.getPerson().getId());
    System.out.println("idCard.person.name= "+idcard.getPerson().getName()); 

    //获取Person对象,在Person对象中获取与它唯一关联的IdCard对象
    Person person=(Person)session.load(Person.class,1);
    System.out.println("idCard.id: "+person.getIdCard().getId());
    System.out.println("idCard.cardNo: "+person.getIdCard().getCardNo()); 

    //提交事务
    session.getTransaction().commit();
  }catch(Exception e){
    e.printStackTrace();
    session.getTransaction().rollback();
  }finally{
    HibernateUtils.closeSession(session);
  }
}

生成的内容:

对比两种映射关系,主键和外键两种映射,都是双向的映射关系,需要在对象的两端同时配置映射关系,不同的是主键只需要使用<one-to-one>因为它不需要生成属性列,但是必须对表的主键采用foreign的主键生成策略,并标示外键对象;外键的生成策略则需要采用<many-to-one>标签来生成新的外键列。

结语
双向关联中的一对一映射至此已经讨论完成,两篇文章主要讨论了双向关联中的两种用法,其实还是很简单的,记住一句话想要生成外键就使用<many-to-one>标签,如果唯一那就添加unique属性,<one-to-one>标签只是指明了一对一的关系它只是指明一个对象如何加载另一个对象并不在关系模型中添加新列。下篇文章将会对一对多关联展开讨论。

(0)

相关推荐

  • 浅析Java的Hibernate框架中的继承关系设计

    这次我们来说一下hibernate的层次设计,层次设计也就是实体之间的继承关系的设计.  也许这样比较抽象,我们直接看例子.  1)我们先看一下普通的做法  直接上代码:三个实类如下: public class TItem implements Serializable{ //省略Get/Set方法 private int id; private String manufacture; private String name; } public class TBook extends TItem

  • Java的Hibernate框架中复合主键映射的创建和使用教程

    复合主键映射需要在映射配置文件中使用<composite-id>标签,该标签是指将一个类指定为相应的复合主键,它的name属性需要指定类文件中定义的属性值,并在该标签中添加<key-property>子标签. Note:想要使用复合映射必须要将复合主键放到一个类中,也就是讲复合主键属性和其它属性分到两个类中,并将复合主键的类实现接口Serializable,该接口隶属于java.io. 复合主键的映射关系的主键是由多个列复合而成的,对应到数据表中相当的简单,如下图: 1.类文件 这

  • Java的Hibernate框架中的基本映射用法讲解

    Hibernate进行了分类整合发现其实Hibernate分为三大部分:核心对象.映射.HQL,这三大部分开发过程中最常使用,前几篇讨论了核心对象及对象之间的转换方法,接下来讨论Hibernate的映射使用方法.   Hibernate一个重要的功能就是映射,它能够在对象模型和关系模型之间转换,是面向对象编程思想提倡使用的,使用映射程序开发人员只需要关心对象模型中代码的编写.对象和关系数据库之间的映射通常是由XML文档来定义的.这个映射文档被设计为易读的,并且可以手动修改.这种映射关系我总结为下

  • java Hibernate save()与persist()区别

    Hibernate 之所以提供与save()功能几乎完全类似的persist()方法,一方面是为了照顾JPA的用法习惯.另一方面,save()和 persist()方法还有一个区别:使用 save() 方法保存持久化对象时,该方法返回该持久化对象的标识属性值(即对应记录的主键值):但使用 persist() 方法来保存持久化对象时,该方法没有任何返回值.因为 save() 方法需要立即返回持久化对象的标识属性,所以程序执行 save() 会立即将持久化对象对应的数据插入数据库:而 persist

  • 简介Java的Hibernate框架中的Session和持久化类

    Session Session对象用于获取与数据库的物理连接. Session对象是重量轻,设计了一个互动是需要与数据库每次被实例化.持久化对象被保存,并通过一个Session对象中检索. 会话中的对象不应该保持开放很长一段时间,因为他们通常不被线程安全的,他们应该被创建并根据需要摧毁他们.这次会议的主要功能是提供创建,读取和删除操作映射的实体类的实例.实例中可能存在以下三种状态之一在给定时间点: 短暂性: 持久化类的未与会话相关联,并在数据库中没有代表性,没有标识值的新实例被Hibernate

  • Java的Hibernate框架中的继承映射学习教程

    一.继承映射 继承是面向对象很重要的特性,它实现了代码的服用,在关系模型中同样也有继承关系,这种继承关系其实可以看做是一种枚举关系,一种类型中可以枚举出很多子类型,这些子类型和父对象形成了继承关系,能够对其进行枚举的大部分都可以看做是一种继承映射,所以这种枚举关系可以看做是继承映射,例如动物就是一种抽象类,它是其它动物猪.猫等的父类,它们之间就是一种继承关系,如下图: 这种继承映射在转化为关系模型后会生成一张表,那么这张表是如何区分这两种类型的呢?用的是关系字段,需要在表中添加类型字段,使用关键

  • Java的Hibernate框架中一对多的单向和双向关联映射

    一.一对多单向关联映射 一对多关系的对象模型在日常生活中也经常看到,就拿学生和班级来说,一个班级里有多个学生,所以班级和学生的关系是一对多的关系,映射到对象模型中,如下图: 对象模型说明了这种一对多的关系是由一的一端来维护的,那么映射成关系模型就是一个班级字段下面会有多个学生,这样就形成了一对多的关系,通过班级能够查询获得学生信息,对应的关系模型如下图: 1.基本配置 有了对象模型接下来就让它们映射为对应的关系代码,在进行关系映射时需要在一的一端添加<one-to-many>标签,另外还需要在

  • Java Hibernate中使用HQL语句进行数据库查询的要点解析

    一.实体对象查询 实体对象查询是hql查询的基础,作为一种对象查询语言,在查询操作时和sql不同,查询字符串中的内容要使用类名和类的属性名来代替.这种查询方法相对简单,只要有SQL功底,使用hql是很简单的,但是有一些问题需要注意,就是查询获取数据不是目的,需要考虑的是如何编写出高效的查询语句,这才是讨论的重点. 1.N+1问题 (1)什么是N+1问题 在刚听到这个名词时疑惑可能是有的,以前根本就没有听过N+1问题,那么它是指什么呢?N+1指的是一张表中有N条数据,那么在获取这N条数据时会产生N

  • 详解Java的Hibernate框架中的Interceptor和Collection

    Interceptor 讲到Interceptor,相信熟悉struts2的童鞋肯定不会陌生了,struts2可以自定义拦截器进行自己想要的一系列相关的工作.而这里我们说的Interceptor也是差不多相似的功能.  废话不说,直接来代码:  下面这个是MyInterceptor类,它实现了Interceptor接口: public String onPrepareStatement(String arg0) { return arg0; } public boolean onSave(Obj

  • Java的Hibernate框架结合MySQL的入门学习教程

    零.关于Hibernate Hibernate是冬眠的意思,它是指动物的冬眠,但是本文讨论的Hibernate却与冬眠毫无关系,而是接下来要讨论的SSH2框架中的一员.Hibernate是一个开源的项目,它是一个对象关系模型的框架,并且对JDBC进行了非常轻量级的封装,程序员在开发时可以使用对象编程思维进行开发. 下载地址:http://hibernate.org/orm/downloads/ Note:轻量级和重量级的区别,轻量级的框架包较小,并且使用较简单,而且测试容易,开发效率高:重量级框

  • 深入解析Java的Hibernate框架中的持久对象

    一.持久对象生命周期 应用程序在使用Hibernate框架后,创建的持久对象会经历一整套生命周期来完成数据库的操作,其中主要的三个状态分别是瞬态(Transient).持久化(Persistent).脱管(detached).这三种状态的转换是能够在应用程序中控制的,如下图: 为了能清楚的了解这几种状态,这里使用一个实例来查看下这几种状态下对象的不同,下面状态内的代码,具体步骤如下: (1)创建Hibernate_session程序集,并添加像相应的jar包: (2)配置Hibernate,添加

  • Java的Hibernate框架中的组合映射学习教程

    一.组合映射 组合是关联关系的一种特殊情况,是关联关系耦合度最高的一种关系,组合的主对象和子对象拥有相同的生命周期,主对像消亡的话子对象也会消亡.这里使用雇主和用户作为示例,用户和雇主都拥有联系方式属性,如果这里站在对象角度思考的话,常常会把对象模型绘制成为组合的方式,抽象出来一个共同的联系方式类,然后两种人分别包含相应的联系方式对象即可,向应的对象模型时它的对象示例如下图所示: 组合对象模型在生成相应的关系模型后会把对应的子类包含到主表中,所以对应的表结构会将相应的属性生成到对应的表中,相应的

随机推荐