简体   繁体   中英

Quarkus Panache OneToMany relation is not persisted in database

I'm currently tinkering with a simple HTTP resource. My model consists of "trees" with multiple "fruits". Both inherit from PanacheEntity.

Tree:

    @Entity
    public class Tree extends PanacheEntity {
    public String name;

    @OneToMany(mappedBy = "tree", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    public List<Fruit> fruits = new ArrayList<>();
    }

Fruit

    @Entity
    public class Fruit extends PanacheEntity {

    public String name;
    public String color;
    public int cores;

    @ManyToOne
    @JsonbTransient
    public Tree tree;
    }

Resource:

    import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;

    public interface TreeResource extends PanacheEntityResource<Tree, Long> { }

This is my POST request that I send via Swagger

{
  "name": "Apple Tree",
  "fruits": [
    {
      "color": "green",
      "cores": 3,
      "name": "Apple2"
    },
    {
      "color": "red",
      "cores": 4,
      "name": "Apple"
    }
  ]
}

The response tells me, that Ids were created for all objects:

{
  "id": 4,
  "fruits": [
    {
      "id": 5,
      "color": "green",
      "cores": 3,
      "name": "Apple2"
    },
    {
      "id": 6,
      "color": "red",
      "cores": 4,
      "name": "Apple"
    }
  ],
  "name": "Apple Tree"
}

BUT when I call the Get all under /trees I get the following response:

[
  {
    "id": 1,
    "fruits": [],
    "name": "Oak"
  },
  {
    "id": 4,
    "fruits": [],
    "name": "Apple Tree"
  }
]

Fruits are always empty. Checking the postgres database reveals that all "tree_id" columns in Fruit are null. I'm pretty sure this is a beginners problem but after checking multiple samples I just cannot find what is wrong with my code.

I had the same problem and solved it by setting parent objects to children. I had Job and JobArgs items to persist.

    // ... somewhere in JobArg.java
    @JsonbTransient
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "job_id", foreignKey = @ForeignKey(name = "job_id_fk"))
    public Job job;
    @OneToMany(mappedBy = "job", cascade = CascadeType.ALL, orphanRemoval = true)
    public List<JobArg> arguments = new ArrayList<>();
    // ... somewhere in Job.java 
    // I used reactive-pgclient so my method return Uni<T>
    public void addArgument(final JobArg jobArg) {
        arguments.add(jobArg);
        jobArg.job = this;
    }
    public static Uni<Job> insert(final UUID userId, final JobDto newJob) {
        final Job job = new Job();
        //... map fields from dto ...
        newJob.getArguments().stream().map(arg -> {
            final JobArg jobArg = new JobArg();
            //... map fields from dto ...
            return jobArg;
        }).forEach(job::addArgument);
        
        final Uni<Void> jobInsert = job.persist();
        final Uni<UserAction> userActionInsert = UserAction.insertAction(type, job.id, userId, null);
        return Uni.combine().all().unis(jobInsert, userActionInsert).combinedWith(result -> job);
    }

This is an example code from Vlad Mihalcea's blog : For bidirectional mappings:

@Entity(name = "Post")
@Table(name = "post")
public class Post {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();
 
    //Constructors, getters and setters removed for brevity
 
    public void addComment(PostComment comment) {
        comments.add(comment);
        comment.setPost(this);
    }
 
    public void removeComment(PostComment comment) {
        comments.remove(comment);
        comment.setPost(null);
    }
}
 
@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String review;
 
    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;
 
    //Constructors, getters and setters removed for brevity
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof PostComment )) return false;
        return id != null && id.equals(((PostComment) o).getId());
    }
 
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}

There are several things to note on the aforementioned mapping:

The @ManyToOne association uses FetchType.LAZY because, otherwise, we'd fall back to EAGER fetching which is bad for performance.

The parent entity, Post, features two utility methods (eg addComment and removeComment) which are used to synchronize both sides of the bidirectional association. You should always provide these methods whenever you are working with a bidirectional association as, otherwise, you risk very subtle state propagation issues.

The child entity, PostComment, implement the equals and hashCode methods. Since we cannot rely on a natural identifier for equality checks, we need to use the entity identifier instead for the equals method. However, you need to do it properly so that equality is consistent across all entity state transitions, which is also the reason why the hashCode has to be a constant value. Because we rely on equality for the removeComment, it's good practice to override equals and hashCode for the child entity in a bidirectional association

.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM