一篇文章带你搞定 SpringDataJpa 中的一对多的多表设计

文章目录

    • 一、表之间关系的划分
    • 二、在JPA框架中表关系的分析步骤
    • 三、JPA 中的一对多
      • 1. 表关系建立
      • 2. 实体类关系建立以及映射配置
      • 3. 测试一对多的关系
      • 4. 一的一方放弃维护权
      • 5. 删除操作
      • 6. 级联操作
    • 四、JPA 中的多对多操作

一、表之间关系的划分

数据库中多表之间存在着三种关系,如图所示:
一篇文章带你搞定 SpringDataJpa 中的一对多的多表设计_第1张图片
从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。
注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。

明确: 我们今天只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用。

二、在JPA框架中表关系的分析步骤

在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。
而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数据库表的操作。所以今天我们的学习重点是:掌握配置实体之间的关联关系

(1)首先确定两张表之间的关系:如果关系确定错了,后面做的所有操作就都不可能正确。
(2)在数据库中实现两张表的关系
(3)在实体类中描述出两个实体的关系
(4)配置出实体类和数据库表的关系映射(重点)

三、JPA 中的一对多

这里以客户和联系人的关系为例来分析 JPA 中的一对多关系

客户:指的是一家公司,我们记为A
联系人:指的是A公司中的员工

公司和员工的关系即为一对多。

1. 表关系建立

在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。

外键:指的是从表中有一列,取值参照主表的主键,这一列就是外键。

一对多数据库关系的建立,如下图所示:
一篇文章带你搞定 SpringDataJpa 中的一对多的多表设计_第2张图片

2. 实体类关系建立以及映射配置

在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息,代码如下:
(1)配置实体类 Customer

/**
 * 客户的实体类
 * 配置映射关系
 * 1.实体类和表的映射关系
 * 2.实体类中属性和表中字段的映射关系
 */
@Entity
@Table(name = "cst_customer")
public class Customer {
     
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId; //客户的主键

    @Column(name = "cust_name")
    private String custName;//客户名称

    @Column(name = "cust_source")
    private String custSource;//客户来源

    @Column(name = "cust_level")
    private String custLevel;//客户级别

    @Column(name = "cust_industry")
    private String custIndustry;//客户所属行业

    @Column(name = "cust_phone")
    private String custPhone;//客户的联系方式

    @Column(name = "cust_address")
    private String custAddress;//客户地址


    //配置客户和联系人之间的关系(一对多关系)
    /**
     * 使用注解的形式配置多表关系
     *      1.声明关系
     *          @OneToMany : 配置一对多关系
     *              targetEntity :对方对象的字节码对象
     *      2.配置外键(中间表)
     *              @JoinColumn : 配置外键
     *                  name:外键字段名称
     *                  referencedColumnName:参照的主表的主键字段名称
     *
     *  * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用
     */

    @OneToMany(targetEntity = LinkMan.class)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
 
    // 省略 getter 和 setter 方法
}

(2)配置实体类 LinkMan

@Entity
@Table(name = "cst_linkman")
public class LinkMan {
     

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "lkm_id")
    private Long 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(targetEntity = Customer.class,fetch = FetchType.LAZY)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Customer customer;
    //省略 getter 和 setter 方法
}

(3)为了精确的控制数据库表的创建,可以添加额外的配置信息:
原先的 application.xml 配置:一篇文章带你快速入门 Spring Data JPA

	<!--注入jpa的配置信息
            加载jpa的基本配置信息和jpa实现方式(hibernate)的配置信息
            hibernate.hbm2ddl.auto : 自动创建数据库表
                create : 每次都会重新创建数据库表
                update:有表不会重新创建,没有表会重新创建表
        -->
        <property name="jpaProperties" >
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>

一篇文章带你搞定 SpringDataJpa 中的一对多的多表设计_第3张图片

3. 测试一对多的关系

(1)测试客户到联系人的关系

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {
     

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    /**
     * 保存一个客户,保存一个联系人
     * 效果:客户和联系人作为独立的数据保存到数据库中
     * 联系人的外键为空
     * 原因?
     * 实体类中没有配置关系
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd() {
     
        //创建一个客户,创建一个联系人
        Customer customer = new Customer();
        customer.setCustName("CSDN");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("yolo");

        /**
         * 配置了客户到联系人的关系
         *      从客户的角度上:发送两条insert语句,发送一条更新语句更新数据库(更新外键)
         * 由于我们配置了客户到联系人的关系:客户可以对外键进行维护
         */
        customer.getLinkMans().add(linkMan);
        customerDao.save(customer);
        linkManDao.save(linkMan);
    }
}

