【Spring Data JPA】08 多表关系 Part1 一对多关系操作

表关系概述:

1、一 对应 一

一对夫妻,一个男人只能有一个老婆,一个女人只能有一个老公,即这种对应关系

2、一 对应 多 【多对一】

一个年级具有多个班级,一个班级具有对应的所属年级,即这种上下层级关系或者其他类似的

3、多 对应 多

授课老师和所授课程 一个授课老师可以教授多门课程,一个课程也可以被多个老师教授。即多重关系

数据库表设计的实现:

一对一只需要一个关联键即可

一对多,需要建立主表(一)从表(多)关系,从表设置外键来检索主表信息

多对多,对应的关系无法用外键实现,所以需要建立关联表,用来存储关联关系映射

实体类设计的实现:

一对一,对象附属,作为属性存在【包含】

一对多,对象仅表示一个记录,所以更改为一个对象的集合容器作为属性存在,或者使用继承实现

多对多

测试环境搭建:

customer.sql

/*创建客户表*/
CREATE TABLE cst_customer (
  cust_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
  cust_name varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
  cust_source varchar(32) DEFAULT NULL COMMENT '客户信息来源',
  cust_industry varchar(32) DEFAULT NULL COMMENT '客户所属行业',
  cust_level varchar(32) DEFAULT NULL COMMENT '客户级别',
  cust_address varchar(128) DEFAULT NULL COMMENT '客户联系地址',
  cust_phone varchar(64) DEFAULT NULL COMMENT '客户联系电话',
  PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;

/*创建联系人表*/
CREATE TABLE cst_linkman (
  lkm_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
  lkm_name varchar(16) DEFAULT NULL COMMENT '联系人姓名',
  lkm_gender char(1) DEFAULT NULL COMMENT '联系人性别',
  lkm_phone varchar(16) DEFAULT NULL COMMENT '联系人办公电话',
  lkm_mobile varchar(16) DEFAULT NULL COMMENT '联系人手机',
  lkm_email varchar(64) DEFAULT NULL COMMENT '联系人邮箱',
  lkm_position varchar(16) DEFAULT NULL COMMENT '联系人职位',
  lkm_memo varchar(512) DEFAULT NULL COMMENT '联系人备注',
  lkm_cust_id bigint(32) NOT NULL COMMENT '客户id(外键)',
  PRIMARY KEY (`lkm_id`),
  KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
  CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

客户:

package cn.echo42.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * @author DaiZhiZhou
 * @file Spring-Data-JPA
 * @create 2020-08-01 9:48
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity//表示当前类是一个实体类
@Table(name="cst_customer")//建立当前实体类和表之间的对应关系
public class Customer implements Serializable {

    @Id//表明当前私有属性是主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//指定主键的生成策略
    @Column(name = "cust_id")//指定和数据库表中的cust_id列对应
    private Long custId;
    @Column(name = "cust_name")//指定和数据库表中的cust_name列对应
    private String custName;
    @Column(name = "cust_source")//指定和数据库表中的cust_source列对应
    private String custSource;
    @Column(name = "cust_industry")//指定和数据库表中的cust_industry列对应
    private String custIndustry;
    @Column(name = "cust_level")//指定和数据库表中的cust_level列对应
    private String custLevel;
    @Column(name = "cust_address")//指定和数据库表中的cust_address列对应
    private String custAddress;
    @Column(name = "cust_phone")//指定和数据库表中的cust_phone列对应
    private String custPhone;

    //配置客户和联系人的一对多关系
    @OneToMany(targetEntity = LinkMan.class)
    @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
    private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
}

联系人:

package cn.echo42.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.io.Serializable;

/**
 * @author DaiZhiZhou
 * @file Spring-Data-JPA
 * @create 2020-08-01 9:49
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name="cst_linkman")
public class LinkMan implements Serializable {
    @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;

    //多对一关系映射:多个联系人对应客户
    @ManyToOne(targetEntity=Customer.class)
    @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
    private Customer customer;//用它的主键,对应联系人表中的外键
}

编写对应的Dao接口:

package cn.echo42.repository;

import cn.echo42.domain.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * @author DaiZhiZhou
 * @file Spring-Data-JPA
 * @create 2020-08-01 9:54
 */
public interface CustomerRepository extends JpaRepository<Customer, Integer> , JpaSpecificationExecutor<Customer> {
}
---------------------------------------------------------------------------------------------------------------------------
package cn.echo42.repository;

import cn.echo42.domain.LinkMan;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * @author DaiZhiZhou
 * @file Spring-Data-JPA
 * @create 2020-08-01 9:54
 */
public interface LinkManRepository extends JpaRepository<LinkMan, Integer>, JpaSpecificationExecutor<LinkMan> {
}

注解配置Jpa的配置信息

自动创建数据库表设置:create每次执行创建数据库表,update没有表才会创建

多表关系插入:

创建测试类:

import cn.echo42.config.ApplicationConfiguration;
import cn.echo42.domain.Customer;
import cn.echo42.domain.LinkMan;
import cn.echo42.repository.CustomerRepository;
import cn.echo42.repository.LinkManRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author DaiZhiZhou
 * @file Spring-Data-JPA
 * @create 2020-08-01 10:06
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfiguration.class)
public class MTRQ_Test {

    @Autowired
    CustomerRepository customerRepository;
    @Autowired
    LinkManRepository linkManRepository;

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

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

        customerRepository.save(customer);
        linkManRepository.save(linkMan);
    }

}

