文章目录

  • 数据库表的关系
    • 一对多
    • 多对多
  • Spring Data JPA实现一对多
    • 基本配置
    • 实现一对多
    • 放弃维护权
  • Spring Data JPA实现多对多
    • 基本配置
    • 实现多对多
  • 级联

之前的所有操作都是在单表查询的前提下,这一期我们要了解Spring Data JPA多表查询的各种配置。

数据库表的关系

数据库表之间的关系可以分为一对一,一对多和多对多,其中一对一可以视为一对多的一种特殊情况。

一对多

所谓一对多关系,即第一个表中的单个行可以与第二个表中的一个或多个行相关,但第二个表中的一个行只可以与第一个表中的一个行相关。

一个简单的例子,如果有一个地址表和一个用户表,那么,一个用户只能有一个地址,而一个地址可以有一个、很多或没有用户。

在这个例子中,地址是一,用户则是多。

在MySQL中,我们通常把多的一方通过外键约束到一的一方的主键上。

# 地址表
+-------+--------+
| a_id  | a_name |
+-------+--------+
| 10001 | 北京   |
| 10002 | 上海   |
| 10003 | 广州   |
| 10004 | 深圳   |
+-------+--------+# 用户表
+------+-----------------+--------------+
| u_id | u_name          | u_address_id |
+------+-----------------+--------------+
|    1 | 冬马和纱        |        10001 |
|    3 | 鹿目圆香        |        10001 |
|    4 | 晓美焰          |        10003 |
|    5 | 雪之下雪乃      |        10001 |
|   10 | 由比滨结衣      |        10004 |
+------+-----------------+--------------+

图解:

从表 用户表 多 主表 地址表 一

东马和纱
鹿目圆香
晓美焰
雪之下雪乃
由比滨结衣
北京
上海
广州
深圳

多对多

所谓一对多关系,即第一个表中的单个行可以与第二个表中的一个或多个行相关,第二个表中的单个行也可以与第二个表中的一个或多个行相关。

比如顾客和商品之间的关系:一个顾客可以购买很多商品,一种商品也可以被很多顾客购买。

要在MySQL中实现多对多关系,需要借助中间表实现。

# 顾客表
+------+-----------------+
| c_id | c_name          |
+------+-----------------+
|    1 | 冬马和纱        |
|    2 | 小木曾雪菜      |
|    3 | 雪之下雪乃      |
|    4 | 由比滨结衣      |
+------+-----------------+# 产品表
+------+-----------+
| p_id | p_name    |
+------+-----------+
|    1 | 咖啡      |
|    2 | 牛奶      |
|    3 | 巧克力    |
+------+-----------+# 中间表
+----------------+---------------+
| cp_customer_id | cp_product_id |
+----------------+---------------+
|              3 |             1 |
|              4 |             2 |
|              1 |             2 |
|              1 |             3 |
|              2 |             3 |
+----------------+---------------+

图解:

产品表 顾客表

咖啡
牛奶
巧克力
冬马和纱
小木曾雪菜
雪之下雪乃
由比滨结衣

Spring Data JPA实现一对多

基本配置

首先我们来实现上面一对多的例子,按照单表查询的规范,先配置好两个实体类和Dao接口:

User类:

package org.koorye.pojo;import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;import javax.persistence.*;@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "user")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "u_id")private int id;@Column(name = "u_name")private String name;@Overridepublic String toString() {return "ID: " + id + ", Name: " + name;}
}

Address类:

package org.koorye.pojo;import lombok.*;import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "address")
public class Address {@Id@Column(name = "a_id")private int id;@Column(name = "a_name")private String name;@Overridepublic String toString() {return "ID: " + id + ", Name: " + name;}
}

UserDao接口:

package org.koorye.dao;import org.koorye.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {}

AddressDao接口:

package org.koorye.dao;import org.koorye.pojo.Address;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;public interface AddressDao extends JpaRepository<Address, Integer>, JpaSpecificationExecutor<Address> {}

实现一对多

