表关联
上一篇介绍了JPA的简单使用,这一篇介绍JPA在表关联上的使用
一对一
配置参数
JPA对于数据实体一对一映射使用的是@OneToOne
注解。
代码
User用户表
- /**
- * 用户信息
- * @author daify
- **/
- @Data
- @Entity
- @Table(name = "cascade_user")
- public class User {
-
- @Id
- @GeneratedValue
- private Long id;
-
- private String name;
-
- private Integer age;
-
- private Double point;
- /**
- * CascadeType包含的类别 级联:给当前设置的实体操作另一个实体的权限
- * CascadeType.ALL 级联所有操作
- * CascadeType.PERSIST 级联持久化(保存)操作
- * CascadeType.MERGE 级联更新(合并)操作
- * CascadeType.REMOVE 级联删除操作
- * CascadeType.REFRESH 级联刷新操作
- * CascadeType.DETACH 级联分离操作,如果你要删除一个实体,但是它有外键无法删除,这个级联权限会撤销所有相关的外键关联。
- */
- @OneToOne(targetEntity = UserCart.class,
- cascade = CascadeType.ALL,
- mappedBy = "user")
- private UserCart userCart;
-
- @OneToOne(targetEntity = UserInfo.class,
- cascade = CascadeType.ALL)
- @JoinTable(name = "user_info_table",
- joinColumns = @JoinColumn(name="user_id"),
- inverseJoinColumns = @JoinColumn(name = "info_id"))
- private UserInfo userInfo;
-
- }
-
UserCart用户购物车表
- @Data
- @Entity
- @Table(name = "cascade_user_cart")
- public class UserCart {
-
- @Id
- @GeneratedValue
- private Long id;
-
- private Integer count;
-
- private Double totalMoney;
-
- private Date updateTime;
-
- /**
- * CascadeType包含的类别 级联:给当前设置的实体操作另一个实体的权限
- * CascadeType.ALL 级联所有操作
- * CascadeType.PERSIST 级联持久化(保存)操作
- * CascadeType.MERGE 级联更新(合并)操作
- * CascadeType.REMOVE 级联删除操作
- * CascadeType.REFRESH 级联刷新操作
- * CascadeType.DETACH 级联分离操作,如果你要删除一个实体,但是它有外键无法删除,这个级联权限会撤销所有相关的外键关联。
- */
- @OneToOne(targetEntity = User.class,
- cascade = {},
- fetch = FetchType.LAZY)
- private User user;
- }
用户信息
- @Data
- @Entity
- @Table(name = "cascade_user_info")
- public class UserInfo {
-
- @Id
- @GeneratedValue
- private Long id;
-
- private String userInfo;
-
- private String config;
- }
上面例子中尝试使用了两种方式来维护一对一的关系,首先在User实体中同样标注了@OneToOne
但是配置了mappedBy,
这样生成的表数据中,User和UserCart的关系将由UserCart负责维护。User表中并不会维护UserCart的信息。
而在User和UserInfo的关系中使用了中间表user_info_table
来维护双方的关系,UserInfo实体中并没有保存任何User的信息。
权限
在User、UserCart、UserInfo三者中User为数据存在的主体,其他两个实体都是依托于User数据的存在而存在。
所以在权限中给User实体提供了全部全部权限。
注解
@OneToOne主要提供了下面的参数内容
注解 | 参数 | 描述 |
---|
@OneToOne | | 描述一个 一对一的关联 |
| targetEntity | 目标类的实体 |
| cascade | 关联到目标的操作 |
| fetch | 是否使用延迟加载 |
| mappedBy | 反向关联 |
测试
因为上面一对一的例子中权限被赋予给User表中,UserCart并没有赋予任何权限,所以保存用户的时候可以关联保存用户购物车,删除购物车的时候并不会删除用户,但是删除用户的时候会删除购物车
通过保存用户关联保存购物车
- @Test
- public void testUserSave() {
- User defUser1 = UserMock.getDefUser1();
- UserCart defUserCart1 = UserCartMock.getDefUserCart1();
- defUser1.setUserCart(defUserCart1);
- defUserCart1.setUser(defUser1);
- // 此时保存用户,用户购物车应该也被保存
- userRepository.save(defUser1);
- List <UserCart> all = userCartRepository.findAll();
- Assert.assertNotNull(all);
- Assert.assertTrue(all.size() == 1);
- }
删除用户购物车的时候,用户不会被删除
- @Test
- public void testUserCartDelete() {
- User defUser1 = UserMock.getDefUser1();
- UserCart defUserCart1 = UserCartMock.getDefUserCart1();
- defUser1.setUserCart(defUserCart1);
- // 此时保存用户,用户购物车应该也被保存
- userRepository.save(defUser1);
- // 此时删除用户购物车并不会删除用户
- userCartRepository.deleteAll();
- List <User> all1 = userRepository.findAll();
- Assert.assertNotNull(all1);
- Assert.assertTrue(all1.size() == 1);
- }
删除用户的时候,购物车会被删除
- @Test
- public void testUserDelete() {
- User defUser1 = UserMock.getDefUser1();
- UserCart defUserCart1 = UserCartMock.getDefUserCart1();
- defUser1.setUserCart(defUserCart1);
- // 此时保存用户,用户购物车应该也被保存
- userRepository.save(defUser1);
- // 此时删除用户购物车并不会删除用户
- userRepository.delete(defUser1);
- List <UserCart> all = userCartRepository.findAll();
- Assert.assertTrue(all.size() == 0);
- }
一对多和多对一
通过@OneToMany和@ManyToOne的组合我们可以实现双向关联。根据JPA规范,我们使用多方来维护关系。
通过在多方维护@JoinColumn
来注释属性。
代码
订单表
- @Data
- @Entity
- @Table(name = "cascade_order")
- public class Order {
-
- @Id
- @GeneratedValue
- private Long id;
-
- private String orderNo;
-
- private Integer count;
-
- private Double money;
-
- @OneToMany(mappedBy = "order",
- cascade = CascadeType.ALL,
- fetch = FetchType.EAGER)
- private List<OrderItem> orderItems;
-
- /**
- * 重写toString防止死循环
- * @return
- */
- @Override
- public String toString() {
- return "OrderItem{" + "id=" + id + ", productId='" + productId + '\'' + ", money=" + money
- + ", count=" + count + ", productName='" + productName + '\'' + ", order=" + order
- + '}';
- }
-
- }
订单子项
- @Data
- @Entity
- @Table(name = "cascade_order_item")
- public class OrderItem {
-
- @Id
- @GeneratedValue
- private Long id;
-
- private String productId;
-
- private Double money;
-
- private Integer count;
-
- private String productName;
-
- @ManyToOne(targetEntity = Order.class,
- cascade = {},
- fetch = FetchType.EAGER)
- @JoinColumn(
- // 设置在OrderItem表中的关联字段(外键)
- name="order_id"
- )
- // 防止json序列化死循环问题解决
- @JsonBackReference
- private Order order;
- }
上门的例子中,订单方为一端,订单子项为多端,在多端除了使用了@ManyToOne
还使用了@JoinColumn
注解来标识Order主键创建到OrderItem表的列的名称,
当然没有此注解的时候JPA会根据默认规则生成一个列名称。
权限
根据JPA规范一对多时候,关系的维护交给了多方来进行,但是很多时候多方的存在是依靠一方的。
比如(订单、订单子项)所以更新删除的权限需要授权给一方(Order)。
@ManyToOne,@OneToMany
注解 | 参数 | 描述 |
---|
@ManyToOne | | 一个多对一的映射,该注解标注的属性通常是数据库表的外键. |
| targetEntity | 目标类的实体 |
| cascade | 关联到目标的操作 |
| fetch | 是否使用延迟加载 |
@OneToMany | | 一个 一对多的关联,该属性应该为集体类型,在数据库中并没有实际字段 |
| targetEntity | 目标类的实体 |
| cascade | 关联到目标的操作 |
| fetch | 是否使用延迟加载 |
| mappedBy | 反向关联 |
| orphanRemoval | 讲移除操作级联到关联的实体中 |
测试
一对多的时候虽然多方维持了两者关系,但是我们把权限赋予了一方所以,删除多方并不会级联操作,删除一方可以移除多方数据。所以下的测试可以通过
- @Test
- public void testOrder() {
- Order defOrder1 = OrderMock.getDefOrder1();
- List <OrderItem> allDefOrder = OrderItemMock.getAllDefOrder();
- // order 维持orderItem关系
- defOrder1.setOrderItems(allDefOrder);
- // orderItem维持order关系
- allDefOrder.forEach(o -> o.setOrder(defOrder1));
- Order save = orderRepository.save(defOrder1);
- List <OrderItem> all = orderItemRepository.findAll();
- Assert.assertTrue(all.size() == allDefOrder.size());
-
- orderItemRepository.delete(all.get(0));
- Order order = orderRepository.findById(save.getId()).get();
- Assert.assertNotNull(order);
-
- orderRepository.deleteById(order.getId());
- List <OrderItem> all1 = orderItemRepository.findAll();
- Assert.assertTrue(all1.size() == 0);
-
- }
多对多
配置方法
和一对多、一对一不同,多对多没法只使用两个数据实体完成相互之间的关系维护,这个时候需要一个关联的中间表来维护他们之间的关系。
对于中间表的配置,你大可不去进行额外的配置让JPA自动生成,当然你也可以使用之前介绍的@JoinTable
注解进行配置。
权限
和一对多、一对一不同,多对多一般是没有办法设置级联操作的。因为双方对应的都是集合对象,
而双方某一条数据都可能被对方多条数据关联。
代码
用户表
- @Data
- @Entity
- @Table(name = "cascade_user")
- public class User {
-
- @Id
- @GeneratedValue
- private Long id;
-
- private String name;
-
- private Integer age;
-
- private Double point;
-
- @ManyToMany(targetEntity = UserTag.class,
- cascade = {},
- fetch = FetchType.LAZY)
- // 假如不定义,jpa会生成“表名1”+“_”+“表名2”的中间表
- @JoinTable(
- // 定义中间表的名称
- name = "cascade_user_tag_table",
- // 定义中间表中关联User表的外键名
- joinColumns = {
- @JoinColumn(name = "user_id",
- referencedColumnName = "id")
- },
- // 定义中间表中关联UserTag表的外键名
- inverseJoinColumns = {
- @JoinColumn(name = "tags_id",
- referencedColumnName = "id")
- }
- )
- private Set<UserTag> tags;
-
- }
用户标签表
- @Data
- @Entity
- @Table(name = "cascade_user_tag")
- public class UserTag {
-
- @Id
- @GeneratedValue
- private Long id;
-
- private String tagName;
-
- @ManyToMany(mappedBy = "tags",
- cascade = {},
- fetch = FetchType.LAZY)
- private List<User> userList;
- }
@ManyToMany
下面是@OneToOne
以及与其配合的@JoinTable
提供的注解参数
注解 | 参数 | 描述 |
---|
@ManyToMany | | 描述一个多对多的关联.多对多关联上是两个一对多关联,但是在ManyToMany描述中,中间表是由ORM框架自动处理 |
| targetEntity | 目标类的实体 |
| cascade | 关联到目标的操作 |
| fetch | 是否使用延迟加载 |
| mappedBy | 反向关联 |
@JoinTable | | JoinTable在many-to-many关系的所有者一边定义。如果没有定义JoinTable,使用JoinTable的默认值。 |
| catalog | 表的目录 |
| foreignKey | 外键约束,创建表的时候使用 |
| indexes | 表的索引,在生成表的时候使用 |
@JoinColumns | | 关系存在多个JoinColumn,用JoinColumns定义多个JoinColumn的属性。 |
| foreignKey | 此列的外键约束 |
| value | JoinColumn的集合 |
| inverseJoinColumns | 联接表的外键列,该列引用不拥有关联的实体的主表 |
| joinColumns | 联接表的外键列,该列引用拥有关联的实体的主表 |
| name | 进行连接的表名称 |
| schema | 表的命名空间 |
测试
多对多的情况下,我们使用虽然User和UserTag的关系由中间表维护,但是我们在User中配置了中间表的关系维护,所以此时删除用户的时候可以成功删除,且可以成功移除中间表数据,但是这个时候移除UserTag数据的时候,却会抛出DataIntegrityViolationException异常。只能通过User移除中间关系。
- @Test
- public void testUserTag() {
- User defUser1 = UserMock.getDefUser1();
- UserTag defTag1 = UserTagMock.getDefTag1();
- UserTag defTag2 = UserTagMock.getDefTag2();
- defUser1 = userRepository.save(defUser1);
- defTag1 = userTagRepository.save(defTag1);
- defTag2 = userTagRepository.save(defTag2);
-
- defUser1.getTags().add(defTag1);
- defUser1.getTags().add(defTag2);
-
- defUser1 = userRepository.save(defUser1);
- // 此时会报错,因为中间表关系为User维护
- try {
- userTagRepository.delete(defTag2);
- } catch (Exception e) {
- log.info(e.getCause().getMessage());
- Assert.assertTrue(e instanceof DataIntegrityViolationException);
- }
- // 修改User表中关系
- defUser1.setTags(new HashSet <>());
- defUser1.getTags().add(defTag1);
- defUser1 = userRepository.save(defUser1);
- // 此时可以删除
- userTagRepository.delete(defTag2);
- // 直接删除用户没问题
- userRepository.delete(defUser1);
- }
本篇文章涉及的源码下载地址:https://gitee.com/daifyutils/springboot-samples
到此这篇关于Spring Boot 整合JPA 数据模型关联操作(一对一、一对多、多对多)的文章就介绍到这了,更多相关Spring Boot 整合JPA 数据模型关联内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!