【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); }