Spring Data JPA为一对多提供了注解的解决方案:

  • @OneToMany(targetEitity = 对方实体类的字节码)
  • @ManyToOne(targetEitity = 对方实体类的字节码)
  • @JoinColumn(name = "自身表的外键", referencedColumnName = "对方表的主键")

为了实现一对多关系,我们在主表的实体类中添加一个Set集合存储用户信息,然后在从表的实体类中添加一个Address表示地址。

接着,将注解添加到用户集合和地址上。

User类:

public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "u_id")private int id;@Column(name = "u_name")private String name;@ManyToOne(targetEntity = Address.class, cascade = CascadeType.ALL)@JoinColumn(name = "u_address_id", referencedColumnName = "a_id")private Address address;@Overridepublic String toString() {return "ID: " + id + ", Name: " + name + ", Address: " + address.getName();}
}

Address类:

public class Address {@Id@Column(name = "a_id")private int id;@Column(name = "a_name")private String name;@OneToMany(targetEntity = User.class, cascade = CascadeType.ALL)@JoinColumn(name = "u_address_id", referencedColumnName = "a_id")private Set<User> userSet = new HashSet<User>();@Overridepublic String toString() {String str = ", Users: ";for (User user : userSet) {str = str.concat(user.getName() + ", ");}str = str.substring(0, str.length() - 2);return "ID: " + id + ", Name: " + name + str;}

此时笔者对User和Address类都配置了注解,这意味着两个实体类对两表的关系都拥有维护权

让我们测试一个案例,从地址表中查询得到一个地址,赋给一个新增的用户:

在这里,我们可以通过User的setAddress方法设定地址,此处体现的是多对一的关系。

  @Test@Transactional@Rollback(value = false)public void TestOneToMany() {Address address = addressDao.findByName("北京");User user = new User();user.setName("小木曾雪菜");user.setAddress(address);userDao.save(user);}
}

重新查表:

+------+-----------------+--------------+
| u_id | u_name          | u_address_id |
+------+-----------------+--------------+
|    1 | 冬马和纱        |        10001 |
|    3 | 鹿目圆香        |        10001 |
|    4 | 晓美焰          |        10003 |
|    5 | 雪之下雪乃      |        10001 |
|   10 | 由比滨结衣      |        10004 |
|   11 | 小木曾雪菜      |        10001 |
+------+-----------------+--------------+

新字段成功添加,Spring Data JPA自动根据得到的地址为新增用户分配了u_address_id.

同样的,由于配置了双向关系,我们也可以利用Address的getUserSet方法,再通过UserSet.add方法添加新用户。此处体现的是一对多的关系。

举例:

  @Test@Transactional@Rollback(value = false)public void TestOneToMany() {Address address = addressDao.findByName("上海");User user = new User();user.setName("比企谷小町");address.getUserSet().add(user);userDao.save(user);}
}

查询结果:

+------+-----------------+--------------+
| u_id | u_name          | u_address_id |
+------+-----------------+--------------+
|    1 | 冬马和纱        |        10001 |
|    3 | 鹿目圆香        |        10001 |
|    4 | 晓美焰          |        10003 |
|    5 | 雪之下雪乃      |        10001 |
|   10 | 由比滨结衣      |        10004 |
|   11 | 小木曾雪菜      |        10001 |
|   12 | 比企谷小町      |        10002 |
+------+-----------------+--------------+

需要注意的是,如果同时使用user.setAddress(address)address.getUserSet().add(user)方法,可能会导致爆栈!因此尽可能不要这样使用。

放弃维护权

刚才我们配置了双向关系,然而,在本例中,user.setAddressaddress.getUserSet().add更加友好,我们可以通过放弃一对多关系的维护权来配置单向的多对一关系。

放弃一对多关系的维护权:

@OneToMany(mapperBy = "对方表实体类指向该表实体类对应的属性名")(当然@ManyToOne也一样)

放弃维护权之后,测试:

  @Test@Transactional@Rollback(value = false)public void TestOneToMany() {Address address = addressDao.findByName("广州");User user = new User();user.setName("五更琉璃");address.getUserSet().add(user);userDao.save(user);}
}

