Hibernate的关联映射

客观世界中很少有对象是独立存在的,比如我们可以通过某个老师获取该老师教的所有学生,我们也可以通过某个学生获得教他的对应的老师,实体之间的互相访问就是关联关系。
在Hibernate中有两种关联关系,即单向关联和双向关联。
单向关联:只能单向访问关联端,如只能通过老师访问学生。
双向关联:关联的两端可以互相访问,如老师可以访问学生,学生可以访问老师。
单向关联可以分为:1->1 1->N N->1 N-N
双向关联可以分为:1-1 1-N N-N

 
一 单向关联部分

1.单向N->1关联
单向N->1是指可以通过N中的某一个找到1中的实体,所以控制的那一端在N端,使用的是@ManyToOne注解。
单向N->1关系,比如多个人对应一个地址,可以从人的这一端找到对应的地址实体,不需要关心某个地址的用户。
无连接表的N->1关联
Person.java

@Entity
@Table(name="person_info")
public class Person {
@Id
@Column(name="person_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private int age; @ManyToOne(targetEntity=Address.class)
@JoinColumn(name="address_id",nullable=false)
@Cascade(CascadeType.ALL)
private Address address;
...//此处省略getter/setter等其他方法
}

Address.java

@Entity
@Table(name="address_info")
public class Address {
@Id
@Column(name="address_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private int addressId;
private String adressDetail;
...//此处省略getter/setter/构造器等方法
}

(1)@ManyToOne注解:
对于无连接表的N->1关联而言,只需要在N的那一端添加@ManyToOne注解,1的那一端作为一个一般的实体类就可。我们用targetEntity属性指定关联实体的类名,大部分时候无需指定targetEntity,但是如果使用@OneToMany或@ManyToMany修饰的1--N、N--N关联,使用Set集合不带泛型信息,就必须指定targetEntity属性
(2)@JoinColumn注解:
@JoinColumn表示通过外键关联策略进行映射,也就是说,程序在N的这一端增加一列外键,让外键记录该对象所属的实体,@JoinColumn用于映射底层的外键列。
(3)cascade注解:指定Hibernate对关联实体采用怎样的级联策略,包括以下五种情况:
CascadeType.ALL:将所有的持久化操作都级联到关联实体
CascadeType.MERGE:将merge操作都级联到关联实体
CascadeType.PERSIST:将persist操作都级联到关联实体
CascadeType.REFRESH:将refresh操作都级联到关联实体
CascadeType.REMOVE:将所remove操作都级联到关联实体
(4)fetch:指定抓取关联实体时的抓取策略
FetchType.EAGER:立即抓取
FetchType.LAZY:延迟抓取
配置文件hibernate.cxf.xml加上:

<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///mydb</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="show_sql">true</property>
<property name="hibernate.format_sql">false</property>
<mapping class="myHibernate.Person"/>
<mapping class="myHibernate.Address"/>
</session-factory>
</hibernate-configuration>

测试:

public class myTest {
public static void main(String[] args) {
Configuration config=new Configuration().configure();
SessionFactory factory=config.buildSessionFactory();
Session session=factory.openSession();
Transaction tx=session.beginTransaction();
Person p=new Person();
p.setName("lyy");p.setAge(22);
//创建一个瞬态的Address对象
Address a=new Address("北京");
//session.save(a);
p.setAddress(a);
session.save(p);
//再次创建一个瞬态的Address对象
Address a2=new Address("南京");
p.setAddress(a2);
tx.commit();
session.close();
}
}

程序执行到创建一个瞬态的Address对象时,我们知道,当某一个表中有关联表时,通常是要先保存(删除)关联表Address,然后再保存(删除)从表Person。也就是说,先有session.save(a);然后再有 session.save(p);但是如果我们把session.save(a);去掉,程序会有什么后果呢。这个时候,由于关联表Address还处于瞬态,没有保存,通常情况下会有两种情况出现:
(1)程序抛出异常:TransientObjectException:object references an unsaved transient instance - save the transient instance before flushing
(2)系统自动级联插入主表记录,再插入从表记录。
在上个例子中,程序会出现第二种情况,因为我们指定了@Cascade(CascadeType.ALL)。这意味着系统将会自动级联插入主表记录,即先持久化Address对象,再持久化Person对象。也就是说,Hibernate先执行了一条insert into address...语句,再执行一条insert into person...语句。也就是说,当实体类中没有@Cascade(CascadeType.ALL)这个注解,程序就会报(1)中的异常。
当程序再次创建一个瞬态的Address对象时,程序会将该瞬态的Address对象关联到已经持久化的Person对象中,注意,这里的Person对象已经save过,是一个持久化的对象,不需要再保存了,重新关联了新的Address对象就相当于是执行了一次更新操作。也就是说,hibernate先执行了一条insert into address...语句,又执行了update person...语句。此时数据库中address表有两列数据,person表有一列数据。

有连接表的N->1关联
通常情况下,我们都是使用基于外键的关联映射,很少用有连接表的关联映射,所以,只在N->1关联中举一个例子。
将Person.java修改为:

@Entity
@Table(name="person_info")
public class Person {
@Id
@Column(name="person_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private int age; @ManyToOne(targetEntity=Address.class)
@JoinTable(name="person_address", //此处指定连接表的表名为person_address
//指定连接表中person_id外键列,参照当前实体对应表的主键列
joinColumns=@JoinColumn(name="person_id",referencedColumnName="person_id",unique=true),
//指定连接表的address_id外键列,参照当前实体的关联实体对应表的主键列
inverseJoinColumns=@JoinColumn(name="address_id",referencedColumnName="address_id"))
@Cascade(CascadeType.ALL)
private Address address;
...
}

joinColumns:可接受多个@joinColumn,用于配置连接表中外键列的列信息,这些外键列参照当前实体对应表的主键列
inverseJoinColumns:可接受多个@joinColumn,用于配置连接表中外键列的列信息,这些外键列参照当前实体的关联实体对应表的主键列

2.单向1->1关联
基于外键的单向1->1关联
只需要在控制的那个实体中使用@OneToOne注解,使用@JoinColumn映射外键列即可。由于是1->1关联,因此还需要为@JoinColumn增加unique=true即可。
person.java

@Entity
@Table(name="person_info")
public class Person {
@Id
@Column(name="person_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private int age; @OneToOne(targetEntity=Address.class)
@JoinColumn(name="address_id",referencedColumnName="address_id",unique=true)
@Cascade(CascadeType.ALL)
private Address address;
...
}

3.单向1->N关联
持久化类发生了改变,因为1的一端要访问N的一端,也就是说,通过一个person找到所有的address,这里的address就需要使用集合属性。也就是说,只需要在1的那一端增加Set类型的成员变量。
person.java

@Entity
@Table(name="person_info")
public class Person {
@Id
@Column(name="person_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private int age; @OneToMany(targetEntity=Address.class)
@JoinColumn(name="person_id",referencedColumnName="person_id")
private Set<Address> addresses=new HashSet<>();
...
}

注意,使用@JoinColumn映射外键列,不同的是,此处的外键列并不是增加到当前实体对应的数据表中,而是增加到关联实体Adress对应的数据表中。比如说,在单向N->1关联中,person表有id字段,name字段,age字段,还有address_id字段进行外键关联。id不同的person可能有同一个address_id。而在1->N关联中,person表并没有address_id字段,而在address表中,有person_id字段进行关联。其实很好理解,id不同的address可能有同一个person_id。如果将外键关联加到person表中,会发现很有很多个相同的person_id,主键不唯一。
测试:

public class myTest {
public static void main(String[] args) {
Configuration config=new Configuration().configure();
SessionFactory factory=config.buildSessionFactory();
Session session=factory.openSession();
Transaction tx=session.beginTransaction();
Person p=new Person();
Address a=new Address("北京");
session.persist(a);
p.setName("lyy");p.setAge(22);
p.getAddresses().add(a);
session.save(p);
Address a2=new Address("南京");
session.persist(a2);
p.getAddresses().add(a2);
tx.commit();
session.close();
}
}

分析一下执行过程,我们就会发现,由于先自动保存关联实体address,而此时address实体中的person_id是无值的,也就是说,程序先通过insert语句插入了一条外键为null的address记录,然后再执行update语句更新刚刚的插入的address记录,这肯定会影响系统性能。应该尽量少用单向1->N关联,而是改为双向1-N关联。而对于双向的1-N关联,使用1的一端控制关联关系会有很多弊端,比如插入数据时无法插入外键列,会额外多出一条update语句,并且外键列还不能增加非空约束。

4.单向N->N关联
控制的一端需要添加一个Set类型的属性,被关联的持久化实例以集合形式存在。N-N关联必须使用关联表。

person.java

@Entity
@Table(name="person_info")
public class Person {
@Id
@Column(name="person_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private int age; @ManyToMany(targetEntity=Address.class)
@JoinTable(name="person_address",
joinColumns=@JoinColumn(name="person_id",referencedColumnName="person_id"),
inverseJoinColumns=@JoinColumn(name="address_id",referencedColumnName="address_id"))
private Set<Address> addresses=new HashSet<>();
...
}

二 双向关联部分

1.双向1-N关联
对于1-N关联,Hibernate推荐使用双向1-N关联,而且用N的一端控制关联关系,使用无连接表的映射策略即可。
N的一端需要增加@ManyToOne注解,并用@JoinColumn映射外键列,而在1的一端使用@OneToMany注解,并指定mappedBy属性,一旦指定了该属性,就说明当前实体不能控制关联关系。
注意,对于指定了mappedBy的@OneToMany,@ManyToMany,@OneToOne注解,都不能与@JoinColumn或@JoinTable同时修饰代表关联实体的属性。

Person.java

@Entity
@Table(name="person_info")
public class Person {
@Id
@Column(name="person_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private int age; @OneToMany(targetEntity=Address.class,mappedBy="person")
private Set<Address> addresses=new HashSet<>();
...
}

Address.java

@Entity
@Table(name="address_info")
public class Address {
@Id
@Column(name="address_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private int addressId;
private String adressDetail;
@ManyToOne(targetEntity=Person.class)
@JoinColumn(name="person_id",referencedColumnName="person_id",nullable=false)
//这里与单向1->N不同,person_id不可以为null
private Person person;
...
}

测试:

public class myTest {
public static void main(String[] args) {
Configuration config=new Configuration().configure();
SessionFactory factory=config.buildSessionFactory();
Session session=factory.openSession();
Transaction tx=session.beginTransaction();
Person p=new Person();
p.setName("lyy");p.setAge(22);
session.save(p); //持久化person对象
Address a=new Address("北京");
a.setPerson(p); //设置person和address之间的关联关系
session.persist(a); //持久化address对象
Address a2=new Address("南京");
a2.setPerson(p);
session.persist(a2);
tx.commit();
session.close();
}
}

由程序可以发现,最好先持久化person对象,因为程序希望在持久化address对象时,可以为address的外键列person_id分配值。

2.双向N-N关联
双向N-N关联需要在两端都使用Set集合属性,两端都增加对集合属性的访问,只能采用连接表来建立两个实体之间的关联关系。
也就是说,两端都要使用@ManyToMany修饰Set集合属性,并在两端都使用@JoinTable显示映射连接表,并且两端指定的连接表的表名应该相同,指定的外键列的列名相互对应。
如果程序希望某一端放弃控制关联关系,则可以在这一端的@ManyToMany注解中指定MappedBy属性,这一端也就不能再指定@JoinTable映射连接表了。
Person.java

@Entity
@Table(name="person_info")
public class Person {
@Id
@Column(name="person_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private int age; @OneToMany(targetEntity=Address.class)
@JoinTable(name="person_address",
joinColumns=@JoinColumn(name="person_id",referencedColumnName="person_id"),
inverseJoinColumns=@JoinColumn(name="address_id",referencedColumnName="address_id",unique=true))
private Set<Address> addresses=new HashSet<>();
...
}

Address.java

@Entity
@Table(name="address_info")
public class Address {
@Id
@Column(name="address_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private int addressId;
private String adressDetail;
@ManyToOne(targetEntity=Person.class)
@JoinTable(name="person_address",
joinColumns=@JoinColumn(name="address_id",referencedColumnName="address_id"),
inverseJoinColumns=@JoinColumn(name="person_id",referencedColumnName="person_id"))
private Person person;
...
}

注意,由于此处管理的是N-N关联,就不能为@JoinColumn注解增加unique=true。

3.双向1-1关联
两端都要使用@OneToOne注解进行映射,外键可以存放在任意一端,即通过@JoinColumn注解来映射外键列。一旦选择其中的一端来增加外键,该表即变为从表,另一个表则为主表。
双向1--1关联的主表对应的实体,也不应该用于控制关联关系,因此要用mappedBy属性
Person.java

@Entity
@Table(name="person_info")
public class Person {
@Id
@Column(name="person_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private int age; @OneToOne(targetEntity=Address.class,mappedBy="person")
private Address address;
...
}

Address.java

@Entity
@Table(name="address_info")
public class Address {
@Id
@Column(name="address_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private int addressId;
private String adressDetail;
@OneToOne(targetEntity=Person.class)
@JoinColumn(name="person_id",referencedColumnName="person_id",unique=true)
private Person
}

Hibernate三 关联关系的更多相关文章

