单向多对一
场景
一个顾客N个订单,一个订单属于1个顾客。
// Order.class
@JoinColumn(name="buyer_id")// 映射外键列名
// 及时查询(默认):fetch=FetchType.EAGER、懒加载:fetch=FetchType.LAZY
@ManyToOne // 映射单向多对一。
publice Buyer getBuyer(){
return buyer;
}
测试
保存
@Test
public void persist() {
Buyer buyer = new Buyer();
// ...添加属性
Order o1 = new Oreder();
// ...添加属性
Order o1 = new Oreder();
// ...添加属性
buyer.getOrders().add(o1);
buyer.getOrders().add(o2);
/**
insert into buyer ...
insert into order ..
insert into order ..
update order set buyer_id ..
update order set buyer_id ..
*/
entityManager.persist(buyer);
entityManager.persist(o1);
entityManager.persist(o2);
/**
insert into buyer ...
insert into order ..
insert into order ..
update order set buyer_id ..
update order set buyer_id ..
*/
entityManager.persist(o1);
entityManager.persist(o2);
entityManager.persist(buyer);
}
- 小结
(先1后N)单向多对一时,优先保存1的一端,后保存N的一端, 效率更高
查询
/**
select xxx from order left join buyer on ..
where order.id = ?
*/
@Test
public void find(){
Order o1 = entityManager.find(Order.class, {主键值});
System.out.println(o1.getXx());
System.out.println(o1.getBuyer().getXxx());
}
- 小结
- 默认使用及时查询,默认会使用左外连接查询出1的一端。
- 采用懒加载,会先查询出N端,再查询出1的一端。
删除
// 删除多的一端
@Test
public void removeN(){
Order o1 = entityManager.find(Order.class, {主键});
entityManager.remove(o1);
}
// 删除有映射关系的1的一端
@Test
public void remove1(){
Buyer buyer = entityManager.find(Buyer.class, {主键});
entityManager.remove(buyer);
}
- 小结
- 删除N端时,能正常删除。
- 删除1的一端时,不能直接删除。因为有外键关联。
更新
public void update(){
Order o1 = entityManager.find(Order.class, {主键});
// 完成更新
o1.getBuyer().setXxx(xx);
}
- 小结
更改持久化对象(entityManager查询出来的对象),会同步更新到数据库
结论
- 保存时,单向多对一时,优先保存1的一端,后保存N的一端。
- 查询N端时,默认会使用左外连接查询出1的一端,即默认使用的及时查询策略。
- 不能直接删除1的一端,因为有外键关联。
- 更改持久化对象,会同步更新到数据库。
单向一对多
// Buyer.class
Set<Order> orders = new HashSet();
@JoinColumn(name="buyer_id")// 映射外键列名
/** 及时查询(默认):fetch=FetchType.EAGER、懒加载:fetch=FetchType.LAZY
级联删除属性:级联删除:cascad={CascadeType.REMOVE}
*/
@OneToMany // 映射单向一对多。
publice Set<Order> getOrders(){
return orders;
}
测试
保存
@Test
public void persist(){
Buyer buyer = new Buyer();
// ...添加属性
Order o1 = new Oreder();
// ...添加属性
Order o1 = new Oreder();
// ...添加属性
buyer.getOrders().add(o1);
buyer.getOrders().add(o2);
/**
insert into buyer ...
insert into order ..
insert into order ..
update order set buyer_id ..
update order set buyer_id ..
*/
entityManager.persist(buyer);
entityManager.persist(o1);
entityManager.persist(o2);
/**
insert into buyer ...
insert into order ..
insert into order ..
update order set buyer_id ..
update order set buyer_id ..
*/
entityManager.persist(o1);
entityManager.persist(o2);
entityManager.persist(buyer);
}
- 小结
先保存1端还是N端,都会执行update语句,因为关联关系是1的一端在维护。
查询
@Test
public void find(){
Buyer buyer = entityManager.find(Buyer.class, {主键});
// 默认懒加载策略,两条select语句 System.out.println(buyer.getXx());
System.out.println(buyer.getOrders().size());
// 及时查询策略时,select .. from buyer left outer join oreder on ..
}
- 小结
- 默认对关联的N端使用懒加载策略。
- 如果采用及时查询策略,会执行左外连接查询语句。
删除
@Test
public void remove(){
Buyer buyer = entityManager.find(Buyer.class,{主键});
// 能直接删除1的一端。先执行order表中对应值外键置空,再删除buyer中数据。
entity.Manager.remove(buyer);
}
- 小结
- 默认不执行级联删除,若删除1的一端,会先将关联的外键置空,在删除1的一端。
更新
- 可以修改@OneToMany的cascad属性为{CascadeType.REMOVE},实现级联删除。
@Test
public void update(){
Buyer buyer = entityManager.find(Buyer.class,{主键});
buyer.getOrders().iterator().next().setOrderName("xx");
}
结论
- 单向1对N时,关联关系执行保存时,一定会多执行update语句,因为关联关系是1的一端在维护,N端在插入时不会插入外键列。
- 查询时,默认对关联的N端使用懒加载策略。如果采用及时查询策略,会执行左外连接查询语句。
- 删除时,默认不是级联删除,若删除1的一端,会先将关联的外键置空,在删除1的一端。可以修改@OneToMany的cascad属性为{CascadeType.REMOVE},实现级联删除。
双向一对多
// Order.class
@JoinColumn(name="buyer_id")// 映射外键列名
// 及时查询(默认):fetch=FetchType.EAGER、懒加载:fetch=FetchType.LAZY
@ManyToOne // 映射单向多对一。
publice Buyer getBuyer(){
return buyer;
}
// Buyer.class
Set<Order> orders = new HashSet();
// @JoinColumn(name="buyer_id")// 映射外键列名
/** 及时查询(默认):fetch=FetchType.EAGER、懒加载:fetch=FetchType.LAZY
级联删除属性:级联删除:cascad={CascadeType.REMOVE}
关联关系维护:mappedBy="buyer" 申明,由N端维护关联关系。此时不能定义@JoinColumn属性。
*/
@OneToMany(mappedBy="buyer") // 映射单向一对多。
publice Set<Order> getOrders(){
return orders;
}
测试
保存
@Test
public void persist(){
Buyer buyer = new Buyer();
// ...添加属性
Order o1 = new Oreder();
// ...添加属性
Order o1 = new Oreder();
// ...添加属性
// 建立关联关系
buyer.getOrders().add(o1);
buyer.getOrders().add(o2);
o1.setBuyer(buyer);
o2.setBuyer(buyer);
/**
// 先保存N的一端
insert into order .. # buyer_id为null
insert into order .. # buyer_id为null
insert into buyer ..
// 从order方维护一次关联关系
update order set order ..
update order set order ..
// 从buyer方维护一次关联关系
update order set order ..
update order set order ..
*/
entityManager.persist(o1);
entityManager.persist(o2);
entityManager.persist(buyer);
/**
// 先保存1的一端
*/
entityManager.persist(buyer);
entityManager.persist(o1);
entityManager.persist(o2);
}
- 小结
- 双向1对N时,若先保存N端,默认会多出n条update语句。若先保存1的一端,会多出n条buyer维护的update
- 在进行双向 1对N关联关系时,建议使用N的一端进行关联关系的维护,1的一端不维护,这样会有效减少SQL语句。
结论
- 在进行双向 1对N关联关系时,建议使用N的一端进行关联关系的维护,1的一端不维护,这样会有效减少SQL语句。可通过在1的一端@OneToManay的mappedBy="{N端中,1端的属性名}" 申明,由N端维护关联关系,此时@JoinColumn属性不能再1的一端使用。
双向一对一
一个经理只能管一个部门,一个部门只能被一个经理管。
// Manager.class
// 及时查询(默认):fetch=FetchType.EAGER、懒加载:fetch=FetchType.LAZY
@OneToOne(mappedBy="mgr") // 映射双向一对一。使用对方的哪个属性
publice Department getDept(){
return dept;
}
// Department.class
@JoinColumn(name="mgr_id", unique=true)
@OneToOne
public Manager getMgr(){
return mgr;
}
测试
保存
@Test
public void persist(){
Manager mgr = new Manager();
// 设置属性..
Department dept = new Department();
// 设置属性..
mgr.setDept(dept);
dept.setMgr(mgr);
// 先保存不维护关联关系的一方。 entityManager.persist(mgr);
entityManager.persist(dept);
}
- 小结
建议先保存不维护关联关系的一方,否则会多出update语句。
查询
@Test
public void find(){
// 获取维护关联关系的一方
Department dept = entityManager.find(Department.class,{主键});
System.out.println(dept.getXx());
System.out.println(dept.getMgr().getXx());
// 获取不维护关联关系的一方
Manager mgr = entityManager.find(Manager.class,{主键});
System.out.println(mgr.getXx());
// 此时不清楚当前mgr有关联关系的dept是否存在。所以,无论怎样都会执行sql语句去查询。 System.out.println(mgr.getDept().getXx());
}
- 小结
- 默认(及时查询),获取维护关联关系的一方,会通过左外连接获取关联对象。可通过@OneToOne的fetch属性修改策略。
- 默认(及时查询),获取不维护关联关系的一方,会通过左外连接获取关联对象。且@OneToOne的fetch属性修改为LAZY后,反而还会多发一条SQL。
结论
- 需要确定哪一方维护关联关系。
- 双向1对1,@JoinColumn需要添加unique=true
- 无外键的一方,需要设置mappedBy="{对方维护映射关系的属性}"
- 双向1对1时,建议先保存无外键的一端,否则会多出update语句。
- 双向1对1时,先查询不维护关联关系的一端时,建议不修改不维护关联关系一端的@OneToOne的fetch属性为LAZY。
双向多对多
一个商品属于多个类别,一个类别有多个商品。
// Item.class
public Set<Category> categories = new HashSet();
@JoinTable(name="item_category",joinColumns={@JoinColumn(name="item_id", referencedColunmnName="{当前类的主键名称}")}, inverseJoinColumns={@JoinColumn(name="category_id", referencedColunmnName="{关联类的主键名称}")})// 映射表
// 及时查询(默认):fetch=FetchType.EAGER、懒加载:fetch=FetchType.LAZY
@ManyToMany // 映射双向多对多。
publice Set<Category> getCategories(){
return categories;
}
// Category.class
public Set<Item> items = new HashSet();
// @ManyToMany(mappedBy="{维护关联关系类中的关联属性名}")
@ManyToMany(mappedBy="categories")
public Set<Item> getItems(){
return items;
}
测试
保存
@Test
public void persist(){
Item i1= new Item();
Item i2= new Item();
Category c1 = new Category();
Category c2 = new Category();
i1.getCategories().add(c1);
i1.getCategories().add(c2);
i2.getCategories().add(c1);
i2.getCategories().add(c2);
c1.getItems().add(i1);
c1.getItems().add(i2);
c2.getItems().add(i1);
c2.getItems().add(i2);
entityManager.persist(i1);
entityManager.persist(i2);
entityManager.persist(c1);
entityManager.persist(c2);
}
查询
@Test
public void find(){
// 获取维护关联关系的一方
Item item = entityManager.find(Item.class, {主键});
System.out.println(item.getXx());
System.out.println(item.getCategories().size());
// 获取非维护关联关系的一方
Category c1 = entiryManager.find(Category.class, {主键});
System.out.println(c1.getXx());
System.out.println(c1.getItems().size());
}
结论
获取维护关联关系的一方,还是获取非维护关联关系的一方,都默认使用懒加载策略,且使用左外连接进行查询。