Prevent duplicate entries in a join table in a many-to-many relationship in JPA

Tiny picture Tiny · Apr 28, 2014 · Viewed 13.1k times · Source

I'm using EclipseLink 2.5.1 (and Hibernate 4.3.5 final). Given the following tables in MySQL.

  • product
  • prod_colour (join table)
  • colour

There is a many-to-many relationship between products and their colours.

A product can have many colours and a colour can in turn be associated with many products. This relationship is expressed in the database by these tables.

The prod_colour table has two reference columns prod_id and colour_id from its related parent tables product and colour respectively.

As obvious, the entity class Product has a list of colours - java.util.List<Colour> which is named colourList.

The entity class Colour has a list of products - java.util.List<Product> which is named productList.


The relationship in the Colour entity :

public class Colour implements Serializable {

    @JoinTable(name = "prod_colour", joinColumns = {
        @JoinColumn(name = "colour_id", referencedColumnName = "prod_id")}, inverseJoinColumns = {
        @JoinColumn(name = "prod_id", referencedColumnName = "colour_id")})
    @ManyToMany(mappedBy = "colourList", fetch = FetchType.LAZY)
    private List<Product> productList; //Getter and setter.

    //---Utility methods---

    //Add rows to the prod_colour table.
    public void addToProduct(Product product) {
        this.getProductList().add(product);
        product.getColourList().add(this);
    }

    //Delete rows from the prod_colour table.
    public void removeFromProduct(Product product) {
        this.getProductList().remove(product);
        product.getColourList().remove(this);
    }
}

The relationship in the Product entity :

public class Product implements Serializable {

    @JoinTable(name = "prod_colour", joinColumns = {
        @JoinColumn(name = "prod_id", referencedColumnName = "prod_id")}, inverseJoinColumns = {
        @JoinColumn(name = "colour_id", referencedColumnName = "colour_id")})
    @ManyToMany(fetch = FetchType.LAZY)
    private List<Colour> colourList; //Getter and setter.
}

From the associated EJB, the insert operation is performed as follows.

@Override
@SuppressWarnings("unchecked")
public boolean insert(List<Colour> colours, Product product)
{
    int i=0;
    Long prodId=product.getProdId();
    for(Colour colour:colours)
    {
        Product p = entityManager.getReference(Product.class, prodId);
        colour.addToProduct(p);

        if(++i%49==0)
        {
            entityManager.flush();
        }
        entityManager.merge(colour);
        entityManager.merge(p);
    }
    return true;
}

Everything works fine.


When duplicate rows are attempted (same Colour entities associated with the same Product entity), they are also inserted into the prod_colour table that I expect should not happen.

Do I need to perform some extra conditional checks to avoid duplicate insertion or EclipseLink/Hibernate have some mechanism to prevent duplicates in such situations?

Answer

Peter Wroblewski picture Peter Wroblewski · Apr 29, 2014

It's also surprising for me. I always was thinking that it made reference columns in join table as composite primary key, but it does not. If you want to have unique records try use Set instead of List or create your own ManyToMany relationship table with composite primary key(color_id, product_id). I don't have better idea.