【问题标题】:JPA Persist parent and child with one to many relationshipJPA坚持父母和孩子的一对多关系
【发布时间】:2016-05-13 21:12:18
【问题描述】:

我想保留具有 20 个子实体的父实体, 我的代码在下面

父类

@OneToMany(mappedBy = "parentId")
private Collection<Child> childCollection;

儿童班

@JoinColumn(name = "parent_id", referencedColumnName = "parent_id")
@ManyToOne(optional=false)
private Parent parent;

String jsonString = "json string containing parent properties and child  collection" 

ObjectMapper mapper = new ObjectMapper();
Parent parent = mapper.readValue(jsonString, Parent.class);

public void save(Parent parent) {
    Collection<Child> childCollection = new ArrayList<>() ;

    for(Child tha : parent.getChildCollection()) { 
        tha.setParent(parent);
        childCollection.add(tha);
    }

    parent.setChildCollection(childCollection);
    getEntityManager().persist(parent);
 }

所以如果有 20 个子表,那么我必须在每个子表中设置父引用,因为我必须编写 20 个 for 循环? 可行吗?有没有其他方法或配置可以自动持久化父子关系?

【问题讨论】:

  • 这似乎更像是一个 JSON 问题而不是 JPA 问题。如果您的 JSON 未编组以便设置正确的关系,那么在保存父级时让子级持久化只需将相关的级联选项添加到 @OneToMany(假设您的映射是正确的)
  • 如果您没有发回 Child->Parent 关系,或者它没有在从 JSON 构建的内容中设置,那么是的,您需要在每个子实体中手动设置它。另一种方法是使关系单向:从 OneToMany 中删除 mappedby="parent" 并指定一个 JoinColumn。这将导致 OneToMany 在子表中设置外键,而不是通过子表对其父表的引用来设置(然后您应该删除子表的父属性和映射)
  • 关于@Chris 所说的,仅提及关联单向@OneToMany 的推荐方法是使用@JoinTable。来自文档:unidirectional one-to-many association on a foreign key is an unusual case, and is not recommended. You should instead use a join table for this kind of association.

标签: jpa one-to-many


【解决方案1】:

我正在使用 lombok 在我的实体类上生成 getter 和 setter 属性。 当我试图保存有孩子的父实体时,我还面临子实体上的 NULL referenceID 问题。 当我添加孩子时,在我的父实体上,我在孩子上设置了父实体的“this”引用。 在我的示例中,我有用户表和地址表,其中一个用户可以有多个地址。
我创建了如下域类。

例如 address.setUser(this);

package com.payment.dfr.entities;

import lombok.Data;

import javax.persistence.*;
import java.math.BigInteger;
@Entity
@Data
@Table(name="User")
public class User {

    @Id
    @GeneratedValue
    private BigInteger RecordId;
    private String Name;
    private String Email;

    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Address> addresses = new ArrayList<>();

    public void addAddress(Address address){
        address.setUser(this);
        addresses.add(address);
    }
    
}

@Entity
@Data
@Table(name="UserAddress")
public class Address {

    @Id
    @GeneratedValue
    private BigInteger RecordId;
    private String AddressLine;
    private String City;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="UserId")
    private User user;

}


This is how I save user with address 

    User newUser = new User();
    newUser.setName("Papa");
    newUser.setEmail("manish@gmail.com");
    Address address1 = new Address();
    address1.setAddressLine("4401 Central Ave");
    address1.setCity("Fremont");
    newUser.addAddress(address1);
    Address address2 = new Address();
    address2.setAddressLine("4402 Central Ave");
    address2.setCity("Fremont");

    newUser.addAddress(address2);
    User user1 = userRepository.save(newUser);
    log.info(user1.getRecordId().toString());

