之前我们接触的,都是独立的持久化类,最多也就是某一个集合作为属性,或者集合里面套集合,或者自定义一个组件作为属性。实际上很少有完全独立的对象,对象间往往需要互相访问。这种相互的联系我们成为关联关系。
单向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。
转载于:https://blog.51cto.com/mengcao/1692685