之前我们接触的,都是独立的持久化类,最多也就是某一个集合作为属性,或者集合里面套集合,或者自定义一个组件作为属性。实际上很少有完全独立的对象,对象间往往需要互相访问。这种相互的联系我们成为关联关系。


单向N-1关联

这种关联关系非常常见,比如说多个人对应同一个住址,我们只需要通过人找地址,而不需要去找某个地址都有哪几个人。为了让两个持久化类支持这种映射关系,我们需要在N添加一个属性,引用1端的关联实体。对于N-1关联,不管是单向还是多向,都需要在N端使用@ManyToOne修饰代表关联的实体的属性。它有如下属性值。

cascade能够指定对关联实体采取什么样的级联策略,可以使CascadeType.ALL、MERGE、PERSIST、REFRESH、REMOVE。

fetch能够指定抓取策略,是立即抓取还是等真正用到的时候再抓取。接受FetchType.EAGER还有FetchType.LAZY两个属性值。

targetEntity用于指定关联实体的类名,默认情况下Hibernate会根据反射来判断关联实体的类名。但是当使用@OneToMany、@ManyToManyx修饰的关联,如果用于表示关联实体的Set集合没有泛型信息,就必须指定这个属性。

optional用于指定关联关系是否可选。


无连接表的N-1关联

对于无连接表的N-1关联,我们只需要在N端增加一个外键,让外键值记录,让外键值记录对应对象所属的实体即可。@JoinColumn来修饰代表关联实体的属性,用于修饰底层的外键列。直接使用这个注释来修饰N-1,则无需连接表,直接使用外键关联策略。下面的例子中perosn类有一个address属性,引用Address实体。

@Entity
@Table(name="person_inf")
public class Person {
	@Id @Column(name="main_id")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Integer id;
	private String name;
	private int age;
	
	@ManyToOne(targetEntity=Address.class,cascade=CascadeType.ALL)
	@JoinColumn(name="address_id",nullable=false)

	private Address address;
}
@Entity
@Table(name="address_inf")
public class Address {
	@Id @Column(name="addrress_id")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int addressId;
	private String addressDetail;
	public Address(){}
	public Address(String addressDetail){
		this.addressDetail=addressDetail;		
	}
}
private static void createAndStorePersons() {
		Session session = HibernateUtil.getSession();
		Transaction tx = session.beginTransaction();
		
		Person p=new Person();
		Address a=new Address("beijing");
		
		p.setName("alvin");
		p.setAge(23);
		p.setAddress(a);
		session.persist(p);
		Address a2=new Address("shanghai");
		p.setAddress(a2);
		
		
		tx.commit();
		session.close();

	}

mysql> select * from address_inf;

+-------------+---------------+

| addrress_id | addressDetail |

+-------------+---------------+

|           1 | beijing       |

|           2 | shanghai      |

+-------------+---------------+


mysql> select * from person_inf;

+---------+-----+-------+------------+

| main_id | age | name  | address_id |

+---------+-----+-------+------------+

|       1 |  23 | alvin |          2 |

+---------+-----+-------+------------+


每个Person都单向地持有一个Address,因此Person里增加了一个Address属性,并使用ManyToOne修饰该属性。同时使用JoinColumn指定了底层外键列。cascade表明了级联策略,所有对Person的持久化操作都会级联到它所关联的Address实体。测试代码中使用persist将person实例持久化,而由于设置了级联策略,会首先把关联的address持久化。之后又新建了一个address实例。当使用set给予person新的address引用时,会首先持久化新的address,然后再建立新的关联关系,也就是使用新的address的id跟新从表中的旧的外键id。相当于一条insert to address,然后再执行一条update to person。在所有既有的基于外键的关联关系中,我们都必须牢记,要么从事县持久化主表记录中对应的实体,要么设置级联策略。否则当插入从表时,如果发现从表记录参照的主表记录不存在,就一定会抛出异常。


有连接表的N-1关联

对于绝大部分单向N-1关联,使用基于外键的关联映射已经够了。但由于底层数据库可以使用连接表来确立这种关系,因此Hibernate也提供了这种支持。此时需使用@JoinTable来指明连接表。它可以指定如下几个属性:

name指定连接表的表明。

catalog设置将连接表放入指定的catalog中,有默认的catalog。

schema指定放入指定的schema中,也有指定的默认schema。

targetEntity指定关联实体的类名,默认情况下会根据反射来判断关联实体的类名。

indexes该属性为@Index注解数组,用于为连接表定义多个索引。

joinColumns可以接受多个@JoinColumn用于配置连接表中外键信息。

inverseJoinColumns可以接受多个@JoinColumn用于配置连接表中外键列信息。

uniqueConstraints用于为连接表增加唯一约束。

@Entity
@Table(name = "personb_inf")
public class PersonB {
	@Id
	@Column(name = "main_id")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	private String name;
	private int age;

	@ManyToOne(targetEntity = Address.class)
	@JoinTable(name = "person_address", 
	joinColumns = @JoinColumn(name = "person_id", referencedColumnName = "person_id", unique = true), 
	inverseJoinColumns = @JoinColumn(name = "address_id", referencedColumnName = "address_id"))
	private Address address;

}

