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

一、表之间关系的划分

数据库中多表之间存在着三种关系,如图所示:
在这里插入图片描述
从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。
注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。

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

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

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

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

三、JPA 中的一对多

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

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

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

1. 表关系建立

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

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

一对多数据库关系的建立,如下图所示:
在这里插入图片描述

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>

在这里插入图片描述

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,因为这里需要用到数据了,不能重新创建
在这里插入图片描述

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

在这里插入图片描述

四、JPA 中的多对多操作

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我来为你讲解如何从安装到多账户操作一套搞定。 首先,你需要安装 Git。如果你使用的是 Mac 或 Linux 操作系统,那么 Git 可以直接通过终端命令安装。如果你使用的是 Windows,那么可以从 Git 官网下载安装包进行安装。安装完成后,你可以在终端或命令行窗口输入 `git` 命令来测试是否安装成功。 接下来,我们来学习 Git 的入门使用。首先,你需要在本地创建一个 Git 仓库。在终端或命令行窗口进入你要创建仓库的目录,然后执行以下命令: ``` git init ``` 这会在当前目录下创建一个名为 `.git` 的隐藏文件夹,这就是 Git 仓库。接下来,你可以使用以下命令来将文件添加到仓库: ``` git add <file> ``` 其 `<file>` 是你要添加的文件名。你可以使用 `git add .` 命令将所有文件添加到仓库。添加完成后,你需要使用以下命令来提交更改: ``` git commit -m "提交信息" ``` 其 `"提交信息"` 是你的提交信息,这个信息应该简明扼要地描述你此次提交的内容。 现在,你已经学会了 Git 的基本操作。接下来,我们来学习如何在 Git 管理多个账户。 在使用 Git 的过程,如果你需要在同一台电脑上使用多个 Git 账户,那么你需要进行如下配置: 1. 生成 SSH Key 在终端或命令行窗口执行以下命令: ``` ssh-keygen -t rsa -C "[email protected]" ``` 其 `"[email protected]"` 是你的邮箱地址。这个命令会在默认路径下生成一个 SSH Key,你可以使用默认路径,也可以指定其他路径。 2. 配置 SSH Key 将 SSH Key 添加到你的 Git 账户。在 Git 账户的设置页面找到 SSH Keys 选项,将生成的 SSH Key 复制到其。 3. 配置多个账户 在终端或命令行窗口执行以下命令: ``` git config --global user.name "Your Name" git config --global user.email "[email protected]" ``` 其 `"Your Name"` 和 `"[email protected]"` 分别是你的用户名和邮箱地址。这个命令会将这些信息保存到 Git 的配置文件。 接下来,你可以在你的电脑上创建多个 SSH Key,然后将这些 Key 分别添加到不同的 Git 账户。每次使用 Git 时,你只需要通过 `git config` 命令来配置当前使用的账户即可。 以上就是从安装到多账户操作一套搞定的入门使用方法。希望能帮助到你!如果你还有其他问题,可以继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南淮北安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值