Hibernate的关联映射(5)-双向1-N关联

双向1-N关联

对于1-N关联,hibernate推荐使用双向关联,而且不要让1的一端控制关联关系,而是用N的一端控制关联关系,双向的1-N关联和N-1关联是完全相同的两种情形,两端都需要增加对关联属性的访问,N的一端增加引用到关联实体的属性,1的一端增加集合属性,集合元素为关联实体;

Hibernate同样对这种双向关联映射提供了两种支持:有连接表的和无连接表的,大部分时候,对于1-N的双向关联映射,使用无连接表的映射策略即可;

-------------------------------------------------------------------------------------------------------------------------------------------------

1>无连接表的双向1-N关联

无连接表的双向1-N关联,N的一端需要增加@ManyToOne注解来修饰代表关联实体的属性,而1的一端需要使用@OneToMany注解来修饰代表关联实体的属性;

底层数据库为了记录这种1-N关联关系,实际上只需要在N的一端的数据表里增加一个外键列即可,因此应该在使用@ManyToOne注解的同时,使用@JoinColumn来映射外键列;

前面已经提到,对于双向的1-N关联映射,通常不应该允许1的一端控制关联关系,而应该使用N的一端来控制关联关系,因此应该在使用@OneToMany注解时指定mappedBy属性------一旦为@OneToMany,@ManyToMany指定了该属性,则表明当前实体不能控制关联关系。当@OneToMany,@ManyToMany,@OneToOne所在的当前实体放弃控制关联关系之后,hibernate就不允许使用@JoinColumn,或@Jointable修饰代表关联实体的属性了。

对于指定了mappedBy属性的@OneToMany,@ManyToMany,@OneToOne注解,都不能与@JoinColumn,@JoinTable同时修饰代表关联实体的属性。

Person实体类

package com.anlw.entity;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "person_inf")
public class Person {
	// 标识属性
	@Id
	@Column(name = "person_id12")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	private String name;
	private int age;
	// 定义该Person实体所有关联的Address实体
	// 指定mappedBy属性表明该Person实体不控制关联关系
	@OneToMany(targetEntity = Address.class,mappedBy="person")
	private Set<Address> addresses = new HashSet<Address>();
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Set<Address> getAddresses() {
		return addresses;
	}
	public void setAddresses(Set<Address> addresses) {
		this.addresses = addresses;
	}
	
}
上面的Person类增加了一个Set集合属性,用于记录他关联的一系列Address实体,程序使用了@OneTomany修饰该集合属性,表明此处是1-N关联,从上面的代码可以看出使用@OneToMany时指定了mappedBy属性,这就表明Person实体不控制关联关系;而Address端则只需要增加一个Person类型的属性,这表明Address和Person存在N-1的关联关系,除此之外,程序需要使用@ManyToOne,@joinColumn来修饰代表关联实体的属性,如下是Address实体类:

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;

@Entity
@Table(name="address_inf")
public class Address {
	//标识属性
	@Id
	@Column(name="address_id")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int addressId;
	//定义地址详细的成员变量
	private String addressDetail;
	//定义该Address实体关联的Person实体
	@ManyToOne(targetEntity=Person.class)
	//定义名为person_id的外键列,该外键列引用person_inf表的person_id列
	@JoinColumn(name="person_id",referencedColumnName="person_id12",nullable=false)
	private Person person;
	public Address(){}
	
	public Address(String addressDetail) {
		this.addressDetail = addressDetail;
	}

	public int getAddressId() {
		return addressId;
	}
	public void setAddressId(int addressId) {
		this.addressId = addressId;
	}
	public String getAddressDetail() {
		return addressDetail;
	}
	public void setAddressDetail(String addressDetail) {
		this.addressDetail = addressDetail;
	}
	public Person getPerson() {
		return person;
	}
	public void setPerson(Person person) {
		this.person = person;
	}
	
}
上面的代码使用了@ManyToOne修饰代表关联实体的属性,也使用了@JoinColumn映射外键列,该注解将会控制在address_inf表中增加了名为person_id的外键列,这意味着address_inf表将作为从表使用;

核心测试代码:

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对象
		Person p = new Person();
		p.setName("kobe");
		p.setAge(36);
		// 持久化Person对象,对应于插入主表记录
		sess.save(p);
		// 创建一个瞬态的Address对象
		Address a = new Address("guangzhou");
		//先设置Person和Address之间的关联关系
		a.setPerson(p);
		//再持久化Address对象,对应于插入从表记录
		sess.persist(a);
		// 创建一个瞬态的Address对象
		Address a2 = new Address("shanghai");
		//先设置Person和Address之间的关联关系
		a2.setPerson(p);
		//再持久化Address对象,对应于插入从表记录
		sess.persist(a2);
		tx.commit();
		sess.close();
		sf.close();
	}
}
上面的主程序保存了一个Person对象,对应于向person_inf表插入一条记录,又保存了两个Address对象,对应于向address_inf表插入两条记录,并建立该Person对象和两个Address对象的关联关系;

