1

I'm working with JPA, Spring boot. Using @OneToMany annotation, when I fetch orders containing cart items. My domain codes are below.

Order:

@Data
@Entity
@Table(name="\"order\"")
@ToString
public class Order {

    @Id
    @GeneratedValue
    @Getter @Setter
    private Long id;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name="order_id")
    @Getter @Setter
    private List<Cart> carts;

    public void addCart(Cart cart) {
        if (this.carts == null) {
            carts = new ArrayList<Cart>();
        }
        carts.add(cart);
    }
}

Cart:

@Data
@Entity
@Table(name="cart")
@ToString
public class Cart {

    @Id
    @GeneratedValue
    @Getter @Setter
    @Column(name="id")
    private Long id;

    @Column(name = "order_id")
    @Getter @Setter
    private Long orderId;

}

This works very well when I fetch only one order, but doesn't work when I fetch more than two orders. I mean when I fetch only one order, carts field's size is 1 or more, but when I fetch two or more orders, the carts field's size is 0.

How can I solve this problem? Thank you in advance!

Benjamin
  • 707
  • 3
  • 8
  • 28

2 Answers2

2

It's nearly impossible to find out what the fetching problem is without seeing the code which is loading / querying your entities. So could you please add it to your question?

Meanwhile there are at least some things you could improve to have cleaner entities and faster code, maybe this also can help a little to hunt down the problem.

At first, you are using redundant configurations, respective annotations you are recreating the default values with.

I assume that is lombok what you are using, right? You could remove the @Getter and @Setter annotations on your fields in both entities and add it once on class level to avoid the declaration on every single field, but since you are using @Data you don't need it at all. Same with @toString, @Data is a convenience annotations for all of it (and a little more).

The JavaDoc of @Data says:

Equivalent to {@code @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode}.

Then, although the @Table(name="\"order\"") on the order entity is needed because order is a reserved word in some DBMS, the @Table(name="cart") on the cart entity is the default.

Up next, I would not recommend lazy initialization of collections, because in general there is no benefit to do that compared to the penalty it causes while checking for null before every access. Just initialize it within declaration and you will never have to care about handling null again. Beside of that you should think about using a Set instead a List for the carts (so you will have no duplicates).

Also think about the FetchType because EAGER is only useful if you work with detached entities and want to initialize the relation in every case. LAZY is the default for @OneToMany and should be preferred.

A thing you already improved is the @JoinColumn, that will prevent Hibernate (I brazenly assume you are using Hibernate) creating a join table. But even more important is thinking about turning the @OneToMany into a @ManyToOne on the other side or making it bidirectional. That would gain some performance on reading (it also prevents the join table so less joins are needed, faster indexing is possible) and writing time (relation managed by parent side).

So what do you think about this:

@Data
@Entity
@Table(name="\"order\"")
public class Order {

    @Id
    @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Cart> carts = new HashSet<>();

}

and this:

@Data
@Entity
public class Cart {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne
    private Order order;

}
Kevin Peters
  • 3,314
  • 1
  • 17
  • 38
  • 1
    The best practices when using Lombok with entity use `@Getter` and `@Setter`, and I don't prefer using `@Data` with entity class because it will generate `hash` and `equal` functions, and these two functions will create a new problem with hibernate – Abdalrhman Alkraien Jan 27 '23 at 01:53
1

Order class:

public class Order {

    @Id
    @GeneratedValue
    @Getter @Setter
    private Long id;

    @OneToMany(mappedBy="orderId" fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    @Getter @Setter
    private Set<Cart> carts;

}

Cart class:

public class Cart {

    @Id
    @GeneratedValue
    @Getter @Setter
    @Column(name="id")
    private Long id;

    @ManyToOne
    @Getter @Setter
    private Order orderId;

}

For example: JPA OneToMany and ManyToOne Relationships

I just wrote it to change. I hope that it will work

Emre Savcı
  • 3,034
  • 2
  • 16
  • 25
  • it has a build error - '@OneToOne or @ManyToOne on com.flowerhada.domain.Cart.orderId references an unknown entity: java.lang.Long' – Benjamin Mar 24 '17 at 16:09
  • sry, i modify it. I change in Order class List to Set collection and remove @JoinColumn in Cart class. Try this. – Gábor Horváth Mar 24 '17 at 17:33
  • thx, but I'm afraid about setting orderId to Cart. In case of setting orderId, I'd like to set orderId Long type, how can I set orderId? – Benjamin Mar 25 '17 at 02:02
  • why? It is a default solution if you use the hibernate / jpa / spring data. But you know. I think that you set Long type then it will not work or the solution will be not best. – Gábor Horváth Mar 27 '17 at 07:12
  • I see. I should set order object. Thx! – Benjamin Mar 27 '17 at 11:35