显示结果:

[org.hibernate.SQL]-alter table cst_linkman drop foreign key FKh9yp1nql5227xxcopuxqx2e7q
[org.hibernate.SQL]-drop table if exists cst_customer
[org.hibernate.SQL]-drop table if exists cst_linkman
[org.hibernate.SQL]-drop table if exists sys_user
[org.hibernate.SQL]-create table cst_customer (cust_id bigint not null auto_increment, cust_address varchar(255), cust_industry varchar(255), cust_level varchar(255), cust_name varchar(255), cust_phone varchar(255), cust_source varchar(255), primary key (cust_id))
[org.hibernate.SQL]-create table cst_linkman (lkm_id bigint not null auto_increment, lkm_email varchar(255), lkm_gender varchar(255), lkm_memo varchar(255), lkm_mobile varchar(255), lkm_name varchar(255), lkm_phone varchar(255), lkm_position varchar(255), lkm_cust_id bigint, primary key (lkm_id))
[org.hibernate.SQL]-create table sys_user (user_id integer not null auto_increment, user_is_del integer, user_name varchar(255), user_password varchar(255), user_status integer, primary key (user_id))
[org.hibernate.SQL]-alter table cst_linkman add constraint FKh9yp1nql5227xxcopuxqx2e7q foreign key (lkm_cust_id) references cst_customer (cust_id)

数据库查看:

可以看到联系人的外键这里没有百度,两个记录不具备任何联系,是相互独立的存在

要建立关系,我们则需要在联系人的集合中增加对象,表现出我们的关联关系:

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

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李");
        
        // 配置多表关系
        customer.getLinkmans().add(linkMan);

        customerRepository.save(customer);
        linkManRepository.save(linkMan);
    }

再次执行的结果:

可以看到已经成功绑定到了

从客户表的角度上观察:

发送了两条插入语句,

一条插入记录

一条更新外键

我们配置了客户到联系人的关系,客户可以对外键进行维护

我们也可以换过来,从联系人的角度上关联客户:

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

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

        // 在联系人的属性中设置客户,以关联
        linkMan.setCustomer(customer);

        customerRepository.save(customer);
        linkManRepository.save(linkMan);
    }

结果一样

但是如果同时绑定设置就会出错了:

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

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

        linkMan.setCustomer(customer);
        customer.getLinkmans().add(linkMan);

        customerRepository.save(customer);
        linkManRepository.save(linkMan);
    }

双方彼此的对象在不断相互引用:

解决方案是任意的一方放弃主键的维护即可:

原先联系人类的维护关系:

如果放弃,就需要这样设置:

But it not work .... 不晓得为什么,难道又有新的策略了?

【发现是使用了Lombok原因,这里也只要记住一点,放弃主键维护注意下Lombok辅助的问题】

级联操作:

ALL 所有操作

MERGE 更新
PERSIST 持久化 -> 保存
REMOVE  移除

取消之前设置的创建数据库表:

测试级联删除:

    @Test
    @Transactional
    @Rollback(false) // 设置不自动回滚
    public void addExecute4(){
        Specification<Customer> customerSpecification = (Specification<Customer>) (root, criteriaQuery, criteriaBuilder) -> {
            // 获取比较的属性
            Path<Object> cust_id = root.get("custId");
            // 模糊要求指定参数类型
            return criteriaBuilder.equal(cust_id, 1);
        };
        Optional<Customer> customerOptional = customerRepository.findOne(customerSpecification);

        Customer customer = customerOptional.get();

        customerRepository.delete(customer);
    }
原文地址:https://www.cnblogs.com/mindzone/p/13413451.html