ManyToMany

User Wishlist with a Join Table

A user can wishlist many products, and a product can be in many wishlists. The relationship lives in a join table with two foreign keys.

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String email;

    @ManyToMany
    @JoinTable(
        name = "wishlist",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "product_id"))
    private Set<Product> wishlist = new HashSet<>();     // owning side

    public void addToWishlist(Product p)      { wishlist.add(p); p.getWishlistedBy().add(this); }
    public void removeFromWishlist(Product p) { wishlist.remove(p); p.getWishlistedBy().remove(this); }
}

// in Product (only if you need it):
@ManyToMany(mappedBy = "wishlist")
private Set<User> wishlistedBy = new HashSet<>();
create table wishlist (
    user_id bigint not null,
    product_id bigint not null,
    primary key (user_id, product_id)
);
-- adding product 7 to user 3's wishlist:
insert into wishlist (user_id, product_id) values (3, 7);

Use Set, Not List

With a List (a "bag" in Hibernate terms), removing one product makes Hibernate delete every row of that user in the join table and insert the rest again. With a Set it deletes just one row.

Example

Removing one product from a wishlist of 3 products:

user.removeFromWishlist(earphones);

Example output

-- with List<Product>
delete from wishlist where user_id=?
insert into wishlist (user_id,product_id) values (?,?)
insert into wishlist (user_id,product_id) values (?,?)

-- with Set<Product>
delete from wishlist where user_id=? and product_id=?

Tip: Set needs a correct equals/hashCode on the entity. Base them on a natural key (like SKU) or on the id with a constant hashCode. The Hibernate & JPA advance level covers this in detail.

When the Link Has Data: Use an Entity

A cart is also "users ↔ products", but each link needs a quantity. @ManyToMany cannot hold extra columns. Model the link as its own entity with two @ManyToOnes. That is exactly what ShopEase's CartItem is.

@Entity
@Table(name = "cart_items",
       uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "product_id"}))
public class CartItem {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "product_id")
    private Product product;

    private int quantity;
    private Instant addedAt;
}

Tip: Many teams never use @ManyToMany at all: links usually gain extra data later (added date, quantity, role), and the link entity is easier to query and paginate.