How would I model the problem.
Database
It would completely remove the% PK%. It allows the same ingredient to be repeated for a product, which does not reflect your business (each ingredient will be associated only once to the product):
alter table produto_ingrediente drop column id_produtoIngrediente;
Instead of artificial PK would use a natural compound PK:
alter table produto_ingrediente add primary key(id_produto, id_ingrediente);
This new model does not allow the same ingredient to be associated multiple times with a product or vice versa.
JPA
Of course, your id_produtoIngrediente
entity should reflect the compound key:
@Entity
@Table(name="produto_ingrediente")
@IdClass(ProdutoIngredienteId.class)
public class ProdutoIngrediente implements Serializable {
@Id
@Column(name="id_produto", nullable = false)
private Integer idProduto;
@Id
@Column(name="id_ingrediente", nullable = false)
private Integer idIngrediente;
@ManyToOne
@PrimaryKeyJoinColumn(name="id_produto")
private Produto produto;
@ManyToOne
@PrimaryKeyJoinColumn(name="id_ingrediente")
private Ingrediente ingrediente;
// Demais campos...
// Getters & Setters
// equals e hashCode como abaixo
}
Have you noticed the ProdutoIngrediente
annotation to map a compound key? You can actually choose this syntax for composite keys or a slightly different syntax using built-in keys . Personally I find the first variation cleaner. Thus, an implementation for @IdClass
follows:
public class ProdutoIngredienteId {
private Integer idProduto;
private Integer idIngrediente;
// getters & setters
// Métodos de exemplo gerados com o IDE
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProdutoIngredienteId that = (ProdutoIngredienteId) o;
if (!idIngrediente.equals(that.idIngrediente)) return false;
if (!idProduto.equals(that.idProduto)) return false;
return true;
}
@Override
public int hashCode() {
int result = idProduto.hashCode();
result = 31 * result + idIngrediente.hashCode();
return result;
}
}
On the product side you have a collection of associations. Since the associations are unique and the order of the ingredients does not seem to be important, I believe that a ProdutoIngredienteId
is more appropriate than a Set
:
@Entity
@Table(name = "produto", catalog = "grupotenkite2")
public class Produto implements java.io.Serializable {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id_produto", unique = true, nullable = false)
private Integer idProduto;
@OneToMany(mappedBy="produto")
private Set<ProdutoIngrediente> ingredientes;
// getters & setters
Finally, as it is the responsibility of your application to keep the two tips of a bidirectional relationship, the code to associate an ingredient with a product is somewhat tedious:
Produto pizza = em.find(Produto.class, idPizza);
Ingrediente farinha = em.find(Ingrediente.class, idFarinha);
// Cria a associacao (tabela many to many)
ProdutoIngrediente associacao = new ProdutoIngrediente();
associacao.setProduto(pizza);
associacao.setIngrediente(farinha);
associacao.setIdProduto(pizza.getId());
associacao.setIdIngrediente(farinha.getId());
// demais parâmetros da associação
// adiciona a associacao do lado do produto
pizza.getIngredientes().add(associacao);
// adiciona a associacao do lado do ingrediente (se existir)
farinha.getProdutos().add(associacao);
In this way, since you will add persistent ingredients to the product, you can hide the complexity of the association in a List
method;
public boolean adicionarIngrediente(Ingrediente ingrediente, boolean opcional,
boolean padrao, BigDecimal valor) { // ...
The advantage of doing this is that your client code will not have to handle associations directly, so you can add new ingredients to the product in a natural way:
Produto pizza = em.find(Produto.class, idPizza);
Ingrediente farinha = em.find(Ingrediente.class, idFarinha);
pizza.adicionarIngrediente(farinha, false, true, new BigDecimal("0.60"));
If the number of association parameters starts to grow too large, rethink your model and create an auxiliary object.
OBS1: I preferred to use the annotations in the fields, but you can adapt this code to annotate properties according to your current code.
OBS2: Sorry for the gigantic answer, but I'd rather run the risk of being pedantic at the risk of taking on previous knowledge and skipping some important information for solving the problem.