为了保证良好的性能(只需要3条insert语句)主程序需要注意以下几点:

1,最好先持久化Person对象,(或者该Person对象本身已经处于持久化状态),因为程序希望持久化Address对象时,hibernate可为Address的外键属性分配值,也就是说,向address_inf数据表插入记录时,该记录的外键列已经指定了值,这表明他参照的主表记录已经存在,也就是Person对象必须已经被持久化;

2,先设置Person和Address的关联关系,再保存持久化Address对象。如果顺序反过来,程序持久化Address对象时,该Address对象还没有关联实体,所以hibernate不可能为对应记录的外键列指定值,等到设置关联关系时,hibernate只能再次使用update语句来修改关联关系;

3,不要通过Person对象来设置关联关系,因为已经在Person映射注解@OneToMany中指定了mappedBy属性,该属性表明Person对象不能控制关联关系;

-------------------------------------------------------------------------------------------------------------------------------------------------

2>有连接表的双向1-N关联

对于有连接表的双向1-N关联而言,1的一端无需任何改变,只要在N的一端使用@JoinTable显示的指定连接表即可;

对于有连接表的双向1-N关联,Person类(1的一端)与不使用连接表的1-N关联的Person代码相同,这里不给出,Address类则需要使用@JoinTable映射连接表;

Address类:

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.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name="address_inf")
public class Address {
	//标识属性
	@Id
	@Column(name="address_id")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int addressId;
	//定义地址详细的成员变量
	private String addressDetail;
	//定义该Address实体关联的Person实体
	@ManyToOne(targetEntity=Person.class)
	// 应映射连接表,指定连接表为p_a
	@JoinTable(name = "p_a",
			// 指定连接表中address_id列参照当前实体对应数据表的address_id主键列
			joinColumns = @JoinColumn(name = "address_id", referencedColumnName = "address_id", unique = true),
			// 指定连接表中person_id列参照当前实体的关联实体对应数据表的person_id主键列
			inverseJoinColumns = @JoinColumn(name = "person_id", referencedColumnName = "person_id12"))
	private Person person;
	public Address(){}
	
	public Address(String addressDetail) {
		this.addressDetail = addressDetail;
	}

	public int getAddressId() {
		return addressId;
	}

	public void setAddressId(int addressId) {
		this.addressId = addressId;
	}

	public String getAddressDetail() {
		return addressDetail;
	}

	public void setAddressDetail(String addressDetail) {
		this.addressDetail = addressDetail;
	}

	public Person getPerson() {
		return person;
	}

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

	
}
上面的代码注解指定连接表为p_a,且指定连接表中两列为person_id和address_id,其中address_id参照Address实体对应的数据表的address_id主键列,person_id参照Person实体对应的数据表的person_id主键列;

对于这种映射方式,由于程序依然在Person一端的@OneToMany注解中指定了mappedBy属性,这意味着该Person实体依然不能控制关联关系;

如果程序希望Person实体也可以控制关联关系---对于有连接表的1-N而言,使用1的一端控制关联关系,并不会影响程序性能,那么程序就需要删除@OneToMany注解的mappedBy属性,同时配合使用@JoinTable注解,下面是Perosn持久化类的源代码:

package com.anlw.entity;

import java.util.HashSet;
import java.util.Set;

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.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "person_inf")
public class Person {
	// 标识属性
	@Id
	@Column(name = "person_id12")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	private String name;
	private int age;
	// 定义该Person实体所有关联的Address实体
	// @OneToMany(targetEntity = Address.class,mappedBy="person")
	@OneToMany(targetEntity = Address.class)
	// 应映射连接表,指定连接表为p_a
	@JoinTable(name = "p_a",
			// 指定连接表中person_id列参照当前实体对应数据表的person_id主键列
			joinColumns = @JoinColumn(name = "person_id", referencedColumnName = "person_id12"),
			// 指定连接表中address_id列参照当前实体的关联实体对应数据表的address_id主键列
			inverseJoinColumns = @JoinColumn(name = "address_id", referencedColumnName = "address_id", unique = true))
	private Set<Address> addresses = new HashSet<Address>();

	public Integer getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Set<Address> getAddresses() {
		return addresses;
	}

	public void setAddresses(Set<Address> addresses) {
		this.addresses = addresses;
	}

}
上面的注解指定了连接表名为p_a;要求1的一端,N的一端的@JoinTable的name属性相同,这就保证他们映射的是同一个连接表,两端使用@JoinTable时指定的外键列的列名也是相互对应的;

对于上面这种映射方式,程序即可让Person实体控制关联关系,也可以让Address实体控制关联关系,而且由于Person实体,Address实体的关联关系交给连接表管理,因此他们不存在主从表关系,因此,程序完全可以想先持久化哪个实体就先持久化哪个实体,无论先持久化哪个实体,程序都不会引发性能问题;






















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值