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. shell脚本学习之case例子

    case和select结构在技术上说并不是循环, 因为它们并不对可执行代码块进行迭代. 但是和循环相似的是, 它们也依靠在代码块顶部或底部的条件判断来决定程序的分支.  在代码块中控制程序分支  ca ...

  2. Jmeter软件测试1--webservice测试

    写在前言 程序猿一枚,原本就是负责安安静静的撸代码,后来公司让兼任下测试的工作,还得照顾下面的几个测试兄弟,无奈本人毫无软件测试理论知识,下面的测试兄弟也是初级水平,又面临公司要求做webservic ...

  3. String、StringBuffer、StringBuilder

    也说String. String:不可变字符序列. StringBuffer:线程安全的可变字符序列. StringBuilder:StringBuffer的非线程安全实现,JDK1.5+. publ ...

  4. iOS 数据持久性存储-属性列表

    iOS上常用四种数据存取方法有: 1.属性列表 2.对象归档 3.iOS的嵌入式关系数据库(SQLite3) 4.苹果公司提供持久性共聚Core Data 由于苹果公司的沙盒机制,每个应用程序都有自己 ...

  5. python3 遍历文件

    程序很简单,parent,dirnames,filenames分别表明当前目录下的文件夹数及文件数,然后通过os.wolk向深入遍历.   import os import os.path # thi ...

  6. python split()黑魔法

    split()用法: #!/usr/bin/python str = "Line1-abcdef \nLine2-abc \nLine4-abcd"; print str.spli ...

  7. Dev gridview 调整字体大小

    //调整表头字体大小 this.gridView1.Appearance.HeaderPanel.Font = new Font("Tahoma", 20, FontStyle.R ...

  8. unix 环境高级编程 读书笔记与习题解答第四篇

    第一章 第六节 第一小节 这一章没有程序设计和API方面的深入学习,而是注重介绍了unix操作系统中的原始数据类型和系统原型函数,错误处理方面的知识. ____unistd.h____ 该文件包含了u ...

  9. Josephus2

    约瑟夫问题升级问题 编号为1~N的N个人按顺时针方向围坐一圈,每个人持有一个密码(正整数,可以自由输入),开始人选一个正整数作为报数上限值M,从第一个人按顺时针方向自1开始顺序报数,报道M是停止报数. ...

  10. ETL控件学习之一从数据库导出数据到平面

    今天主要进行ETL控件的学习.主要是使用微软的SSDT工具.使用DataFlowTask 将数据源导出到目标文件的方式. 1.打开SSDT新建一个SSIS的project,如下图所示: 2.在SSIS ...