在领域模型中, 类与类之间最普遍的关系就是关联关系.

在 UML 中, 关联是有方向的。

以 Customer 和 Order 为例: 一个用户能发出多个订单, 而一个订单只能属于一个客户。从 Order 到 Customer 的关联是多对一关联; 而从 Customer 到 Order 是一对多关联。

【1】Customer与Order

关联关系映射为Order:Customer=N:1,Order中有Customer属性,Customer中没有Order属性。

映射单向 n-1的关联关系核心:

  • 使用@ManyToOne来映射多对一的关联关系
  • 使用@JoinColumn 来映射外键
  • 使用 @ManyToOne 的fetch 属性来修改默认的关联属性的加载策略

Order如下:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Table(name="JPA_ORDERS")
@Entity
public class Order {

private Integer id;
private String orderName;

private Customer customer;

@GeneratedValue
@Id
public Integer getId() {
return id;
}

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

@Column(name="ORDER_NAME")
public String getOrderName() {
return orderName;
}

public void setOrderName(String orderName) {
this.orderName = orderName;
}

//映射单向 n-1的关联关系
//使用@ManyToOne来映射多对一的关联关系
//使用@JoinColumn 来映射外键
//可使用 @ManyToOne 的fetch 属性来修改默认的关联属性的加载策略
@JoinColumn(name="CUSTOMER_ID")
// @ManyToOne(fetch=FetchType.LAZY)
@ManyToOne(fetch=FetchType.EAGER)
public Customer getCustomer() {
return customer;
}

public void setCustomer(Customer customer) {
this.customer = customer;
}

}

Customer如下:

单向多对一中,一的一端(Customer)并不需要保持多的一端(Order)。

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

import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

@NamedQuery(name="testNamedQuery", query="FROM Customer c WHERE c.id = ?")
@Cacheable(true)
@Table(name="JPA_CUTOMERS")
@Entity
public class Customer {

private Integer id;
private String lastName;
private String email;
private int age;
private Date createdTime;
private Date birth;

public Customer() {
}

public Customer(String lastName, int age) {
super();
this.lastName = lastName;
this.age = age;
}

// @TableGenerator(name="ID_GENERATOR",
// table="jpa_id_generators",
// pkColumnName="PK_NAME",
// pkColumnValue="CUSTOMER_ID",
// valueColumnName="PK_VALUE",
// allocationSize=100)
// @GeneratedValue(strategy=GenerationType.TABLE,generator="ID_GENERATOR")
@GeneratedValue(strategy=GenerationType.AUTO)
@Id
public Integer getId() {
return id;
}

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

@Column(name="LAST_NAME",length=50,nullable=false)
public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
//定义日期格式
@Temporal(TemporalType.TIMESTAMP)
public Date getCreatedTime() {
return createdTime;
}

public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}

@Temporal(TemporalType.DATE)
public Date getBirth() {
return birth;
}

public void setBirth(Date birth) {
this.birth = birth;
}

@Transient
public String getInfo(){
return "lastName: " + lastName + ", email: " + email;
}

@Override
public String toString() {
return "Customer [id=" + id + ", lastName=" + lastName + ", email="
+ email + ", age=" + age + ", createdTime=" + createdTime
+ ", birth=" + birth + "]";
}

}

【2】多对一的持久化操作

保存多对一时,建议先保存1的一端,后保存n的一端,这样不会多出额外的update语句。

实例代码如下:

@Test
public void testManyToOnePersist(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("gg@163.com");
customer.setLastName("GG");

Order order1 = new Order();
order1.setOrderName("G-GG-1");

Order order2 = new Order();
order2.setOrderName("G-GG-2");

//设置关联关系
order1.setCustomer(customer);
order2.setCustomer(customer);

//ִ执行保存操作
entityManager.persist(customer);
entityManager.persist(order1);
entityManager.persist(order2);
}

控制台输出如下:

Hibernate: 
insert
into
JPA_CUTOMERS
(age, birth, createdTime, email, LAST_NAME)
values
(?, ?, ?, ?, ?)
Hibernate:
insert
into
JPA_ORDERS
(CUSTOMER_ID, ORDER_NAME)
values
(?, ?)
Hibernate:
insert
into
JPA_ORDERS
(CUSTOMER_ID, ORDER_NAME)
values
(?, ?)