  1. Hibernate之关联关系映射(一对一主键映射和一对一外键映射)

    1:Hibernate的关联关系映射的一对一外键映射: 1.1:第一首先引包,省略 1.2:第二创建实体类: 这里使用用户信息和身份证信息的关系,用户的主键编号既可以做身份证信息的主键又可以做身份证信 ...

  2. hibernate有关联关系删除子表时可能会报错,可以用个clear避免错误

    //清除子表数据 public SalesSet removeSalesSetDistributor(SalesSet salesSet ){ List<SalesSetDistributor& ...

  3. 简单理解Hibernate三种状态的概念及互相转化

    本文描述了Hibernate三种状态的概念及互相转化.Java对象的生命周期中有三种状态,而且互相转化.它们分别是临时状态,持久化状态,以及游离状态. AD:WOT2015 互联网运维与开发者大会 热 ...

  4. [转]hibernate三种状态详解

    本文来自 http://blog.sina.com.cn/u/2924525911 hibernate 三种状态详解 (2013-04-15 21:24:23) 转载▼   分类: hibernate ...

  5. Hibernate JPA 关联关系

    Hibernate JPA 关联关系: 使用cascade做级联操作(只有在满足数据库约束时才会生效): CascadeType.PERSIST: 级联保存,只有调用persist()方法,才会级联保 ...

