Hibernate的关联映射(1)-单向N-1关联
Hibernate的关联映射
1,单向关系:只需单向访问关联端,例如:只能通过老师访问学生,或者只能通过学生访问老师;
2,双向关系:关联的两端可以互相访问,例如老师和学生之间可以互相访问;
单向关联可以分:
单向1-1
单向1-N
单向N-1
单向N-N
双向关联可以分:
双向1-1
双向1-N
双向N-N
双向关系里没有N-1,因为双向N-1和1-N是完全一样的;
=================================================
单向N-1关联
对于N-1关联,无论是单向还是双向,都需要在N的一端使用@ManyToOne修饰代表关联实体的属性,使用manyToOne注解可以使用下面的属性:
cascase:(非必须属性)指定Hibernate对关联实体采用怎样的级联策略,该级联策略支持如下五个属性:
1.CascadeType.ALL:指定Hibernate将多有的持久化操作都级联到关联实体;
2.CascadeType.MERGE:指定Hibernate将merge操作级联到关联实体;
3.CascadeType.PERSIST:指定Hibernate将persist操作级联到关联实体;
4.CascadeType.REFRESH:指定Hibernate将refresh操作级联到关联实体;
5.CascadeType.REMOVE:指定Hibernate将remove操作级联到关联实体;
fetch:(非必须属性)指定抓取关联实体时的抓取策略,该属性支持如下两个属性值:
1.FetchType.EAGER:抓取实体时,立即抓取关联实体,这是默认值;
2.FetchType.LAZY:抓取实体时,延迟抓取关联实体,等到正真用到关联实体时采取抓取;
optional:(非必须属性)该属性指定关联关系是否可选
targetEntity:(非必须属性)该属性指定关联实体的类名,在默认情况下,Hibernate将通过反射来判断关联实体的类名;
(cascade={CascadeType.ALL})这个必须有
对于大部分的关联关系,Hibernate都可以通过反射来确定关联实体的类型,因此可以无需指定targetEntity属性,但在一些特殊情况下,例如使用@OneToMany,@ManyToMany修饰的1-N,N-N关联,如果用于表示关联实体的Set集合不带泛型信息,那就必须指定targetEntity属性;
1>无连接表的N-1关联
对于无连接表的N-1关联而言,程序只要在N的一端增加一列外键,让外键值记录该对象所属的实体即可,Hiberante可以使用@JoinColumn来修饰代表关联实体的属性,@JoinColumn用于映射底层的外键列;
直接使用@JoinColumn注解来映射N-1关联时,hibernate将无需使用连接表,直接使用外键关联策略来处理这种关联映射;
下面的两个持久化类描述了这种关联关系,Person类中增加了一个Address类型的属性,引用关联的Address实体,为了让Hibernate理解该Address类型的属性是关联实体,程序需要使用@ManyToOne,@JoinColumn修饰该属性;
1,实体类
<span style="font-size:18px;">package com.anlw.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
@Entity
@Table(name="person_inf")
public class Person {
//标识属性
@Id
@Column(name="person_id11")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name1;
private int age;
//定义该Person实体关联的Address实体
@ManyToOne(targetEntity=Address.class)
//映射外键列,指定外键列的的列名为address_id,不允许为空
@JoinColumn(name="address_id",nullable=false)
@Cascade(CascadeType.ALL)
private Address address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name1;
}
public void setName(String name1) {
this.name1 = name1;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
</span>
上面程序使用了Hibernate本身提供的@Cascade注解,该注解用于指定级联操作策略,此处指定的级联操作策略的CascadeType.ALL—-这表明对Person实体的所有持久化操作都会级联到它关联的Address实体;
2,实体类:
<span style="font-size:18px;">package com.anlw.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="address_inf")
public class Address {
//标识属性
@Id
@Column(name="address_id1")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer addressId;
//定义地址详细的成员变量
private String addressDetail;
//无参构造器
public Address(){}
//初始化全部成员变量的构造器
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
}
</span>
3,核心代码:
<span style="font-size:18px;">package com.anlw.test;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import com.anlw.entity.Address;
import com.anlw.entity.Person;
public class Test {
public static void main(String[] args) {
Configuration conf = new Configuration().configure();
ServiceRegistry st = new StandardServiceRegistryBuilder().applySettings(conf.getProperties()).build();
SessionFactory sf = conf.buildSessionFactory(st);
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction();
Person p = new Person();
Address a = new Address("guangzhou");
p.setName("crazyit.org");
p.setAge(11);
p.setAddress(a);
sess.persist(p);
Address a2 = new Address("shanghai");
p.setAddress(a2);
tx.commit();
sess.close();
sf.close();
}
}
</span>
上面的程序创建了三个持久化实体,即一个Pserson对象,两个Address对象,程序只保存了一次Person对象,从来没有保存过Address对象,程序第一次创建了一个瞬态的Address对象,当程序执行到persist()处时,系统准备保存person对象,系统要向person_inf数据表插入一条记录,但是该记录参照的主表记录(Address,被参照的Address实体还处于瞬态)还不曾保存,这时可能出现下面两种情况:
(1),系统抛出TransientObjectException异常:object references an unsaved transient instance,因为主表记录不曾插入,所以参照该记录的从表记录无法插入;
(2),系统先自动级联插入主表记录,再插入从表记录;
因为上面的实体类中使用了@Cascade(CascadeType.All)注解,修饰代表关联实体的属性,这意味着系统将自动级联插入主表记录,也就是先持久化Address对象,再持久化Person对象,也就是说,Hibernate在执行persist()方法时,先执行一条insert into address…语句,再执行一条insert into person…语句;从另一个角度讲,如果上面的实体类缺少了@Cascade(CascadeType.All)注解,则程序在运行到persist()时会抛出TransientObjectException异常;
程序第二次创建瞬态的Address对象,但当程序执行到p.setAddress(a2)时,程序将瞬态的Address对象关联到持久化状态下的Person对象,类似的,系统也会自动持久化Address对象,再建立Address和Person对象之间的关联,也就是说,Hibernate在p.setAddress(a2)时先执行一条insert into …语句插入记录,再执行update person…语句修改该Person记录的外键值;
注意:在所有既有的基于外键约束的关联关系中,都必须牢记:要么总是先持久化主表记录对应的实体,要么设置级联操作,否则当Hibernate试图插入从表记录时,如果发现该从表记录参照的主表记录不存在,那么一定会抛出异常;
==============================================================================================================================================================================
2>有连接表的N-1关联
与无连接表的代码只是Person实体类的注解不同,其他address实体类和核心代码一样的;
<span style="font-size:18px;">package com.anlw.entity;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name="person_inf11")
public class Person {
//标识属性
@Id
@Column(name="person_id11")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name2;
private int age;
//定义该Person实体关联的Address实体
@ManyToOne(cascade={CascadeType.ALL},targetEntity=Address.class)
//显示使用@JoinTable映射连接表
@JoinTable(name="person_address",//指定连接表的表名为person_address
//指定连接表中person_id外键列,参照到当前实体对应表的主键列
joinColumns=@JoinColumn(name="person_idss"
,referencedColumnName="person_id11",unique=true),
//指定连接表中address_id外键列,参照到当前实体的关联实体对应表的主键列
inverseJoinColumns=@JoinColumn(name="address_idff"
,referencedColumnName="address_id")
)
//@Cascade(CascadeType.ALL)
private Address address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName2() {
return name2;
}
public void setName2(String name2) {
this.name2 = name2;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
</span>
这个要注意,
<span style="font-size:18px;">@ManyToOne(cascade={CascadeType.ALL},targetEntity=Address.class)</span>
一定要添加级联注解,否则会抛异常;
对于使用连接表的N-1关联而言,由于两个实体对应的数据表都无需增加外键列,因此两个实体对应的数据表不存在主从关系,程序完全可以想先持久化哪个实体,就先持久化哪个实体,无论先持久化哪个实体,程序都不会引发性能问题;
@JoinTable专门用于映射底层连接表的信息,使用@JoinTable注解时可以指定下面的属性:
name:(非必须属性)指定该连接表的表名;
catalog:(非必须属性)设置将该连接表放入指定的catalog中,如果没有指定该属性,连接表将放入默认的catalog中;
schema:(非必须属性)设置将该连接表放入指定的schema中,如果没有指定该属性,连接表将放入默认的schema中;
targetEntity:(非必须属性)该属性指定关联实体的类名,在默认情况下,hibernate将通过反射来判断关联实体的类名;
indexes:(非必须属性)该属性值为@Index注解数组,用于为该连接表定义多个索引;
joinColumns:(非必须属性)该属性值可以接受多个@JoinColumn,用于配置连接表中的外键列信息,这些外键列参照当前实体对应表的主键列;
inverseJoinColumns:(非必须属性)该属性值可以接受多个@JoinColumn,用于配置连接表中外键列的列信息,这些外键列参照当前实体的关联实体对应表的主键列;
uniqueConstraints:(非必须属性)该属性用于为连接表增加唯一约束;
还没有评论,来说两句吧...