I'm using EclipseLink 2.5.1 (and Hibernate 4.3.5 final). Given the following tables in MySQL.
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?
Solution. Select column values in a specific order within rows to make rows with duplicate sets of values identical. Then you can use SELECT DISTINCT to remove duplicates. Alternatively, retrieve rows in such a way that near-duplicates are not even selected.
In JPA we use the @ManyToMany annotation to model many-to-many relationships. This type of relationship can be unidirectional or bidirectional: In a unidirectional relationship only one entity in the relationship points the other. In a bidirectional relationship both entities point to each other.
Issue with @Id column, If we check closely, @Id column value is same for all the rows. Hence hibernate/JPA not able to get different records, it just get 1st record with this @Id and return duplicate records of it. Solution - Use @IdClass with columns which result in unique row instead of duplicate row.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With