JPA映射关联关系-双向一对多
双向一对多 = 双向多对一
什么是双向?
我们来对比一下单向和双向
单向/双向 | Customer实体类中是否有List< Order> orders/Set<Order> orders | Order实体类中是否有Customer |
单向多对一 | 无 | 有 |
单向一对多 | 有 | 无 |
双向一对多(双向多对一) | 有 | 有 |
怎么构建关系,各自的实体类如下:
package com.ppl.entity;
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.OneToMany;
import javax.persistence.Table;
@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;
// 一对多使用
private Set<Order> orders = new HashSet<>();
// 使用 @OneToMany 来映射 1-n 的关联关系
// 使用 @JoinColumn 来映射外键列的名称
// 可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略
// 可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略.
// 注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用
// @JoinColumn 属性了.
@JoinColumn(name = "CUSTOMER_ID")
@OneToMany(fetch = FetchType.EAGER, cascade = { CascadeType.REMOVE })
// @OneToMany(fetch=FetchType.LAZY,cascade={CascadeType.REMOVE},mappedBy="customer")
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Customer() {
// TODO Auto-generated constructor stub
}
public Customer(String lastName, int age) {
super();
this.lastName = lastName;
this.age = age;
}
@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;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Customer [id=" + id + ", lastName=" + lastName + ", email=" + email + ", age=" + age + "]";
}
}
package com.ppl.entity;
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;
}
// 使用 @ManyToOne 来映射多对一的关联关系
// 使用 @JoinColumn 来映射外键. (数据库中外键的名字为CUSTOMER_ID )
// 可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
@JoinColumn(name = "CUSTOMER_ID")
@ManyToOne(fetch = FetchType.LAZY)
// @ManyToOne
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
注意,两个实体类中的@JoinColumn(name=”CUSTOMER_ID”) 值要一致
插入操作,注意在此是先插入“一”的一端
//若是双向 1-n 的关联关系, 执行保存时
//若先保存 n 的一端, 再保存 1 的一端, 默认情况下, 会多出 2*n 条 UPDATE 语句.
//若先保存 1 的一端, 则会多出 n 条 UPDATE 语句
//在进行双向 1-n 关联关系时, 建议使用 n 的一方来维护关联关系, 而 1 的一方不维护关联系, 这样会有效的减少 SQL 语句.
//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.
@Test
public void testBothOneToManyPersist(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("mm@163.com");
customer.setLastName("MM");
Order order1 = new Order();
order1.setOrderName("O-MM-1");
Order order2 = new Order();
order2.setOrderName("O-MM-2");
//建立关联关系
customer.getOrders().add(order1);
customer.getOrders().add(order2);
order1.setCustomer(customer);
order2.setCustomer(customer);
//执行保存操作
entityManager.persist(customer);
entityManager.persist(order1);
entityManager.persist(order2);
}
注意:既要把customer放到order中,也要把order加到customer中
看截图,你会发现有两次的udpate操作,这是因为要维护关联关系。
如果你先插入两个order,在插入customer,将会发现有四次的update操作,这会影响效率。
要怎么解决呢?
在这种一对多的情况下,我们可以指明由“多”的一方来维护关联关系也就是只在多的一方里有@JoinColumn(name = "CUSTOMER_ID"),而在在Customer实体类中
// 使用 @OneToMany 来映射 1-n 的关联关系
// 使用 @JoinColumn 来映射外键列的名称
// 可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略
// 可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略.
// 注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.
//@JoinColumn(name = "CUSTOMER_ID")
@OneToMany(fetch=FetchType.LAZY,cascade={CascadeType.REMOVE},mappedBy="customer")
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
注意:使用了mappedBy之后,就不能使用@JoinColumn注解,否则抛异常
再次执行插入操作,发现没有再多出update操作了
注意:这种情况下必须是先插入Customer,若是先插入Order,还是会多出n条UPDATE语句。
//若是双向 1-n 的关联关系, 执行保存时
//若先保存 n 的一端, 再保存 1 的一端, 默认情况下, 会多出 2*n 条 UPDATE 语句.
//若先保存 1 的一端, 则会多出 n 条 UPDATE 语句
//在进行双向 1-n 关联关系时, 建议使用 n 的一方来维护关联关系, 而 1 的一方不维护关联系, 这样会有效的减少 SQL 语句.
//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.
@Test
public void testBothOneToManyPersist(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("mm@163.com");
customer.setLastName("MM");
Order order1 = new Order();
order1.setOrderName("O-MM-1");
Order order2 = new Order();
order2.setOrderName("O-MM-2");
//建立关联关系
customer.getOrders().add(order1);
customer.getOrders().add(order2);
order1.setCustomer(customer);
order2.setCustomer(customer);
//执行保存操作
entityManager.persist(customer);
entityManager.persist(order1);
entityManager.persist(order2);
}
总结:在一对多和多对一的情况下,插入操作要先插入“一”的对象,再插入“多”的对象,这样可以减少sql语句。如果是双向的,要使用mappedBy指明由“多”的一方(Order)中的customer来维护关联关系
查询
//默认对关联的多的一方使用懒加载的加载策略.
//可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略
@Test
public void testBothOneToManyFind(){
Customer customer = entityManager.find(Customer.class, 9);
System.out.println(customer.getLastName());
System.out.println(customer.getOrders().size());
}