Spring Boot学习笔记(五)Spring Data Jpa 快速上手(四)多对一@ManyToOne、一对多@OneToMany,以及级联操作
阅读此文章需要有基础的jpa知识,此处不讲述entity、repository。
可以查看我之前的文章Spring Data Jpa 快速上手(一)使用spring data jpa 实现增删改查
1.建立表、实体类
讲述一下两个实体类的关系。Customer
为客户公司,linkMan
为客户公司的联系人,一个公司有多个联系人(员工),所以Customer
和linkMan
是一对多的关系。用到了@ManyToOne、@OneToMany、JoinColumn注解
@ManyToOne
和@OneToMany
顾名思义,是一对多和多对一,@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。有以下属性:
- name:指定外键字段的名称
- referencedColumnName:指定引用主表的主键字段名称
- unique:是否唯一。默认值不唯一
- nullable:是否允许为空。默认值允许
- insertable:是否允许插入。默认值允许
- updatable:是否允许更新。默认值允许
- columnDefinition:列的定义信息
下面是Customer
的表与实体类
@Entity
@Table(name = "cst_customer")
@Data
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id")
private Integer custId;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_phone")
private String custPhone;
@Column(name = "cust_source")
private String custSource;
//// 声明关系
//// 配置外键(中间表)
// @OneToMany(targetEntity = LinkMan.class)
//// 从表的外键名称、以及其所对应的主表的名称,若没有放弃外键维护权,则在新增用户的时候,会在lkm_cust_id修改对应的联系人信息(为联系人添加外键)
// @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
// private Set<LinkMan> linkMans = new HashSet<>();
// 声明关系,mappedBy 放弃外键维护权,mappedBy是对方配置关系的属性名称
// cascade 级联操作,允许增删改查或全部允许(ALL),加了此代码后,才可被维护
// fetch = FetchType.LAZY,设置关联对象为立即加载,默认为懒加载(要用的时候再查,推荐懒加载)
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<LinkMan> linkMans = new HashSet<>();
}
可发现有一部分代码被注释掉了,被注释的代码,有维护外键的权利,这导致每次对customer
进行操作的时候,都会对linkMan
的外键进行操作。
由于是cusotmer
与linkMan
是一对多的关系,对customer
进行操作时,会影响多个linkMan
,所以推荐使用未被注释的代码,即customer
放弃外键的维护权利。
但是cascade
操作又允许操作从表linkMan
,是否和放弃维护冲突呢?实际上不是的,虽然允许级联操作,可以在保存customer
的时候保存linkMan
数据,但是linkMan
内的外键为空,因为custmer放弃了维护外键的权利
仍混乱的朋友推荐看这篇文章@OneToMany 及 @Cascade级联操作
下面是linkMan
的表和实体类,其中lim_cust_id
为外键
@Entity
@Table(name = "cst_linkman")
@Getter
@Setter
public class LinkMan {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "lkm_id")
private Integer lkmId; //联系人编号(主键)
@Column(name = "lkm_name")
private String lkmName;//联系人姓名
@Column(name = "lkm_gender")
private String lkmGender;//联系人性别
@Column(name = "lkm_phone")
private String lkmPhone;//联系人办公电话
@Column(name = "lkm_mobile")
private String lkmMobile;//联系人手机
@Column(name = "lkm_email")
private String lkmEmail;//联系人邮箱
@Column(name = "lkm_position")
private String lkmPosition;//联系人职位
@Column(name = "lkm_memo")
private String lkmMemo;//联系人备注
/**
* 配置联系人到客户的多对一关系
* 使用注解的形式配置多对一关系
* 1.配置表关系
* @ManyToOne : 配置多对一关系
* targetEntity:对方的实体类字节码
* 2.配置外键(中间表)
*
* * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
*
*/
// ManyToOne,默认fetch = FetchType.EAGER,立即加载,建议使用FetchType.EAGER
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.EAGER)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
}
2.建立repository
Customer的repository
public interface LinkManRepository extends JpaRepository<LinkMan, Integer> {
}
linkMan的repository
public interface LinkManRepository extends JpaRepository<LinkMan, Integer>{
}
[email protected],@OneToMany的调用方法
下面的方法可以实现保存customer
、linkMan
,并且在linkMan中会有customer
的id
作为外键。先插入customer
后,再插入linkMan
才可以,因为linkMan
需要customer
的id作为外键。
@Test
@Transactional
@Rollback(false)
public void testAdd() {
Customer customer = new Customer();
customer.setCustName("百度2");
LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小白2");
//若customer没有放弃维护权利,插入两条数据后,再执行update,更新linkman的外键状态
linkMan.setCustomer(customer);
customerRepository.save(customer);
linkManRepository.save(linkMan);
}
若customer
没有放弃外键的维护权,可以使用下面的代码,但这样会多执行一条update语句,因为会先插入两条数据,然后再更新linkMan
的外键。
@Test
@Transactional
@Rollback(false)
public void testAdd() {
Customer customer = new Customer();
customer.setCustName("百度2");
LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小白2");
//若没有放弃维护权利,插入两条数据后,再执行update,更新linkman的外键状态
customer.getLinkMans().add(linkMan);
customerRepository.save(customer);
linkManRepository.save(linkMan);
}
上面两个代码的执行结果相同,推荐使用第一个方式,这样可以少一个update语句。
3.1 级联删除
customer
与linkMan
是一对多的关系,若删除customer
时,必然想要将其所对应的的linkMan
删除,此时我们可以通过配置级联,即本文章最上方中的cascade = CascadeType.ALL
。配置之后使用以下代码,即可删除id为117的customer
,以及与它相关的linkMan
@Test
@Transactional
@Rollback(false)
public void testCascadeDel() {
// 因为entity中有级联关系,所以删除customer的时候,会将linkMan一同删除
customerRepository.deleteById(117);
}
3.2 级联新增、更新
可发现,我们只调用了customerRepository进行了保存操作
@Test
@Transactional
@Rollback(false)
public void testCascadeUpdate() {
Customer customer = customerRepository.findById(119).get();
LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小白5");
linkMan.setCustomer(customer);
customer.getLinkMans().add(linkMan);
customerRepository.save(customer);
}
但是我们可以在数据库中查看到id为119的customer,在linkMan表中,有两个数据数据的外键是119,一个是原本就有的小白4,一个是新增的小白5
如果对所执行的具体SQL语句感兴趣的话,可以查看@OneToMany 及 @Cascade级联操作
3.3 对象导航查询
对象导航查询指的是,通过customer
查对应的linkMan
,通过linkMan
查对应的customer
@Test
@Transactional
public void testGetOneToMany() {
Customer customer = customerRepository.getOne(116);
// 对象导航查询
Set<LinkMan> linkManSet = customer.getLinkMans();
for (LinkMan linkMan: linkManSet) {
System.out.println(linkMan);
}
}
@Test
@Transactional
public void testGetManyToOne() {
LinkMan linkMan = linkManRepository.findById(14).get();
Customer customer = linkMan.getCustomer();
System.out.println(customer);
}