查表:

+------+-----------------+--------------+
| u_id | u_name          | u_address_id |
+------+-----------------+--------------+
|    1 | 冬马和纱        |        10001 |
|    3 | 鹿目圆香        |        10001 |
|    4 | 晓美焰          |        10003 |
|    5 | 雪之下雪乃      |        10001 |
|   10 | 由比滨结衣      |        10004 |
|   11 | 小木曾雪菜      |        10001 |
|   12 | 比企谷小町      |        10002 |
|   13 | 五更琉璃        |         NULL |
+------+-----------------+--------------+

新字段的u_address_id被置为NULL,外联关联失败!

然而,多对一关系的维护权仍然是生效的:

  @Test@Transactional@Rollback(value = false)public void TestOneToMany() {Address address = addressDao.findByName("广州");User user = userDao.findByName("五更琉璃");user.setAddress(address);userDao.save(user);}

查表:

+------+-----------------+--------------+
| u_id | u_name          | u_address_id |
+------+-----------------+--------------+
|    1 | 冬马和纱        |        10001 |
|    3 | 鹿目圆香        |        10001 |
|    4 | 晓美焰          |        10003 |
|    5 | 雪之下雪乃      |        10001 |
|   10 | 由比滨结衣      |        10004 |
|   11 | 小木曾雪菜      |        10001 |
|   12 | 比企谷小町      |        10002 |
|   13 | 五更琉璃        |        10003 |
+------+-----------------+--------------+

新用户的u_address_id字段设置成功。

Spring Data JPA实现多对多

Spring Data JPA同样为我们提供了多对多的注解方案。

基本配置

同样我们先配置好Customer类和Product类,并提供CustomerDao和ProductDao接口。

Customer类:

package org.koorye.pojo;import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;import javax.persistence.*;@NoArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "customer")
public class Customer {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "c_id")private int id;@Column(name = "c_name")private String name;@Overridepublic String toString() {return "ID: " + id + ", Name: " + name;}
}

Product类:

package org.koorye.pojo;import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;import javax.persistence.*;@NoArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "product")
public class Product {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "p_id")private int id;@Column(name = "p_name")private String name;@Overridepublic String toString() {return "ID: " + id + ", Name: " + name;}
}

CustomerDao接口:

package org.koorye.dao;import org.koorye.pojo.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;public interface CustomerDao extends JpaRepository<Customer, Integer>, JpaSpecificationExecutor<Customer> {}

ProductDao接口:

package org.koorye.dao;import org.koorye.pojo.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;public interface ProductDao extends JpaRepository<Product, Integer>, JpaSpecificationExecutor<Product> {}

实现多对多

既然是多对多,那么两边都要使用Set的形式配置。

接着,Spring Data JPA为多对多关系提供了两个注解:

  • @ManyToMany(targetEntity = 对方实体类的字节码) 映射多对多关系

  • @JoinTable(name = "中间表名称",
    joinColumns = {@JoinColumn(name = "中间表对应自身表的外键",
    referencedColumnName = "自身表的主键")},
    inverJoinColumns = {@JoinColumn(name = "中间表对应对方表的外键",
    referencedColumnName = "外方表的主键")})

写的有些乱,让我们看一下例子:

Customer类:

public class Customer {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "c_id")private int id;@Column(name = "c_name")private String name;@ManyToMany(targetEntity = Product.class)@JoinTable(name = "customer__product",joinColumns = {@JoinColumn(name = "cp_customer_id", referencedColumnName = "c_id")},inverseJoinColumns = {@JoinColumn(name = "cp_product_id", referencedColumnName = "p_id")})private Set<Product> productSet;@Overridepublic String toString() {return "ID: " + id + ", Name: " + name;}
}

Product类:

public class Product {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "p_id")private int id;@Column(name = "p_name")private String name;@ManyToMany(targetEntity = Customer.class)@JoinTable(name = "customer__product",joinColumns = {@JoinColumn(name = "cp_product_id", referencedColumnName = "p_id")},inverseJoinColumns = {@JoinColumn(name = "cp_customer_id", referencedColumnName = "c_id")})private Set<Customer> customerSet;@Overridepublic String toString() {return "ID: " + id + ", Name: " + name;}
}

案例,给一个已有顾客添加一个已有商品:

  @Test@Transactional@Rollback(value = false)public void TestManyToMany() {Customer customer = customerDao.findByName("小木曾雪菜");Product product = productDao.findByName("咖啡");customer.getProductSet().add(product);customerDao.save(customer);}
}

查表:

+----------------+---------------+
| cp_customer_id | cp_product_id |
+----------------+---------------+
|              3 |             1 |
|              4 |             2 |
|              1 |             2 |
|              1 |             3 |
|              2 |             3 |
|              2 |             1 |
+----------------+---------------+

新字段成功添加。

案例,查询用户购买的商品:

  @Test@Transactional@Rollback(value = false)public void TestManyToMany() {Customer customer = customerDao.findByName("冬马和纱");Set<Product> productSet = customer.getProductSet();for (Product product : productSet) {System.out.println(product);}}
}

输出结果:

Hibernate: select customer0_.c_id as c_id1_0_, customer0_.c_name as c_name2_0_ from customer customer0_ where customer0_.c_name=?
Hibernate: select productset0_.cp_customer_id as cp_custo2_1_0_, productset0_.cp_product_id as cp_produ1_1_0_, product1_.p_id as p_id1_2_1_, product1_.p_name as p_name2_2_1_ from customer__product productset0_ inner join product product1_ on productset0_.cp_product_id=product1_.p_id where productset0_.cp_customer_id=?
ID: 3, Name: 巧克力
ID: 2, Name: 牛奶Process finished with exit code 0

多对多同样支持放弃维护权,本文不再演示。

级联

级联是Spring Data JPA提供的强大功能,指多个对象之间的映射关系,建立数据之间的级联关系提高管理效率:

  • 级联插入 插入两条关联的记录时,插入其中一条,另一条也会自动插入
  • 级联删除 删除一条记录时,其他表中和该记录有关的记录也会自动删除

级联的配置非常简单,只需要在@OneToMany / @ManyToOne / @ManyToMany中配置cascade = CascadeType.All即可,指所有操作都进行级联。

除外还有:

  • CascadeType.PERSIST 保存时级联
  • CascadeType.MERGE 更新时级联
  • CascadeType.ROMOVE 删除时更新

案例,添加一个新顾客和一个新商品,并给新顾客添加该新商品:

  @Test@Transactional@Rollback(value = false)public void TestManyToMany() {Customer customer = new Customer();customer.setName("鹿目圆香");Product product = new Product();product.setName("猫");Set<Product> productSet = new HashSet<>();productSet.add(product);customer.setProductSet(productSet);customerDao.save(customer);}

查表:

mysql> select * from customer;
+------+-----------------+
| c_id | c_name          |
+------+-----------------+
|    1 | 冬马和纱        |
|    2 | 小木曾雪菜      |
|    3 | 雪之下雪乃      |
|    4 | 由比滨结衣      |
|    5 | 鹿目圆香        |
+------+-----------------+
5 rows in set (0.01 sec)mysql> select * from product;
+------+-----------+
| p_id | p_name    |
+------+-----------+
|    1 | 咖啡      |
|    2 | 牛奶      |
|    3 | 巧克力    |
|    4 | 猫        |
+------+-----------+
4 rows in set (0.00 sec)mysql> select * from customer__product;
+----------------+---------------+
| cp_customer_id | cp_product_id |
+----------------+---------------+
|              3 |             1 |
|              4 |             2 |
|              1 |             2 |
|              1 |             3 |
|              2 |             3 |
|              2 |             1 |
|              5 |             4 |
+----------------+---------------+
7 rows in set (0.01 sec)

新字段成功添加并关联。

