1 环境搭建
- 参考链接:SpringBoot二:整合其他框架
-
SQL脚本【可以不执行下面SQL、仅备用而已,因为JPA可以根据配置的实体类自动创建表】
-- 创建客户表 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;
-
创建普通maven工程,添加依赖
<properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <!-- spring-boot 测试类依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>2.5.6</version> </dependency> <!-- jpa 依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.6.0</version> </dependency> <!-- mysql 依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> <!-- lombok 依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency> </dependencies>
-
配置文件
application.yml
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://192.168.192.134:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false jpa: show-sql: true hibernate: # ddl-auto: update ddl-auto: create # jpa.hibernate.ddl-auto 说明 # create:每次都会重新创建数据库表 # update:有表不会重新创建,没有表会重新创建表 # 注 # 1. 测试一对多时,为了防止以前数据造成影响,设置为create,即每次测试时重新建表 # 2. 测试一对多级联删除时,改成update,否则若是重新建表数据库中没有数据!(注:测试级联删除之前运行级联添加方法造一些数据) # 3. 测试多对多,没写sql语句需要jpa自己创建,为了防止上次结果造成影响,因此改为create # 4. 测试多对多级联删除时,改成update,否则若是重新建表数据库中没有数据!(注:测试多对多级联删除之前运行级联添加方法造一些数据) # 5. 测试对象导航查询,改为update(自己去数据库中造一些数据)
-
实体类
- JPA 中使用 lombok 的 “@Data” 注解潜在一个巨坑!!!暂时先不解决,见下文 “2.1 编码” 中的测试类 testSave3()
package cn.entity; import lombok.Data; import javax.persistence.*; @Entity @Table(name = "cst_customer") @Data public class CustomerEntity { @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_industry") private String custIndustry; // 客户所属行业 @Column(name = "cust_level") private String custLevel; // 客户级别 @Column(name = "cust_address") private String custAddress; @Column(name = "cust_phone") private String custPhone; }
package cn.entity; import lombok.Data; import javax.persistence.*; @Entity @Table(name = "cst_linkman") @Data public class LinkManEntity { @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; // 联系人备注 }
-
持久层
package cn.repository; import cn.entity.CustomerEntity; import org.springframework.data.jpa.repository.JpaRepository; public interface CustomerRepository extends JpaRepository<CustomerEntity,Long> { }
package cn.repository; import cn.entity.LinkManEntity; import org.springframework.data.jpa.repository.JpaRepository; public interface LinkManRepository extends JpaRepository<LinkManEntity, Long> { }
-
启动类
package cn; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
-
环境测试
package test; import cn.App; import cn.entity.CustomerEntity; import cn.repository.CustomerRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest(classes = App.class) public class TestEnv { @Autowired private CustomerRepository customerRepository; /** * 环境测试 */ @Test public void testEnv() { List<CustomerEntity> list = customerRepository.findAll(); list.forEach(customer -> System.out.println(customer)); // 此时结果为空,因为数据库中根本就没有插入数据 /** * 注:若 application.yml 中 jpa.hibernate.ddl-auto=create,则会看到JPA框架会根据实体类配置自动创建数据库中的表 * * DDL SQL * Hibernate: drop table if exists cst_customer * Hibernate: drop table if exists cst_linkman * Hibernate: 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)) engine=InnoDB * Hibernate: 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)) engine=InnoDB * Hibernate: alter table cst_linkman add constraint FKh9yp1nql5227xxcopuxqx2e7q foreign key (lkm_cust_id) references cst_customer (cust_id) * * 查询SQL * Hibernate: select customeren0_.cust_id as cust_id1_0_, customeren0_.cust_address as cust_add2_0_, customeren0_.cust_industry as cust_ind3_0_, customeren0_.cust_level as cust_lev4_0_, customeren0_.cust_name as cust_nam5_0_, customeren0_.cust_phone as cust_pho6_0_, customeren0_.cust_source as cust_sou7_0_ from cst_customer customeren0_ */ } }
-
项目结构如下
-
特别说明:关联映射包含三种关系,即一对一、一对多、多对多。但在实际开发中,一对一比较简单且基本上很少用到,因此本文主要讲一对多和多对多
2 一对多
概述
-
一对多关系分为两种,即一对多、多对一。
-
概念
- 主表:"一"的一方
- 从表:"多"的一方
- 外键:需要在从表上新建一列作为外键,他的取值来源于主表的主键
- 总结:即在“多”的一方引入“一”的一方的主键作为外键
分析步骤
- 案例:客户和联系人
- 明确表关系
- 客户可以理解为"一家公司",联系人可以理解为"一家公司的员工",因此客户和联系人为一对多的关系
- 确定表关系(描述 外键或中间表,一对多用外键、多对多用中间表)
- 主表:客户表("一"的一方)
- 从表:联系人表("多"的一方)
- 因此需要在从表上加外键
- 编写实体类,在实体类中描述表关系(即包含关系)
- 客户:在客户的实体类中包含一个联系人的集合;
- 联系人:在联系人的实体类中包含一个客户对象.
- 配置映射关系
- 使用JPA注解配置一对多映射关系
2.1 添加
- 在上面环境的基础上进行修改
-
修改实体类
package cn.entity; import lombok.Getter; import lombok.Setter; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "cst_customer") //@Data @Getter @Setter public class CustomerEntity { @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_industry") private String custIndustry; // 客户所属行业 @Column(name = "cust_level") private String custLevel; // 客户级别 @Column(name = "cust_address") private String custAddress; @Column(name = "cust_phone") private String custPhone; /*-------------------------------------- 一对多 --------------------------------------*/ /** * 注: 此时在客户实体类上(即"一"的一方)添加了外键了配置, * 所以对于客户而言,也具备了维护外键的作用 * 此时是双向关系,即客户能找联系人,联系人也能找客户 * 因为双方分别使用了@OneToMany和@ManyToOne注解 */ // @OneToMany(targetEntity = LinkManEntity.class) // @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id") // private Set<LinkManEntity> linkMans = new HashSet<LinkManEntity>(); /** * "一"的一方放弃外键维护权(此时就不会出现多余的更新外键update语句) * mappedBy:对方配置关系的属性名称(即参照对方 多对一@ManyToOne的配置) */ @OneToMany(mappedBy = "customer") private Set<LinkManEntity> linkMans = new HashSet<LinkManEntity>(); }
package cn.entity; import lombok.Getter; import lombok.Setter; import javax.persistence.*; @Entity @Table(name = "cst_linkman") //@Data @Getter @Setter public class LinkManEntity { @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; // 联系人备注 /*-------------------------------------- 一对多 --------------------------------------*/ /** * @JoinColumn 注解用于配置 外键(一对多、多对一)或中间表(多对多) * name:外键字段名称 * referencedColumnName:主表的主键名称 * <p> * 这些配置看创建数据库的SQL语句(若是没SQL则会根据配置自动建表) * 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 * 因为name配置外键,故name="lkm_cust_id" * 主表主键:PRIMARY KEY (`cust_id`), 因此referencedColumnName = "cust_id" */ @ManyToOne(targetEntity = CustomerEntity.class) @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id") private CustomerEntity customer; }
-
测试
package test; import cn.App; import cn.entity.CustomerEntity; import cn.entity.LinkManEntity; import cn.repository.CustomerRepository; import cn.repository.LinkManRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; import javax.transaction.Transactional; @SpringBootTest(classes = App.class) public class TestOne2Many { @Autowired private CustomerRepository customerRepository; @Autowired private LinkManRepository linkManRepository; /** * 测试1:保存一个客户,并保存该客户的一个联系人 * <p> * 因为分别调用了customerRepository和linkManRepository的save()方法,故需要加@Transactional保持事务的一致性 * 而添加@Transactional注解后,在测试类中默认事务是回滚的,虽然测试时添加成功了、但测试程序结束后程序默认回滚导致看不到结果 * 因此再加一个@Rollback(false),主要是方便人在数据库中查看结果,看是否符合预期 * <p> * 测试环境要求: * 1.CustomerEntity实体中使用 @OneToMany 和 @JoinColumn 两个注解进行一对多关系的映射 * 2.application.yml 中 jpa.hibernate.ddl-auto=create */ @Test @Transactional @Rollback(false) public void testSave1() { //创建一个客户,创建一个联系人 CustomerEntity customer = new CustomerEntity(); customer.setCustName("百度"); LinkManEntity linkMan = new LinkManEntity(); linkMan.setLkmName("小李"); // 配置客户到联系人的关系(一对多) customer.getLinkMans().add(linkMan); customerRepository.save(customer); linkManRepository.save(linkMan); /** * 此时控制台SQL(建表语句略) * Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?) * Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?) * Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=? * * 从客户的角度上:发送两条insert语句,发送一条update语句更新数据库(更新外键) * 是由于我们使用@OneToMany配置了客户到联系人的关系,造成了客户也对外键进行维护 */ } /** * 测试2:联系人到客户,仅两条insert(与上面相比无update) * 这种方式更贴近需求,因为没有多余的update * <p> * 测试环境要求: * 1.CustomerEntity实体中使用 @OneToMany(mappedBy = "customer") * 2.application.yml 中 jpa.hibernate.ddl-auto=create */ @Test @Transactional @Rollback(false) public void testSave2() { CustomerEntity customer = new CustomerEntity(); customer.setCustName("腾讯"); LinkManEntity linkMan = new LinkManEntity(); linkMan.setLkmName("小王"); // 配置联系人到客户的关系(多对一) linkMan.setCustomer(customer); customerRepository.save(customer); linkManRepository.save(linkMan); /** * 此时控制台SQL * Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?) * Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?) * * 只发送了两条insert语句,由于配置了联系人到客户的映射关系(多对一) */ } /** * 测试3:代码中即配置客户到联系人,又配置联系人到客户 * <p> * 测试环境要求: * 1.CustomerEntity实体中使用 @OneToMany 和 @JoinColumn 两个注解进行一对多关系的映射 * 2.application.yml 中 jpa.hibernate.ddl-auto=create * <p> * 巨坑出现:java.lang.StackOverflowError !!! * 这是因为两个实体类 CustomerEntity 和 LinkManEntity 都使用了 @Data 注解造成的, * 最主要的原因是因为 @Data 注解中包含了 @ToString 方法,在 JPA 的框架中,两者互相打印对方造成 StackOverflowError * <p> * 解决办法: * 两个实体类 CustomerEntity 和 LinkManEntity 不要直接使用 @Data 注解 * 换成 @Getter 和 @Setter 注解,至于需要用到 toString() 方法打印结果时, * 手动重写该方法,注意双方一定不要相互包含!!! * (用 toString 的感觉也不多、因为查看结果一般是手动进入数据库查看数据的) */ @Test @Transactional @Rollback(false) public void testSave3() { CustomerEntity customer = new CustomerEntity(); customer.setCustName("腾讯"); LinkManEntity linkMan = new LinkManEntity(); linkMan.setLkmName("小王"); // 即配置客户到联系人,又配置联系人到客户 customer.getLinkMans().add(linkMan); linkMan.setCustomer(customer); customerRepository.save(customer); linkManRepository.save(linkMan); /** * 此时控制台SQL(也是两条insert,一条update. 同测试1结果) * Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?) * Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?) * Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=? */ } /** * 测试4:Customer放弃外键维护权 * (代码中也配置客户到联系人,又配置联系人到客户) * <p> * 测试环境要求: * 1.CustomerEntity实体中使用 @OneToMany(mappedBy = "customer") * 2.application.yml 中 jpa.hibernate.ddl-auto=create */ @Test @Transactional @Rollback(false) public void testSave4() { CustomerEntity customer = new CustomerEntity(); customer.setCustName("阿里"); LinkManEntity linkMan = new LinkManEntity(); linkMan.setLkmName("小赵"); customer.getLinkMans().add(linkMan); linkMan.setCustomer(customer); customerRepository.save(customer); linkManRepository.save(linkMan); /** * 此时控制台SQL(同测试2结果) * Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?) * Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?) */ } /** * 由上面的测试结果可知,主要有两大条件 * 1. "一"的一方是否维护外键 ==> 若是维护则三条SQL、不维护则仅两条SQL * 2. 单向/双向绑定 * <p> * 经分析,"一"的一方不维护外键性能会好一些,能达到相同的效果但少一条SQL语句(少一个与数据库的网络交互) * 因此剩下 testSave2 和 testSave4,但 testSave4 是双向绑定、代码会稍多 * 因此 testSave2 是目前能达到效果,SQL最少、代码最少的样例 * 但此时 testSave2 的单向绑定是联系人到客户,故想再测试下 * 当"一"的一方放弃维护外键后,客户到联系人的单向绑定是否也能达到相同效果 * <p> * 测试5:联系人到客户,仅两条insert(与上面相比无update) * 这种方式更贴近需求,因为没有多余的update * <p> * 测试环境要求: * 1.CustomerEntity实体中使用 @OneToMany(mappedBy = "customer") * 2.application.yml 中 jpa.hibernate.ddl-auto=create */ @Test @Transactional @Rollback(false) public void testSave5() { CustomerEntity customer = new CustomerEntity(); customer.setCustName("腾讯"); LinkManEntity linkMan = new LinkManEntity(); linkMan.setLkmName("小小王"); // // 配置联系人到客户的关系(多对一) // linkMan.setCustomer(customer); // 配置客户到联系人的关系(一对多) customer.getLinkMans().add(linkMan); customerRepository.save(customer); linkManRepository.save(linkMan); /** * 此时控制台SQL(即也只有两条insert) * Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?) * Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?) * * 虽然此时只有两条SQL,但观察数据库中的数据发现,cst_linkman 联系人表中,外键 lkm_cust_id 为 null,即表示根本没有维护外键!!! * 因此该方案没有达到预期效果,直接pass * * 最终结论: * "一"的一方放弃维护外键、单向绑定(多对一绑定),这种方案发送的SQL最少、代码量最少,即样例 testSave2() * (单向绑定只能是多对一、即配置联系人到客户的关系; * 而不能是一对多、配置客户到联系人的关系,这样会造成外键根本没有插入) */ } }
-
分析:由上面测试结果可以看出,主要由两大条件,第一是 "一"的一方是否维护外键关系,即"一"的一方是否配置了
@JoinColumn
注解;第二是 代码中的单向/双向绑定(即配置客户到联系人、又配置联系人到客户)。测试结果总结如下序号 条件 结果 测试一: testSave1
"一"的一方维护外键
单向绑定两个 insert、一个update (用于维护外键) 测试二: testSave2
"一"的一方不维护外键
单向绑定(联系人到客户,多对一)仅两个 insert 测试三: testSave3
"一"的一方维护外键
双向绑定两个 insert、一个update (用于维护外键) 测试四: testSave4
"一"的一方不维护外键
双向绑定仅两个 insert 测试五: testSave5
"一"的一方不维护外键
单向绑定(客户到联系人,一对多)仅两个 insert、但此时外键没插入数据,
功能都没达到效果,直接pass!- 结果:由
testSave5()
注解说明可知:"一"的一方放弃维护外键、单向绑定(多对一绑定),这种方案发送的SQL最少、代码量最少,即样例testSave2()
代码。特别说明,单向绑定只能是多对一、即配置联系人到客户的关系;而不能是一对多、配置客户到联系人的关系,这样会造成外键根本没有插入。
- 结果:由
2.2 级联
概述
- 多表关系的删除
- "多"的一方(即从表)可以随意删除数据,而"一"的一方(即主表)主键有可能被引用、即存在外键约束,不能随意删除数据。
- 样例:客户与联系人中,"多"的一方联系人表可以随意删除数据,而"一"的一方客户表不能随意删数据。
- 级联概述
- 含义:操作一个对象的时候也操作它的关联对象
- 分类:级联添加、级联删除、级联更新
- 样例:级联添加即保存一个客户的同时保存联系人;级联删除即删除一个客户的同时也删除他的所有联系人
- 级联操作
- 需要区分操作主题
- 需要在操作主题的实体类上,添加级联属性
- 配置级联:可以配置到设置多表的映射关系的注解上。
PERSIST
(保存)、MERGE
(更新)、REMOVE
(删除)、ALL
(包含增删改),推荐配置成ALL
- 样例:
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
- Date JPA 的删除
- 删除从表数据:可以随时任意删除
- 删除主表数据:【1】 有从表数据引用。(a) 在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了;(b) 如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null,没有关系)因为在删除时,它根本不会去更新从表的外键字段了;© 如果还想删除,使用级联删除引用。(注:实际开发中级联删除慎用!)【2】没有从表数据引用:随便删
编码
-
修改实体类
- 即在客户实体中添加个 cascade 属性,变为
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
,其他代码不动。
package cn.entity; import lombok.Getter; import lombok.Setter; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "cst_customer") //@Data @Getter @Setter public class CustomerEntity { @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_industry") private String custIndustry; // 客户所属行业 @Column(name = "cust_level") private String custLevel; // 客户级别 @Column(name = "cust_address") private String custAddress; @Column(name = "cust_phone") private String custPhone; // @OneToMany(mappedBy = "customer") @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL) private Set<LinkManEntity> linkMans = new HashSet<LinkManEntity>(); }
package cn.entity; import lombok.Getter; import lombok.Setter; import javax.persistence.*; @Entity @Table(name = "cst_linkman") //@Data @Getter @Setter public class LinkManEntity { @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 = CustomerEntity.class) @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id") private CustomerEntity customer; }
- 即在客户实体中添加个 cascade 属性,变为
-
测试
package test; import cn.App; import cn.entity.CustomerEntity; import cn.entity.LinkManEntity; import cn.repository.CustomerRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; import javax.transaction.Transactional; @SpringBootTest(classes = App.class) public class TestOne2ManyCascade { @Autowired private CustomerRepository customerRepository; /** * 测试级联添加,即保存一个客户的同时、保存他的所有联系人 * <p> * 测试环境要求: * 1.CustomerEntity实体中使用 @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL) * 2.application.yml 中 jpa.hibernate.ddl-auto=create */ @Test @Transactional @Rollback(false) public void testCascadeAdd() { // 模拟一个客户和两个联系人 CustomerEntity customer = new CustomerEntity(); customer.setCustName("美团"); LinkManEntity linkMan1 = new LinkManEntity(); linkMan1.setLkmName("小明"); LinkManEntity linkMan2 = new LinkManEntity(); linkMan2.setLkmName("小强"); // 级联时必需双向配置关系! linkMan1.setCustomer(customer); customer.getLinkMans().add(linkMan1); linkMan2.setCustomer(customer); customer.getLinkMans().add(linkMan2); // 仅保存客户 customerRepository.save(customer); /** * 控制台SQL * Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?) * Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?) * Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?) * 即把该客户和他的联系人均已添加 */ } /** * 测试级联删除,即删除一个客户的同时、删除他的所有联系人 * <p> * 测试环境要求: * 1.CustomerEntity实体中使用 @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL) * 2.application.yml 中 jpa.hibernate.ddl-auto=update * 因为若是create时重新建表数据库中没有数据;可以先测试下上面的级联添加,有数据后改为update,然后测试级联删除 */ @Test @Transactional @Rollback(false) public void testCascadeRemove() { // 步骤:先查询1号客户,再删除(且删除他的所有联系人) CustomerEntity customer = customerRepository.getById(1L); customerRepository.delete(customer); /** * 控制台SQL * Hibernate: select customeren0_.cust_id as cust_id1_0_0_, customeren0_.cust_address as cust_add2_0_0_, customeren0_.cust_industry as cust_ind3_0_0_, customeren0_.cust_level as cust_lev4_0_0_, customeren0_.cust_name as cust_nam5_0_0_, customeren0_.cust_phone as cust_pho6_0_0_, customeren0_.cust_source as cust_sou7_0_0_ from cst_customer customeren0_ where customeren0_.cust_id=? * Hibernate: select linkmans0_.lkm_cust_id as lkm_cust9_1_0_, linkmans0_.lkm_id as lkm_id1_1_0_, linkmans0_.lkm_id as lkm_id1_1_1_, linkmans0_.lkm_cust_id as lkm_cust9_1_1_, linkmans0_.lkm_email as lkm_emai2_1_1_, linkmans0_.lkm_gender as lkm_gend3_1_1_, linkmans0_.lkm_memo as lkm_memo4_1_1_, linkmans0_.lkm_mobile as lkm_mobi5_1_1_, linkmans0_.lkm_name as lkm_name6_1_1_, linkmans0_.lkm_phone as lkm_phon7_1_1_, linkmans0_.lkm_position as lkm_posi8_1_1_ from cst_linkman linkmans0_ where linkmans0_.lkm_cust_id=? * Hibernate: delete from cst_linkman where lkm_id=? * Hibernate: delete from cst_linkman where lkm_id=? * Hibernate: delete from cst_customer where cust_id=? * * 即会先查询客户表和联系人表,然后把该客户和他的所有联系人都删除 */ } }
链接
- 源码链接:blogs-jpa
- 关联映射多对多链接:JPA四:关联映射(二)