  6. Hibernate 一对一关联关系

    双向一对一关联关系: 域模型: 例如,部门只有一个部门经理,一个经理也只能管理一个部门.即,Department 中有一个Manager的引用,Manager 中又有一个Department 的引用. ...

  7. 框架之 hibernate之关联关系映射

    案例:完成CRM的联系人的保存操作 需求分析 1. 因为客户和联系人是一对多的关系,在有客户的情况下,完成联系人的添加保存操作 技术分析之Hibernate的关联关系映射之一对多映射(重点) 1. J ...

  8. Hibernate的关联关系映射

    技术分析之Hibernate的关联关系映射之一对多映射(重点)        1. JavaWEB中一对多的设计及其建表原则        2. 先导入SQL的建表语句                 ...

  9. Hibernate —— 映射关联关系

    一.映射多对一关联关系. 1.单向的多对一 (1)以 Customer 和 Order 为例:一个用户可以发出多个订单,而一个订单只能属于一个客户.从 Order 到 Customer 是多对一关联关 ...

随机推荐

  1. ios进行打包

    原文转载:http://blog.csdn.net/azhou_hui/article/details/9058677   公司刚搞了个299美刀的仅提供真机测试的企业账号,这个不需要添加设备ID,而 ...

  2. CI框架多目录设置

