欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Boot学习笔记(五)Spring Data Jpa 快速上手(四)多对一@ManyToOne、一对多@OneToMany,以及级联操作

程序员文章站 2022-04-12 20:29:01
...

阅读此文章需要有基础的jpa知识,此处不讲述entity、repository。
可以查看我之前的文章Spring Data Jpa 快速上手(一)使用spring data jpa 实现增删改查

1.建立表、实体类

讲述一下两个实体类的关系。
Customer为客户公司,linkMan为客户公司的联系人,一个公司有多个联系人(员工),所以CustomerlinkMan是一对多的关系。用到了@ManyToOne、@OneToMany、JoinColumn注解

@ManyToOne@OneToMany顾名思义,是一对多和多对一,
@JoinColumn作用:用于定义主键字段和外键字段的对应关系。有以下属性:

  • name:指定外键字段的名称
  • referencedColumnName:指定引用主表的主键字段名称
  • unique:是否唯一。默认值不唯一
  • nullable:是否允许为空。默认值允许
  • insertable:是否允许插入。默认值允许
  • updatable:是否允许更新。默认值允许
  • columnDefinition:列的定义信息

下面是Customer的表与实体类
Spring Boot学习笔记(五)Spring Data Jpa 快速上手(四)多对一@ManyToOne、一对多@OneToMany,以及级联操作

@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的外键进行操作。
由于是cusotmerlinkMan是一对多的关系,对customer进行操作时,会影响多个linkMan,所以推荐使用未被注释的代码,即customer放弃外键的维护权利

但是cascade操作又允许操作从表linkMan,是否和放弃维护冲突呢?实际上不是的,虽然允许级联操作,可以在保存customer的时候保存linkMan数据,但是linkMan内的外键为空,因为custmer放弃了维护外键的权利

仍混乱的朋友推荐看这篇文章@OneToMany 及 @Cascade级联操作


下面是linkMan的表和实体类,其中lim_cust_id为外键
Spring Boot学习笔记(五)Spring Data Jpa 快速上手(四)多对一@ManyToOne、一对多@OneToMany,以及级联操作

@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的调用方法

下面的方法可以实现保存customerlinkMan,并且在linkMan中会有customerid作为外键。先插入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语句。
Spring Boot学习笔记(五)Spring Data Jpa 快速上手(四)多对一@ManyToOne、一对多@OneToMany,以及级联操作
Spring Boot学习笔记(五)Spring Data Jpa 快速上手(四)多对一@ManyToOne、一对多@OneToMany,以及级联操作

3.1 级联删除

customerlinkMan是一对多的关系,若删除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
Spring Boot学习笔记(五)Spring Data Jpa 快速上手(四)多对一@ManyToOne、一对多@OneToMany,以及级联操作
Spring Boot学习笔记(五)Spring Data Jpa 快速上手(四)多对一@ManyToOne、一对多@OneToMany,以及级联操作
如果对所执行的具体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);
    }