桥山之巅,姬水之畔

JPA映射关系

2020.07.20

场景

一个顾客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. 默认使用及时查询默认会使用左外连接查询出1的一端。
  2. 采用懒加载,会先查询出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);
}
  • 小结
  1. 删除N端时,能正常删除。
  2. 删除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 ..
}
  • 小结
  1. 默认对关联的N端使用懒加载策略。
  2. 如果采用及时查询策略,会执行左外连接查询语句。

删除

@Test
public void remove(){
	Buyer buyer = entityManager.find(Buyer.class,{主键});
	
	// 能直接删除1的一端。先执行order表中对应值外键置空,再删除buyer中数据。
	entity.Manager.remove(buyer);
}
  • 小结
  1. 默认不执行级联删除,若删除1的一端,会先将关联的外键置空,在删除1的一端。

更新

  1. 可以修改@OneToMany的cascad属性为,实现级联删除。
@Test
public void update(){
	Buyer buyer = entityManager.find(Buyer.class,{主键});

	buyer.getOrders().iterator().next().setOrderName("xx");
}

结论

  1. 单向1对N时,关联关系执行保存时,一定会多执行update语句,因为关联关系是1的一端在维护,N端在插入时不会插入外键列。
  2. 查询时,默认对关联的N端使用懒加载策略。如果采用及时查询策略,会执行左外连接查询语句。
  3. 删除时,默认不是级联删除,若删除1的一端,会先将关联的外键置空,在删除1的一端。可以修改@OneToMany的cascad属性为,实现级联删除。

双向一对多

// 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. 双向1对N时,若先保存N端,默认会多出n条update语句。若先保存1的一端,会多出n条buyer维护的update
  2. 在进行双向 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());
}
  • 小结
  1. 默认(及时查询),获取维护关联关系的一方,会通过左外连接获取关联对象。可通过@OneToOne的fetch属性修改策略。
  2. 默认(及时查询),获取不维护关联关系的一方,会通过左外连接获取关联对象。且@OneToOne的fetch属性修改为LAZY后,反而还会多发一条SQL。

结论

  1. 需要确定哪一方维护关联关系。
  2. 双向1对1,@JoinColumn需要添加unique=true
  3. 无外键的一方,需要设置mappedBy="{对方维护映射关系的属性}"
  4. 双向1对1时,建议先保存无外键的一端,否则会多出update语句。
  5. 双向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());
}

结论

获取维护关联关系的一方,还是获取非维护关联关系的一方,都默认使用懒加载策略,且使用左外连接进行查询。