上面程序中使用了@JoinTable来指定一个连接表。连接的实体是person跟address。与不适用连接表相比,这种方式少了JoinColumn来设定从表外键。它的name属性指定了连接表的表明,joinColumns属性指定了连接表中的外键为person_id,并告诉架构这个外键是由从表的person_id生成的。下一行中使用了inverseJoinColumns这个属性指定了表中的另一个外键,参照的是当前实体关联的实体中的主键:address_id。程序是怎么找到这个键的呢?由于使用的是inverseJoinColumns,所以这里面的referenced会去与Person关联的表中找名为address_id的主键,拿来做连接表的外键。由于是N-1关联,所以设置person_id时还要制定unique属性。

这种策略下,person_id即作为外键列参照person_inf表的主键,又作为连接表的主键列。这也就是为什么连接表中的person_id不能重复,同样也因为只有这样,才能保证一个人只有一个地址。实际上由于使用了连接表,因此两个表都无需使用外键,所以也就没有主表从表这个说法。因此程序完全可以想先持久化哪个类就持久化哪个。注意ManyToOne里面没有指定cascade属性!


单向1-1关联

对于单向的1-1关联关系,需要在持久化类里增加代表关联实体的成员变量。对于1-1关联,不管是单向还是多向,都需要在1端使用@OneToOne修饰代表关联实体的属性。可以指定cascade,fetch,optional,targetEntity,mappedBy和orphanRemoval属性。mappedBy的属性值为关联实体的属性名,用于指定关联实体中哪个属性可以引用到当前实体。orphanRemoval用于设置是否删除孤儿实体。如果某个实体所关联的父类不存在,该实体就是所谓的孤儿。


基于外键的单向1-1关联

只需使用OneToOne,然后增加外键即可。由于是1-1关联,因此JoinColumn的unique属性应该为true。

@Entity
@Table(name = "personb_inf")
public class PersonB {
	@Id
	@Column(name = "main_id")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	private String name;
	private int age;

	@OneToOne(targetEntity=Address.class)
	@JoinColumn(name="address_id",referencedColumnName="addredd_id",unique=true)
	private Address adress;

}

由于是单向,Address中并不需要什么注解,只要自己映射到底层数据库就行。自己在弄一个自己的主键。


有连接表的单向1-1关联

这种情况很少见,麻烦,但是Hibernate也支持。跟之前一样,使用一个JoinTable来指定连接表名,然后向表中加两列。分别参照person_id和address_id。

@Entity
@Table(name = "personb_inf")
public class PersonB {
	@Id
	@Column(name = "main_id")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	private String name;
	private int age;

	@OneToOne(targetEntity = Address.class)
	@JoinTable(name = "person_address", joinColumns = @JoinColumn(name = "person_id", referencedColumnName = "person_id", unique = true), inverseJoinColumns = @JoinColumn(name = "address_id", referencedColumnName = "address_id", unique = true)

	)
	private Address adress;

}

与之前的N-1连接表没什么区别,只是两边都要设置unique,以确保一个person只能有一个address,而一个address也只对应一个person。


单向1-N关联

单向1-N关联的持久化类中需要使用集合属性。集合中保存的是关联实体。这里我们在1端增加Set类型的成员变量,记录当前实体的所有关联实体。此外我们需要使用OneToMany注释Set。它有cascade、fetch、orphanRemoval、mappedBy和targetEntity属性。


无连接表得单向1-N关联

我们需要在N端增加外键列维护关联关系,而在1端增加JoinColumn修饰的Set集合,以映射外键即可。

@Entity
@Table(name = "personb_inf")
public class PersonB {
	@Id
	@Column(name = "main_id")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	private String name;
	private int age;

	@OneToMany(targetEntity=Address.class)
	@JoinColumn(name="person_id",referencedColumnName="person_id")
	private Set<Address> address=new HashSet<>();
}
@Entity
@Table(name="address_inf")
public class Address {
	@Id @Column(name="addrress_id")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int addressId;
	private String addressDetail;
	public Address(){}
	public Address(String addressDetail){
		this.addressDetail=addressDetail;		
	}
}

Person类中使用了OneToMany修饰Set集合属性,又使用JoinColumn映射了外键列。这里的外键列并不是增加到当前实体对应的数据表中,而是增加到关联实体Address对应的数据表中。Address类中没有特别的注释。


有连接表的单向1-N关联

同样使用OneToMany修饰代表关联实体的集合属性。此外还需使用@JoinTable显示指定连接表。

@Entity
@Table(name = "personb_inf")
public class PersonB {
	@Id
	@Column(name = "main_id")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	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> address = new HashSet<>();

}

由于address_id设置了unique,因此连接表中address_id不能重复,也就是说一个address只能对应一个person,但一个person可以对应多个address实体。由于使用了连接表,person与address中都没有外键列,因此无所谓主从关系,程序可以随意先持久化哪个实体就持久化哪一个。


单向N-N关联

这与单向1-N关联的持久化代码完全相同,控制关系的一端需要一个Set属性,被关联的持久化实例以集合形式存在。需要使用ManyToMany来修饰。可以设置cascade、fetch、mappedBy和targetEntity属性。N-N必须使用连接表。只需要去掉连接表1-N中的inverseJoinColumns里面的unique。