在这里插入图片描述
(2)测试联系人到客户的关系(多对一)

@Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd() {
     
        //创建一个客户,创建一个联系人
        Customer customer = new Customer();
        customer.setCustName("百度");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小张");

        /**
         * 配置联系人到客户的关系(多对一)
         *    只发送了两条insert语句
         * 由于配置了联系人到客户的映射关系(多对一)
         */
        linkMan.setCustomer(customer);

        customerDao.save(customer);
        linkManDao.save(linkMan);
    }

4. 一的一方放弃维护权

这里我们设置客户和联系人的双向关系

 /**
     * 会有一条多余的update语句
     * * 由于一的一方可以维护外键:会发送update语句
     * * 解决此问题:只需要在一的一方放弃维护权即可
     */
	@Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd2() {
     
        //创建一个客户,创建一个联系人
        Customer customer = new Customer();
        customer.setCustName("百度");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李");


        linkMan.setCustomer(customer);//由于配置了多的一方到一的一方的关联关系(当保存的时候,就已经对外键赋值)
        customer.getLinkMans().add(linkMan);//由于配置了一的一方到多的一方的关联关系(发送一条update语句)

        customerDao.save(customer);
        linkManDao.save(linkMan);
    }

通过保存的案例,我们可以发现在设置了双向关系之后,会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权
在这里插入图片描述
设置实体类 Customer 放弃维护权

 /**
     * 放弃外键维护权
     *      mappedBy:对方配置关系的属性名称\
     * cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
     *      CascadeType.all         : 所有
     *                  MERGE       :更新
     *                  PERSIST     :保存
     *                  REMOVE      :删除
     *
     * fetch : 配置关联对象的加载方式
     *          EAGER   :立即加载
     *          LAZY    :延迟加载

     */
    @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
    private Set<LinkMan> linkMans = new HashSet<>();

问题解决:
在这里插入图片描述

5. 删除操作

删除从表数据:可以随时任意删除。

删除主表数据:

  • 有从表数据:
    (1)在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。
    (2)如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
    (3)如果还想删除,使用级联删除引用

  • 没有从表数据引用:随便删

在实际开发中,级联删除请慎用!(在一对多的情况下)

6. 级联操作

级联操作:指操作一个对象同时操作它的关联对象

  • 需要区分操作主体
  • 需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
  • cascade(配置级联)
 /**
     * 放弃外键维护权
     *      mappedBy:对方配置关系的属性名称\
     * cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
     *      CascadeType.all         : 所有
     *                  MERGE       :更新
     *                  PERSIST     :保存
     *                  REMOVE      :删除
     *
     * fetch : 配置关联对象的加载方式
     *          EAGER   :立即加载
     *          LAZY    :延迟加载
     */
    @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
    private Set<LinkMan> linkMans = new HashSet<>();

测试级联添加操作:

   /**
     * 级联添加:保存一个客户的同时,保存客户的所有联系人
     * 需要在操作主体的实体类上,配置casacde属性
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testCascadeAdd() {
     
        Customer customer = new Customer();
        customer.setCustName("yolo_cascade");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("cascade");

        linkMan.setCustomer(customer);
        customer.getLinkMans().add(linkMan);
        customerDao.save(customer);
    }

在这里插入图片描述
测试级联删除:
注意 applicationContext 配置文件里,这里需要设置为 update,因为这里需要用到数据了,不能重新创建
一篇文章带你搞定 SpringDataJpa 中的一对多的多表设计_第4张图片

    /**
     * 级联删除:
     * 删除1号客户的同时,删除1号客户的所有联系人
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testCascadeRemove() {
     
        //1.查询1号客户
        Customer customer = customerDao.findOne(1l);
        //2.删除1号客户
        customerDao.delete(customer);
    }

在这里插入图片描述

四、JPA 中的多对多操作

你可能感兴趣的:(Spring,Data,JPA,学习笔记,java,spring,jpa)