    1,设置目的,前台与后台实现独立目录管理 2.通过http://www.myci.com  访问前台,通过http://www.myci.com/admin 访问后台,   多目录的意思是指在同一个网 ...

  3. ajax跨域传值

    <script type="text/javascript"> function xmlpage(){ $.ajax({ url:'http://localhost/3 ...

  4. TCP/UDP基本概念部分

    最近在读<Unix网络编程>和<TCP/IP详解>两本书,有了一些自己的心得与体会,总结下其中典型的问题. 1. 为什么建立连接需要三次握手? 谢希仁的<计算机网络> ...

  5. SGU 122.The book (哈密顿回路)

    题目描述 有一群人从1到N标号,而且这群人中每个人的朋友个数不少于 (N+1)/2 个. 编号为1的人有一本其他人都想阅读的书. 写一个程序,找到一种传阅顺序使得书本只经过每个人手中一次,并且一个人只 ...

  6. 【BZOJ1861】【splay】Book 书架

    Description 小 T有一个很大的书柜.这个书柜的构造有些独特,即书柜里的书是从上至下堆放成一列.她用1到n的正整数给每本书都编了号. 小T在看书的时候,每次取出一本书,看完后放回书柜然后再拿 ...

  7. Jdbc工具类(连接及释放)

    package cn.gdpe.jdbc; import java.io.File;import java.io.FileInputStream;import java.io.FileNotFound ...

  8. iOS把两张图片合成一张图片

    0x00 步骤 先读取两张图片把创建出CGImageRef 创建上下文画布 把图片依次画在画布指定位置上 从上下文中获得合并后的图片 关闭上下文 释放内存 0x01 代码实现 - (void)comp ...

  9. CSS3中动画transform必须要了解的Skew变化原理

    transform是CSS3中比较安全的动画(对于性能来说),其中有一些动画属性,来执行不同的变化.今天我们来了解skew的变化原理. skew,其实使用的频率不是很高,当然也没有最低.但是往往,一直 ...

  10. windows 下 pgsql 拓展开启

    环境:windowxp + apache2.2+php5.2.13+postgresql8.3 操作步骤:1.修改php.ini,去掉“extension=php_pgsql.dll ”和“exten ...