即,连续三条插入语句。

如果先保存Order呢?将会多出Update语句

如下所示:

//ִ执行保存操作
entityManager.persist(order1);
entityManager.persist(order2);
entityManager.persist(customer);

控制台输出如下:

Hibernate: 
insert
into
JPA_ORDERS
(CUSTOMER_ID, ORDER_NAME)
values
(?, ?)
Hibernate:
insert
into
JPA_ORDERS
(CUSTOMER_ID, ORDER_NAME)
values
(?, ?)
Hibernate:
insert
into
JPA_CUTOMERS
(age, birth, createdTime, email, LAST_NAME)
values
(?, ?, ?, ?, ?)
Hibernate:
update
JPA_ORDERS
set
CUSTOMER_ID=?,
ORDER_NAME=?
where
id=?
Hibernate:
update
JPA_ORDERS
set
CUSTOMER_ID=?,
ORDER_NAME=?
where
id=?

总结如下:

保存多对一时,建议先保存1的一端,后保存n的一端,这样不会多出额外的update语句。


【3】多对一的获取操作

默认情况下,使用左外连接的方式来获取n的一端的对象和其关联的1的一端的对象。可使用 @ManyToOne 的fetch 属性来修改默认的关联属性的加载策略。

实例如下:

@Test
public void testManyToOneFind(){
Order order = entityManager.find(Order.class, 1);
System.out.println(order.getOrderName());
System.out.println(order.getCustomer().getLastName());
}

此时Order实体类中fetch为eager(默认值):

@JoinColumn(name="CUSTOMER_ID")
@ManyToOne(fetch=FetchType.EAGER)
public Customer getCustomer() {
return customer;
}

JPA - 单向多对一映射_hibernate

即,默认使用左外连接方式来获取n的一端的对象和其关联的1的一端的对象。

如果将feteh属性改为lazy:

@JoinColumn(name="CUSTOMER_ID")
@ManyToOne(fetch=FetchType.LAZEY)
public Customer getCustomer() {
return customer;
}

其控制台输出如下:

JPA - 单向多对一映射_对象关联关系_02


【4】多对一的删除操作

① 可以任意删除n的一端,但是不能随意删除1的一端,因为有外键约束。

示例如下:

//不能直接删除1的一端,因为有外键约束
@Test
public void testManyToOneRemove(){
Order order = entityManager.find(Order.class, 6);
entityManager.remove(order);

}

JPA - 单向多对一映射_对象关联关系_03


② 如果删除1的一端将会抛出异常:

//不能直接删除1的一端,因为有外键约束
@Test
public void testManyToOneRemove(){
Customer customer = entityManager.find(Customer.class, 4);
entityManager.remove(customer);
}

JPA - 单向多对一映射_jpa_04


③ 如果外键约束不存在,则可以删除1的一端:

JPA - 单向多对一映射_对象关联关系_05


④ 删除一个不存在的实体,同样会抛出异常:

JPA - 单向多对一映射_jpa_06


【5】多对一的更新操作

示例如下:

@Test
public void testManyToOneUpdate(){
Order order = entityManager.find(Order.class, 2);
order.getCustomer().setLastName("FFF");
}

控制台输出如下:

Hibernate: 
select
order0_.id as id1_1_1_,
order0_.CUSTOMER_ID as CUSTOMER3_1_1_,
order0_.ORDER_NAME as ORDER_NA2_1_1_,
customer1_.id as id1_0_0_,
customer1_.age as age2_0_0_,
customer1_.birth as birth3_0_0_,
customer1_.createdTime as createdT4_0_0_,
customer1_.email as email5_0_0_,
customer1_.LAST_NAME as LAST_NAM6_0_0_
from
JPA_ORDERS order0_
left outer join
JPA_CUTOMERS customer1_
on order0_.CUSTOMER_ID=customer1_.id
where
order0_.id=?
Hibernate:
update
JPA_CUTOMERS
set
age=?,
birth=?,
createdTime=?,
email=?,
LAST_NAME=?
where
id=?