【Spring Data JPA自学笔记五】一对多、多对多和级联相关推荐

  1. 【Spring Data JPA自学笔记二】初识Spring Data JPA

    文章目录 Spring Data JPA是什么? Spring Data JPA的配置 配置pom.xml 配置applicationContext.xml Spring Data JPA的使用 Sp ...

  2. 【Spring Data JPA自学笔记三】Spring Data JPA的基础和高级查询方法

    文章目录 调用接口的基础方法查询 Repository CrudRepository PagingAndSortingRepository JPARepository JpaSpecification ...

  3. 【Spring Data JPA自学笔记一】JPA是什么?JPA访问数据库初体验

    文章目录 JPA是什么? JDBC的诞生 JPA的诞生 如何使用JPA? 配置JPA 配置pom.xml 配置persistence.xml 实现POJO类 调用JPA方法 关于EntityManag ...

  4. 第九章SpringBoot整合Spring Data JPA

    目录 1 概述 2 Spring Data JPA整合 2.1 pom文件 2.2 配置文件 2.3 实体类 2.4 Dao接口 2.5 启动类 2.6 编写测试类 3 Spring Data JPA ...

  5. Spring Data JPA 教程(翻译)

    写那些数据挖掘之类的博文 写的比较累了,现在翻译一下关于spring data jpa的文章,觉得轻松多了. 翻译正文: 你有木有注意到,使用Java持久化的API的数据访问代码包含了很多不必要的模式 ...

  6. 【笔记】Spring - Spring Data JPA

    相关 官方文档⭐️: https://docs.spring.io/spring-data/jpa/docs/2.6.0/reference/html/#preface 翻译: https://blo ...

  7. Spring Data JPA 五分钟快速入门和实践

    Spring Data JPA(类似于Java Web 中的 DAO) 操作声明持久层的接口(Repository) 三个核心接口: CrudRepository PagingAndSortingRe ...

  8. Spring Boot(五):spring data jpa的使用

    Spring Boot(五):spring data jpa的使用 一.spring data jpa介绍 1,JPA是什么 JPA(Java Persistence API)是Sun官方提出的Jav ...

  9. SpringBoot学习笔记:Spring Data Jpa的使用

    更多请关注公众号 Spring Data Jpa 简介 JPA JPA(Java Persistence API)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化规范(JSR ...

最新文章

  1. [转载]Ubuntu安装配置Mysql
  2. android p wifi一直在扫描_在Android上的每次WiFi扫描之间我应该使用什么时间间隔?...
  3. 创建对象并且使用对象的属性和方法
  4. 如何通过一个SDK轻松搞定人脸识别,拯救初入职场的程序猿
  5. Mongo Replica set的Primary 客户端不回自动重连新的primary
  6. 聚类分析上证50成分股
  7. CAM350 - 导出 DXF 文件
  8. 华硕t100ha刷linux,华硕T100一键重装win7系统教程
  9. Java服务端接入苹果内购。实现票据二次校验、自动续期订阅
  10. OSI模型工作模式解析
  11. dmaengine,dmatest, DW_DMAC driver
  12. Web前端相关面试题
  13. C#实现格式转换:wmf转png
  14. 在python中使用FP-growth算法
  15. 【转载】一个软件测试工程师的学习体验 (受用)
  16. 机器学习图像分割——模型评价总结(含完整代码)
  17. 数说亚洲杯小组赛:冷门四宗“最”知多少?
  18. 搭建微服务架构的电商平台系统
  19. EMC测试有哪些项?
  20. python3+任务计划实现的人人影视网站自动签到

热门文章

  1. detached entity passed to persist:***
  2. RHEL/CentOS修改hostname
  3. 【自制yum仓库一】自定义RPM包
  4. 微信公众号扫一扫封装接口
  5. 九宫格做法ps入门基本教程大全#ps抠图#ps教程
  6. IDEA去除代码重负导致的波浪黄线
  7. UGUI-- 异形按钮
  8. 成为中国绅士的障碍—自私
  9. Vue 打包优化之 externals 抽离公共的第三方库
  10. 读《理想国》——柏拉图