【讨论】:

    【解决方案2】:

    修复你的父类:

    @OneToMany(mappedBy = "parent")
    

    ma​​ppedBy 属性应指向关系另一端的字段。正如JavaDoc 所说:

    拥有关系的字段。除非关系是单向的,否则是必需的。

    您还应该明确地将子实体保持在循环中:

    for(Child tha : parent.getChildCollection()) { 
        ...
        getEntityManager().persist(tha);
        ...
    }
    

    正如Alan Hay 在评论中注意到的那样,您可以使用级联设施并让 EntityManager 自动持久化您的所有子实体:

    @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
    

    您可以在Vlad Mihalcea's blog 中找到有关级联(以及 JPA 本身)的更多详细信息。

    【讨论】:

    • 他不需要@OneToMany 的级联选项吗?
    • 但是对于 20 个不同的子集合,我将不得不编写 20 个不同的 for 循环?
    • @Bharath Reddy,我认为是的,因为 20 个不同的子集合是 20 个不同的 Parent 对象,您将调用方法 save(...) 20 次。
    • 我使用了 cascade = CascadeType.PERSIST,但我将外键设为 null
    • 正如我上面提到的,如果内存中的对象模型是正确的,JPA 部分是微不足道的。内存模型的正确性,或者其他,显然取决于您未能发布的 JSON 的结构,因此很难提供任何进一步的帮助。如果 JSON 不是反序列化程序可以从 Child > Parent 设置反向引用的格式,那么您需要在代码中处理这个 - 正如您所做的那样 - 在调用 persist 之前(否则您将得到一个 null FK,如您所见)。如果您确实想发布此 JSON。 你应该问一个关于 JSON 而不是 JPA 的问题
    【解决方案3】:

    正如 cmets 中所指出的,您必须注意对象图与子/父关系的一致性。当 JSON 直接来自 POST 请求时,这种一致性不会免费。

    您必须使用@JsonBackReference@JsonManagedReference 注释父字段和子字段。

    父类:

    @OneToMany(mappedBy = "parentId")
    @JsonBackReference
    private Collection<Child> childCollection;
    

    儿童班:

    @JoinColumn(name = "parent_id", referencedColumnName = "parent_id")
    @ManyToOne(optional=false)
    @JsonManagedReference
    private Parent parent;
    

    与答案类似的问题是here

    此外,如果您在 javax.persistence 注释类上使用 @JsonBackReference/@JsonManagedReference 并结合 Lombok 的 @ToString 注释,则会导致 stackoverflow 错误。

    只需将childCollectionparent 字段从带有@ToString( exclude = ...)@ToString 注释中排除

    Lombok 生成的equals() 方法(@Data@EqualsAndHashCode)也会发生同样的情况。只需手动实现这些方法或仅使用@Getter@Setter 注释即可。

    【讨论】:

    • 这是一个很大的帮助,但您切换了一些东西,您应该在父级上添加 JsonManagedReference,在子级上添加 JsonBackReference。不要忘记删除您拥有的 JsonIgnore 案例,因为它会在请求中为您提供“不支持的媒体类型”
    • @JsonBackReference 注释不能用于子对象的集合。情况恰恰相反。在父类的子属性列表上使用@JsonManagedReference@OneToMany,在指向父类的子实体类的属性上使用@JsonBackReference@ManyToOne
    【解决方案4】:

    一般来说,@JoinColumn 表示实体是关系的所有者,mappedBy 表示实体是关系的倒数

    所以,如果你想像下面这样尝试

    @OneToMany(mappedBy = "parent")
    private Collection<Child> childCollection;
    

    这意味着它是关系的逆向,它不会将父引用设置为其子引用。

    要设置父级对其子级的引用,您必须通过以下方式使上述实体关系的所有者

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn
    private Collection<Child> childCollection;
    

    您不需要设置任何子引用,因为上面的代码将在子表中创建一个列。

    【讨论】:

    【解决方案5】:

    我会让父母坚持自己的孩子

    package com.greg;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.OneToMany;
    
    @Entity(name = "PARENT")
    public class Parent {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        @Column(name = "NAME")
        private String name;
    
        @Column(name = "DESCRIPTION")
        private String description;
    
        @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
        @JoinColumn(name = "parent", referencedColumnName = "id", nullable = false)
        private List<Child> children = new ArrayList<Child>();
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public List<Child> getChildren() {
            return children;
        }
    
        public void setChildren(List<Child> children) {
            this.children = children;
        }
    
    }
    

    【讨论】:

    • 在我们只希望父级也持久化子级的情况下是否需要使用FetchType.EAGER
    • @Stephane 不,我认为我这样做是出于测试原因
    猜你喜欢
    • 1970-01-01
    • 2014-03-16
    • 2018-02-12
    • 1970-01-01
    • 1970-01-01
    • 2017-06-25
    • 2020-06-21
    • 1970-01-01
    相关资源
    最近更新 更多