对于N-1
关联,不管是单向还是双向,都需要在N
的一端使用@ManyToOne
注释修饰代表关联实体的属性。
在所有基于外键约束的关联关系中,要么总先持久化主表记录对应的实体,要么设置级联操作; 否则会抛出异常。
无连接表的N-1关联
对于无连接表的N-1
关联,只要在N
的一端增加一个外键列,让外键列的值记录该对象所属的实体即可。使用@JoinColumn
修饰关联实体的属性。
例子: 多个人可以对应一个地点。
Person
类:
//无连接表的N-1
@Entity
@Table(name = "person_inf")
public class PersonSingleN1 {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private int age;
//定义该Person实体关联的Address实体
@ManyToOne(targetEntity = Address.class, fetch = FetchType.LAZY)
//映射外键列,指定外键列的名称为address_id,非空
@JoinColumn(name = "address_id", nullable = false)
//指定级联策略,ALL表明对Person实体的所有持久化操作都会级联到它关联的Address实体
@Cascade(CascadeType.ALL)
private Address address;
//省略get、set、构造函数
...
}
@ManyToOne(targetEntity = Address.class, fetch = FetchType.LAZY)
:指定关联的实体类型,延迟加载。@JoinColumn(name = "address_id", nullable = false)
: 指定指向关联实体的外键列的名称。@Cascade(CascadeType.ALL)
: 指定加载策略。
Address
类:
@Entity
@Table(name = "address_inf")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int addressId;
private String addressDetail;
//省略get、set、构造函数
...
}
Address
实体是一个普通的持久化类,不需要关心Person
类。
测试代码:
数据表中原已经存在一行Address
数据,一行Person
数据。
session.beginTransaction();
Address address = new Address("浙江杭州");
PersonSingleN1 personSingleN1 = new PersonSingleN1();
personSingleN1.setName("sweat");
personSingleN1.setAge(21);
personSingleN1.setAddress(address);
session.save(personSingleN1);
session.getTransaction().commit();
数据库数据:
mysql> select * from person_inf;
+----+-----+-------+------------+
| id | age | name | address_id |
+----+-----+-------+------------+
| 1 | 21 | sweat | 1 |
+----+-----+-------+------------+
1 row in set (0.00 sec)
mysql> select * from address_inf;
+-----------+---------------+
| addressId | addressDetail |
+-----------+---------------+
| 1 | 浙江杭州 |
+-----------+---------------+
1 row in set (0.00 sec)
可以看到Person
列中多了一列指向关联实体的外键列。
这里最需要注意的就是Person
实体类中的@Cascade(CascadeType.ALL)
注解。该注解修饰关联实体的属性,意味着会先自动级联插入主表记录,就是会先持久化Address
对象,然后在持久化Person
对象。如果没有该注解, 按照测试代码,会抛出TransientPropertyValueException
异常。 这是因为Hibernate
会先尝试插入Person
的数据,但是数据表中开始是没有Address
的对应数据的,所以造成异常。但是使用该注解就会先插入Address
数据,然后在插入Person
数据。
有连接表的N-1关联
通过额外的添加一个连接表来关联实体。
先看Person
类:
@Entity
@Table(name = "person_inf")
public class PersonSingleN1_2 {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private int age;
@ManyToOne(targetEntity = Address.class)
@JoinTable(name = "person_address",
joinColumns = @JoinColumn(name = "person_id", unique = true),
inverseJoinColumns = @JoinColumn(name = "address_id",
referencedColumnName = "addressId"))
private Address address;
//省略set、get、构造函数
...
}
@ManyToOne(targetEntity = Address.class)
: 修饰关联的属性,表示这是多对一。@JoinTable(name = "person_address", joinColumns = @JoinColumn(name = "person_id", unique = true), inverseJoinColumns = @JoinColumn(name = "address_id", referencedColumnName = "addressId"))
:@JoinTable
注解指定使用连接表,name
指定连接表的名称;joinColumns
中的@JoinColumn
指定关联表对应当前实体(Person
)的外键列名称。inverseJoinColumns
的@JoinColumn
指定关联表对应关联实体(Address
)的外键列名称。
Address
类:
@Entity
@Table(name = "address_inf")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int addressId;
private String addressDetail;
//省略get、set、构造函数
...
}
数据库数据:
+-------------------+
| Tables_in_servlet |
+-------------------+
| address_inf |
| person_address |
| person_inf |
+-------------------+
3 rows in set (0.00 sec)
总共有3个表。
mysql> select * from person_inf;
+----+-----+-------+
| id | age | name |
+----+-----+-------+
| 1 | 21 | sweat |
+----+-----+-------+
1 row in set (0.00 sec)
mysql> select * from person_address;
+------------+-----------+
| address_id | person_id |
+------------+-----------+
| 1 | 1 |
+------------+-----------+
1 row in set (0.00 sec)
mysql> select * from address_inf;
+-----------+---------------+
| addressId | addressDetail |
+-----------+---------------+
| 1 | 浙江杭州 |
+-----------+---------------+
1 row in set (0.00 sec)
连接表的描述:
mysql> desc person_address;
+------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------+------+-----+---------+-------+
| address_id | int(11) | YES | MUL | NULL | |
| person_id | int(11) | NO | PRI | NULL | |
+------------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)
person_id
是表person_address
主键,同时时person_inf
表的外键。