7

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?

Tiny
  • 27,221
  • 105
  • 339
  • 599

2 Answers2

7

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.

Tiny
  • 27,221
  • 105
  • 339
  • 599
  • Thank you. Earlier I was using `java.util.Set`s but I replaced them with `java.util.List`s because `Set`s have their own limitations I came across somewhere. In case of using `Set`s, yes it does insert duplicate rows as being happened in this case. Using a composite primary key is, many a times not recommended. – Tiny Apr 29 '14 at 11:52
  • 1
    Why this is not recommended? I prefer using my own relation table in many to many relationship. Why? If your chief tell you 'i want to know number of product with that colour' where you put this information? product_colour table is the best place. And you have much more control when you define own relationship table. – Peter Wroblewski Apr 29 '14 at 12:45
  • I have changed this relationship to use `java.util.Set` to prevent duplicate insert into the `prod_colour` table. – Tiny Jun 10 '14 at 11:04
2

You can specify a uniqueConstraints on the JoinTable as shown below:

@JoinTable(name = "prod_colour", joinColumns = {
        @JoinColumn(name = "prod_id", referencedColumnName = "prod_id")}, inverseJoinColumns = {
        @JoinColumn(name = "colour_id", referencedColumnName = "colour_id")}, uniqueConstraints = @UniqueConstraint(columnNames = {
                            "colour_id", "prod_id" }))

This will prevent duplication of entries when a direct sql is being executed directly against the table and via your application code.